151

I am doing a git bisect and after arriving to the problematic commit, I am now trying to get a step forward/backward to make sure I am in the right one.

I know of HEAD^ to go backwards in history but is there another shortcut to get me forward (towards a specific commit in the future) like so:

A - B - C(HEAD) - D - E - F

I know that my target is F and I want to move from C to D.


NOTE: this is not a duplicate of Git: How to move back and forth between commits, my question is slightly different and is not answered there

Community
  • 1
  • 1
Kostas
  • 8,356
  • 11
  • 47
  • 63
  • 1
    http://stackoverflow.com/questions/2263674/how-do-i-find-the-next-commit-in-git can help too. – VonC Jul 20 '11 at 09:44
  • 1
    `git checkout -b new_branch HEAD~4` to go back 4 commits from HEAD as in https://stackoverflow.com/a/4940090/911945 – Anton Tarasenko Oct 30 '17 at 10:02

16 Answers16

95

I believe you can do:

git reset HEAD@{1}

To go one commit forward in time. To go forward multiple commits, use HEAD@{2}, HEAD@{3}, etc.

S.S. Anne
  • 15,171
  • 8
  • 38
  • 76
w0utert
  • 1,343
  • 9
  • 8
81

I've experimented a bit and this seems to do the trick to navigate forwards (edit: it works well only when you have a linear history without merge commits):

git checkout $(git rev-list --topo-order HEAD..towards | tail -1)

where towards is a SHA1 of the commit or a tag.

Explanation:

  • the command inside $() means: get all the commits between current HEAD and towards commit (excluding HEAD), and sort them in the precedence order (like in git log by default -- instead of the chronological order which is weirdly the default for rev-list), and then take the last one (tail), i.e. the one we want to go to.
  • this is evaluated in the subshell, and passed to git checkout to perform a checkout.

You can define a function accessible as a parameter-expecting alias in your .profile file to navigate forward towards the particular commit:

# Go forward in Git commit hierarchy, towards particular commit 
# Usage:
#  gofwd v1.2.7
# Does nothing when the parameter is not specified.
gofwd() {
  git checkout $(git rev-list --topo-order HEAD.."$*" | tail -1)
}

# Go back in Git commit hierarchy
# Usage: 
#  goback
alias goback='git checkout HEAD~'
jakub.g
  • 38,512
  • 12
  • 92
  • 130
  • 2
    Going forward works fine on straight parts of history but goes into loops when encountering a merge. – Kostas Jan 18 '13 at 15:23
  • Yeah, I actually haven't tested it on merges. I'll try to to have a look in spare time, but I have little incentive temporarily, since we've agreed to have a strictly linear history in our project ;) – jakub.g Jan 18 '13 at 16:48
  • 4
    Great answer! Modified to automatically specify current branch: http://stackoverflow.com/a/23172256/480608 – Raine Revere Apr 19 '14 at 16:08
  • This is incorrect. `git log` shows commits in chronological order, by default, the same as `rev-list`, except when using the `--graph` flag. – papiro Jun 17 '19 at 15:20
  • Pretty convincing evidence that git is too complex. For something that's usually as simple as Undo or Redo, here we have a crazy list of conflicting answers. And I haven't even gone into any of the linked answers. FWIW, I tried a version of this with a simple linear set of commits and finally just gave up. – Snowcrash Aug 20 '19 at 11:47
  • 2
    By adding `forward = !sh -c \"git switch --detach $(git rev-list --topo-order HEAD.."$1" | tail -1)\"` to the `[alias]` section of your `.gitconfig`, you can enhance git with the `forward` sub command. For example, in order to move to master you'd enter `git forward master`. – trkoch Dec 03 '20 at 16:47
  • 1
    For people working with merges, eg walking forwards on master taking only merge commits you can add `--first-parent` to `git rev-list`. – Zev Isert May 25 '22 at 19:39
57

All you need to get clear, not detached head state is to reset, not checkout.

