Git: How do you replace your current working directory with a previous commit without branching or git revert?

The name of the pictureThe name of the pictureThe name of the pictureClash Royale CLAN TAG#URR8PPP



Git: How do you replace your current working directory with a previous commit without branching or git revert?



Is there an easy way to the following with git?



commit copy paste diagram



Basically I want to create a new commit at the top of my commit history that is equivalent to a previous commit from my history (e.g. to restore a project to a previous state).



I don’t want to use branches because I’m teaching this to people who have never used git before, and I want to keep things as “linear” as possible. (Most of my audience just needs to back stuff up, review project history, and restore a project to a previous state if necessary, but nothing else remotely fancy.)



I don’t want to use git revert --no-commit 49a732c..HEAD because this gives an error message if there does happen to be a merge after 49a732c (which I admit is unlikely in my audience, but it does sometimes happen through frantic attempts to make error messages go away).


git revert --no-commit 49a732c..HEAD


49a732c



I also don’t want to delete/rewrite history.



Essentially, is there a simpler way to do this?


# make sure on master and working directory clean
me@laptop:~/Desktop/proj$ git status
On branch master
nothing to commit, working directory clean

# check out commit to restore
me@laptop:~/Desktop/proj$ git checkout 49a732c
Note: checking out '49a732c'.

You are in 'detached HEAD' state. You can look around, make experimental
changes and commit them, and you can discard any commits you make in this
state without impacting any branches by performing another checkout.

If you want to create a new branch to retain commits you create, 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 49a732c... Fix bugs.

# copy project checked out at 49a732c to temp folder
me@laptop:~/Desktop/proj$ cp -r . ../temp

# check out master again
me@laptop:~/Desktop/proj$ git checkout master
Previous HEAD position was 49a732c... Fix bugs.
Switched to branch 'master'

# remove everything except .git
me@laptop:~/Desktop/proj$ rm -rfv !(".git")
rm: refusing to remove '.' or '..' directory: skipping '.'
rm: refusing to remove '.' or '..' directory: skipping '..'
removed 'file_1.py'
removed 'file_2.py'

# copy everything except .git from temp (checked out at 49a732c) into working directory
# (so working directory is identical to project at commit 49a732c)
me@laptop:~/Desktop/proj$ cp -nr ../temp/* ./

# make new commit identical to project state at 49a732c
me@laptop:~/Desktop/proj$ git add *
me@laptop:~/Desktop/proj$ git add -u
me@laptop:~/Desktop/proj$ git commit -m “Restore project to commit 49a732c.”
[master 2b6416c] Restore project to commit 49a732c.
3 files changed, 18 deletions(-)

# remove temp
me@laptop:~/Desktop/proj$ rm -rf ../temp

# new commit 2b6416c is now identical to 49a732c



Alternatively, if branching is the best way to do this, is there a sequence of commands that will always work and won't yield any merge conflicts or tell you to use git stash (assuming current working directory is clean)?





See both stackoverflow.com/q/4114095/1256452 and stackoverflow.com/q/1895059/1256452, and note that you want to revert to a previous commit, without using git reset, As such the two-resets-and-commit method described in the accepted answer to the second question is one of the short ways to achieve the desired result. A third way that is one command shorter is to use git read-tree -u <hash> followed by git commit, though this is perhaps the most obscure method.
– torek
Aug 13 at 5:48


git reset


git read-tree -u <hash>


git commit




3 Answers
3



To create a new commit, restoring the content of an old commit, you can:



First, mark the current HEAD of your branch (assuming master here): we will need to move that HEAD without modifying master, so let's create a new temporary branch called 'tmp' where master is.


master


master


tmp


master


git checkout master
git checkout -b tmp



(yes, that is against the "without branching" of the question, but you will delete that tmp branch soon)



Then we go back to an older commit with the right content.


git reset --hard <old_commit>



That resets the index (and working tree) to the right content, but that also moves HEAD. However, that moves tmp HEAD, not master HEAD.


tmp


master



move back tmp HEAD to where master is, but without modifying the index or the working tree (which are representing what we need for a new commit)


tmp


master


git reset --soft master



make a new commit, on top of master/tmp HEAD, which represents the right content (the old commit).


master


tmp


git commit -m "new commit, image of an old one"



Finally, force master to be where tmp is: one new commit later.


master


tmp


git branch -M tmp master
git checkout master
git branch -d tmp



Now a regular git push is enough, any collaborator can simply pull as usual, and still get the old reset content.


git push


git push





Just tested this and it works. Exactly what I was looking for. Note also that git branch -M tmp master leaves you on master and deletes tmp, so the next two lines can be skipped. Thanks much.
– rkp
Aug 13 at 5:16


git branch -M tmp master



Assuming you start from a clean worktree, you could do:


cd <root_directory_of_your_repo>
git checkout master
git checkout 49a732c -- .



When you specify a file (in this case . (the root directory of your repo)) as an argument to git checkout, the checkout will not switch branch (the repo HEAD will remain the same). It will just update the index to make that file match the version of that file from the specified commit. Since you specified the root directory of the repo, all files in the index will be updated to match the specified commit 49a732c


.


git checkout


HEAD


49a732c





Thanks. However, this just checks out all files from 49a732c into the working tree; if the working tree has new files not in 49a732c, these will still be in the working tree after committing, so the result will not be identical to 49a732c.
– rkp
Aug 13 at 22:29





@rkp After this command, you could run git clean -df in order to remove all untracked files which are not ignored (the ones which would show up as untracked in git status), or git clean -xdf in order to remove all untracked files (including files listed in .gitignore). It would be advisable to run git clean -dn or git clean -xdn respectively before running the commands above. (-dn or -xdn will simply print which files will be removed, without removing them, so that you can double check that you are not removing some file which you want to keep)
– Alderath
Aug 14 at 6:53



git clean -df


git status


git clean -xdf


.gitignore


git clean -dn


git clean -xdn


-dn


-xdn



In situation when you want to return to some state you do following:



git reset --hard 49a732c


git reset --hard 49a732c



This step put your master branch into desired state. If you want to save you previous branch state:


master



git checkout 48ah14s -b archive/my-unrecognized-experiments


git checkout 48ah14s -b archive/my-unrecognized-experiments



You still can do it after reset because reset doesn't delete commits.



PS Branching is essential part of git. It is better to teach branching then teach such complicated (in git) things as you pictured.



EDIT
If you want your master to stay consistent with remotes:


master


git reset 49a732c # move HEAD back, all changes still in working tree
git commit -am "My unrecognized experiments" # save all changes as one commit
git reset --hard 48ah14s # restore master HEAD
git revert <hash of my unrecognized experiments> # apply reverted changes to master






By clicking "Post Your Answer", you acknowledge that you have read our updated terms of service, privacy policy and cookie policy, and that your continued use of the website is subject to these policies.

Popular posts from this blog

Firebase Auth - with Email and Password - Check user already registered

Dynamically update html content plain JS

How to determine optimal route across keyboard