DEV Community

Cover image for Learn to Fix Common Git Mistakes
notHanii
notHanii

Posted on

Learn to Fix Common Git Mistakes

This *workshop * will explain the* common mistakes* many people make when utilizing
Git. It will begin by breaking down states in which a file can exist and build
on it from there. When you complete this workshop, you can expect to never have
to worry about losing data or messing up a repo again.

Change a Commit Message that Hasn't Been Pushed Yet

To start a new Git project, go to New on Github and enter a repository name. After creating the repository, find the clone link for the repo and git clone <repo-link>. You can then cd into that folder and touch index.html to create your first file. Inside your index.html file you can insert code and add the file to your commit.

index.html

<html>
  <head>
  </head>
  <body>

    <h1>Fixing git mistakes</h1>

  </body>
</html>  

You can do a git status and see that the index.html is an untracked file. Once you git add index.html, it stages the file and prepares it for a commit. You can commit that with a message like git commit -m "Adding index.html to git-mistokes".

After typing the commit message, we see we typed it incorrectly and want to change it. Since we haven't pushed it yet, you can use git commit --amend -m "Adding index.html to git-mistakes" to change the message. Now, if we git log --oneline, we can see the initial commit Github made to the project along with our rewritten message Adding index.html to git-mistakes.

Add More Files and Changes to a Commit Before Pushing

We can use git log --oneline to see information about our previous commits like what files were added to certain commits. There is something we can do if we want to add more than one file to the same commit. We can touch app.js and edit it.

app.js

// our app js code

To see that, we need to add it in the <head> of the index.html in our previous commit. We type <script type="text/javascript" src="/app.js"></script>.

index.html

<html>
  <head>
    <script type="text/javascript" src="/app.js"></script>
  </head>
  <body>

    <h1>Fixing git mistakes</h1>

  </body>
</html>  

Now that we have our script tag added to our index.html, when we do git status we have one untracked file and one modified file. However, we want to add those to the same commit. To do this, we add both to the stage with git add -A and we have both as a change to be committed. We then git commit --amend -m "Adding index.html and app.js" to add them to the same previous commit. Now, with git status, we have no changes to be committed. A git log --oneline, all files were added to the same commit along with a rewritten commit message.

Remove Files from Staging Before Committing

We can add another Javascript file to our project with touch lib.js. After doing so, we can git status to confirm it is untracked. Then, a git add lib.js will stage it for a commit. However, we might realize after adding it that we may not want to commit that file at all.

Although Git tells us how to do it in the terminal (git reset HEAD <file>...), we want to figure out what the HEAD means. With git log --oneline we find the HEAD is just a pointer to a branch and that branch is just a pointer to the commit specified by a hash on the left.

After running git reset HEAD lib.js, we can check if we successfully removed it from being staged for a commit with `git status.

Inside of the app.js created earlier, we can create a function helloWorld that contains alert("hello!"). Later, you might close that file and open the index.html and make changes to that file like adding a description.

app.js

`js
// our app js code

function helloWorld() {
alert("hello!")
}
`

index.html

`html




<h1>Fixing git mistakes</h1>
<p>Here's how to fix some common git mistakes</p>




