One day I saw this tweet:
Adam Rackis@adamrackisSeriously am I the only one around here using SourceTree?
Forget about the shell commands you have to memorize to use git: just being able to *see* your repo, click on a commit, view every file that was changed, with a full diff, etc.
sourcetreeapp.com twitter.com/hhariri/status…15:55 PM - 12 Oct 2020Hadi Hariri @hhaririGit is not a success story. Git is a failure as a system with a crap user experience that forces you to learn more about the tool you're using that about getting your work done.
It brought me back to several years ago when I just start using git every day, and I haven't understood what's the difference between rebasing and merging yet, my dear team-mate typed CLI commands like being possessed by the flash, got things done in just a few seconds. Before I could even see what happened, he cleared the screen (he got OCD on that LOL). That's the moment I started to realize GUI may not always be faster and easier than CLI.
So here, I would like to share some tips that work really well for me. The tips contain:
- git commands - you may always get more details via
- Oh-my-zsh alias - Oh-my-zsh does not only provide fancy terminal UI, it also presets commonly used command shortcuts. I only used a small part that I feel very intuitive and easy to remember.
- VSCode extensions
Most of time I just copy and paste the hint given by websites to the terminal:
Or simply typing
git clone <git_url> [destination]
With Oh-my-zsh, could even make it shorter to
gcl Which also includes an option to recursively clone the submodules of the repository.
But my favorite now is using the VSCode git command, so after clone, it could help me to open the project right away and start my work:
pin Mac or
pin windows to open command palette, and type
- Enter the repository URL
Or just use the awesome "Clone from Github" to select GitHub repository:
- Choose destination:
- And clone
- Once finished, it will ask for opening the repo:
I usually check out my own branch or the main branches with Oh-my-zsh alias
With the help of the tab key to auto-complete. The original command is:
git checkout <branch>
Checking out a new branch just need to add a
git checkout -b <branch> // In Oh-my-zsh: gco -b <branch>
Sometimes I found my branch name has a typo or got a better idea of the name before I pushing it to the remote. Then I will do
git branch -m <new_branch_name> // In Oh-my-zsh: gb -m <new_branch_name>
For existing branches that have been pushed to remote before, it's a good habit to check the status of it after a while, in case someone else has pushed a new thing to the branch, or myself pushed something on my home laptop but forgot to update the work laptop to the latest version.
Here is the command:
git fetch git status
And it will log something like:
Sometimes this is not clear enough, then I will check the git history. And the basic log could satisfy this for most of the time:
This command provides a lot of interesting options (quite complicated to remember honestly), like
--stat to show simple file diffs,
--graph to draw a commit graph,
--oneline to show only one line for each commits... Well, adding these will become too long to type, so I prefer to use Oh-my-zsh alias:
glg // git log --stat glgg // git log --graph glo // git log --oneline --decorate
Then just do:
git pull // In oh-my-zsh gl
To update the local branch.
If there are some new changes in the local branch, then things will be a little complicated. Well, let's discuss later in the "Resolving conflicts" section.
Before I commit the changes, I love to double-check my changes in case I commit some testing code (e.g. console logs) that I forgot to remove. And the VSCode built-in git diff is really helpful here:
And enter a proper message in the textbox, click the "tick" button to commit.
However, this may easily fail thanks to the commit hooks(linting configured with husky) without an intuitive error message display(Actually the output is available in the "OUTPUT" console, but the format is simplified and not that readable). So I often like to commit in the terminal instead:
git commit -m 'My commit message' // In Oh-my-zsh gc -m 'My commit message'
Or simply add all changes and commit together:
git commit -am 'My commit message' // In Oh-my-zsh gc -am 'My commit message' // Or gcam 'My commit message'
Note that the
-a option will not include the newly created untracked files. So I will need to do this instead sometimes:
// Add all files under the current folder: git add . // In Oh-my-zsh: ga . // Or just want to add all not ignored files in the repo: git add --all // In Oh-my-zsh: gaa
Like my branch naming, I may found some typo in my commit message, or missed some files in my commit. Then I will add the file changes in and do:
git commit --am // In Oh-my-zsh gc --am
If the branch is new and never set the remote upstream, it's better to:
git push -u origin <branch_name> // In Oh-my-zsh: gpsup
So I could track branch status like I mentioned in "Keeping branch up to date" section. And next time I will not need to assign the upstream name anymore, just do:
git push // In Oh-my-zsh: gp // or ggp
rebase is always my first choice of few commits differences. So it will help to make sure my changes are newer than the latest source branch.
To rebase my upstream branch, can use
git pull --rebase // In Oh-my-zsh: ggu
Or more often, rebase on the main branch like this:
git rebase origin/master // In Oh-my-zsh: grb origin/master // Or grbm
Adding upstream name there will ensure git to fetch remote updates first and then do the rebase, making sure rebasing on the latest commit.
Sometimes I made many commits during the development but they are too small to be kept in the main branch, or I changed one part of code back and forth to improve it. And these small commits cause me quite some trouble to rebase the main branch. In this scenario, I may consider squash the commits before resolving conflicts.
If all the commits are mine and I just want to simply squash all commits into 1, I will:
- Check how many commits in my merge request (e.g. this) or from my git log (refer to Checking branch status section)
git reset --soft HEAD~<commit_count>or
git reset --soft <commit_SHA>(The commit SHA here shall be the last commit to keep) to remove those commits and put the changes back to uncommitted status - NOTE that resetting a merge commit will reset all the commits in that merge together
gc -m <new_commit_message>to create a new commit
gp -f- If the commits has been pushed before, it is required to force push here as it changes the git history. NOTE that force push is a dangerous action, make sure it is safe to do that.
If it requires to keep the commit messages and authors. Then use the interactive mode:
git rebase -i HEAD~<commit_counts>or
git rebase -i <commit_SHA>- similar to the git reset above
- Then it will open a command line editor like
vi: Follow the instructions and update the command word like
pickat the start of each line. Let's say I just want to squash all into 1, then I will require to keep the top commit as
pick, and change the rest to
- And save. If successful, I will see such kind of message to confirm the squash:
If I am trying to make a shared base branch up to date, rebasing will cause all the child branches to get significant conflicts. If I am working on a big change, and there are many small commits for better version controlling, rebasing may cause many times of conflict resolving which could be super confusing until I got lost in the conflicts. And now it's better to use
To merge my upstream branch, can use
git pull // In Oh-my-zsh: ggl
Or more often, merge the main branch like this:
git merge origin/master // In Oh-my-zsh: gm origin/master
I used to love the conflict resolver in Webstorm a lot, but sadly Webstorm always eats up my CPU and memory. So I gave it up one day and never come back. Well, I have to say, it was really really good that I took quite some time to get used to the new flow with VSCode.
When there's a conflict, refreshing the "SOURCE CONTROL" panel, and will see the conflict files under "Merge changes":
Select and open the conflict file, and I have to make a choice
Current Change or
Incoming Change. Well, at least it looks not that confusing as
The comparison is unlike the Webstorm's side-by-side but an up-and-down style, which only works well when the conflict block of code is not that large.
Well, here I just stick on this tool as I don't want to introduce any new tools for not that much happen case.
When something is broken or looks weird, the meaningful git message and the long git history finally appears to be a big help. And GitLens is super helpful here.
Despite it provides so many functions, I almost only use
LINE HISTORY and
FILE HISTORY to help me understand how things get here step by step, like this:
If I have ever checkout any remote branches that have been merged later, those references will not go away in my local. Usually, it is fine but in the large codebase it could mess up the git commands to prevent me from committing. So I do regular clean up now by:
git remote prune origin // or maybe even better git fetch --all --prune // In Oh-my-zsh: gfa
By contrast, clearing in local is not that simple. I did clear manually for quite some time in this way:
git branch | cat // to list all the local branches git branch -D <branch_name>
Then I found the shortcut in Oh-my-zsh to remove all the branches that have been merged to