2 Copyright (C) 2009, Catalin Marinas <catalin.marinas@gmail.com>
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.
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.
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
18 from stgit
import argparse
19 from stgit
.argparse
import opt
20 from stgit
.commands
import common
21 from stgit
.config
import config
22 from stgit
.lib
import git
, stack
, transaction
23 from stgit
.out
import out
24 from stgit
import utils
26 help = 'Push the stack changes to a merge-friendly branch'
28 usage
= ['[options] [--] [branch]']
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.
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
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).
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
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).
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'
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.
64 The public branch name can be set via the branch.<branch>.public configuration
65 variable (defaulting to "<branch>.public").
68 args
= [argparse
.all_branches
]
70 opt('-b', '--branch', args
= [argparse
.stg_branches
],
71 short
= 'Use BRANCH instead of the default branch'),
72 opt('-l', '--last', action
= 'store_true',
73 short
= 'Show the last published patch'),
74 opt('-u', '--unpublished', action
= 'store_true',
75 short
= 'Show applied patches that have not been published')
76 ] + (argparse
.author_options()
77 + argparse
.message_options(save_template
= False)
78 + argparse
.sign_options())
80 directory
= common
.DirectoryHasRepositoryLib()
82 def __create_commit(repository
, tree
, parents
, options
, message
= ''):
83 """Return a new Commit object."""
85 tree
= tree
, parents
= parents
, message
= message
,
86 author
= git
.Person
.author(), committer
= git
.Person
.committer())
87 cd
= common
.update_commit_data(cd
, options
)
89 return repository
.commit(cd
)
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)
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
:
108 def func(parser
, options
, args
):
109 """Publish the stack changes."""
110 repository
= directory
.repository
111 stack
= repository
.get_stack(options
.branch
)
114 public_ref
= common
.get_public_ref(stack
.name
)
118 parser
.error('incorrect number of arguments')
120 if not public_ref
.startswith('refs/heads/'):
121 public_ref
= 'refs/heads/' + public_ref
123 # just clone the stack if the public ref does not exist
124 if not repository
.refs
.exists(public_ref
):
125 if options
.unpublished
or options
.last
:
126 raise common
.CmdException('"%s" does not exist' % public_ref
)
127 repository
.refs
.set(public_ref
, stack
.head
, 'publish')
128 out
.info('Created "%s"' % public_ref
)
131 public_head
= repository
.refs
.get(public_ref
)
132 public_tree
= public_head
.data
.tree
134 # find the last published patch
136 last
= __get_last(stack
, public_tree
)
138 raise common
.CmdException('Unable to find the last published patch '
139 '(possibly rebased stack)')
140 out
.info('%s' % last
)
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
)
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
:
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
)
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
)
164 if not stack
.base
in merge_bases
:
165 message
= 'Merge %s into %s' %
(repository
.describe(stack
.base
).strip(),
166 utils
.strip_prefix('refs/heads/',
168 public_head
= __create_commit(repository
, stack
.head
.data
.tree
,
169 [public_head
, stack
.base
], options
,
171 repository
.refs
.set(public_ref
, public_head
, 'publish')
172 out
.info('Merged the stack base into "%s"' % public_ref
)
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
)
185 cd
= pc
.data
.set_parent(public_head
)
186 public_head
= repository
.commit(cd
)
187 public_tree
= public_head
.data
.tree
188 out
.info('Published new patch "%s"' % p
)
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
)
195 # update the public head
196 repository
.refs
.set(public_ref
, public_head
, 'publish')
197 out
.info('Updated "%s"' % public_ref
)