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 | |
22 | from stgit.lib import git, stack | |
23 | from stgit.out import out | |
df6e6d0c | 24 | from stgit import utils |
e58f264a CM |
25 | |
26 | help = 'Publish the stack changes to a merge-friendly head' | |
27 | kind = 'stack' | |
28 | usage = ['[options] [branch]'] | |
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 | ||
57 | The public branch name can be set via the branch.<branch>.public configuration | |
58 | variable (defaulting to "<branch>.public"). | |
59 | """ | |
60 | ||
61 | args = [argparse.all_branches] | |
62 | options = [ | |
63 | opt('-b', '--branch', args = [argparse.stg_branches], | |
64 | short = 'Use BRANCH instead of the default branch') | |
65 | ] + (argparse.author_options() | |
66 | + argparse.message_options(save_template = False) | |
67 | + argparse.sign_options()) | |
68 | ||
69 | directory = common.DirectoryHasRepositoryLib() | |
70 | ||
71 | def __create_commit(repository, tree, parents, options, message = ''): | |
72 | """Return a new Commit object.""" | |
73 | cd = git.CommitData( | |
74 | tree = tree, parents = parents, message = message, | |
75 | author = git.Person.author(), committer = git.Person.committer()) | |
76 | cd = common.update_commit_data(cd, options, allow_edit = True) | |
77 | ||
78 | return repository.commit(cd) | |
79 | ||
80 | def func(parser, options, args): | |
81 | """Publish the stack changes.""" | |
82 | repository = directory.repository | |
83 | stack = repository.get_stack(options.branch) | |
84 | ||
85 | if not args: | |
5660771c | 86 | public_ref = common.get_public_ref(stack.name) |
e58f264a CM |
87 | elif len(args) == 1: |
88 | public_ref = args[0] | |
89 | else: | |
90 | parser.error('incorrect number of arguments') | |
91 | ||
df6e6d0c CM |
92 | if not public_ref.startswith('refs/heads/'): |
93 | public_ref = 'refs/heads/' + public_ref | |
94 | ||
e58f264a CM |
95 | # just clone the stack if the public ref does not exist |
96 | if not repository.refs.exists(public_ref): | |
97 | repository.refs.set(public_ref, stack.head, 'publish') | |
98 | out.info('Created "%s"' % public_ref) | |
99 | return | |
100 | ||
101 | public_head = repository.refs.get(public_ref) | |
102 | public_tree = public_head.data.tree | |
103 | ||
104 | # check for same tree (already up to date) | |
105 | if public_tree.sha1 == stack.head.data.tree.sha1: | |
106 | out.info('"%s" already up to date' % public_ref) | |
107 | return | |
108 | ||
109 | # check for rebased stack. In this case we emulate a merge with the stack | |
110 | # base by setting two parents. | |
111 | merge_bases = repository.get_merge_bases(public_head, stack.base) | |
112 | if not stack.base in merge_bases: | |
11125113 | 113 | message = 'Merge %s into %s' % (repository.describe(stack.base), |
df6e6d0c CM |
114 | utils.strip_prefix('refs/heads/', |
115 | public_ref)) | |
e58f264a CM |
116 | public_head = __create_commit(repository, stack.head.data.tree, |
117 | [public_head, stack.base], options, | |
118 | message) | |
119 | repository.refs.set(public_ref, public_head, 'publish') | |
120 | out.info('Merged the stack base into "%s"' % public_ref) | |
121 | return | |
122 | ||
123 | # check for new patches from the last publishing. This is done by checking | |
124 | # whether the public tree is the same as the bottom of the checked patch. | |
125 | # If older patches were modified, new patches cannot be detected. The new | |
126 | # patches and their metadata are pushed directly to the published head. | |
127 | for p in stack.patchorder.applied: | |
128 | pc = stack.patches.get(p).commit | |
129 | if public_tree.sha1 == pc.data.parent.data.tree.sha1: | |
130 | if pc.data.is_nochange(): | |
131 | out.info('Ignored new empty patch "%s"' % p) | |
132 | continue | |
133 | cd = pc.data.set_parent(public_head) | |
134 | public_head = repository.commit(cd) | |
135 | public_tree = public_head.data.tree | |
eb0c552f | 136 | out.info('Published new patch "%s"' % p) |
e58f264a CM |
137 | |
138 | # create a new commit (only happens if no new patches are detected) | |
139 | if public_tree.sha1 != stack.head.data.tree.sha1: | |
140 | public_head = __create_commit(repository, stack.head.data.tree, | |
141 | [public_head], options) | |
142 | ||
143 | # update the public head | |
144 | repository.refs.set(public_ref, public_head, 'publish') | |
145 | out.info('Updated "%s"' % public_ref) |