85

I have a newbie question about Git:

I need to move back and forth in a history of a branch. That means, I need to get all the files to the state they were in in some old revision, and then I need to get back to the latest state in the repository. I don't need to commit.

With SVN, it would be

svn up -r800

to get to revision 800, and

svn up

to get in sync with the repository.

I know the hash of the commit I want to get back to, so I tried

git reset <hash>

which seems to get me there. But then I tried

git pull

but that complains about conflicts.

So what's the proper way to move through the history of the branch?

I'm thinking in terms of SVN, so don't hezitate to point me to some nice tutorial. Note that I've already checked http://git.or.cz/course/svn.html and http://www.youtube.com/watch?v=8dhZ9BXQgc4 .

Thanks, Ondra.

1
  • 1
    Side note: I got used to avoid git pull altogether. Instead, I use git fetch --all aliased to gu in bash, and have gitk open all the time, viewing all branches - see View -> edit -> check all 4 checkboxes. Then I move using git reset or gist stash + git co, depends on what I need. Commented Mar 16, 2012 at 21:09

6 Answers 6

75

I'm a former svn user too and now use git for all my projects.

When using git, you should change your way of thinking from the client-server architecture that's used in svn. In svn, every change needs a connection with the server. Using git, your repo is in the working directory. You don't need a connection for every repo action.

Only use git push and git pull to synchronise with the repo. Think of it as using rsync or any backup solution to make two places have exactly the same content. Just like when you connect external backup hard disk, then make the content in it same with the content in your main. That's the usage of git pull and git push.

If you just want to go back and forth in the history, do it using git checkout. See the revision id using git log. If you're using Linux, use gitk to see the revision tree. In Windows, tortoise git can display it using revision graph.

To get back to latest revision, use git checkout master. Before doing any command, always make yourself do git status. This command will display anything you need to know about current repo condition, and what action you need to do to make it right. Before doing git pull and git push, it's better to make sure that the git status result shows working directory clean.

If you need to revert a file to its previous revision, you can do it with git merge. Before doing it to a file, test it first with git diff. Ex: git diff rev1:rev2 filename. It will print out any differences between two revisions. Changes in rev1 will be replaced by the changes in rev2. So to do revert, rev2 will be the older than rev1. After you are satisfied with the diff result, do it with git merge, just replace diff with merge and all other parameters stay the same.

I hope this helps you. The main key is to see that your working dir is your repo. Understanding this will help you use git to it's full capability. Good luck.

1
  • git checkout master. master is the branch name. currently, GitHub uses main. if branch is main. its git checkout main Commented Sep 15, 2022 at 13:21
44

You can use git checkout to checkout any commit and then use it with a branch name to go back to a named branch.

git checkout with a commit id and not a branch name moves you off any named branch and on to what is known as a detached head.

If you use git reset then it will move your branch itself back to an old state, orphaning the more recent commits which probably isn't what you want.

4
  • What the checkout really does? Does it change the files to the state of the given commit, by reversly applying the patches of the commits between current state and the given commit's state? Commented Jan 22, 2010 at 23:43
  • I've found git reset and git reset origin. I will not do any commits, so it does not matter whether I loose the recent commits as far as it's easily recoverable (which means, by a single command). Commented Jan 22, 2010 at 23:45
  • 1
    git checkout don't make the change permanent. To do it, use git merge. See my answers. Commented Jan 22, 2010 at 23:46
  • 1
    checkout reset's the index/cache/staging area to the tree designated by the given commit and then updates the working tree to match the cache. It doesn't need to do any patch re-applying; it just uses the tree as at the given commit. (It also has error checking and updates the checked out branch if you pass it a branch name.)
    – CB Bailey
    Commented Jan 22, 2010 at 23:54
34

UPDATE 2024-03-17: Fix from @deltacrux + Advanced Setup

The other answers are informative, but I believe this is closest to what the OP wants:

Add these two functions to your ~/.bashrc:

# checkout prev (older) revision
git_prev() {
    git checkout HEAD~
}

# checkout next (newer) commit
git_next() {
  branch=$(git branch --contains HEAD | grep -v HEAD | tail -1 | sed 's/^[ *]*//g')
  hash=$(git rev-parse $branch)
  next=$(git rev-list --topo-order HEAD..$hash | tail -1)
  git checkout $next
}

Usage:

$ git_prev
Previous HEAD position was 7042c8a... Commit message2
HEAD is now at d753ecc... Commit message1

$ git_next
Previous HEAD position was d753ecc... Commit message1
HEAD is now at 7042c8a... Commit message2

Note: These commands always enter detached HEAD state. If you git_prev then git_next from a currently checked out branch, you will end up back at the latest revision but you will be in detached HEAD state. Do git checkout BRANCH_NAME to get back to normal.