`

You may be tempted to use git add -A alongside a simple commit message after that, but it will be clear that you are ahead by 2 commits. If you're able to see this before committing, you want to see what you're about to push. To check, run git diff origin/master HEAD to see the changes to all files edited.

If you realize you want to get rid of something before you push, check the log with git log --oneline to see which commit you'd like to undo. You have to decide where you want to reset to, so after finding the desired commit use git reset {LOCATION}. The location can be a hash associated with a commit or something like HEAD~1 which takes you to HEAD, then back down the tree 1 time.

After performing a git reset, your files should return to the unstaged location. It's important to be careful to not use git reset after pushing a commit because it'll change the history other people may have already downloaded. So, only use reset on branches that aren't pushed yet.

Now, you can git add index.html and git commit -m "Adds desc for index" to fix what went wrong earlier. Using git status will us we have the app.js not staged for commit and that we are two commits ahead of origin/master. You can use git log --oneline to verify the old commit was replaced with the new one.

Use and Compare the Different git Reset Options: --hard, --soft, and --mixed

If our app.js is modified and we run the commands


git add app.js
git commit -m "app js changes"
git log --oneline

it will commit the file locally and we can see the commit in your tree.

Running git --help reset will show the different reset options.

The common flags you will see are --soft, --hard, and --mixed. If you try git reset --soft HEAD~1 to reset back one from the HEAD, it will take the previously committed app.js and move it back to the staging area.

We can redo this with git commit -m "take 2" and try a reset with git reset --mixed HEAD~1, the app.js removes the commit as well as unstages the changes.

Trying another reset, we can run git add app.js and git commit -m "take 3" to prepare our file. We can enter git reset --hard HEAD~1, but see that it actually causes us to lose our work. It gets rid of the commit, unstages the changes, and removes them from our directory. Because we lose our work doing so, you don't usually want to do a git reset --hard.

Recover Local Changes from git reset --hard with git reflog

After doing a git reset --hard HEAD~1, it removed the code in our app.js, but we want to get it back. We can look at git reflog, something used to look at all the different things you have done in your local git repository. You can see the latest resets you've done with that command, note the hashes which you'll be using later.

One thing to note is that we are trying to recover a hard reset, so the commit is considered abandoned and will get garbage collected eventually if we don't save it. git reflog will work to save commits, but only if they haven't been garbage collected by git yet.

Once you find the commit you need to return to to recover your code, use that hash and run git reset --hard {HASH}. It will then return your code back to its state. Using git log --oneline shows the regular commits, as well the old commit. After successfully recovering, remember to git push to store in Github to view.

Undo a Commit that has Already Been Pushed

If we just made a push to Github, we can use git log --oneline to see our local branch is up to date with our origin. We are presented with a problem when we try to undo a commit that's already been pushed. We have to be careful because if we undo a commit already pushed, we would affect those who pulled the changes, effectively rewriting history.

We are going to use git revert {HASH} with the hash being the commit we want to revert. After doing so, it'll want you to make a revert commit. It's similar to a merge commit because it adds another commit to the tree, plus we have to give it a message. After entering the message, running git log --oneline we find that it successfully reverted the last commit.

The important thing is that when using git revert, the history is still there. You can go back to an earlier commit to revive previous work/code. In addition, anyone who pulled the origin/master branch during the time it took to revert will have a clean history tree because we used git revert {HASH} instead of git reset --hard {HASH}.

Push a New Branch to github that Doesn't Exist Remotely Yet

We can create branches with git branch {BRANCH-NAME} or git checkout -b {BRANCH-NAME}. Let's create one called js-changes with git checkout -b js-changes. Using git status shows you which branch you're currently on and git branch -vv shows the current commit and remote you're on for each branch. We can make a change on the branch by creating a function in our app.js.

app.js

`js
// our app js code

function helloWorld() {
alert("Hi!")
}
`

After saving, we stage the file with git add app.js and a commit message git commit -m "Adds hello world". Using the git log --oneline shows that we have diverged from the master branch. If we tried to push at this point, we would receive a fatal error. Using git branch -vv displays we don't have js-changes linked to any remote branch. However, Git gives us the fix in the terminal. We have to push while setting the upstream to origin js-changes. We then run git push -u origin js-changes to push it, as -u is just an alternative to --set-upstream. Now, we successfully pushed our new branch to Github and trying git branch -vv can show us that the old js-changes is now mapped to origin/js-changes.

Copy a Commit from One Branch to Another

If we do git branch, we notice we have two branches. In addition, git log --oneline shows us our latest branch has our commit diverging from master, the reversion of the take 3 commit. We can switch over to master with git checkout master leaving our app.js empty.

A problem we could face is that we want the function we created on our separate branch js-changes transferred/copied to master without pulling the entire branch. We simply want to pick the individual commit to js-changes and move it to master.

git log --oneline shows us our current latest commit in master isn't up to date with the separate branch. What we can do to match the commit in our branches is use cherry picking. We run git cherry-pick {HASH} with the hash being the desired commit. Now, our git log --oneline actually shows there was a new commit hash created as it came over. What happened is we copied the commit from js-changes over to master and pushed it up, so the commit exists in both trees.

Move a Commit that was Committed on the Wrong Branch

Using git branch shows us we have two branches, js-changes and master, and we are on master. We can write a second function in our app.js.

app.js

`js
// our app js code

