One day I saw this tweet:
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
git --help
- 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
Preparing to work
Cloning a remote repository to the local environment
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:
-
command
+shift
+p
in Mac orctrl
+shift
+p
in windows to open command palette, and typegit
to findGit: Clone
- 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:
Checking out a specific branch
I usually check out my own branch or the main branches with Oh-my-zsh alias
gco <branch>
With the help of the tab key to auto-complete. The original command is:
git checkout <branch>
But when it comes to other people's branches, it is not that effective. And again, the VSCode git extension saved me with a nice branch selection on the status bar:
Checking out a new branch
Checking out a new branch just need to add a -b
option:
git checkout -b <branch>
// In Oh-my-zsh:
gco -b <branch>
Renaming a 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>
Checking branch status
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
In Oh-my-zsh:
gf
gst
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:
git log
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
Keeping branch up to date
If my local branch hasn't had any changes yet, and the git status
notifies me there are some new commits in the remote branch like this:
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.
Finishing and committing the changes
Verifying the changes
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:
Committing the changes
After verification, I may stage the files that I would like to commit at the same time:
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
Fixing the commit msg
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
Push the changes to remote
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
Resolving the conflicts
Rebase is my first choice
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
:
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.
Squashing commits before rebasing or merging
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>
orgit 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>
orgit 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 likepick
at 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 aspick
, and change the rest tosquash
ors
. - And save. If successful, I will see such kind of message to confirm the squash:
Merging for a complicated situation
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 merge
instead.
To merge my upstream branch, can use git pull
:
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
Resolving the conflicts
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 ours
and theirs
.
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.
Tracing what happened
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:
Clearing staled branches
In Remote
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
In Local
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 master
/develop
/dev
:
gbda
Top comments (1)
👏