From 60602ea822489a491176e00a8a9bcc8bdca933c1 Mon Sep 17 00:00:00 2001 From: Rich Fromm Date: Tue, 7 Jun 2016 14:04:07 -0700 Subject: [PATCH 1/4] Skip create tag during finish if it already exists The tagging during finish had a comment copied directly copied from the source of the bash implementation of git-flow: In case a previous attempt to finish this release branch has failed, but the merge into develop was successful, we skip it now. But this had not been implemented. As long as tagging_info was set, the tag creation was unconditional. This change conditions it on the tag not yet existing. Without this change, the call to finish is not idempotent. Consider this sequence: * User calls finish * Finish tags the repo * Finish fails merging back to develop due to merge conflict * User resolves merge conflict * User calls finish again * Finish fails tagging the repo because the tag already exists By conditioning the tag, finish is idempotent. The second tag attempt is a NOP. Another commented acknowledged the non-implementation (via ":todo:") of a check that if the tag exists, it points to the commit. This change implements this check as well. Raise TagExistsError if the check fails. --- gitflow/branches.py | 14 +++++++++----- 1 file changed, 9 insertions(+), 5 deletions(-) diff --git a/gitflow/branches.py b/gitflow/branches.py index 32f885266..45bc8eb2f 100644 --- a/gitflow/branches.py +++ b/gitflow/branches.py @@ -423,11 +423,15 @@ def finish(self, name, fetch=False, rebase=False, keep=False, # In case a previous attempt to finish this release branch # has failed, but the tag was set successful, we skip it # now. - # :todo: check: if tag exists, it must point to the commit - tag = gitflow.tag( - tagname, self.gitflow.master_name(), - **tagging_info) - to_push.append(tagname) + if tagname not in self.gitflow.repo.tags: + tag = gitflow.tag( + tagname, self.gitflow.master_name(), + **tagging_info) + to_push.append(tagname) + elif self.gitflow.repo.tags[tagname].commit != self.gitflow.master().commit: + # if tag exists, it must point to the commit + raise TagExistsError('Tag already exists and does not point to %s branch %s' + % (self.identifier, name)) # merge the master branch back into develop; this makes the # master branch - and the new tag (if provided) - a parent of From f0d2bc2337744716c6dfa4221708e6256716e93f Mon Sep 17 00:00:00 2001 From: Rich Fromm Date: Tue, 7 Jun 2016 14:08:54 -0700 Subject: [PATCH 2/4] Add test_finish_release_merge_conflict_tag Implement ":todo:" item of: * test-cases for finish + tag with merge-conflicts on develop Without the fix in the previous commit, this new test fails. With the fix in the previous commit, all tests pass. Modify helpers as needed to support this new test. Note that two related ":todo:" items are still *not* implemented: * test-cases for finish with merge-conflicts on master * test-cases for finish + tag with merge-conflicts on master --- tests/gitflow/test_branches.py | 53 ++++++++++++++++++++++++++++++++-- tests/helpers/__init__.py | 16 ++++++++-- 2 files changed, 64 insertions(+), 5 deletions(-) diff --git a/tests/gitflow/test_branches.py b/tests/gitflow/test_branches.py index cb02fbffa..f6c4d453f 100644 --- a/tests/gitflow/test_branches.py +++ b/tests/gitflow/test_branches.py @@ -20,7 +20,8 @@ HotfixBranchManager, SupportBranchManager) from tests.helpers import (copy_from_fixture, remote_clone_from_fixture, - fake_commit, all_commits, set_gnupg_home) + fake_commit, write_file, + all_commits, set_gnupg_home) from tests.helpers.factory import create_git_repo __copyright__ = "2010-2011 Vincent Driessen; 2012-2013 Hartmut Goebel; 2015 Christian Assing" @@ -1066,8 +1067,56 @@ def test_finish_release_unresolved_merge_conflict(self): self.assertRaises(GitCommandError, mgr.finish, '1.0') + @copy_from_fixture('release') + def test_finish_release_merge_conflict_tag(self): + """ + finish + tag with merge-conflicts on develop + """ + version_filename = 'VERSION' + new_version = '1.1\n' + + gitflow = GitFlow(self.repo).init() + fmgr = FeatureBranchManager(gitflow) + fmgr.finish('even') + fake_commit(self.repo, 'Overwrite version', + filename=version_filename, + change=new_version) + + # verify that the tag does not yet exist + # "v" comes form "versiontag" prefix in the gitflow config for the "release" fixture + self.assertNotIn('v1.0', self.repo.tags) + + mgr = ReleaseBranchManager(gitflow) + taginfo = dict( + message='Tagging version 1.0', + ) + self.assertRaises(MergeError, + mgr.finish, '1.0', tagging_info=taginfo) + + # verify that the tag exists, even though there was a failed merge + self.assertIn('v1.0', self.repo.tags) + + # resolve the conflict + # this is in favor of the change on develop + write_file(filename=version_filename, + append=False, + change=new_version) + gitflow.git.add(version_filename) + gitflow.git.commit('-F.git/MERGE_MSG') + # the release branch is still here + self.assertIn('rel/1.0', + [b.name for b in self.repo.branches]) + # finish the release again + # this should skip the tagging, since that part previously succeeded + mgr.finish('1.0', tagging_info=taginfo) + # now the release branch is gone + self.assertNotIn('rel/1.0', + [b.name for b in self.repo.branches]) + + # verify that the tag still exists + self.assertIn('v1.0', self.repo.tags) + # :todo: test-cases for finish with merge-conflicts on master - # :todo: test-cases for finish + tag with merge-conflicts on develop # :todo: test-cases for finish + tag with merge-conflicts on master diff --git a/tests/helpers/__init__.py b/tests/helpers/__init__.py index f6ebdcdf8..3795e53b8 100644 --- a/tests/helpers/__init__.py +++ b/tests/helpers/__init__.py @@ -177,16 +177,26 @@ def set_git_working_dir(repo, *args, **kwargs): return set_git_working_dir -@git_working_dir -def fake_commit(repo, message, append=True, filename='newfile.py'): +def write_file(filename, append, change): + """ + Write the contents of "change" into "filename" + + If "append", append to the end. Else, replace the contents. + """ if append: f = open(filename, 'a') else: f = open(filename, 'w') try: - f.write('This is a dummy change.\n') + f.write(change) finally: f.close() + + +@git_working_dir +def fake_commit(repo, message, append=True, + filename='newfile.py', change='This is a dummy change.\n'): + write_file(filename, append, change) repo.index.add([filename]) return repo.index.commit(message) From 51db3c7d333d5a0005d838ad1a29bf7127e456ef Mon Sep 17 00:00:00 2001 From: Christian Assing Date: Wed, 15 Jun 2016 20:23:38 +0200 Subject: [PATCH 3/4] changes --- CHANGES.txt | 9 +++++++++ 1 file changed, 9 insertions(+) diff --git a/CHANGES.txt b/CHANGES.txt index 76aeed259..453fccbab 100644 --- a/CHANGES.txt +++ b/CHANGES.txt @@ -1,3 +1,12 @@ +v1.0.2: + +Changes since 1.0.0dev + +* tests - Use GIT_* envvars for author and committer +* Call higher level merge_base() to compare branches +* Add tests for _compare_branches() +* Skip create tag during finish if it already exists - The previous comparison of the merge base to either of the branches was not correct. + v1.0.0dev --------- From 7887759329ec9847224eba8a9a25b04427e8c2e0 Mon Sep 17 00:00:00 2001 From: Christian Assing Date: Wed, 15 Jun 2016 20:24:32 +0200 Subject: [PATCH 4/4] release 1.0.2 --- gitflow/__init__.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/gitflow/__init__.py b/gitflow/__init__.py index 46408d314..ccda0bb8b 100644 --- a/gitflow/__init__.py +++ b/gitflow/__init__.py @@ -13,7 +13,7 @@ # Distributed under a BSD-like license. For full terms see the file LICENSE.txt # -VERSION = (1, 0, 1,) +VERSION = (1, 0, 2,) __version__ = ".".join(map(str, VERSION[0:3])) + "".join(VERSION[3:]) __author__ = "Christian Assing, Hartmut Goebel, Vincent Driessen"