diff --git a/pym/bob/scm/git.py b/pym/bob/scm/git.py index 8a467a869..ee5c39586 100644 --- a/pym/bob/scm/git.py +++ b/pym/bob/scm/git.py @@ -295,14 +295,30 @@ async def __checkoutTagOnBranch(self, invoker, fetchCmd, switch): else: # We're switching the ref and the branch exists already. Be extra # careful: the user might have committed to this branch, some other - # branch might be checked out currently or both of that. To keep - # things simple, assume a fast-forward of the commit on the branch. - # It will catch user changes and is usually safe wrt. submodules. + # branch might be checked out currently or both of that. await invoker.checkCommand(["git", "checkout", "--no-recurse-submodules", self.__branch], cwd=self.__dir) preUpdate = await self.__updateSubmodulesPre(invoker) - await invoker.checkCommand(["git", "-c", "submodule.recurse=0", "merge", - "--ff-only", commit], cwd=self.__dir) + + # check if any remote or any other than the local branch holds the current + # commit. Otherwise we'd lose it when going back in history. + contains = await invoker.runCommand(["git", "branch", "-a", + "--format=%(refname:lstrip=2)", + "--contains", "HEAD"], + cwd=self.__dir, stdout=True) + currentBranch = await invoker.runCommand(["git", "rev-parse", + "--abbrev-ref", "HEAD"], + cwd=self.__dir, stdout=True) + for b in contains.stdout.splitlines(): + if b.rstrip() != currentBranch.stdout.rstrip(): + break + else: + # neither a remote nor another local branch contains the current commit + # move to attic + invoker.fail("Cannot switch: Current state woulde be lost.") + + await invoker.checkCommand(["git", "-c", "submodule.recurse=0", "reset", + "--keep", commit], cwd=self.__dir) await self.__updateSubmodulesPost(invoker, preUpdate) async def __checkoutTag(self, invoker, fetchCmd, switch): diff --git a/test/black-box/git-scm-switch/run.sh b/test/black-box/git-scm-switch/run.sh index 0e624b259..b7ebcc807 100755 --- a/test/black-box/git-scm-switch/run.sh +++ b/test/black-box/git-scm-switch/run.sh @@ -136,9 +136,52 @@ run_bob dev -c submodules -DSCM_DIR="$git_dir1" -DSCM_REV="$d1_c0" -DSCM_BRANCH= expect_output "foobar" git -C dev/src/root2/1/workspace rev-parse --abbrev-ref HEAD expect_output "$d1_c0" git -C dev/src/root2/1/workspace rev-parse HEAD expect_not_exist dev/src/root2/1/workspace/submod/sub.txt +expect_output "hello world" cat dev/src/root2/1/workspace/test.txt # Moving to a later commit will update the branch and bring in the submodules. run_bob dev -c submodules -DSCM_DIR="$git_dir1" -DSCM_REV="$d1_c1" -DSCM_BRANCH=foobar root2 -vv expect_output "foobar" git -C dev/src/root2/1/workspace rev-parse --abbrev-ref HEAD expect_output "$d1_c1" git -C dev/src/root2/1/workspace rev-parse HEAD expect_exist dev/src/root2/1/workspace/submod/sub.txt + +# Move to c2 but without submodules. Otherwise every revision change triggers a move to attic and +# hides potential issues when going back to older revisions +run_bob dev -DSCM_DIR="$git_dir1" -DSCM_REV="$d1_c2" -DSCM_BRANCH=foobar root2 -vv +expect_output "changed" cat dev/src/root2/1/workspace/test.txt + +# Move back to a older commit +run_bob dev -DSCM_DIR="$git_dir1" -DSCM_REV="$d1_c0" -DSCM_BRANCH=foobar root2 -vv +expect_output "foobar" git -C dev/src/root2/1/workspace rev-parse --abbrev-ref HEAD +expect_output "$d1_c0" git -C dev/src/root2/1/workspace rev-parse HEAD +expect_not_exist dev/src/root2/1/workspace/submod/sub.txt +expect_output "hello world" cat dev/src/root2/1/workspace/test.txt + +# make a new commit but do not push it and run the update. This should move to attic as we'd lose +# the commit when switching. +pushd dev/src/root2/1/workspace +git config user.email "bob@bob.bob" +git config user.name test +echo "local_commit" > test.txt +git commit -a -m "just a local change" +popd +rm dev/src/root/1/attic* -rf +run_bob dev -DSCM_DIR="$git_dir1" -DSCM_REV="$d1_c1" -DSCM_BRANCH=foobar root2 -vv +ls -la dev/src/root2/1/workspace +expect_output "hello world" cat dev/src/root2/1/workspace/test.txt +expect_exist dev/src/root2/1/attic + +# make a new commit, switch to a different local branch and run the update. This should not move to attic. +# But first go back... +run_bob dev -DSCM_DIR="$git_dir1" -DSCM_REV="$d1_c0" -DSCM_BRANCH=foobar root2 -vv +pushd dev/src/root2/1/workspace +git config user.email "bob@bob.bob" +git config user.name test +echo "local_commit" > test.txt +git commit -a -m "just a local change" +git checkout -b foobar2 +popd +rm dev/src/root2/1/attic* -rf +run_bob dev -DSCM_DIR="$git_dir1" -DSCM_REV="$d1_c1" -DSCM_BRANCH=foobar root2 -vv +ls -la dev/src/root2/1/workspace +expect_output "hello world" cat dev/src/root2/1/workspace/test.txt +expect_not_exist dev/src/root2/1/attic