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 | |
24 | ||
25 | help = 'Publish the stack changes to a merge-friendly head' | |
26 | kind = 'stack' | |
27 | usage = ['[options] [branch]'] | |
28 | description = """ | |
29 | This command commits a set of changes on a separate (called public) branch | |
30 | based on the modifications of the given or current stack. The history of the | |
31 | public branch is not re-written, making it merge-friendly and feasible for | |
32 | publishing. The heads of the stack and public branch may be different but the | |
33 | corresponding tree objects are always the same. | |
34 | ||
35 | If the trees of the stack and public branch are different (otherwise the | |
36 | command has no effect), StGit first checks for a rebase of the stack since the | |
37 | last publishing. If a rebase is detected, StGit creates a commit on the public | |
38 | branch corresponding to a merge between the new stack base and the latest | |
39 | public head. | |
40 | ||
41 | If no rebasing was detected, StGit checks for new patches that may have been | |
42 | created on top of the stack since the last publishing. If new patches are | |
43 | found and are not empty, they are checked into the public branch keeping the | |
44 | same commit information (e.g. log message, author, committer, date). | |
45 | ||
46 | If the above tests fail (e.g. patches modified or removed), StGit creates a | |
47 | new commit on the public branch having the same tree as the stack but the | |
48 | public head as its parent. The editor will be invoked if no "--message" option | |
49 | is given. | |
50 | ||
51 | It is recommended that stack modifications falling in different categories as | |
52 | described above are separated by a publish command in order to keep the public | |
53 | branch history cleaner (otherwise StGit would generate a big commit including | |
54 | several stack modifications). | |
55 | ||
56 | The public branch name can be set via the branch.<branch>.public configuration | |
57 | variable (defaulting to "<branch>.public"). | |
58 | """ | |
59 | ||
60 | args = [argparse.all_branches] | |
61 | options = [ | |
62 | opt('-b', '--branch', args = [argparse.stg_branches], | |
63 | short = 'Use BRANCH instead of the default branch') | |
64 | ] + (argparse.author_options() | |
65 | + argparse.message_options(save_template = False) | |
66 | + argparse.sign_options()) | |
67 | ||
68 | directory = common.DirectoryHasRepositoryLib() | |
69 | ||
70 | def __create_commit(repository, tree, parents, options, message = ''): | |
71 | """Return a new Commit object.""" | |
72 | cd = git.CommitData( | |
73 | tree = tree, parents = parents, message = message, | |
74 | author = git.Person.author(), committer = git.Person.committer()) | |
75 | cd = common.update_commit_data(cd, options, allow_edit = True) | |
76 | ||
77 | return repository.commit(cd) | |
78 | ||
79 | def func(parser, options, args): | |
80 | """Publish the stack changes.""" | |
81 | repository = directory.repository | |
82 | stack = repository.get_stack(options.branch) | |
83 | ||
84 | if not args: | |
5660771c | 85 | public_ref = common.get_public_ref(stack.name) |
e58f264a CM |
86 | elif len(args) == 1: |
87 | public_ref = args[0] | |
88 | else: | |
89 | parser.error('incorrect number of arguments') | |
90 | ||
91 | # just clone the stack if the public ref does not exist | |
92 | if not repository.refs.exists(public_ref): | |
93 | repository.refs.set(public_ref, stack.head, 'publish') | |
94 | out.info('Created "%s"' % public_ref) | |
95 | return | |
96 | ||
97 | public_head = repository.refs.get(public_ref) | |
98 | public_tree = public_head.data.tree | |
99 | ||
100 | # check for same tree (already up to date) | |
101 | if public_tree.sha1 == stack.head.data.tree.sha1: | |
102 | out.info('"%s" already up to date' % public_ref) | |
103 | return | |
104 | ||
105 | # check for rebased stack. In this case we emulate a merge with the stack | |
106 | # base by setting two parents. | |
107 | merge_bases = repository.get_merge_bases(public_head, stack.base) | |
108 | if not stack.base in merge_bases: | |
11125113 CM |
109 | message = 'Merge %s into %s' % (repository.describe(stack.base), |
110 | public_ref) | |
e58f264a CM |
111 | public_head = __create_commit(repository, stack.head.data.tree, |
112 | [public_head, stack.base], options, | |
113 | message) | |
114 | repository.refs.set(public_ref, public_head, 'publish') | |
115 | out.info('Merged the stack base into "%s"' % public_ref) | |
116 | return | |
117 | ||
118 | # check for new patches from the last publishing. This is done by checking | |
119 | # whether the public tree is the same as the bottom of the checked patch. | |
120 | # If older patches were modified, new patches cannot be detected. The new | |
121 | # patches and their metadata are pushed directly to the published head. | |
122 | for p in stack.patchorder.applied: | |
123 | pc = stack.patches.get(p).commit | |
124 | if public_tree.sha1 == pc.data.parent.data.tree.sha1: | |
125 | if pc.data.is_nochange(): | |
126 | out.info('Ignored new empty patch "%s"' % p) | |
127 | continue | |
128 | cd = pc.data.set_parent(public_head) | |
129 | public_head = repository.commit(cd) | |
130 | public_tree = public_head.data.tree | |
131 | out.start('Published new patch "%s"' % p) | |
132 | ||
133 | # create a new commit (only happens if no new patches are detected) | |
134 | if public_tree.sha1 != stack.head.data.tree.sha1: | |
135 | public_head = __create_commit(repository, stack.head.data.tree, | |
136 | [public_head], options) | |
137 | ||
138 | # update the public head | |
139 | repository.refs.set(public_ref, public_head, 'publish') | |
140 | out.info('Updated "%s"' % public_ref) |