Track
In certain situations, we may wish to reset the state of a local Git branch to align it with the remote repository, thereby discarding any local changes. This is commonly referred to as a forced pull.
This creates the misconception that the way to archive this is to use the --force
option in the git pull
command. In this article, we learn that these are different things.
If you're new to Git, consider reading our Git push and pull tutorial first.
We can overwrite our local state with the remote state by using the commands listed below, substituting <branch_name>
with the name of your branch. This action will permanently delete all your local changes in that branch.
git fetch
git reset --hard origin/<branch_name>
This article will delve into the details of how these commands work. We go beyond quick fixes to understand the underlying mechanics, thereby learning more effective methods to achieve the desired outcome without the risk of losing important changes. We also clear the misconception and learn what the --force
option of git pull
actually does.
Become a Data Engineer
When to Consider Overwriting Local Changes
In general, overwriting local changes in this way goes against the intended usage of Git. For almost any situation, there’s a proper Git workflow to solve it. However, some of these often require a deeper understanding of Git and take more time to execute. Even if it’s not a best practice, overwriting local changes can sometimes be the fastest way to get work done.
For me, this usually happens when I work on a feature that I’m not very familiar with. I start by creating a branch and making a few commits. When I get stuck, I ask a colleague for help. They begin from an earlier commit, before I made the mistake, resolve the issue, and push their changes. At this point, I need to replace my local version with the updated remote version while discarding my local changes, so I can continue working on the feature.
Another example is when there’s a conflict, and I feel it’s harder or more time-consuming to fix the conflict than to re-implement our changes on the latest version of the code.
Regardless of the reason, always make sure that those changes really aren’t necessary and can safely be deleted or to back up your branch to avoid losing important work.
How to Overwrite Local Changes Correctly
The first step is to fetch (download) the latest content from the remote repository on our local machine. This is done using the fetch
command from git:
git fetch
By default, this will fetch the contents of the current branch which is usually what we need. We can optionally specify the branch we wish to download (replacing <branch_name>
below with the name of the branch we want to fetch):
git fetch origin/<branch_name>
Alternatively, we can use the --all
option to download everything:
git fetch –all
Fetching the changes from the remote repository will not affect the files in your current working branch directory. The fetch
command downloads the changes from the remote repository but doesn't merge them. The local repository keeps a view of both the local files and the remote files, and fetching updates the view of the remote files.
After downloading the changes from the remote repository, we can use the reset
command from git to reset the current working branch into a given state:
git reset --hard origin/<branch_name>
The --hard
option is required to make sure that the local changes are overwritten by the remote changes. In the command above, we chose to replace the current working branch with origin/<banch_name>
which corresponds to the remote version of that branch that was just fetched.
The git reset
command will delete local changes, even if they’re committed. It is, therefore, recommended to create a local backup of the current branch before executing that command. This can be done using:
git branch <name_of_the_backup_branck>
Replacing <name_of_the_backup_branch>
with the name you want the backup branch to have.
Putting it all together, we can override all local changes from a given branch with the latest remote version by doing the following:
git fetch
git branch <name_of_the_backup_branch>
git reset --hard origin/<branch_name>
If you'd like to expand your knowledge on working with branches in Git, check out the Git Clone a Specific Branch tutorial.
Full cleanup by deleting untracked files
Git will not touch untracked files (files that were never using git add
). If we also want to delete these files, we can do it using:
git clean -fdx
This is a dangerous command that is used to remove untracked files from the working directory. It is essential to use this command with caution because it permanently deletes these files, making it impossible to recover them through Git. Here's a breakdown of the command options:
-f
: This option forces the clean operation to proceed. Git will refuse to delete untracked files from the working directory without this flag as a safety measure to prevent accidental data loss.-d
: This tellsgit clean
to remove not just untracked files but also untracked directories. By default,git clean
targets only untracked files and ignores directories.-x
: By default,git clean
will respect the.gitignore
rules and not remove files or directories that match these patterns. Including -x overrides this behavior, instructing Git to also remove the files/directories that are ignored by.gitignore
or other ignore rules. This results in a very thorough cleaning, removing all untracked files in the repository, including build outputs, log files, and other generated artifacts that are typically ignored.
Understanding Git Pull
The usual way we update our local repository with remote changes is by using the git pull
command. However, this command cannot be used to override our local changes because git pull
will try to merge our local version with the remote.
Under the hood, git pull
performs the following steps:
- It uses
git fetch
to download the latest version of the remote repository. - It uses
git merge
to merge the local and remote branches.
Because of the merge step, git pull
isn’t a suitable command in situations where we want to ignore and discard local changes.
How about the git pull --force
?
By looking at the documentation of the git pull
command we see that it offers a --force
option. It is a common misconception that this option will force the git pull
command to execute by erasing the local changes with the remote changes.
When we provide the --force
option to git pull
, this option is passed down to git fetch
. Thus git pull --force
performs the following steps:
git fetch --force
git merge
We learned that fetch is used to update the local view of the remote repository. If the only difference between your current local repository and the remote repository is some missing commits (the local branch is behind the remote version), then git fetch
will simply update the local view by appending those new commits to the history.
However, it could be the case that someone rewrote the commit history of the remote branch making it incompatible with the local view. In this case, fetch won’t work.
We can get around this using the --force
option which will force Git to ignore the local history and overwrite it with the remote history.
We see that the --force
option is related to forcing commit history overwrite and has nothing to do with ignoring local changes in the working branch directory.
Integrating Local Changes
Overwriting the local changes with the remote changes is not a common Git practice but can be useful in certain cases where we don’t want to deal with conflicts or don’t care about our local changes.
Merging
Usually, one would pull the changes and deal with the conflicts. People tend to be scared to deal with conflicts. Some conflicts can be overwhelming to deal with, but that’s not often the case. A conflict occurs when:
- Both the local branch and the remote branch have changes on the same part of a file.
- A file is deleted on one side and modified on the other.
In these cases, Git cannot guess which changes we want to keep, and we need to let it know manually. Git will highlight the conflict in the files like so:
To resolve a conflict, we edit the file by deleting these lines except the ones we want to keep. After handling all of the conflicts, we can commit the changes. For an in-depth guide, check out this tutorial on how to resolve merge conflicts in Git.
Cherry-picking
If we really want to first make our local version match the remote version and then eventually integrate our changes, we can bring the commits from the backup branch into the current branch, like so:
git cherry-pick <commit_hash>
Here, <commit_hash>
is the hash of the commit we want to integrate. This assumes that before doing git reset
, we created a backup branch with git branch <name_of_the_backup_branck>
and that the local changes had been committed.
To find the <commit_hash>
, we can list the commit hashes in the backup branch using the following command:
git log <name_of_the_backup_branck>
Stashing
Alternatively, if we don’t want to commit our changes or create a backup branch, we can stash our changes before merging using:
git stash
This command can be used to temporarily save the current work without committing it to the branch history. Stashing sets aside your modifications, allowing you to have a clean working directory.
After resetting, we can bring out changes back using the following command:
git stash pop
Using stashing, the whole workflow would look something like this:
git fetch
git stash
git reset --hard origin/<branch_name>
git stash pop
To learn more, check out this cheat sheet with stash and other git commands.
Conclusion
Despite not being a best practice, sometimes we find ourselves in a situation where we just want to make our local branch match the contents of the remote branch while discarding our local changes.
There’s a misconception that this is achieved using git pull --force
. This is incorrect because git pull --force
combines git fetch --force
and git merge
. Therefore, it attempts to merge the local changes with the remote changes, not overwriting them.
The correct way to overwrite local changes with the contents of the remote repository is:
git fetch
git reset --hard origin/<branch_name>
The git reset
command must be used with caution because it will delete local changes, even if they have been committed. If we had local commits and created a backup branch, we can bring them back using git cherry-pick
. If we didn’t commit and want to temporarily save our local work we can use git stash
before resetting and then git stash pop
to bring our changes back.
Get certified in your dream Data Engineer role
Our certification programs help you stand out and prove your skills are job-ready to potential employers.