git reset HEAD@{1}
d3day
  • 872
  • 8
  • 12
  • 7
    or `git reset "HEAD@{1}"` in certain shells like fish and powershell.. `git reflog` can also be useful to find the correct commit. – steve cook Jul 15 '18 at 07:04
  • 2
    Always use single quotes in shell commands unless you explicitly want the shell to attempt to interpret/expand the contents. This is especially important in cases like this, where the goal is to prevent the shell from interpreting special characters. That way, you don’t need to know whether the string contains anything that’s problematic. – Chris Page Mar 26 '19 at 05:12
  • I did that to have a look back in time, then I did `git reset HEAD` to go back to where I was... now I have no idea what state my repository is in and everything is scary. What should I do now? – theonlygusti Jul 18 '20 at 22:20
  • That shouldn't have affected much, to make sure though you can just use `git checkout master` or any branch you want to go to. – Ray Apr 05 '22 at 15:08
31

This is what I'm using to navigate back and forth.

moving to next commit

function n() {
    git log --reverse --pretty=%H master | grep -A 1 $(git rev-parse HEAD) | tail -n1 | xargs git checkout
}

moving to previous commit

function p() {
    git checkout HEAD^1
}
M K
  • 9,138
  • 7
  • 43
  • 44
  • 2
    Thanks! I'm using this now! **Notes for other beginners like me**: to reattach HEAD, `git checkout ` attaches to latest commit. `git checkout -b ` from current commit allows changes in new branch. `git rebase -i` also works. **Also**, I named my `n()` function as `nx()` to avoid conflicting with node version manager "n". Make sure to check aliases! – Steven Choi Dec 25 '18 at 19:44
  • function(){...} is for authoring a Unix/Linux bash scripting file, I come from Windows, a little hard for me to understand firstly – IcyBrk Apr 19 '19 at 23:56
9

Say F is the latest commit on trunk (insert your own branch name here) ... you can refer to it as trunk~0 (or just trunk), E as trunk~1, D as trunk~2 etc.

Take a look in your reflog for yet more ways to name commits.

Useless
  • 64,155
  • 6
  • 88
  • 132
  • 1
    ~ goes back, not foward, trunk~2 is A – EmmanuelMess Nov 30 '17 at 23:39
  • Yes. This answer assumes you have a branch called trunk pointing to `F`, and that you know where in the history of that branch you want to move to. It's not trying to move forward relative to HEAD, but less-far-back relative to trunk. – Useless Mar 28 '19 at 16:42
  • @theonlygusti You move back from HEAD twice. – EmmanuelMess Jul 19 '20 at 22:31
  • You're still assuming that the branch `trunk` and your current `HEAD` are identical which is _not_ shown in the question, which I've stated is _not_ what I'm assuming, and which is very unlikely half-way through a `bisect` – Useless Jul 20 '20 at 07:17
7

Traversing backward is trivial since you are moving down the tree, and there's always one way to go

  function git_down
        git checkout HEAD^
  end

When traversing forward you are moving up the tree, so you need to be explicit which branch you are targeting:

  function git_up 
        git log --reverse --pretty=%H $argv | grep -A 1 (git rev-parse HEAD) | tail -n1 | xargs git checkout
  end

Usage: git_down, git_up <branch-name>

mike rodent
  • 14,126
  • 11
  • 103
  • 157
Dziamid
  • 11,225
  • 12
  • 69
  • 104
  • 2
    Going backward isn't entirely unique either, when merges are involved. Though `HEAD^` is usually a reasonable default. – kdb Mar 26 '20 at 13:14
3

If you want to see ahead, you can do this trick, as Git doesn't have strict command for it.

git log --reverse COMMIT_HASH..

Example

List of log history hashes:

A
B
C -> put this
D

using command git log --reverse C.., in output you will see B and A.

dimpiax
  • 12,093
  • 5
  • 62
  • 45
  • this is only going to work if you are not in commit `C`. Otherwise, you'll have to specify an ending commit or branch. – Federico Jan 20 '21 at 20:38
2

I just did a test on this. say for example you are in master branch Then do:

git checkout HEAD@{3}

So head gets detached, and you can then try it again to go to any other commit:

git checkout HEAD@{4}

