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.

Important Notes

  • 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

Demos

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

Demo 1: git commit --amend

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 by git 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 and git reset <hash> --hard to reset main to undo the git amend command you ran
  • Run git branch <branch> or git checkout -b <branch> to create a new branch with this pre-amended commit
  • Run git merge or git rebase to get the changes you want back into main

The next demo will explore the git reset option.

Demo 2: git reset

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 the main 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.

Demo 3: git rebase

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 the zero-wing branch
  • Run git rebase main
  • Run git tree
  • Notice that zero-wing's parent commit has changed, and that its parent is now main'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 ran git rebase

To clean up for the next demo, run git checkout zero-wing followed by git reset --hard origin/zero-wing.

Demo 4: git reflog

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 onto main
  • 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 before zero-wing was rebased
  • 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.

Demo 5 (Bonus): git fsck

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

Summary

Commits are immutable and can be recovered even if its not tracked by any branch.

⚠️ **GitHub.com Fallback** ⚠️