Advanced Setup:

Here's the setup I use personally.

  • Shortens commands to gp (git_prev) and gn (git_next).
  • Adds gc command for git checkout.
  • Adds gcr command to exit detached HEAD state (git checkout reset).
  • Saves the branch name in .git/_PREV_BRANCH so that there is no risk of "hopping" to a different branch that contains the same commit.
  • Automatically exits detached HEAD state on gn back to the branch commit (so you can gp + gn to get right back where you started).
# save branch name (if not detached)
# this allows gcr to exit detached HEAD state and gn to better determine the next commit
git_save_branch() {
  branch=$(git rev-parse --abbrev-ref HEAD)
  if [ "$branch" != "HEAD" ]; then
    echo "$branch" >.git/_PREV_BRANCH
  fi
}

# loads the previously saved branch name, or guesses what branch we are in if detached
git_load_branch() {
  if [ -f .git/_PREV_BRANCH ]; then
    cat .git/_PREV_BRANCH
  else
    git branch --contains HEAD | grep -v HEAD | tail -1 | sed 's/^[ *]*//g'
  fi
}

# checkout
gc() {
  git_save_branch
  git checkout "$@"
}

# reset to last saved branch
gcr() {
  branch=$(git_load_branch)
  if [ -n "$branch" ]; then
    git checkout "$branch"
  else
    echo "No saved branch to reset to"
    return 1
  fi
}

# checkout prev (older) commit
gp() {
  git_save_branch
  git checkout HEAD~
}

# checkout next (newer) commit
gn() {
  branch=$(git_load_branch)
  hash=$(git rev-parse $branch)

  # stop if we are already on the branch, i.e. there are no newer commits
  head_hash=$(git rev-parse HEAD)
  if [ $hash = $head_hash ]; then
    echo "On branch $branch"
    return 0
  fi

  next=$(git rev-list --topo-order HEAD..$hash | tail -1)

  # if the next commit is the branch commit, checkout the branch to exit detached HEAD state
  if [ $next = $hash ]; then
    git checkout $branch
  # otherwise, checkout the next commit
  else
    git checkout $next
  fi
}
4
  • Once you're doing such things, take a looking a look at git aliases may be worthwile.
    – rethab
    Commented Oct 13, 2015 at 16:07
  • 1
    I love this answer and it was working for a minute but now when I run git_next i get this usage: grep [-abcDEFGHhIiJLlmnOoqRSsUVvwxZ] [-A num] [-B num] [-C[num]] [-e pattern] [-f file] [--binary-files=value] [--color=when] [--context[=num]] [--directories=action] [--label] [--line-buffered] [--null] [pattern] [file ...]
    – Squirrl
    Commented Oct 30, 2018 at 18:43
  • I had the same problem as above, which seems to be because git show-ref -s -- HEAD~ is returning nothing. I had some luck replacing the first line of git_next with BRANCH=git branch --contains HEAD | grep -v HEAD | sort | uniq.
    – deltacrux
    Commented May 3, 2021 at 3:04
  • 1
    with a non-linear history this doesn't seem to work. adding --ancestry-path seemed to work as mentioned here: stackoverflow.com/a/58226704
    – scott
    Commented Mar 7, 2023 at 12:29
16

Try git reflog, this lists commits and checkouts you have done to switch between the commits, even the commits you have lost when checkout to a previous commit.

Then you can try git checkout <hash of a commit> to switch to that commit.

Hope this helps!

7

To checkout a different version of a file, use

git checkout rev -- filename

Where rev can be the ID of a commit, the name of a branch, the name of a tag, or a relative version.

Use git log, gitk to look examine versions to see which version of the file you want.

To make this version of the file permanent, you need to commit the file: git add filename; git commit filename

I would not recommend git pull to examine versions because it does a merge -- potentially modifying your current state.

You do not need to use git reset in this case, unless you git add a file you decide not to commit.

2
  • sweet jeebus be careful when doing this. My coworker did this command to try out changes from my branch, wiped out about a weeks worth of my work and pushed it to the repo by doing this...
    – karina
    Commented May 2, 2016 at 22:37
  • 1
    As long as your coworker didn't do a push -f, you could always revert his commit and recover the missing work. If you have access to the repo he pushed to, even a force push is recoverable via git reflog. Also, if you still have your branch, it is recoverable. But yes, you have to be careful. Commented Jun 29, 2016 at 0:18
0

Depending on your reason for moving back and forth through the git history, you might consider just viewing the history.

I found this post trying to figure out how to best view the commit history and changes so I could familiarize myself with a project. I ended up using git log -p --oneline.

The -p flag shows the differences between each commit. See more here.

Not the answer you're looking for? Browse other questions tagged or ask your own question.