function helloWorld() {
alert("Hi!")
}

function secondFunction() {
alert("This is number 2")
}
`

After modifying, we add and commit it.

Terminal Input


git add -A
git commit -m "My second function"

However, after doing so, doing git branch shows us we made this commit on master branch and we meant to do it on the js-changes branch. git log --oneline shows us which commit we need to move from master to js-changes, but the problem is master is pointing to it. A lot of solutions to this involve git reset --hard, but beware because they can cause a loss of data.

What we want to do is switch to the js-changes branch with git checkout js-changes. Now, to get the function into it, we copy the commit hash and cherry-pick that commit hash we want to move over with git cherry-pick {HASH}. Now, on the js-changes branch, we have the code we want and can push that with git push.

At this moment, our code on js-changes is correct, but master still contains that old commit, as cherry-picking copies the commit but doesn't remove it. To switch master to be back at a previous commit, we do a git reset {HASH} to the commit we want to return to. An alternative to returning the hash would be making the it HEAD~1.

Now, git status shows app.js is still a modified file. However, after resetting we can git checkout app.js to reset it whatever it was at the hash we reset to. After entering that, git status shows app.js is not changed and git log --oneline shows the commit is gone. This approach is like manually doing a reset hard, but we can choose exactly what files we want to reset making it a safe and powerful option.

Use git stash to Save Local Changes While Pulling

Save the local changes,

git stash

Get remote changes

git pull

To apply the stashed changed

git stash pop

You will need to fix the merge conflict.

Then drop the change from the stash

git stash drop stash@{0}

Explore Old Commits with a Detached HEAD, and then Recover

checkout the hash of an old commit

git checkout [HASH]

we'll be in a "detached HEAD" state
Save the work by creating a new branch

git checkout -b my-new-branch

Fix a Pull Request that has a Merge Conflict


git checkout -b conflicts_branch

Add 'Line4' and 'Line5'

git commit -am "add line4 and line5"
git push origin conflicts_branch
git checkout master

Add 'Line6' and 'Line7'`

git commit -am "add line6 and line7"
git push origin master

Cleanup and Delete Branches After a Pull Request

use the github interface to delete the branch remotely

Locally

Confirm that remote is gone

git remote prune origin --dry-run
git remote prune origin

clean up the feature branch

git branch -d feature-branch

Change the Commit Message of a Previous Commit with Interactive Rebase

git log --oneline

start the interactive rebase

git rebase -i HEAD~3

and then change pick to reword.

We can now reword the commit message

git Ignore a File that has Already been Committed and Pushed

We make a file and accidentally push it to github
To remove it, add it to .gitignore file
remove all of our files from our git cache

git rm -r --cached .

add back all the files we want with

git add -A

Add a File to a Previous Commit with Interactive Rebase

git rebase -i HEAD~2

during the interactive rebase, we can add the file, and amend the commi

git commit --amend --no-edit

git rebase --continue

Fix Merge Conflicts While Changing Commits During an Interactive Rebase

enter interactive rebase

git rebase -i HEAD~2

Then we can fix that merge conflict like normal, but finish up the rebase

git rebase --continue

Squash Commits Before they are Pushed with Interactive Rebase

git rebase -i HEAD~3

Make the changes in interactive rebase, make the commit message for that commit, and once we save the message, we'll be left with just a single commit

Completely Remove a File from Pushed git History

Prune the entire history and garbage collect the remains

git reflog expire --expire=now --all && git gc --prune=now --aggressive

use git push to push that change to github, and remove the .env file from all of the history

Resources

When to Use Git Reset, Git Revert & Git Checkout

Top comments (2)

Collapse
 
lakincoder profile image
Lakin Mohapatra

Really nice one

Collapse
 
nothanii profile image
notHanii

❤️