Understand various states in GIT
In the world of version control with Git, the phrase "git discard changes" is a lifeline for developers. It denotes the ability to undo, revert, or simply discard changes that haven't been finalized, ensuring that mistakes or unwanted modifications don't become a permanent part of the project history. To understand the depth of "git discard changes", it's essential to grasp Git's core operational states: the working directory, staging area, and commit history.
- Working Directory: This is your playground. Here, you actively modify files, but Git hasn't recorded these changes yet. Think of it as your current workspace, reflecting the live, often unsaved, state of your project.
- Staging Area: Consider this the waiting room. After making changes in the working directory, you can choose which ones you're ready to commit by moving them to the staging area. It's your way of telling Git, "I'm considering these changes for my next commit."
- Commit History: This is the ledger of your project. Every time you finalize changes, they get saved as a commit in this history. It's a timeline that captures every saved change, letting you travel back in time to any version of your project.
After creating or modifying files in the working directory, git temporarily takes a snapshot of the index changes before storing them in the git database when each commit becomes part of the history. The history tells more about the commit, such as the author, date created, and most importantly, a unique hash called the commit hash.
The last commit hash is often referred to as the HEAD. Changes that have not passed the index (a.k.a staging area) are called uncommitted, while those with a history are called committed changes.
Passed the local workflow, you can send the changes to a remote server on a website like GitHub, GitLab, or Bitbucket. Then the changes are pushed. Otherwise, they are unpushed.
That is the basic explanation of the commit HEAD, uncommitted, and committed changes. Let's see their role in discarding git changes.
Commands to be used at different git state for discarding changes
1. Discarding Local Changes in the Working Directory:
- At times, you might find yourself with local modifications that you no longer want, either because they were experimental, mistakes, or superseded by newer ideas.
- This is the state where you've made local modifications to files but haven't added them to the staging area yet. These changes are "unstaged."
- Running
git status
will show these files under "Changes not staged for commit." - Commands relevant to this state for discarding changes:Â Â
git checkout
,Âgit clean
,Âgit stash
2. Unstaging Changes:
- After adding changes to the staging area, you might reconsider which modifications should be part of the next commit.
- After making local changes and adding them to Git's "staging area" with
git add
, they're ready for commit. However, they aren't committed yet. This is the "staged" state. - Running
git status
will show these files under "Changes to be committed." - Commands relevant to this state for discarding changes:Â
git reset
,Âgit stash
,git restore
3. Reverting Commits:
- Sometimes, even after finalizing and committing changes, you realize they might be erroneous or unnecessary. Rather than deleting or hiding these commits, Git allows for a safer method: creating a new commit that undoes the changes of a previous one. This ensures the history remains intact while effectively "reverting" the unwanted modifications.
- Once changes are committed, they become part of the repository's history. If you need to undo a commit, you're looking to revert a change that's already been finalized.
- Running
git log
lets you see the list of past commits. Any commit in this log can be considered for reverting. - Commands relevant to this state for discarding changes:
git revert
,git reset
Discarding Changes in the Working Directory
1. Using git checkout
:
This command can discard changes in specific files or the entire working directory.
Specific File: To discard changes in a single file (example.txt
for instance):
git checkout -- example.txt
Entire Directory: To discard changes in all files in the directory:
git checkout -- .
2. Using git clean
:
This command is used to remove untracked files from the working directory. Note that it won't affect tracked files (files that are already part of the Git repository).
Remove Untracked Files: To delete all untracked files:
git clean -f
Remove Untracked Directories: To remove untracked directories in addition to untracked files:
git clean -fd
git clean
is a destructive command. Once you remove untracked files/directories with it, they're gone for good. Always double-check what you're about to delete (using git clean -n
to do a dry run can help).
3. Using git stash
:
This command temporarily shelves (or "stashes") changes you've made to your working directory. It can be used to store both staged and unstaged changes.
Stash Changes: To stash all changes in the working directory and staging area:
git stash
Stash with Message: To stash with a descriptive message:
git stash save "My descriptive message"
Apply and Drop Stashed Changes: If you want to bring back the stashed changes:
git stash apply
And if you're sure you no longer need the stashed changes:
git stash drop
4. Using git restore
This is a newer command introduced in Git version 2.23:
Specific File: To discard changes in a specific file (example.txt
):
git restore --source=HEAD --staged --worktree example.txt
Entire Directory: To discard changes in the entire directory:
git restore --source=HEAD --staged --worktree .
Unstaging Changes
Unstaging changes refers to the action of moving changes from the staging area (where they are prepped for commit) back to the working directory. This is particularly useful if you've decided you want to modify some of the changes or exclude them from the next commit. Here are the primary commands to achieve this:
1. Using git reset
:
The git reset
command is a traditional way to unstage changes. It operates by moving the current HEAD pointer backward (which can be risky if you're not careful), but when used without any commit reference, it only acts on the staging area.
Unstage a Specific File: If you've staged changes for a file named example.txt
and you wish to unstage them:
git reset example.txt
Unstage All Changes: To remove all files from the staging area:
git reset
2. Using git restore
:
Introduced in Git version 2.23, git restore
is designed to make unstaging (and other tasks) more intuitive.
Unstage a Specific File: For unstaging changes in a file called example.txt
:
git restore --source=HEAD --staged example.txt
Unstage All Changes: To unstage all changes in the staging area:
git restore --source=HEAD --staged .
Note:
- The
--source=HEAD
option ingit restore
indicates that you're using the latest commit as the source for the restore operation. - Both
git reset
andgit restore
used in the contexts above will only affect the staging area. Your working directory changes will remain intact.
Reverting to a Previous Commit
1. Using git reset
:
To revert to a previous commit but keep all changes in the working directory (soft reset):
git reset COMMIT_HASH
To revert to a previous commit, unstage changes, but keep them in the working directory (mixed reset, this is also the default):
git reset --mixed COMMIT_HASH
To revert everything (commit history, staging area, and working directory) to the state of a previous commit (hard reset, use with caution):
git reset --hard COMMIT_HASH
2. Using git revert
:
git revert
doesn't actually move the HEAD back in history. Instead, it creates a new commit with changes that reverse a previous commit:
git revert COMMIT_HASH
This is a safer option in shared repositories since it doesn't rewrite commit history.
Replace COMMIT_HASH
with the actual hash (or another identifier, like a branch name) of the commit you're referencing. Always be cautious when reverting or resetting, especially with the --hard
option, as these changes can be difficult or impossible to undo.
Scenario-1: Git discard uncommitted changes
Example-1: Using git clean command
The git clean command is crucial in discarding changes in untracked files. Although we can use it with several flags, its most familiar forms are -f
for untracked files only, -fd
for both untracked files and directories, and -i
for interactive file discard.
For instance, let's stage file1.txt
and f2.txt
, leaving the third.txt
file and dir
directory untracked.
git add file1.txt f2.txt
Running the command
git clean -f
clears the third.txt
file from the working directory.
Similarly, introducing the -d
flag dumps untracked files and directories.
git clean -fd
Rechecking git status
git status
The untracked dir
directory got discarded!
Lastly, we can use the -i
flag by picking the clean
option.
Create a file.
touch file
Delete it.
git clean -i
Git prompts us for an option. Let's pick 1: clean
to git discard changes
Example-2: Use either git checkout or git restore command
The checkout command has several uses in the git workflow. Apart from switching between branches and commits, you can apply it to git discard changes. All you do is be at the root of the working directory and run either
git checkout -p
or
git checkout -- .
commands.
If it fails to remove your recent changes, use the restore command
git restore <file>
which has been git's preferred way to discard changes since the introduction of git version 2.23 in 2019.
Let's modify file1.txt
.
echo line1 >> file1.txt
Assume we want to delete the most recent changes on file1.txt
. We can do that by running the command:
git restore file1.txt
We can also modify file1.txt
and f2.txt
then git discard changes on them as follows
echo line1 >> file1.txt echo l2 >> f2.txt git restore .
All the new changes disappeared!
Example-3: Use git stash command to stash the changes
Assume you want to git discard changes hoping to restore them later. The command to use is git stash
. Before clearing your changes from the index, it records them in the .git/refs/stash
file in your working tree, only retaining changes as per the commit HEAD.
Let's modify the two files: file1.txt
and f2.txt
to practice git stash
.
echo line1 >> file1.txt echo line2 >> f2.txt
Stage the changes.
git add *
then check status.
git status
Discard the changes using the stash command.
git stash
Then recheck the status.
git status
Our uncommitted changes disappeared
We can see the stashed changes using
git stash show
and restore them using git stash apply
as follows
git stash apply
Another way to discard the changes in the index is to do a mixed reset on the changes.
Example-4: Reset the HEAD
Running the mixed reset command
git reset HEAD
untracks the files from the index and dumps the changes in the working directory.
Scenario-2: Git discard already committed changes
Example-5: Discarding un-pushed files
The hard reset is the most straightforward command to git discard changes already committed. We can use it as follows.
Let's restage the files.
git add .
And commit them.
git commit -m "Reset the HEAD"
View the HEAD
.
git log
before resetting it.
git reset --hard HEAD^
Recheck the history.
git log
The hard reset command on the HEAD^
 removed the latest commit.
Note: git reset hard discards both changes and files from the working directory. The only way to recover the changes is to inspect the reflog
and checkout
the target commit hash.
Better yet, avoid the hard reset command if you have pushed the changes.
Example-6: Discarding remote pushed files
Doing a hard reset on local files can cause a conflict error when used on files already committed. Git revert is the best alternative to discard commit changes on pushed files.
Running
git log
shows we have four commits
Let's revert the last three commits by making the HEAD point the initial commit hash.
git revert b4ad96
Our default text editor prompts us for a new commit message. We can go with the default message then close the text editor.
Let's push the changes.
git push
Summary
When working with Git, developers often find themselves needing to modify their selection of changes before finalizing a commit. The process of selecting changes for a commit is known as "staging," and once changes are staged, they are ready to be committed to the project history.
However, there might be times when a developer decides to alter what changes should be included in the next commit, or even reverse a decision altogether. This act of modifying what's staged is known as "unstaging."
There are two primary Git commands to help with unstaging:
- git reset: A traditional command for unstaging, which can be used in several scenarios. When used without specifying a commit, it operates on the staging area, effectively removing changes from being staged. It can target specific files or all changes.
- git restore: A more recent addition to Git (from version 2.23), offering a more intuitive approach to unstaging changes. By using specific flags like
--source=HEAD
and--staged
, developers can selectively move changes out of the staging area.
Regularly using git status
ensures clarity on what is currently staged and what remains in the working directory. When used effectively, Git's staging and unstaging capabilities allow developers to curate their commits for clarity and meaningful project history.