chiark / gitweb /
e4b1a8d6e7fc8ecd8ca300f1ffcf776752e1605b
[stgit] / stgit / commands / publish.py
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:
85         public_ref = config.get('branch.%s.public' % stack.name)
86         if not public_ref:
87             public_ref = 'refs/heads/%s.public' % stack.name
88     elif len(args) == 1:
89         public_ref = args[0]
90     else:
91         parser.error('incorrect number of arguments')
92
93     # just clone the stack if the public ref does not exist
94     if not repository.refs.exists(public_ref):
95         repository.refs.set(public_ref, stack.head, 'publish')
96         out.info('Created "%s"' % public_ref)
97         return
98
99     public_head = repository.refs.get(public_ref)
100     public_tree = public_head.data.tree
101
102     # check for same tree (already up to date)
103     if public_tree.sha1 == stack.head.data.tree.sha1:
104         out.info('"%s" already up to date' % public_ref)
105         return
106
107     # check for rebased stack. In this case we emulate a merge with the stack
108     # base by setting two parents.
109     merge_bases = repository.get_merge_bases(public_head, stack.base)
110     if not stack.base in merge_bases:
111         message = 'Merge ' + repository.describe(stack.base)
112         public_head = __create_commit(repository, stack.head.data.tree,
113                                       [public_head, stack.base], options,
114                                       message)
115         repository.refs.set(public_ref, public_head, 'publish')
116         out.info('Merged the stack base into "%s"' % public_ref)
117         return
118
119     # check for new patches from the last publishing. This is done by checking
120     # whether the public tree is the same as the bottom of the checked patch.
121     # If older patches were modified, new patches cannot be detected. The new
122     # patches and their metadata are pushed directly to the published head.
123     for p in stack.patchorder.applied:
124         pc = stack.patches.get(p).commit
125         if public_tree.sha1 == pc.data.parent.data.tree.sha1:
126             if pc.data.is_nochange():
127                 out.info('Ignored new empty patch "%s"' % p)
128                 continue
129             cd = pc.data.set_parent(public_head)
130             public_head = repository.commit(cd)
131             public_tree = public_head.data.tree
132             out.start('Published new patch "%s"' % p)
133
134     # create a new commit (only happens if no new patches are detected)
135     if public_tree.sha1 != stack.head.data.tree.sha1:
136         public_head = __create_commit(repository, stack.head.data.tree,
137                                       [public_head], options)
138
139     # update the public head
140     repository.refs.set(public_ref, public_head, 'publish')
141     out.info('Updated "%s"' % public_ref)