| 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 = common.get_public_ref(stack.name) |
| 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: |
| 109 | message = 'Merge ' + repository.describe(stack.base) |
| 110 | public_head = __create_commit(repository, stack.head.data.tree, |
| 111 | [public_head, stack.base], options, |
| 112 | message) |
| 113 | repository.refs.set(public_ref, public_head, 'publish') |
| 114 | out.info('Merged the stack base into "%s"' % public_ref) |
| 115 | return |
| 116 | |
| 117 | # check for new patches from the last publishing. This is done by checking |
| 118 | # whether the public tree is the same as the bottom of the checked patch. |
| 119 | # If older patches were modified, new patches cannot be detected. The new |
| 120 | # patches and their metadata are pushed directly to the published head. |
| 121 | for p in stack.patchorder.applied: |
| 122 | pc = stack.patches.get(p).commit |
| 123 | if public_tree.sha1 == pc.data.parent.data.tree.sha1: |
| 124 | if pc.data.is_nochange(): |
| 125 | out.info('Ignored new empty patch "%s"' % p) |
| 126 | continue |
| 127 | cd = pc.data.set_parent(public_head) |
| 128 | public_head = repository.commit(cd) |
| 129 | public_tree = public_head.data.tree |
| 130 | out.start('Published new patch "%s"' % p) |
| 131 | |
| 132 | # create a new commit (only happens if no new patches are detected) |
| 133 | if public_tree.sha1 != stack.head.data.tree.sha1: |
| 134 | public_head = __create_commit(repository, stack.head.data.tree, |
| 135 | [public_head], options) |
| 136 | |
| 137 | # update the public head |
| 138 | repository.refs.set(public_ref, public_head, 'publish') |
| 139 | out.info('Updated "%s"' % public_ref) |