Skip to content

Commit

Permalink
git: fix scm switch when going back to older revision
Browse files Browse the repository at this point in the history
If gitCommitOnBranch is active and commit and branch are configured in
the recipe changing the commit back to a older one did not update the
sources back to the configured version. Instead they remained on the
current version.

To fix this use `git reset --keep` instead of `git merge --ff-only` when
updating, but make sure we do not lose any commits.
  • Loading branch information
rhubert committed Sep 5, 2023
1 parent d8326d7 commit 31ed740
Show file tree
Hide file tree
Showing 2 changed files with 60 additions and 5 deletions.
26 changes: 21 additions & 5 deletions pym/bob/scm/git.py
Expand Up @@ -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):
Expand Down
39 changes: 39 additions & 0 deletions test/black-box/git-scm-switch/run.sh
Expand Up @@ -136,9 +136,48 @@ 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
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
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

0 comments on commit 31ed740

Please sign in to comment.