{"thread":{"id":"19555","subject":"[RFC][StGit PATCH] Add support for merge-friendly branches","startedAt":"2009-05-28T11:12:42Z","lastAt":"2009-05-29T11:59:20Z","messageCount":12,"participants":["Catalin Marinas","martin f krafft","Karl Hasselström"],"isPatch":true,"patchVersion":1,"patchTotal":null},"messages":[{"id":"114916","messageId":"20090528111212.21925.45527.stgit@pc1117.cambridge.arm.com","threadId":"19555","inReplyTo":null,"subject":"[RFC][StGit PATCH] Add support for merge-friendly branches","fromName":"Catalin Marinas","fromEmail":"catalin.marinas@arm.com","sentAt":"2009-05-28T11:12:42Z","receivedAt":"2009-05-28T11:12:42Z","isPatch":true,"sender":{"key":"catalin.marinas@arm.com","avatar":null},"body":"The main issue with publishing StGit branches is that the Git history\nrepresented by patches is volatile, making it difficult for people\nwanting to merge such branch. One solution is for all the downstream\ndevelopers to always rebase but that's not always desirable. Another\nsolution is provided by tools like TopGit but the visible Git history\nbecomes complicated, especially with repeated reordering.\n\nThe patch proposes a new StGit command called \"publish\". This command\nallows one to develop patches normally on a StGit branch but publish the\nstack changes to a separate, merge-friendly branch whose history is not\nre-writable.\n\nMore about its behaviour can be found in the command description in this\npatch.\n\nSigned-off-by: Catalin Marinas <catalin.marinas@gmail.com>\n---\n stgit/commands/common.py  |   26 ++++++++\n stgit/commands/new.py     |   22 -------\n stgit/commands/publish.py |  139 +++++++++++++++++++++++++++++++++++++++++++++\n stgit/lib/git.py          |    5 ++\n t/t4100-publish.sh        |  129 ++++++++++++++++++++++++++++++++++++++++++\n 5 files changed, 300 insertions(+), 21 deletions(-)\n create mode 100644 stgit/commands/publish.py\n create mode 100755 t/t4100-publish.sh\n\ndiff --git a/stgit/commands/common.py b/stgit/commands/common.py\nindex e46412e..04314f3 100644\n--- a/stgit/commands/common.py\n+++ b/stgit/commands/common.py\n@@ -447,6 +447,32 @@ def readonly_constant_property(f):\n         return getattr(self, n)\n     return property(new_f)\n \n+def update_commit_data(cd, options, allow_edit = False):\n+    \"\"\"Return a new CommitData object updated according to the command line\n+    options.\"\"\"\n+    # Set the commit message from commandline.\n+    if options.message != None:\n+        cd = cd.set_message(options.message)\n+\n+    # Modify author data.\n+    cd = cd.set_author(options.author(cd.author))\n+\n+    # Add Signed-off-by: or similar.\n+    if options.sign_str != None:\n+        sign_str = options.sign_str\n+    else:\n+        sign_str = config.get(\"stgit.autosign\")\n+    if sign_str != None:\n+        cd = cd.set_message(\n+            add_sign_line(cd.message, sign_str,\n+                          cd.committer.name, cd.committer.email))\n+\n+    # Let user edit the commit message manually.\n+    if allow_edit and not options.message:\n+        cd = cd.set_message(edit_string(cd.message, '.stgit-new.txt'))\n+\n+    return cd\n+\n class DirectoryException(StgException):\n     pass\n \ndiff --git a/stgit/commands/new.py b/stgit/commands/new.py\nindex 2c98431..9fd51c3 100644\n--- a/stgit/commands/new.py\n+++ b/stgit/commands/new.py\n@@ -67,32 +67,12 @@ def func(parser, options, args):\n     cd = gitlib.CommitData(\n         tree = stack.head.data.tree, parents = [stack.head], message = '',\n         author = gitlib.Person.author(), committer = gitlib.Person.committer())\n-\n-    # Set patch commit message from commandline.\n-    if options.message != None:\n-        cd = cd.set_message(options.message)\n-\n-    # Modify author data.\n-    cd = cd.set_author(options.author(cd.author))\n-\n-    # Add Signed-off-by: or similar.\n-    if options.sign_str != None:\n-        sign_str = options.sign_str\n-    else:\n-        sign_str = config.get(\"stgit.autosign\")\n-\n-    if sign_str != None:\n-        cd = cd.set_message(\n-            utils.add_sign_line(cd.message, sign_str,\n-                                cd.committer.name, cd.committer.email))\n+    cd = common.update_commit_data(cd, options, allow_edit = True)\n \n     if options.save_template:\n         options.save_template(cd.message)\n         return utils.STGIT_SUCCESS\n \n-    # Let user edit the commit message manually.\n-    if not options.message:\n-        cd = cd.set_message(utils.edit_string(cd.message, '.stgit-new.txt'))\n     if name == None:\n         name = utils.make_patch_name(cd.message,\n                                      lambda name: stack.patches.exists(name))\ndiff --git a/stgit/commands/publish.py b/stgit/commands/publish.py\nnew file mode 100644\nindex 0000000..06c32d0\n--- /dev/null\n+++ b/stgit/commands/publish.py\n@@ -0,0 +1,139 @@\n+__copyright__ = \"\"\"\n+Copyright (C) 2009, Catalin Marinas <catalin.marinas@gmail.com>\n+\n+This program is free software; you can redistribute it and/or modify\n+it under the terms of the GNU General Public License version 2 as\n+published by the Free Software Foundation.\n+\n+This program is distributed in the hope that it will be useful,\n+but WITHOUT ANY WARRANTY; without even the implied warranty of\n+MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the\n+GNU General Public License for more details.\n+\n+You should have received a copy of the GNU General Public License\n+along with this program; if not, write to the Free Software\n+Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA\n+\"\"\"\n+\n+from stgit import argparse\n+from stgit.argparse import opt\n+from stgit.commands import common\n+from stgit.config import config\n+from stgit.lib import git, stack\n+from stgit.out import out\n+\n+help = 'Publish the stack changes to a merge-friendly head'\n+kind = 'stack'\n+usage = ['[options] [branch]']\n+description = \"\"\"\n+This command commits a set of changes on a separate (called public) branch\n+based on the modifications of the given or current stack. The history of the\n+public branch is not re-written, making it merge-friendly and feasible for\n+publishing. The heads of the stack and public branch may be different but the\n+corresponding tree objects are always the same.\n+\n+If the trees of the stack and public branch are different (otherwise the\n+command has no effect), StGit first checks for a rebase of the stack since the\n+last publishing. If a rebase is detected, StGit creates a commit on the public\n+branch corresponding to a merge between the new stack base and the latest\n+public head.\n+\n+If no rebasing was detected, StGit checks for new patches that may have been\n+created on top of the stack since the last publishing. If new patches are\n+found and are not empty, they are checked into the public branch keeping the\n+same commit information (e.g. log message, author, committer, date).\n+\n+If the above tests fail (e.g. patches modified or removed), StGit creates a\n+new commit on the public branch having the same tree as the stack but the\n+public head as its parent. The editor will be invoked if no \"--message\" option\n+is given.\n+\n+It is recommended that stack modifications falling in different categories as\n+described above are separated by a publish command in order to keep the public\n+branch history cleaner (otherwise StGit would generate a big commit including\n+several stack modifications).\n+\n+The public branch name can be set via the branch.<branch>.public configuration\n+variable (defaulting to \"<branch>.public\").\n+\"\"\"\n+\n+args = [argparse.all_branches]\n+options = [\n+    opt('-b', '--branch', args = [argparse.stg_branches],\n+        short = 'Use BRANCH instead of the default branch')\n+] + (argparse.author_options()\n+     + argparse.message_options(save_template = False)\n+     + argparse.sign_options())\n+\n+directory = common.DirectoryHasRepositoryLib()\n+\n+def __create_commit(repository, tree, parents, options):\n+    \"\"\"Return a new Commit object.\"\"\"\n+    cd = git.CommitData(\n+        tree = tree, parents = parents, message = '',\n+        author = git.Person.author(), committer = git.Person.committer())\n+    cd = common.update_commit_data(cd, options, allow_edit = True)\n+\n+    return repository.commit(cd)\n+\n+def func(parser, options, args):\n+    \"\"\"Publish the stack changes.\"\"\"\n+    repository = directory.repository\n+    stack = repository.get_stack(options.branch)\n+\n+    if not args:\n+        public_ref = config.get('branch.%s.public' % stack.name)\n+        if not public_ref:\n+            public_ref = 'refs/heads/%s.public' % stack.name\n+    elif len(args) == 1:\n+        public_ref = args[0]\n+    else:\n+        parser.error('incorrect number of arguments')\n+\n+    # just clone the stack if the public ref does not exist\n+    if not repository.refs.exists(public_ref):\n+        repository.refs.set(public_ref, stack.head, 'publish')\n+        out.info('Created \"%s\"' % public_ref)\n+        return\n+\n+    public_head = repository.refs.get(public_ref)\n+    public_tree = public_head.data.tree\n+\n+    # check for same tree (already up to date)\n+    if public_tree.sha1 == stack.head.data.tree.sha1:\n+        out.info('\"%s\" already up to date' % public_ref)\n+        return\n+\n+    # check for rebased stack. In this case we emulate a merge with the stack\n+    # base by setting two parents.\n+    merge_base = repository.get_merge_base(public_head, stack.base)\n+    if merge_base.sha1 != stack.base.sha1:\n+        public_head = __create_commit(repository, stack.head.data.tree,\n+                                      [public_head, stack.base], options)\n+        repository.refs.set(public_ref, public_head, 'publish')\n+        out.info('Merged the stack base into \"%s\"' % public_ref)\n+        return\n+\n+    # check for new patches from the last publishing. This is done by checking\n+    # whether the public tree is the same as the bottom of the checked patch.\n+    # If older patches were modified, new patches cannot be detected. The new\n+    # patches and their metadata are pushed directly to the published head.\n+    for p in stack.patchorder.applied:\n+        pc = stack.patches.get(p).commit\n+        if public_tree.sha1 == pc.data.parent.data.tree.sha1:\n+            if pc.data.is_nochange():\n+                out.info('Ignored new empty patch \"%s\"' % p)\n+                continue\n+            cd = pc.data.set_parent(public_head)\n+            public_head = repository.commit(cd)\n+            public_tree = public_head.data.tree\n+            out.start('Published new patch \"%s\"' % p)\n+\n+    # create a new commit (only happens if no new patches are detected)\n+    if public_tree.sha1 != stack.head.data.tree.sha1:\n+        public_head = __create_commit(repository, stack.head.data.tree,\n+                                      [public_head], options)\n+\n+    # update the public head\n+    repository.refs.set(public_ref, public_head, 'publish')\n+    out.info('Updated \"%s\"' % public_ref)\ndiff --git a/stgit/lib/git.py b/stgit/lib/git.py\nindex 6f2c977..4a17c8a 100644\n--- a/stgit/lib/git.py\n+++ b/stgit/lib/git.py\n@@ -609,6 +609,11 @@ class Repository(RunWithEnv):\n             raise DetachedHeadException()\n     def set_head_ref(self, ref, msg):\n         self.run(['git', 'symbolic-ref', '-m', msg, 'HEAD', ref]).no_output()\n+    def get_merge_base(self, commit1, commit2):\n+        \"\"\"Return the merge base of two commits.\"\"\"\n+        sha1 = self.run(['git', 'merge-base',\n+                         commit1.sha1, commit2.sha1]).output_one_line()\n+        return self.get_commit(sha1)\n     def simple_merge(self, base, ours, theirs):\n         index = self.temp_index()\n         try:\ndiff --git a/t/t4100-publish.sh b/t/t4100-publish.sh\nnew file mode 100755\nindex 0000000..17e07bc\n--- /dev/null\n+++ b/t/t4100-publish.sh\n@@ -0,0 +1,129 @@\n+#!/bin/sh\n+#\n+# Copyright (c) 2009 Catalin Marinas\n+#\n+\n+test_description='Exercise the publish command.\n+\n+Create/modify patches on the stack and publish them to a separate branch.'\n+\n+. ./test-lib.sh\n+\n+test_same_tree () {\n+\tstack_tree=$(git rev-parse master^{tree})\n+\tpublic_tree=$(git rev-parse master.public^{tree})\n+\ttest \"$stack_tree\" = \"$public_tree\"\n+}\n+\n+test_expect_success \\\n+\t'Initialize the StGit repository' \\\n+\t'\n+\tstg init\n+\t'\n+\n+test_expect_success \\\n+\t'Create some patches' \\\n+\t'\n+\tstg new p1 -m p1 &&\n+\techo foo1 > foo1.txt &&\n+\tgit add foo1.txt &&\n+\tstg refresh &&\n+\tstg new p2 -m p2 &&\n+\techo foo2 > foo2.txt &&\n+\tgit add foo2.txt &&\n+\tstg refresh &&\n+\tstg new p3 -m p3 &&\n+\techo foo3 > foo3.txt &&\n+\tgit add foo3.txt &&\n+\tstg refresh\n+\t'\n+\n+test_expect_success \\\n+\t'Publish the stack for the first time' \\\n+\t'\n+\tstg publish &&\n+\ttest \"$(stg id)\" = \"$(stg id master.public)\"\n+\t'\n+\n+test_expect_success \\\n+\t'Modify a patch and publish the changes' \\\n+\t'\n+\tstg pop &&\n+\techo foo2 >> foo2.txt &&\n+\tstg refresh &&\n+\tstg push &&\n+\told_public=$(stg id master.public) &&\n+\tstg publish -m \"p2 updated\" &&\n+\ttest_same_tree &&\n+\tnew_public=$(stg id master.public) &&\n+\ttest \"$(git rev-list $old_public..$new_public | wc -l)\" = \"1\"\n+\t'\n+\n+test_expect_success \\\n+\t'Create new patches and publish them' \\\n+\t'\n+\tstg new p4 -m p4 &&\n+\techo foo4 > foo4.txt &&\n+\tgit add foo4.txt &&\n+\tstg refresh &&\n+\tstg new p5 -m p5 &&\n+\techo foo5 > foo5.txt &&\n+\tgit add foo5.txt &&\n+\tstg refresh &&\n+\tstg new empty -m empty &&\n+\told_public=$(stg id master.public) &&\n+\tstg publish -m \"Ignored message\" &&\n+\ttest_same_tree &&\n+\tnew_public=$(stg id master.public) &&\n+\ttest \"$(git rev-list $old_public..$new_public | wc -l)\" = \"2\"\n+\t'\n+\n+test_expect_success \\\n+\t'Rebase the current stack and publish a merge' \\\n+\t'\n+\tstg pop -a &&\n+\techo foo0 > foo0.txt &&\n+\tgit add foo0.txt &&\n+\tgit commit -m \"foo0.txt added\" &&\n+\tstg push -a &&\n+\told_public=$(stg id master.public) &&\n+\tstg publish -m \"Merge with base\" &&\n+\ttest_same_tree &&\n+\tnew_public=$(stg id master.public) &&\n+\ttest \"$(git rev-list $old_public..$new_public | wc -l)\" = \"2\" &&\n+\ttest \"$(git merge-base master.public master)\" = \"$(stg id {base})\"\n+\t'\n+\n+test_expect_success \\\n+\t'Re-publish without any changes' \\\n+\t'\n+\told_public=$(stg id master.public) &&\n+\tstg publish -m \"Ignored message\" &&\n+\ttest_same_tree &&\n+\tnew_public=$(stg id master.public) &&\n+\ttest \"$old_public\" = \"$new_public\"\n+\t'\n+\n+test_expect_success \\\n+\t'Reorder patches and publish the changes' \\\n+\t'\n+\tstg float p5 p4 p3 p2 p1 &&\n+\told_public=$(stg id master.public) &&\n+\tstg publish -m \"Ignored message\" &&\n+\ttest_same_tree &&\n+\tnew_public=$(stg id master.public) &&\n+\ttest \"$old_public\" = \"$new_public\"\n+\t'\n+\n+test_expect_success \\\n+\t'Pop a patch and publish the changes' \\\n+\t'\n+\tstg pop p3 &&\n+\told_public=$(stg id master.public) &&\n+\tstg publish -m \"p3 removed\" &&\n+\ttest_same_tree &&\n+\tnew_public=$(stg id master.public) &&\n+\ttest \"$(git rev-list $old_public..$new_public | wc -l)\" = \"1\"\n+\t'\n+\n+test_done\n"},{"id":"114919","messageId":"20090528121222.GA28495@piper.oerlikon.madduck.net","threadId":"19555","inReplyTo":"20090528111212.21925.45527.stgit-hhZApKj8DF/YkXV2EHHjLW3o5bpOHsLO@public.gmane.org","subject":"Fwd: [RFC][StGit PATCH] Add support for merge-friendly branches","fromName":"martin f krafft","fromEmail":"madduck-8fiuurrzop0dnm+yrofe0a@public.gmane.org","sentAt":"2009-05-28T12:12:22Z","receivedAt":"2009-05-28T12:12:22Z","isPatch":true,"sender":{"key":"madduck-8fiuurrzop0dnm+yrofe0a@public.gmane.org","avatar":null},"body":"[Forwarded to vcs-pkg-discuss]\n\nHey vcs-pkg people,\n\nThis may be of interest, I don't have any time right now to check\nthis out and don't remember StGit too well either.\n\nI suggest that comments be fed back to the git mailing list, which\nhas a Cc policy, so just reply to all.\n\nI have put the list and Catalin and Karl on Cc so that they know\nthat http://vcs-pkg.org has sighted this stuff. ;)\n\n----- Forwarded message from Catalin Marinas <catalin.marinas-5wv7dgnIgG8@public.gmane.org> -----\n\nDate: Thu, 28 May 2009 12:12:42 +0100\nFrom: Catalin Marinas <catalin.marinas-5wv7dgnIgG8@public.gmane.org>\nTo: git-u79uwXL29TY76Z2rM5mHXA@public.gmane.org, Karl Hasselström <kha-bX70CJONkLNBDgjK7y7TUQ@public.gmane.org>\nSubject: [RFC][StGit PATCH] Add support for merge-friendly branches\nMessage-ID: <20090528111212.21925.45527.stgit-hhZApKj8DF/YkXV2EHHjLW3o5bpOHsLO@public.gmane.org>\n\nThe main issue with publishing StGit branches is that the Git history\nrepresented by patches is volatile, making it difficult for people\nwanting to merge such branch. One solution is for all the downstream\ndevelopers to always rebase but that's not always desirable. Another\nsolution is provided by tools like TopGit but the visible Git history\nbecomes complicated, especially with repeated reordering.\n\nThe patch proposes a new StGit command called \"publish\". This command\nallows one to develop patches normally on a StGit branch but publish the\nstack changes to a separate, merge-friendly branch whose history is not\nre-writable.\n\nMore about its behaviour can be found in the command description in this\npatch.\n\nSigned-off-by: Catalin Marinas <catalin.marinas-Re5JQEeQqe8AvxtiuMwx3w@public.gmane.org>\n---\n stgit/commands/common.py  |   26 ++++++++\n stgit/commands/new.py     |   22 -------\n stgit/commands/publish.py |  139 +++++++++++++++++++++++++++++++++++++++++++++\n stgit/lib/git.py          |    5 ++\n t/t4100-publish.sh        |  129 ++++++++++++++++++++++++++++++++++++++++++\n 5 files changed, 300 insertions(+), 21 deletions(-)\n create mode 100644 stgit/commands/publish.py\n create mode 100755 t/t4100-publish.sh\n\ndiff --git a/stgit/commands/common.py b/stgit/commands/common.py\nindex e46412e..04314f3 100644\n--- a/stgit/commands/common.py\n+++ b/stgit/commands/common.py\n@@ -447,6 +447,32 @@ def readonly_constant_property(f):\n         return getattr(self, n)\n     return property(new_f)\n \n+def update_commit_data(cd, options, allow_edit = False):\n+    \"\"\"Return a new CommitData object updated according to the command line\n+    options.\"\"\"\n+    # Set the commit message from commandline.\n+    if options.message != None:\n+        cd = cd.set_message(options.message)\n+\n+    # Modify author data.\n+    cd = cd.set_author(options.author(cd.author))\n+\n+    # Add Signed-off-by: or similar.\n+    if options.sign_str != None:\n+        sign_str = options.sign_str\n+    else:\n+        sign_str = config.get(\"stgit.autosign\")\n+    if sign_str != None:\n+        cd = cd.set_message(\n+            add_sign_line(cd.message, sign_str,\n+                          cd.committer.name, cd.committer.email))\n+\n+    # Let user edit the commit message manually.\n+    if allow_edit and not options.message:\n+        cd = cd.set_message(edit_string(cd.message, '.stgit-new.txt'))\n+\n+    return cd\n+\n class DirectoryException(StgException):\n     pass\n \ndiff --git a/stgit/commands/new.py b/stgit/commands/new.py\nindex 2c98431..9fd51c3 100644\n--- a/stgit/commands/new.py\n+++ b/stgit/commands/new.py\n@@ -67,32 +67,12 @@ def func(parser, options, args):\n     cd = gitlib.CommitData(\n         tree = stack.head.data.tree, parents = [stack.head], message = '',\n         author = gitlib.Person.author(), committer = gitlib.Person.committer())\n-\n-    # Set patch commit message from commandline.\n-    if options.message != None:\n-        cd = cd.set_message(options.message)\n-\n-    # Modify author data.\n-    cd = cd.set_author(options.author(cd.author))\n-\n-    # Add Signed-off-by: or similar.\n-    if options.sign_str != None:\n-        sign_str = options.sign_str\n-    else:\n-        sign_str = config.get(\"stgit.autosign\")\n-\n-    if sign_str != None:\n-        cd = cd.set_message(\n-            utils.add_sign_line(cd.message, sign_str,\n-                                cd.committer.name, cd.committer.email))\n+    cd = common.update_commit_data(cd, options, allow_edit = True)\n \n     if options.save_template:\n         options.save_template(cd.message)\n         return utils.STGIT_SUCCESS\n \n-    # Let user edit the commit message manually.\n-    if not options.message:\n-        cd = cd.set_message(utils.edit_string(cd.message, '.stgit-new.txt'))\n     if name == None:\n         name = utils.make_patch_name(cd.message,\n                                      lambda name: stack.patches.exists(name))\ndiff --git a/stgit/commands/publish.py b/stgit/commands/publish.py\nnew file mode 100644\nindex 0000000..06c32d0\n--- /dev/null\n+++ b/stgit/commands/publish.py\n@@ -0,0 +1,139 @@\n+__copyright__ = \"\"\"\n+Copyright (C) 2009, Catalin Marinas <catalin.marinas-Re5JQEeQqe8AvxtiuMwx3w@public.gmane.org>\n+\n+This program is free software; you can redistribute it and/or modify\n+it under the terms of the GNU General Public License version 2 as\n+published by the Free Software Foundation.\n+\n+This program is distributed in the hope that it will be useful,\n+but WITHOUT ANY WARRANTY; without even the implied warranty of\n+MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the\n+GNU General Public License for more details.\n+\n+You should have received a copy of the GNU General Public License\n+along with this program; if not, write to the Free Software\n+Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA\n+\"\"\"\n+\n+from stgit import argparse\n+from stgit.argparse import opt\n+from stgit.commands import common\n+from stgit.config import config\n+from stgit.lib import git, stack\n+from stgit.out import out\n+\n+help = 'Publish the stack changes to a merge-friendly head'\n+kind = 'stack'\n+usage = ['[options] [branch]']\n+description = \"\"\"\n+This command commits a set of changes on a separate (called public) branch\n+based on the modifications of the given or current stack. The history of the\n+public branch is not re-written, making it merge-friendly and feasible for\n+publishing. The heads of the stack and public branch may be different but the\n+corresponding tree objects are always the same.\n+\n+If the trees of the stack and public branch are different (otherwise the\n+command has no effect), StGit first checks for a rebase of the stack since the\n+last publishing. If a rebase is detected, StGit creates a commit on the public\n+branch corresponding to a merge between the new stack base and the latest\n+public head.\n+\n+If no rebasing was detected, StGit checks for new patches that may have been\n+created on top of the stack since the last publishing. If new patches are\n+found and are not empty, they are checked into the public branch keeping the\n+same commit information (e.g. log message, author, committer, date).\n+\n+If the above tests fail (e.g. patches modified or removed), StGit creates a\n+new commit on the public branch having the same tree as the stack but the\n+public head as its parent. The editor will be invoked if no \"--message\" option\n+is given.\n+\n+It is recommended that stack modifications falling in different categories as\n+described above are separated by a publish command in order to keep the public\n+branch history cleaner (otherwise StGit would generate a big commit including\n+several stack modifications).\n+\n+The public branch name can be set via the branch.<branch>.public configuration\n+variable (defaulting to \"<branch>.public\").\n+\"\"\"\n+\n+args = [argparse.all_branches]\n+options = [\n+    opt('-b', '--branch', args = [argparse.stg_branches],\n+        short = 'Use BRANCH instead of the default branch')\n+] + (argparse.author_options()\n+     + argparse.message_options(save_template = False)\n+     + argparse.sign_options())\n+\n+directory = common.DirectoryHasRepositoryLib()\n+\n+def __create_commit(repository, tree, parents, options):\n+    \"\"\"Return a new Commit object.\"\"\"\n+    cd = git.CommitData(\n+        tree = tree, parents = parents, message = '',\n+        author = git.Person.author(), committer = git.Person.committer())\n+    cd = common.update_commit_data(cd, options, allow_edit = True)\n+\n+    return repository.commit(cd)\n+\n+def func(parser, options, args):\n+    \"\"\"Publish the stack changes.\"\"\"\n+    repository = directory.repository\n+    stack = repository.get_stack(options.branch)\n+\n+    if not args:\n+        public_ref = config.get('branch.%s.public' % stack.name)\n+        if not public_ref:\n+            public_ref = 'refs/heads/%s.public' % stack.name\n+    elif len(args) == 1:\n+        public_ref = args[0]\n+    else:\n+        parser.error('incorrect number of arguments')\n+\n+    # just clone the stack if the public ref does not exist\n+    if not repository.refs.exists(public_ref):\n+        repository.refs.set(public_ref, stack.head, 'publish')\n+        out.info('Created \"%s\"' % public_ref)\n+        return\n+\n+    public_head = repository.refs.get(public_ref)\n+    public_tree = public_head.data.tree\n+\n+    # check for same tree (already up to date)\n+    if public_tree.sha1 == stack.head.data.tree.sha1:\n+        out.info('\"%s\" already up to date' % public_ref)\n+        return\n+\n+    # check for rebased stack. In this case we emulate a merge with the stack\n+    # base by setting two parents.\n+    merge_base = repository.get_merge_base(public_head, stack.base)\n+    if merge_base.sha1 != stack.base.sha1:\n+        public_head = __create_commit(repository, stack.head.data.tree,\n+                                      [public_head, stack.base], options)\n+        repository.refs.set(public_ref, public_head, 'publish')\n+        out.info('Merged the stack base into \"%s\"' % public_ref)\n+        return\n+\n+    # check for new patches from the last publishing. This is done by checking\n+    # whether the public tree is the same as the bottom of the checked patch.\n+    # If older patches were modified, new patches cannot be detected. The new\n+    # patches and their metadata are pushed directly to the published head.\n+    for p in stack.patchorder.applied:\n+        pc = stack.patches.get(p).commit\n+        if public_tree.sha1 == pc.data.parent.data.tree.sha1:\n+            if pc.data.is_nochange():\n+                out.info('Ignored new empty patch \"%s\"' % p)\n+                continue\n+            cd = pc.data.set_parent(public_head)\n+            public_head = repository.commit(cd)\n+            public_tree = public_head.data.tree\n+            out.start('Published new patch \"%s\"' % p)\n+\n+    # create a new commit (only happens if no new patches are detected)\n+    if public_tree.sha1 != stack.head.data.tree.sha1:\n+        public_head = __create_commit(repository, stack.head.data.tree,\n+                                      [public_head], options)\n+\n+    # update the public head\n+    repository.refs.set(public_ref, public_head, 'publish')\n+    out.info('Updated \"%s\"' % public_ref)\ndiff --git a/stgit/lib/git.py b/stgit/lib/git.py\nindex 6f2c977..4a17c8a 100644\n--- a/stgit/lib/git.py\n+++ b/stgit/lib/git.py\n@@ -609,6 +609,11 @@ class Repository(RunWithEnv):\n             raise DetachedHeadException()\n     def set_head_ref(self, ref, msg):\n         self.run(['git', 'symbolic-ref', '-m', msg, 'HEAD', ref]).no_output()\n+    def get_merge_base(self, commit1, commit2):\n+        \"\"\"Return the merge base of two commits.\"\"\"\n+        sha1 = self.run(['git', 'merge-base',\n+                         commit1.sha1, commit2.sha1]).output_one_line()\n+        return self.get_commit(sha1)\n     def simple_merge(self, base, ours, theirs):\n         index = self.temp_index()\n         try:\ndiff --git a/t/t4100-publish.sh b/t/t4100-publish.sh\nnew file mode 100755\nindex 0000000..17e07bc\n--- /dev/null\n+++ b/t/t4100-publish.sh\n@@ -0,0 +1,129 @@\n+#!/bin/sh\n+#\n+# Copyright (c) 2009 Catalin Marinas\n+#\n+\n+test_description='Exercise the publish command.\n+\n+Create/modify patches on the stack and publish them to a separate branch.'\n+\n+. ./test-lib.sh\n+\n+test_same_tree () {\n+\tstack_tree=$(git rev-parse master^{tree})\n+\tpublic_tree=$(git rev-parse master.public^{tree})\n+\ttest \"$stack_tree\" = \"$public_tree\"\n+}\n+\n+test_expect_success \\\n+\t'Initialize the StGit repository' \\\n+\t'\n+\tstg init\n+\t'\n+\n+test_expect_success \\\n+\t'Create some patches' \\\n+\t'\n+\tstg new p1 -m p1 &&\n+\techo foo1 > foo1.txt &&\n+\tgit add foo1.txt &&\n+\tstg refresh &&\n+\tstg new p2 -m p2 &&\n+\techo foo2 > foo2.txt &&\n+\tgit add foo2.txt &&\n+\tstg refresh &&\n+\tstg new p3 -m p3 &&\n+\techo foo3 > foo3.txt &&\n+\tgit add foo3.txt &&\n+\tstg refresh\n+\t'\n+\n+test_expect_success \\\n+\t'Publish the stack for the first time' \\\n+\t'\n+\tstg publish &&\n+\ttest \"$(stg id)\" = \"$(stg id master.public)\"\n+\t'\n+\n+test_expect_success \\\n+\t'Modify a patch and publish the changes' \\\n+\t'\n+\tstg pop &&\n+\techo foo2 >> foo2.txt &&\n+\tstg refresh &&\n+\tstg push &&\n+\told_public=$(stg id master.public) &&\n+\tstg publish -m \"p2 updated\" &&\n+\ttest_same_tree &&\n+\tnew_public=$(stg id master.public) &&\n+\ttest \"$(git rev-list $old_public..$new_public | wc -l)\" = \"1\"\n+\t'\n+\n+test_expect_success \\\n+\t'Create new patches and publish them' \\\n+\t'\n+\tstg new p4 -m p4 &&\n+\techo foo4 > foo4.txt &&\n+\tgit add foo4.txt &&\n+\tstg refresh &&\n+\tstg new p5 -m p5 &&\n+\techo foo5 > foo5.txt &&\n+\tgit add foo5.txt &&\n+\tstg refresh &&\n+\tstg new empty -m empty &&\n+\told_public=$(stg id master.public) &&\n+\tstg publish -m \"Ignored message\" &&\n+\ttest_same_tree &&\n+\tnew_public=$(stg id master.public) &&\n+\ttest \"$(git rev-list $old_public..$new_public | wc -l)\" = \"2\"\n+\t'\n+\n+test_expect_success \\\n+\t'Rebase the current stack and publish a merge' \\\n+\t'\n+\tstg pop -a &&\n+\techo foo0 > foo0.txt &&\n+\tgit add foo0.txt &&\n+\tgit commit -m \"foo0.txt added\" &&\n+\tstg push -a &&\n+\told_public=$(stg id master.public) &&\n+\tstg publish -m \"Merge with base\" &&\n+\ttest_same_tree &&\n+\tnew_public=$(stg id master.public) &&\n+\ttest \"$(git rev-list $old_public..$new_public | wc -l)\" = \"2\" &&\n+\ttest \"$(git merge-base master.public master)\" = \"$(stg id {base})\"\n+\t'\n+\n+test_expect_success \\\n+\t'Re-publish without any changes' \\\n+\t'\n+\told_public=$(stg id master.public) &&\n+\tstg publish -m \"Ignored message\" &&\n+\ttest_same_tree &&\n+\tnew_public=$(stg id master.public) &&\n+\ttest \"$old_public\" = \"$new_public\"\n+\t'\n+\n+test_expect_success \\\n+\t'Reorder patches and publish the changes' \\\n+\t'\n+\tstg float p5 p4 p3 p2 p1 &&\n+\told_public=$(stg id master.public) &&\n+\tstg publish -m \"Ignored message\" &&\n+\ttest_same_tree &&\n+\tnew_public=$(stg id master.public) &&\n+\ttest \"$old_public\" = \"$new_public\"\n+\t'\n+\n+test_expect_success \\\n+\t'Pop a patch and publish the changes' \\\n+\t'\n+\tstg pop p3 &&\n+\told_public=$(stg id master.public) &&\n+\tstg publish -m \"p3 removed\" &&\n+\ttest_same_tree &&\n+\tnew_public=$(stg id master.public) &&\n+\ttest \"$(git rev-list $old_public..$new_public | wc -l)\" = \"1\"\n+\t'\n+\n+test_done\n\n-- \n .''`.   martin f. krafft <madduck@d.o>      Related projects:\n: :'  :  proud Debian developer               http://debiansystem.info\n`. `'`   http://people.debian.org/~madduck    http://vcs-pkg.org\n  `-  Debian - when you have better things to do than fixing systems\n \na farmer is a man outstanding in his field.\n\n\n_______________________________________________\nvcs-pkg-discuss mailing list\nvcs-pkg-discuss-XbBxUvOt3X2LieD7tvxI8l/i77bcL1HB@public.gmane.org\nhttp://lists.alioth.debian.org/mailman/listinfo/vcs-pkg-discuss\n"},{"id":"114920","messageId":"20090528124817.GA22262@diana.vm.bytemark.co.uk","threadId":"19555","inReplyTo":"20090528111212.21925.45527.stgit@pc1117.cambridge.arm.com","subject":"Re: [RFC][StGit PATCH] Add support for merge-friendly branches","fromName":"Karl Hasselström","fromEmail":"kha@treskal.com","sentAt":"2009-05-28T12:48:17Z","receivedAt":"2009-05-28T12:48:17Z","isPatch":true,"sender":{"key":"kha@treskal.com","avatar":"https://gravatar.com/avatar/f0120c734b5279b345075a28521e1ac66acb20c9913ffe9bf6ae97e53f7f3f13?d=mp&s=160"},"body":"On 2009-05-28 12:12:42 +0100, Catalin Marinas wrote:\n\n> The patch proposes a new StGit command called \"publish\". This\n> command allows one to develop patches normally on a StGit branch but\n> publish the stack changes to a separate, merge-friendly branch whose\n> history is not re-writable.\n\nHmm, interesting. I don't think I'd want to use a command like this\nmyself, but I can see how it might be useful.\n\n> +    # check for same tree (already up to date)\n> +    if public_tree.sha1 == stack.head.data.tree.sha1:\n> +        out.info('\"%s\" already up to date' % public_ref)\n> +        return\n> +\n> +    # check for rebased stack. In this case we emulate a merge with the stack\n> +    # base by setting two parents.\n> +    merge_base = repository.get_merge_base(public_head, stack.base)\n> +    if merge_base.sha1 != stack.base.sha1:\n> +        public_head = __create_commit(repository, stack.head.data.tree,\n> +                                      [public_head, stack.base], options)\n> +        repository.refs.set(public_ref, public_head, 'publish')\n> +        out.info('Merged the stack base into \"%s\"' % public_ref)\n> +        return\n\nHmm. Couldn't the merge base conceivably be higher up in the stack?\nLike, right at the beginning, don't we have public_head == stack.head?\nThat would be caught by the \"same tree\" check\" a bit earlier, but\nafter adding another patch, don't we have public_head == stack.head^ ?\nWhich would give merge_base == public_head.\n\n> +    def get_merge_base(self, commit1, commit2):\n> +        \"\"\"Return the merge base of two commits.\"\"\"\n> +        sha1 = self.run(['git', 'merge-base',\n> +                         commit1.sha1, commit2.sha1]).output_one_line()\n> +        return self.get_commit(sha1)\n\nThis funcion should probably return a list of zero or more merge\nbases. See the --all flag to git merge-base.\n\n-- \nKarl Hasselström, kha@treskal.com\n      www.treskal.com/kalle\n"},{"id":"114923","messageId":"b0943d9e0905280738n51476ab7vd0498ea7a236c4a7@mail.gmail.com","threadId":"19555","inReplyTo":"20090528124817.GA22262@diana.vm.bytemark.co.uk","subject":"Re: [RFC][StGit PATCH] Add support for merge-friendly branches","fromName":"Catalin Marinas","fromEmail":"catalin.marinas@gmail.com","sentAt":"2009-05-28T14:38:44Z","receivedAt":"2009-05-28T14:38:44Z","isPatch":true,"sender":{"key":"catalin.marinas@gmail.com","avatar":null},"body":"2009/5/28 Karl Hasselström <kha@treskal.com>:\n> On 2009-05-28 12:12:42 +0100, Catalin Marinas wrote:\n>\n>> The patch proposes a new StGit command called \"publish\". This\n>> command allows one to develop patches normally on a StGit branch but\n>> publish the stack changes to a separate, merge-friendly branch whose\n>> history is not re-writable.\n>\n> Hmm, interesting. I don't think I'd want to use a command like this\n> myself, but I can see how it might be useful.\n\nFor me it is useful. I publish a kernel tree with over 100 patches.\nLater I find that one patch is buggy. The current merge-friendly\nsolution is to add another patch but I may want to just update the\nbuggy patch as it's easier when time comes to submit upstream. This,\nhowever, rewrites the history. So with the \"publish\" command I just\ngenerate another commit on top of the public branch and I always end\nup with the same tree as on my stack.\n\n>> +    # check for same tree (already up to date)\n>> +    if public_tree.sha1 == stack.head.data.tree.sha1:\n>> +        out.info('\"%s\" already up to date' % public_ref)\n>> +        return\n>> +\n>> +    # check for rebased stack. In this case we emulate a merge with the stack\n>> +    # base by setting two parents.\n>> +    merge_base = repository.get_merge_base(public_head, stack.base)\n>> +    if merge_base.sha1 != stack.base.sha1:\n>> +        public_head = __create_commit(repository, stack.head.data.tree,\n>> +                                      [public_head, stack.base], options)\n>> +        repository.refs.set(public_ref, public_head, 'publish')\n>> +        out.info('Merged the stack base into \"%s\"' % public_ref)\n>> +        return\n>\n> Hmm. Couldn't the merge base conceivably be higher up in the stack?\n> Like, right at the beginning, don't we have public_head == stack.head?\n> That would be caught by the \"same tree\" check\" a bit earlier, but\n> after adding another patch, don't we have public_head == stack.head^ ?\n> Which would give merge_base == public_head.\n\nWe could have public_head == stack.head^... but that's not an issue.\nThe merge_base above is checked against the base of the stack rather\nthan the top as we assume that the base isn't volatile. So even if\npublic_head is the same as some patch commit, the merge_base above\nwould always be the base of the stack. Only if the stack base was\nupdated, we get a different merge_base (equal to the previous stack\nbase).\n\n>> +    def get_merge_base(self, commit1, commit2):\n>> +        \"\"\"Return the merge base of two commits.\"\"\"\n>> +        sha1 = self.run(['git', 'merge-base',\n>> +                         commit1.sha1, commit2.sha1]).output_one_line()\n>> +        return self.get_commit(sha1)\n>\n> This funcion should probably return a list of zero or more merge\n> bases. See the --all flag to git merge-base.\n\nOK, I'll add this and check the stack base against this set(list).\n\n-- \nCatalin\n"},{"id":"114924","messageId":"b0943d9e0905280751k3b9f26c9gd907a7c11ca9d5e2@mail.gmail.com","threadId":"19555","inReplyTo":"b0943d9e0905280738n51476ab7vd0498ea7a236c4a7@mail.gmail.com","subject":"Re: [RFC][StGit PATCH] Add support for merge-friendly branches","fromName":"Catalin Marinas","fromEmail":"catalin.marinas@gmail.com","sentAt":"2009-05-28T14:51:20Z","receivedAt":"2009-05-28T14:51:20Z","isPatch":true,"sender":{"key":"catalin.marinas@gmail.com","avatar":null},"body":"2009/5/28 Catalin Marinas <catalin.marinas@gmail.com>:\n> 2009/5/28 Karl Hasselström <kha@treskal.com>:\n>> On 2009-05-28 12:12:42 +0100, Catalin Marinas wrote:\n>>> +    def get_merge_base(self, commit1, commit2):\n>>> +        \"\"\"Return the merge base of two commits.\"\"\"\n>>> +        sha1 = self.run(['git', 'merge-base',\n>>> +                         commit1.sha1, commit2.sha1]).output_one_line()\n>>> +        return self.get_commit(sha1)\n>>\n>> This funcion should probably return a list of zero or more merge\n>> bases. See the --all flag to git merge-base.\n>\n> OK, I'll add this and check the stack base against this set(list).\n\nWhat about this change to the original patch (it's faster to just\nreturn the sha1 refs than building the Commit objects):\n\n\ndiff --git a/stgit/commands/publish.py b/stgit/commands/publish.py\nindex 06c32d0..ce08a19 100644\n--- a/stgit/commands/publish.py\n+++ b/stgit/commands/publish.py\n@@ -106,8 +106,8 @@ def func(parser, options, args):\n\n     # check for rebased stack. In this case we emulate a merge with the stack\n     # base by setting two parents.\n-    merge_base = repository.get_merge_base(public_head, stack.base)\n-    if merge_base.sha1 != stack.base.sha1:\n+    merge_base = set(repository.get_merge_base_sha1(public_head, stack.base))\n+    if not stack.base.sha1 in merge_base:\n         public_head = __create_commit(repository, stack.head.data.tree,\n                                       [public_head, stack.base], options)\n         repository.refs.set(public_ref, public_head, 'publish')\ndiff --git a/stgit/lib/git.py b/stgit/lib/git.py\nindex 4a17c8a..5bd4e4d 100644\n--- a/stgit/lib/git.py\n+++ b/stgit/lib/git.py\n@@ -609,11 +609,10 @@ class Repository(RunWithEnv):\n             raise DetachedHeadException()\n     def set_head_ref(self, ref, msg):\n         self.run(['git', 'symbolic-ref', '-m', msg, 'HEAD', ref]).no_output()\n-    def get_merge_base(self, commit1, commit2):\n-        \"\"\"Return the merge base of two commits.\"\"\"\n-        sha1 = self.run(['git', 'merge-base',\n-                         commit1.sha1, commit2.sha1]).output_one_line()\n-        return self.get_commit(sha1)\n+    def get_merge_base_sha1(self, commit1, commit2):\n+        \"\"\"Return the merge base of two commits as a list of sha1 refs.\"\"\"\n+        return self.run(['git', 'merge-base', '--all',\n+                         commit1.sha1, commit2.sha1]).output_lines()\n     def simple_merge(self, base, ours, theirs):\n         index = self.temp_index()\n         try:\n\n-- \nCatalin\n"},{"id":"114985","messageId":"20090529072050.GA9760@diana.vm.bytemark.co.uk","threadId":"19555","inReplyTo":"b0943d9e0905280751k3b9f26c9gd907a7c11ca9d5e2@mail.gmail.com","subject":"Re: [RFC][StGit PATCH] Add support for merge-friendly branches","fromName":"Karl Hasselström","fromEmail":"kha@treskal.com","sentAt":"2009-05-29T07:20:50Z","receivedAt":"2009-05-29T07:20:50Z","isPatch":true,"sender":{"key":"kha@treskal.com","avatar":"https://gravatar.com/avatar/f0120c734b5279b345075a28521e1ac66acb20c9913ffe9bf6ae97e53f7f3f13?d=mp&s=160"},"body":"On 2009-05-28 15:51:20 +0100, Catalin Marinas wrote:\n\n> 2009/5/28 Catalin Marinas <catalin.marinas@gmail.com>:\n>\n> > 2009/5/28 Karl Hasselström <kha@treskal.com>:\n> >\n> > > This funcion should probably return a list of zero or more merge\n> > > bases. See the --all flag to git merge-base.\n> >\n> > OK, I'll add this and check the stack base against this set(list).\n>\n> What about this change to the original patch (it's faster to just\n> return the sha1 refs than building the Commit objects):\n\nCreating Commit objects is really cheap---just look at the\nconstructor. I made them that way on purpose, so that we'd never have\nto think twice about using Commit objects instead of passing sha1s\naround.\n\nAlso, you said \"set\", and I agree---the return value of\nget_mege_bases() should be a set. That's what it _is_, conceptually,\nand it makes little sense to obscure that fact.\n\n-- \nKarl Hasselström, kha@treskal.com\n      www.treskal.com/kalle\n"},{"id":"114993","messageId":"20090529083739.GB9760@diana.vm.bytemark.co.uk","threadId":"19555","inReplyTo":"b0943d9e0905280738n51476ab7vd0498ea7a236c4a7@mail.gmail.com","subject":"Re: [RFC][StGit PATCH] Add support for merge-friendly branches","fromName":"Karl Hasselström","fromEmail":"kha@treskal.com","sentAt":"2009-05-29T08:37:39Z","receivedAt":"2009-05-29T08:37:39Z","isPatch":true,"sender":{"key":"kha@treskal.com","avatar":"https://gravatar.com/avatar/f0120c734b5279b345075a28521e1ac66acb20c9913ffe9bf6ae97e53f7f3f13?d=mp&s=160"},"body":"On 2009-05-28 15:38:44 +0100, Catalin Marinas wrote:\n\n> 2009/5/28 Karl Hasselström <kha@treskal.com>:\n>\n> > On 2009-05-28 12:12:42 +0100, Catalin Marinas wrote:\n> >\n> > > The patch proposes a new StGit command called \"publish\". This\n> > > command allows one to develop patches normally on a StGit branch\n> > > but publish the stack changes to a separate, merge-friendly\n> > > branch whose history is not re-writable.\n> >\n> > Hmm, interesting. I don't think I'd want to use a command like\n> > this myself, but I can see how it might be useful.\n>\n> For me it is useful. I publish a kernel tree with over 100 patches.\n> Later I find that one patch is buggy. The current merge-friendly\n> solution is to add another patch but I may want to just update the\n> buggy patch as it's easier when time comes to submit upstream. This,\n> however, rewrites the history. So with the \"publish\" command I just\n> generate another commit on top of the public branch and I always end\n> up with the same tree as on my stack.\n\nI think I would've kludged this by making --theirs merges from the\nStGit branch to the public branch. But \"stg publish\" should definitely\nmake the kludge history less ugly.\n\n> > Hmm. Couldn't the merge base conceivably be higher up in the\n> > stack? Like, right at the beginning, don't we have public_head ==\n> > stack.head? That would be caught by the \"same tree\" check\" a bit\n> > earlier, but after adding another patch, don't we have public_head\n> > == stack.head^ ? Which would give merge_base == public_head.\n>\n> We could have public_head == stack.head^... but that's not an issue.\n> The merge_base above is checked against the base of the stack rather\n> than the top as we assume that the base isn't volatile. So even if\n> public_head is the same as some patch commit, the merge_base above\n> would always be the base of the stack. Only if the stack base was\n> updated, we get a different merge_base (equal to the previous stack\n> base).\n\nThe situation I described looks like this:\n\n    B--o--o--o--o--o--P--T\n\nTime goes from left to right. B is the stack base, P the head of the\npublic branch, T the stack top. merge_base(P, T) is P, and not B.\n\n-- \nKarl Hasselström, kha@treskal.com\n      www.treskal.com/kalle\n"},{"id":"114994","messageId":"b0943d9e0905290140i57aeaa33r8de34302b63ffa5e@mail.gmail.com","threadId":"19555","inReplyTo":"20090529072050.GA9760@diana.vm.bytemark.co.uk","subject":"Re: [RFC][StGit PATCH] Add support for merge-friendly branches","fromName":"Catalin Marinas","fromEmail":"catalin.marinas@gmail.com","sentAt":"2009-05-29T08:40:59Z","receivedAt":"2009-05-29T08:40:59Z","isPatch":true,"sender":{"key":"catalin.marinas@gmail.com","avatar":null},"body":"2009/5/29 Karl Hasselström <kha@treskal.com>:\n> On 2009-05-28 15:51:20 +0100, Catalin Marinas wrote:\n>\n>> 2009/5/28 Catalin Marinas <catalin.marinas@gmail.com>:\n>>\n>> > 2009/5/28 Karl Hasselström <kha@treskal.com>:\n>> >\n>> > > This funcion should probably return a list of zero or more merge\n>> > > bases. See the --all flag to git merge-base.\n>> >\n>> > OK, I'll add this and check the stack base against this set(list).\n>>\n>> What about this change to the original patch (it's faster to just\n>> return the sha1 refs than building the Commit objects):\n>\n> Creating Commit objects is really cheap---just look at the\n> constructor. I made them that way on purpose, so that we'd never have\n> to think twice about using Commit objects instead of passing sha1s\n> around.\n\nI was worried it may invoke git to get the CommitData.\n\n> Also, you said \"set\", and I agree---the return value of\n> get_mege_bases() should be a set. That's what it _is_, conceptually,\n> and it makes little sense to obscure that fact.\n\nIf we return a set of commits, I suspect the Repository object\nguarantees that having the same sha1 value always returns the same\nCommit object and the code below is valid:\n\n    merge_bases = repository.get_merge_bases(public_head, stack.base)\n    if not stack.base in merge_bases:\n        public_head = __create_commit(repository, stack.head.data.tree,\n                                      [public_head, stack.base], options)\n        repository.refs.set(public_ref, public_head, 'publish')\n        out.info('Merged the stack base into \"%s\"' % public_ref)\n        return\n\n...\n\n    def get_merge_bases(self, commit1, commit2):\n        \"\"\"Return a set of merge bases of two commits.\"\"\"\n        sha1_list = self.run(['git', 'merge-base', '--all',\n                              commit1.sha1, commit2.sha1]).output_lines()\n        return set(self.get_commit(sha1) for sha1 in sha1_list)\n\n-- \nCatalin\n"},{"id":"114996","messageId":"20090529091208.GA12039@diana.vm.bytemark.co.uk","threadId":"19555","inReplyTo":"b0943d9e0905290140i57aeaa33r8de34302b63ffa5e@mail.gmail.com","subject":"Re: [RFC][StGit PATCH] Add support for merge-friendly branches","fromName":"Karl Hasselström","fromEmail":"kha@treskal.com","sentAt":"2009-05-29T09:12:08Z","receivedAt":"2009-05-29T09:12:08Z","isPatch":true,"sender":{"key":"kha@treskal.com","avatar":"https://gravatar.com/avatar/f0120c734b5279b345075a28521e1ac66acb20c9913ffe9bf6ae97e53f7f3f13?d=mp&s=160"},"body":"On 2009-05-29 09:40:59 +0100, Catalin Marinas wrote:\n\n> 2009/5/29 Karl Hasselström <kha@treskal.com>:\n>\n> > On 2009-05-28 15:51:20 +0100, Catalin Marinas wrote:\n> >\n> > > 2009/5/28 Catalin Marinas <catalin.marinas@gmail.com>:\n> > >\n> > > What about this change to the original patch (it's faster to\n> > > just return the sha1 refs than building the Commit objects):\n> >\n> > Creating Commit objects is really cheap---just look at the\n> > constructor. I made them that way on purpose, so that we'd never\n> > have to think twice about using Commit objects instead of passing\n> > sha1s around.\n>\n> I was worried it may invoke git to get the CommitData.\n\nOnly if you try to access it---it's evaluated lazily.\n\n> > Also, you said \"set\", and I agree---the return value of\n> > get_mege_bases() should be a set. That's what it _is_,\n> > conceptually, and it makes little sense to obscure that fact.\n>\n> If we return a set of commits, I suspect the Repository object\n> guarantees that having the same sha1 value always returns the same\n> Commit object\n\nYes, exactly. As long as you get all your Commit objects from the\nRepository object like you're supposed to, there'll be at most one\nCommit object for each sha1. In fact, the Commit objects don't have an\n__eq__ method on purpose---straight object comparison already does\nexactly what we want!\n\n> and the code below is valid:\n\nYes, this is precisely what I meant.\n\n-- \nKarl Hasselström, kha@treskal.com\n      www.treskal.com/kalle\n"},{"id":"114997","messageId":"b0943d9e0905290216m3c2bb639kc951510c72212ff@mail.gmail.com","threadId":"19555","inReplyTo":"20090529083739.GB9760@diana.vm.bytemark.co.uk","subject":"Re: [RFC][StGit PATCH] Add support for merge-friendly branches","fromName":"Catalin Marinas","fromEmail":"catalin.marinas@gmail.com","sentAt":"2009-05-29T09:16:52Z","receivedAt":"2009-05-29T09:16:52Z","isPatch":true,"sender":{"key":"catalin.marinas@gmail.com","avatar":null},"body":"2009/5/29 Karl Hasselström <kha@treskal.com>:\n> On 2009-05-28 15:38:44 +0100, Catalin Marinas wrote:\n> I think I would've kludged this by making --theirs merges from the\n> StGit branch to the public branch. But \"stg publish\" should definitely\n> make the kludge history less ugly.\n\nThat's what I'm trying to do, keep the public history clean. One\nadvantage of merging the full StGit branch is that people could\nretrieve the latest patch version but for those interesting in\ncherry-picking you can just export the volatile StGit branch.\n\nRegarding the resulting tree, rebasing a StGit stack is equivalent, on\na linear history branch, to a merge of the new stack base into the\nlinear branch. Rather than having to solve conflicts twice, the pubish\ncommand just fakes this merge and sets the resulting tree.\n\n>> > Hmm. Couldn't the merge base conceivably be higher up in the\n>> > stack? Like, right at the beginning, don't we have public_head ==\n>> > stack.head? That would be caught by the \"same tree\" check\" a bit\n>> > earlier, but after adding another patch, don't we have public_head\n>> > == stack.head^ ? Which would give merge_base == public_head.\n>>\n>> We could have public_head == stack.head^... but that's not an issue.\n>> The merge_base above is checked against the base of the stack rather\n>> than the top as we assume that the base isn't volatile. So even if\n>> public_head is the same as some patch commit, the merge_base above\n>> would always be the base of the stack. Only if the stack base was\n>> updated, we get a different merge_base (equal to the previous stack\n>> base).\n>\n> The situation I described looks like this:\n>\n>    B--o--o--o--o--o--P--T\n>\n> Time goes from left to right. B is the stack base, P the head of the\n> public branch, T the stack top. merge_base(P, T) is P, and not B.\n\nI don't check merge_base(P, T) but merge_base(P, B) to avoid the\nissues you described. So that's always B.\n\n-- \nCatalin\n"},{"id":"115000","messageId":"b0943d9e0905290313gc37d98cy769f57a4715cae14@mail.gmail.com","threadId":"19555","inReplyTo":"b0943d9e0905290140i57aeaa33r8de34302b63ffa5e@mail.gmail.com","subject":"Re: [RFC][StGit PATCH] Add support for merge-friendly branches","fromName":"Catalin Marinas","fromEmail":"catalin.marinas@gmail.com","sentAt":"2009-05-29T10:13:14Z","receivedAt":"2009-05-29T10:13:14Z","isPatch":true,"sender":{"key":"catalin.marinas@gmail.com","avatar":null},"body":"2009/5/29 Catalin Marinas <catalin.marinas@gmail.com>:\n>    merge_bases = repository.get_merge_bases(public_head, stack.base)\n>    if not stack.base in merge_bases:\n>        public_head = __create_commit(repository, stack.head.data.tree,\n>                                      [public_head, stack.base], options)\n>        repository.refs.set(public_ref, public_head, 'publish')\n>        out.info('Merged the stack base into \"%s\"' % public_ref)\n>        return\n\nOne minor improvement here, trying to guess what was merged:\n\ndiff --git a/stgit/commands/publish.py b/stgit/commands/publish.py\nindex fe7f7c6..401fbdf 100644\n--- a/stgit/commands/publish.py\n+++ b/stgit/commands/publish.py\n@@ -67,10 +67,10 @@ options = [\n\n directory = common.DirectoryHasRepositoryLib()\n\n-def __create_commit(repository, tree, parents, options):\n+def __create_commit(repository, tree, parents, options, message = ''):\n     \"\"\"Return a new Commit object.\"\"\"\n     cd = git.CommitData(\n-        tree = tree, parents = parents, message = '',\n+        tree = tree, parents = parents, message = message,\n         author = git.Person.author(), committer = git.Person.committer())\n     cd = common.update_commit_data(cd, options, allow_edit = True)\n\n@@ -106,8 +106,10 @@ def func(parser, options, args):\n     # base by setting two parents.\n     merge_bases = repository.get_merge_bases(public_head, stack.base)\n     if not stack.base in merge_bases:\n+        message = 'Merge ' + repository.describe(stack.base)\n         public_head = __create_commit(repository, stack.head.data.tree,\n-                                      [public_head, stack.base], options)\n+                                      [public_head, stack.base], options,\n+                                      message)\n         repository.refs.set(public_ref, public_head, 'publish')\n         out.info('Merged the stack base into \"%s\"' % public_ref)\n         return\ndiff --git a/stgit/lib/git.py b/stgit/lib/git.py\nindex 3303eea..6e3bb4f 100644\n--- a/stgit/lib/git.py\n+++ b/stgit/lib/git.py\n@@ -614,6 +614,10 @@ class Repository(RunWithEnv):\n         sha1_list = self.run(['git', 'merge-base', '--all',\n                               commit1.sha1, commit2.sha1]).output_lines()\n         return set(self.get_commit(sha1) for sha1 in sha1_list)\n+    def describe(self, commit):\n+        \"\"\"Use git describe --all on the given commit.\"\"\"\n+        return self.run(['git', 'describe', '--all', commit.sha1]\n+                       ).discard_stderr().discard_exitcode().raw_output()\n     def simple_merge(self, base, ours, theirs):\n         index = self.temp_index()\n         try:\n\n-- \nCatalin\n"},{"id":"115005","messageId":"20090529115920.GA14429@diana.vm.bytemark.co.uk","threadId":"19555","inReplyTo":"b0943d9e0905290216m3c2bb639kc951510c72212ff@mail.gmail.com","subject":"Re: [RFC][StGit PATCH] Add support for merge-friendly branches","fromName":"Karl Hasselström","fromEmail":"kha@treskal.com","sentAt":"2009-05-29T11:59:20Z","receivedAt":"2009-05-29T11:59:20Z","isPatch":true,"sender":{"key":"kha@treskal.com","avatar":"https://gravatar.com/avatar/f0120c734b5279b345075a28521e1ac66acb20c9913ffe9bf6ae97e53f7f3f13?d=mp&s=160"},"body":"On 2009-05-29 10:16:52 +0100, Catalin Marinas wrote:\n\n> 2009/5/29 Karl Hasselström <kha@treskal.com>:\n>\n> > The situation I described looks like this:\n> >\n> >    B--o--o--o--o--o--P--T\n> >\n> > Time goes from left to right. B is the stack base, P the head of\n> > the public branch, T the stack top. merge_base(P, T) is P, and not\n> > B.\n>\n> I don't check merge_base(P, T) but merge_base(P, B) to avoid the\n> issues you described. So that's always B.\n\nAh, so that's where I got myself confused. Thanks.\n\n-- \nKarl Hasselström, kha@treskal.com\n      www.treskal.com/kalle\n"}]}