Once you are done looking around, you can go back to your original state just by checking out to that branch. In my example: master branch

git checkout master

If you don't want to go to original state, and want so keep one of the commits as your head and continue from there, then you need to branch out from there. for example after "git checkout HEAD@{4}" , you can issue

git checkout -b MyNewBranch
user1322977
  • 141
  • 2
  • 9
2

I'm joining this comments a little bit later but I built this line of code that helped me go "forward" on any git tree and I hope you can find it useful as well.

Note that I am not a bash magician so if you find something that could be changed, comment and we can make this line of code a better one!

To go backward:

git log --all --decorate --oneline | grep -A 1 $(git rev-parse --short HEAD) | awk '{print $1}' | tail -1 | xargs -I {} git checkout {}

To go forward:

git log --all --decorate --oneline | grep -B 1 $(git rev-parse --short HEAD) | awk '{print $1}' | head -1 | xargs -I {} git checkout {}

Here is some explanation:

git reflog # Here we get all the commits we need

grep -A 1 $(git rev-parse --short HEAD) # Here we grep for the short commit hash

awk '{print $1}' # Here we get just the hash

tail -1 # We will get at least 2 results, pick the last line, the first would be 0

xargs -I {} git checkout {} # Here we go "forward by 1"

Hope this can be helpful to anyone that would like to get this same behaviour. I'm building a CLI which has a command that moves you "back and forth" in the commits tree without prior knowledge of hashes or logs.

0

As a workaround, you can just return to HEAD with

git checkout <branch>

And then move to the commit you'd like to, with

git checkout HEAD~<offset>
Patrizio Bertoni
  • 2,582
  • 31
  • 43
0

If you are using vs code then Git history is an awesome plugin where you can efficiently see commits and check their contents in the editor itself. check out the link

Rahil Ahmad
  • 3,056
  • 1
  • 16
  • 21
0
branchName=master; commitInOrder=1; git checkout $(git log --pretty=%H "${branchName}" | tac | head -n "${commitInOrder}" | tail -n 1)

where:

branchName equals branch name

commitInOrder equals a commit in order from very first commit in the selected branch (so 1 is the very first commit, 2 is second commit in branch, etc.)

guest
  • 1
0

Probably not the nicest way but you can use git log to view the list of commits and then use git checkout [sha1 of D] to move to D.

bb-generation
  • 1,487
  • 12
  • 9
0

To GUI and vscode users I would recommend using the extension git graph

enter image description here

Norfeldt
  • 8,272
  • 23
  • 96
  • 152
0

I would use git-reflog and git-reset.

It is not the same case as you run git-bisect, but suppose you git-reset to commit C and want to move it back to commit F.

At the point, git-reflog looks like this:

$ git reflog show
4444444 (HEAD -> main) HEAD@{0}: reset: moving to 4444444
1111111 HEAD@{1}: commit: F
2222222 HEAD@{2}: commit: E
3333333 HEAD@{3}: commit: D
4444444 (HEAD -> main) HEAD@{4}: commit: C
5555555 HEAD@{5}: commit: B
6666666 HEAD@{6}: commit: A

Then, you can run git-reset to go back to any commit by specifying SHA1 hash or offset number from HEAD.

In your case, run git-reset as follows:

$ git reset 1111111

or

$ git reset HEAD@{1}
ys64
  • 290
  • 2
  • 8
0

I had the similar issue when I had to move to F I did git checkout branch_name which will move the HEAD to the latest commit in that particular branch

When I used git reset HEAD{0} all the changes I have made after the current commit (here it would be C) were changed to modified, which was not what I wanted.

Kushal
  • 1
  • 1
  • This does not provide an answer to the question. Once you have sufficient [reputation](https://stackoverflow.com/help/whats-reputation) you will be able to [comment on any post](https://stackoverflow.com/help/privileges/comment); instead, [provide answers that don't require clarification from the asker](https://meta.stackexchange.com/questions/214173/why-do-i-need-50-reputation-to-comment-what-can-i-do-instead). - [From Review](/review/late-answers/34712279) – MrMister Jul 24 '23 at 20:21