tags:

views:

56

answers:

3

So here's the scenario. I've got a script that runs some tests. I need to make another script that accepts as a parameter a git commit name and then does the following:

  1. Switches to a detached HEAD at the specified commit
  2. Runs the test script against that commit
  3. Switches back so HEAD is the same as it was before this business

I need to make sure this script is robust so that it's never destructive no matter the state of the repository. It should work when it's run from a detached HEAD or from a regular branch, and preferably it should work even when there are uncommitted or unstaged changes around.

I feel like this should be an easy question to answer, since running a test script against a previous commit seems like a really common task to want to automate. But I can't seem to find any simple series of commands to do it.

A: 

try (not tested)

git checkout HEAD@{1}

to "switches back so HEAD" as it was before your checkout xxx.

See also "HEAD and ORIG_HEAD in Git"


All the other revision specification are here: rev_parse, "SPECIFYING REVISIONS" section.
For instance, to get back to the previous branch, you can try @{-1}.

Just tested it (the "previous HEAD" option):

Simple git repo with 3 files added in three commits (a, then b, then c):

C:\git\tests\p3>git log --oneline
6e5b961 c
66c68e3 b
77e9a40 a

I checkout the first commit (DETACHED HEAD)

C:\git\tests\p3>git checkout 77e9a40
Note: moving to '77e9a40' which isn't a local branch
If you want to create a new branch from this checkout, you may do so
(now or later) by using -b with the checkout command again. Example:
  git checkout -b <new_branch_name>
HEAD is now at 77e9a40... a

C:git\tests\p3>dir
08/12/2010  12:27 PM                 4 a.txt

I try to get back to the previous HEAD before making the DETACHED HEAD:

C:\git\tests\p3>git checkout HEAD@{1}
Previous HEAD position was 77e9a40... a
HEAD is now at 6e5b961... c

It works!

You get back the right commit, but not the right branch (i.e. you are still in a detached mode)

C:\git\tests\p3>git branch
* (no branch)
  master

In this configuration, trying to get back to the previous branch wouldn't work

C:\git\tests\p3>git checkout HEAD@{-1}
error: pathspec 'HEAD@{-1}' did not match any file(s) known to git.

So the only real solution to get back to the HEAD (not to a commit in a detached mode) is to memorize it first

git symbolic-ref HEA

See Jefromi's answer.

VonC
Didn't work--after the git checkout HEAD@{1}, the repository was in a detached HEAD state, with HEAD pointing to the last commit on the branch, instead of just being on that branch...
Eddy
@Eddy: I just tested it, it works, see my updated answer. (but make sure you weren't already in a DETACHED HEAD mode before making a `git checkout commit`, making *another DETACHED HEAD`)
VonC
Hm, does this really work? It looks to me, like Eddy said, that it's checking out the commit that was previously referred to by HEAD, and even if that HEAD was actually pointing to master which pointed to that commit, and master hasn't since moved, it still doesn't point HEAD to master, just the commit.
Jefromi
Perhaps you're looking for `@{-1}`, as mentioned in the answer linked to by Stefan Näwe.
Jefromi
@Eddy: I have updated my answer to explain why it doesn't work. See Jeformi's answer for a more complete solution.
VonC
+1  A: 

You mean like in this question ?

Stefan Näwe
No. It won't work. `HEAD@{1}` won't restore the right branch (or won't restore any branch at all). See my updated answer.
VonC
+1  A: 

If it's in a script, for only this one use case, you don't need to do anything super-fancy, just store where HEAD was before, and check it out again after:

# If HEAD is a sym-ref, the first assignment will work
# otherwise, it's detached, so get the SHA1 with rev-parse
if ! head=$(git symbolic-ref HEAD 2>&1); then
    head=$(git rev-parse HEAD)
fi
# trim a refs/heads/ prefix; no-op otherwise
head=${head#refs/heads/}


# now go on and do your stuff, test, whatever you like

# then return to where you were
git checkout -f $head

This has the advantage of working no matter what you do in the middle - in particular you could do a lot of git operations there - perhaps a test merge, or cherry-pick a commit for testing (maybe testing that commit, maybe it contains some build configuration settings purely for testing). Since those operations create commits, they'd cause the HEAD@{1} approach to fail (you'd want HEAD@{2} instead). Even better, if your testing actually involves creating temporary branches, this will still work, while the @{-1} approach would not.

(Plus, as far as I can tell, HEAD@{1} always checks out the commit referred to by HEAD at that point, not the branch that was then pointing to that commit. That kind of makes sense, as the branch could've conceivably changed since then.)

Jefromi
I have updated my answer to illustrate its drawback (detached mode at the end). +1 to your script.
VonC
This approach isn't restoring the right branch either. "git symbolic-ref HEAD" gives "refs/heads/master", and if I do a "git checkout refs/heads/master" it actually causes HEAD to become detached. The @{-1} notation did work, and I might end up going with that (even though, as you mentioned, it wouldn't work if the testing involved certain git operations... but I think I can guarantee that it won't).
Eddy
@Eddy: Oh, I forgot it did that. I edited it to trim the refs/heads/ prefix. No big deal.
Jefromi