Commits are Immutable - neverendingqs/git-commit-it GitHub Wiki
Commits are immutable. Even when they no longer appear in your commit graph, they can still exist.
- Git eventually cleans up objects that are no longer referenced
- This is based on time and on the number of "loose objects" currently in your repository
- This includes any commits not on a branch
- The command to trigger this cleanup is
git gc
- Commits that have not been pushed to a remote location (e.g. GitHub) only exist in your local repository
These demos are meant to run in the order listed below. YMMV if you run them in a different order.
Note: I will be using the following aliases:
-
git amend
: "amends" the latest commit on the branch instead of adding a new commit -
git tree
: a replacement for SourceTree or gitk for getting a visual view of your branches
This command is most commonly used to adjust the latest commit on your branch. For example, you may want to run this if you forgot to include a file in your latest commit.
Officially (from the docs), this
Replace[s] the tip of the current branch by creating a new commit.
You can see this in action by doing the following:
- Make some changes to a file, and then run
git commit -am "<your message>"
to commit it - Run
git tree
, and copy the commit hash - Make some changes to the same file or a different file, and then run
git add .
followed bygit amend
to "amend" the commit you just made. - Run
git tree
again, and notice that the commit hash has changed, even though the message is the same - Run
git checkout <hash>
(Git will log some useful information here) - Run
git tree
again, and notice that you are pointing to the "pre-amended" commit
At this point, you have several options (non-exclusive):
- Run
git checkout main
andgit reset <hash> --hard
to resetmain
to undo thegit amend
command you ran - Run
git branch <branch>
orgit checkout -b <branch>
to create a new branch with this pre-amended commit - Run
git merge
orgit rebase
to get the changes you want back intomain
The next demo will explore the git reset
option.
git reset
is often used to unstage changes and/or "delete" commits. However, when used to "delete" commits, it only changes where your branch points to. The following will demonstrate this.
After the previous demo, you should have two commits with the same message, and the main
branch should point to one of them. Make a note of the hashes for both commits. For this demo, <original hash>
will refer to the hash of the commit before it was "amended", and <amended hash>
(which main
currently points to) will refer to the hash of the commit after it was "amended".
- Run
git checkout main
to switch back to themain
branch (Git will log a warning as it runs this command) - Run
git tree
- Notice that
<original hash>
does not show up - Run
git reset --hard <amended hash>
- Run
git tree
- Notice that
main
is now pointing to<original hash>
- Run
git reset --hard <original hash>
- Run
git tree
- Notice that
main
is once again pointing to<amended hash>
Note: git reset --hard
can cause data loss if you have changes staged or in your working directory. If you're not sure, it's usually safer to run git reset
or git reset --soft
. To find out more, I recommend reading 7.7 Git Tools - Reset Demystified.
To clean up for the next demo, run git checkout main
followed by git reset --hard origin/main
.
git-rebase - Reapply commits on top of another base tip
git rebase
is a tool known for changing the "base" of your commits. Because it reapplies commits, the original commits are still available after a rebase if you need them.
- Run
git checkout zero-wing
- Make some changes, and run
git commit -am "your message"
- Run
git tree
, and note the commit hash of the latest commit of thezero-wing
branch - Run
git rebase main
- Run
git tree
- Notice that
zero-wing
's parent commit has changed, and that its parent is nowmain
's latest commit - Run
git checkout <hash>
- Notice that you are pointing to the same commit the
zero-wing
branch was pointing to before you rangit rebase
To clean up for the next demo, run git checkout zero-wing
followed by git reset --hard origin/zero-wing
.
Imagine you can use Git to version your Git repository state before you run any command. This is what Git's reference logs (almost) do:
Reference logs, or "reflogs", record when the tips of branches and other references were updated in the local repository.
Note: this does not track uncommitted objects.
- Make some changes, and run
git commit -am "your message"
- Run
git rebase main
to rebase the branch ontomain
- Run
git reflog
- Notice that the reference logs track all the stages of the rebase
- Look for the commit hash beside
HEAD@{4}
- You can run
git branch old-zero-wing <hash>
to create a new branch that points to the commit beforezero-wing
was rebased
- You can run
- Run
git reset --hard HEAD@{4}
- Notice that
zero-wing
is now at the same place as it was before the rebase
To clean up after this demo, run git checkout zero-wing
followed by git reset --hard origin/zero-wing
.
If you simply do not have a commit, but have staged your changes at some point, you can try to recover them with git fsck --lost-and-found
.
- Make some changes, and run
git add .
to stage them- Suggestion: use a unique string you haven't used before for these demos to easily identify your change later on
- Run
git reset --hard main
- Notice that your changes are gone
- Run
git fsck --lost-found
- Navigate to
.git/lost-found/other/
- One of those files should contain the changes you staged
-
grep
might be a useful tool here
-
Given the amount of files that could be in lost-found
, it is best to avoid these scenarios by committing often
Commits are immutable and can be recovered even if its not tracked by any branch.