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
Top comments (2)
Really nice one
❤️