My notes on git
I began using Git on the very first day I started coding, and it has become a tool I rely on nearly every day. Reflecting on my experience, here are the Git notes I wish I had known earlier.
I will just quickly run through the concept without the "How to do it".
.git folder
If you want to track a project using Git. Run git init
at the root folder of that project. Git will create a .git
folder where it saves all git-related things there.
You can verify the .git
folder is there by running ls -a
To make the project untracked by git, simply delete the .git
folder
git commit
When you commit sth, Git will generate a sha key which has 40 characters. This sha is calculated from the contents of change, author, time, and more.
Example: 8784bb991899fe9e20b73984d43ec2265de79f24
You only need the first 7 characters of a sha for git to identify what commit you are referring to (eg: 8784bb9
)
// git log --oneline
83bc0ec (HEAD -> master) Third commit
d969431 Second commit
a8514ed First commit
That explains why if we commit the same things, our sha key is different - since we commit at different times and the author is also different.
Each time you commit, Git saves the entire project - not the changes. Thus, with the sha, you have the assurance that you can recover the entire project as it existed at that specific commit
git config
The first time you set up your git, remember to add your use.name
and user.email
.
git config --add --global user.name "your-name"
git config --add --global user.email "your-email"
Git comes with a config that is global and local (project) level. And it works like a Javascript Object.assign
. The local config will override the global config.
const config = Object.assign({}, globalConfig, localConfig)
The config key is in the following shaped <section>.<key>
Useful commands
git config --list
git config --add <key> "value"
git config --get <key>
git config --unset <key>
Use the flag --global
or --local
to set the scope.
Example: view all global config git config --list --global
Nothing is magical here as git saves all its config to a file. For example, you can find your local config in the config
file in the .git
folder.
git merge
A merge is an attempt to combine two histories together that have diverged at some point. The common point between the two is called "merge base"
Let's say we want to merge the foo
branch into the master
๐ Example 1: the foo
branch and the master
is diverged
The A commit here is a merge base.
B --- C foo
/
A --- D --- E master
After the merge
B --- C -------- foo
/ \
A --- D --- E --- ccf9a73 master
Since there are also some changes to the to both master
and the foo
branch. Git will apply the "3 way merge". From the sets of different commits, git creates a new (merged) commit at the tip of the branch that is being merged.
ccf9a73
here is a merged commit and it contains the changes of B and C. Note that the merged commit would have 2 parents C and E
๐ Example 2:
This time there is no change to the master
after we branch out the foo
from the E commit. Git then does the fast-forward merge
X - Y foo
/
A --- D --- E master
After the merge
X - Y foo
/
A --- D --- E --- X --- Y master
Note that there is no merge commit. Git simply appends the commit X and Y to the tip of the master branch.
git rebase
With the following setup, let's say we are working on the foo
branch and someone updates the master
. We then want to keep the foo
branch updated with the master
B --- C foo
/
A --- D --- E --- X --- Y master
We can do the merge. Let's merge the master
into our foo
branch
B --- C------------------- ccf9a73 foo
/ /
A --- D --- E --- X --- Y --- master
Git then creates a merged commit ccf9a73
to our foo
branch.
As we keep working and syncing the foo
branch with the master
. Our commit history would look like this
B --- C --- ccf9a73 --- D --- E --- ccf9a72 --- F
We got "random" merged commits in between our commits!
๐ Let's see if we can do if we use rebase
instead of merge
B --- C foo
/
A --- D --- E --- X --- Y master
After rebasing
B --- C foo
/
A --- D --- E --- X --- Y master
What rebase does is to checkout the master
branch. It then applies one commit at a time to the foo
branch (B and C) on top of the master
branch. Once finished, it will update the foo
branch to the current commit (C).
Now we can update the foo
branch with the master
WITHOUT adding a random merged commit to our history.
rebase
will change the history of our foo
branch. If we have a foo
public branch in remote, we need to push --force
to update it. It's not a good idea to push --force
if someone else is also working on that branch.
The problem with rebase
is that if you have a conflict with the master branch, every time you rebase, you have to resolve the same conflict again and again. To solve this issue, read more about the config.rerere
config.
git squash
Basically, you can squash many commits into one. Example:
ieuwj23 Add 3
f3bcfw3 Add 2
83bc0ec Add 1
d969431 Second commit
a8514ed First commit
We can now squash the Add 1
, Add 2
and Add 3
commit into one commit Add 1, 2 and 3
ki32dio Add 1, 2 and 3
d969431 Second commit
a8514ed First commit
The primary use case is that you can commit often. And at the end, we can squash these small commits into one with a proper message to have a nice history.
In case we want to undo the changes, instead of revert 3 commits, we only revert 1.
We can do the squashing using interactive rebases. You can read more about it.
git rebase --i
git bisect
Let's say you found a bug in your code and want to find out the exact commit that caused the bug. And you had 100+ commits.
We can use git log
and search for a specific commit based on the commit message. Example: find a commit with the word redirect
git log --grep redirect
It's quite tricky as we may have no idea which keyword we should look for + people do not usually write a good commit message.
The solution: use git bisect
. Just google how to use it!
git reset
When you want to get rid of the changes or walk back the commit
soft: it will move back to a commit and alter the staging area to match the content of the commit
it's useful when you need to make a commit that is partially finished and you want to edit that commit and change the contents. Example:
git reset --soft HEAD~1
hard: will do the same thing as soft except it will drop changes to the staging. That means any work that is being tracked by git will be destroyed
Obviously, this is dangerous as we have just altered the history of a branch
git stash
When you are working on a feature branch foo
and you need to switch to another branch for some reason. What would you do with those changes?
you can commit this change and change the branch. Later, edit the commit with the git reset or continue working and squash those commits.
you can stash the changes and change the branch. Later, you switch back to the
foo
branch and apply the changes from the stash back to the branch.
git stash
will take every change tracked by git (change to index + change to work tree) and store that result, much like a commit, into the "stash."
git stash
git stash -m "message here"
git stash list
git stash pop --index <index>