Commit | Line | Data |
---|---|---|
e58f264a CM |
1 | __copyright__ = """ |
2 | Copyright (C) 2009, Catalin Marinas <catalin.marinas@gmail.com> | |
3 | ||
4 | This program is free software; you can redistribute it and/or modify | |
5 | it under the terms of the GNU General Public License version 2 as | |
6 | published by the Free Software Foundation. | |
7 | ||
8 | This program is distributed in the hope that it will be useful, | |
9 | but WITHOUT ANY WARRANTY; without even the implied warranty of | |
10 | MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the | |
11 | GNU General Public License for more details. | |
12 | ||
13 | You should have received a copy of the GNU General Public License | |
14 | along with this program; if not, write to the Free Software | |
15 | Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA | |
16 | """ | |
17 | ||
18 | from stgit import argparse | |
19 | from stgit.argparse import opt | |
20 | from stgit.commands import common | |
21 | from stgit.config import config | |
eec51c11 | 22 | from stgit.lib import git, stack, transaction |
e58f264a | 23 | from stgit.out import out |
df6e6d0c | 24 | from stgit import utils |
e58f264a | 25 | |
737b7d8e | 26 | help = 'Push the stack changes to a merge-friendly branch' |
e58f264a | 27 | kind = 'stack' |
54239abf | 28 | usage = ['[options] [--] [branch]'] |
e58f264a CM |
29 | description = """ |
30 | This command commits a set of changes on a separate (called public) branch | |
31 | based on the modifications of the given or current stack. The history of the | |
32 | public branch is not re-written, making it merge-friendly and feasible for | |
33 | publishing. The heads of the stack and public branch may be different but the | |
34 | corresponding tree objects are always the same. | |
35 | ||
36 | If the trees of the stack and public branch are different (otherwise the | |
37 | command has no effect), StGit first checks for a rebase of the stack since the | |
38 | last publishing. If a rebase is detected, StGit creates a commit on the public | |
39 | branch corresponding to a merge between the new stack base and the latest | |
40 | public head. | |
41 | ||
42 | If no rebasing was detected, StGit checks for new patches that may have been | |
43 | created on top of the stack since the last publishing. If new patches are | |
44 | found and are not empty, they are checked into the public branch keeping the | |
45 | same commit information (e.g. log message, author, committer, date). | |
46 | ||
47 | If the above tests fail (e.g. patches modified or removed), StGit creates a | |
48 | new commit on the public branch having the same tree as the stack but the | |
49 | public head as its parent. The editor will be invoked if no "--message" option | |
50 | is given. | |
51 | ||
52 | It is recommended that stack modifications falling in different categories as | |
53 | described above are separated by a publish command in order to keep the public | |
54 | branch history cleaner (otherwise StGit would generate a big commit including | |
55 | several stack modifications). | |
56 | ||
eec51c11 CM |
57 | The '--unpublished' option can be used to check if there are applied patches |
58 | that have not been published to the public branch. This is done by trying to | |
59 | revert the patches in the public tree (similar to the 'push --merged' | |
064161c3 CM |
60 | detection). The '--last' option tries to find the last published patch by |
61 | checking the SHA1 of the patch tree agains the public tree. This may fail if | |
62 | the stack was rebased since the last publish command. | |
eec51c11 | 63 | |
e58f264a CM |
64 | The public branch name can be set via the branch.<branch>.public configuration |
65 | variable (defaulting to "<branch>.public"). | |
66 | """ | |
67 | ||
68 | args = [argparse.all_branches] | |
69 | options = [ | |
70 | opt('-b', '--branch', args = [argparse.stg_branches], | |
eec51c11 | 71 | short = 'Use BRANCH instead of the default branch'), |
064161c3 CM |
72 | opt('-l', '--last', action = 'store_true', |
73 | short = 'Show the last published patch'), | |
eec51c11 CM |
74 | opt('-u', '--unpublished', action = 'store_true', |
75 | short = 'Show applied patches that have not been published') | |
e58f264a CM |
76 | ] + (argparse.author_options() |
77 | + argparse.message_options(save_template = False) | |
78 | + argparse.sign_options()) | |
79 | ||
80 | directory = common.DirectoryHasRepositoryLib() | |
81 | ||
82 | def __create_commit(repository, tree, parents, options, message = ''): | |
83 | """Return a new Commit object.""" | |
84 | cd = git.CommitData( | |
85 | tree = tree, parents = parents, message = message, | |
86 | author = git.Person.author(), committer = git.Person.committer()) | |
af734f49 | 87 | cd = common.update_commit_data(cd, options) |
e58f264a CM |
88 | |
89 | return repository.commit(cd) | |
90 | ||
eec51c11 CM |
91 | def __get_published(stack, tree): |
92 | """Check the patches that were already published.""" | |
93 | trans = transaction.StackTransaction(stack, 'publish') | |
94 | published = trans.check_merged(trans.applied, tree = tree, quiet = True) | |
95 | trans.abort() | |
96 | ||
97 | return published | |
98 | ||
064161c3 CM |
99 | def __get_last(stack, tree): |
100 | """Return the name of the last published patch.""" | |
101 | for p in reversed(stack.patchorder.applied): | |
102 | pc = stack.patches.get(p).commit | |
103 | if tree.sha1 == pc.data.tree.sha1: | |
104 | return p | |
105 | ||
106 | return None | |
107 | ||
e58f264a CM |
108 | def func(parser, options, args): |
109 | """Publish the stack changes.""" | |
110 | repository = directory.repository | |
111 | stack = repository.get_stack(options.branch) | |
112 | ||
113 | if not args: | |
5660771c | 114 | public_ref = common.get_public_ref(stack.name) |
e58f264a CM |
115 | elif len(args) == 1: |
116 | public_ref = args[0] | |
117 | else: | |
118 | parser.error('incorrect number of arguments') | |
119 | ||
df6e6d0c CM |
120 | if not public_ref.startswith('refs/heads/'): |
121 | public_ref = 'refs/heads/' + public_ref | |
122 | ||
e58f264a CM |
123 | # just clone the stack if the public ref does not exist |
124 | if not repository.refs.exists(public_ref): | |
064161c3 | 125 | if options.unpublished or options.last: |
eec51c11 | 126 | raise common.CmdException('"%s" does not exist' % public_ref) |
e58f264a CM |
127 | repository.refs.set(public_ref, stack.head, 'publish') |
128 | out.info('Created "%s"' % public_ref) | |
129 | return | |
130 | ||
131 | public_head = repository.refs.get(public_ref) | |
132 | public_tree = public_head.data.tree | |
133 | ||
064161c3 CM |
134 | # find the last published patch |
135 | if options.last: | |
136 | last = __get_last(stack, public_tree) | |
137 | if not last: | |
138 | raise common.CmdException('Unable to find the last published patch ' | |
139 | '(possibly rebased stack)') | |
140 | out.info('%s' % last) | |
141 | return | |
142 | ||
e58f264a CM |
143 | # check for same tree (already up to date) |
144 | if public_tree.sha1 == stack.head.data.tree.sha1: | |
145 | out.info('"%s" already up to date' % public_ref) | |
146 | return | |
147 | ||
eec51c11 CM |
148 | # check for unpublished patches |
149 | if options.unpublished: | |
150 | published = set(__get_published(stack, public_tree)) | |
151 | for p in stack.patchorder.applied: | |
152 | if p not in published: | |
153 | print p | |
154 | return | |
155 | ||
e58f264a CM |
156 | # check for rebased stack. In this case we emulate a merge with the stack |
157 | # base by setting two parents. | |
158 | merge_bases = repository.get_merge_bases(public_head, stack.base) | |
6c052b02 CM |
159 | if public_head in merge_bases: |
160 | # fast-forward the public ref | |
161 | repository.refs.set(public_ref, stack.head, 'publish') | |
162 | out.info('Fast-forwarded "%s"' % public_ref) | |
163 | return | |
e58f264a | 164 | if not stack.base in merge_bases: |
9a4a9c2c | 165 | message = 'Merge %s into %s' % (repository.describe(stack.base).strip(), |
df6e6d0c CM |
166 | utils.strip_prefix('refs/heads/', |
167 | public_ref)) | |
e58f264a CM |
168 | public_head = __create_commit(repository, stack.head.data.tree, |
169 | [public_head, stack.base], options, | |
170 | message) | |
171 | repository.refs.set(public_ref, public_head, 'publish') | |
172 | out.info('Merged the stack base into "%s"' % public_ref) | |
173 | return | |
174 | ||
175 | # check for new patches from the last publishing. This is done by checking | |
176 | # whether the public tree is the same as the bottom of the checked patch. | |
177 | # If older patches were modified, new patches cannot be detected. The new | |
178 | # patches and their metadata are pushed directly to the published head. | |
179 | for p in stack.patchorder.applied: | |
180 | pc = stack.patches.get(p).commit | |
181 | if public_tree.sha1 == pc.data.parent.data.tree.sha1: | |
182 | if pc.data.is_nochange(): | |
183 | out.info('Ignored new empty patch "%s"' % p) | |
184 | continue | |
185 | cd = pc.data.set_parent(public_head) | |
186 | public_head = repository.commit(cd) | |
187 | public_tree = public_head.data.tree | |
eb0c552f | 188 | out.info('Published new patch "%s"' % p) |
e58f264a CM |
189 | |
190 | # create a new commit (only happens if no new patches are detected) | |
191 | if public_tree.sha1 != stack.head.data.tree.sha1: | |
192 | public_head = __create_commit(repository, stack.head.data.tree, | |
193 | [public_head], options) | |
194 | ||
195 | # update the public head | |
196 | repository.refs.set(public_ref, public_head, 'publish') | |
197 | out.info('Updated "%s"' % public_ref) |