Git productive - helpful commands and VSCode tips to get things done fast
Tags: git, productivity, vscode
Published: 2020-10-25 12:54:31

One day I saw this tweet:

{% twitter 1315682500971298817 %}

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](https://github.com/ohmyzsh/ohmyzsh/wiki/Cheatsheet). 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 

```bash
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:

1. `command` + `shift` + `p` in Mac or `ctrl` + `shift` + `p` in windows to open command palette, and type `git` to find `Git: Clone`

   ![VSCode command platte - git clone](https://dev-to-uploads.s3.amazonaws.com/i/exrweb9wtt8cagdma1or.png) 

2. Enter the repository URL

   ![VSCode command platte - git clone](https://dev-to-uploads.s3.amazonaws.com/i/uhewy82rlwecbhmiw6cw.png)

   Or just use the awesome "Clone from Github" to select GitHub repository:

   ![VSCode command palette - git clone from Github](https://dev-to-uploads.s3.amazonaws.com/i/0o6ny011bae8ehrymn5b.png)

3. Choose destination:

   ![VSCode command platte - git clone](https://dev-to-uploads.s3.amazonaws.com/i/jka1n5fsyryzhd5wa5my.png)

4. And clone

   ![VSCode command platte - git clone](https://dev-to-uploads.s3.amazonaws.com/i/mt4z615u2x35c26q95ou.png)

5. Once finished, it will ask for opening the repo:

   ![VSCode command palette - git clone](https://dev-to-uploads.s3.amazonaws.com/i/a7rc5mwqiqbz5ewa7fmd.png)

### Checking out a specific branch

I usually check out my own branch or the main branches with Oh-my-zsh alias

```bash
gco <branch>
```

With the help of the tab key to auto-complete. The original command is:

```bash
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:
![VSCode status bar - switch git branch](https://dev-to-uploads.s3.amazonaws.com/i/lg2ffr7ehy6rvf1pg1x7.png)

![VSCode status bar - switch git branch](https://dev-to-uploads.s3.amazonaws.com/i/mfjuhia2mbdxchsqzwo7.png)

### Checking out a new branch

Checking out a new branch just need to add a `-b` option:

```bash
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

```bash
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:

```bash
git fetch
git status
```

In Oh-my-zsh:

```bash
gf
gst
```

And it will log something like:

![Branch is not up to date](https://dev-to-uploads.s3.amazonaws.com/i/u4eszedmm3h3bxttdzuj.png)

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:

```bash
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:

```bash
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:
![Branch is not up to date](https://dev-to-uploads.s3.amazonaws.com/i/u4eszedmm3h3bxttdzuj.png)

Then just do:

```bash
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](#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](https://code.visualstudio.com/docs/editor/versioncontrol) is really helpful here:
![VSCode built-in source control](https://dev-to-uploads.s3.amazonaws.com/i/0y6hro7xwwod8u38y8zz.png)

### Committing the changes

After verification, I may stage the files that I would like to commit at the same time:
![VSCode source control - stage file](https://dev-to-uploads.s3.amazonaws.com/i/ttudnzhrt6p6shfe22ey.png)

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:

```bash
git commit -m 'My commit message'
// In Oh-my-zsh
gc -m 'My commit message'
```

Or simply add all changes and commit together:

```bash
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:

```bash
// 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:

```bash
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:

```bash
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](#keeping_branch_up_to_date)" section. And next time I will not need to assign the upstream name anymore, just do:

```bash
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.

![git rebase](https://dev-to-uploads.s3.amazonaws.com/i/by0p7elwec3a4uhzy275.gif)

To rebase my upstream branch, can use `git pull`:

```bash
git pull --rebase
// In Oh-my-zsh:
ggu
```

Or more often, rebase on the main branch like this:

```bash
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:

1. Check how many commits in my merge request (e.g. [this](https://github.com/JennieJi/protobuf2swagger/pull/11/commits)) or from my git log (refer to [Checking branch status](#checking_branch_status) section)
2. `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
3.  `gc -m <new_commit_message>` to create a new commit
4. `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:

1. `git rebase -i HEAD~<commit_counts>`  or `git rebase -i <commit_SHA>` - similar to the git reset above
2. Then it will open a command line editor like `vi` :
   ![git rebase -i](https://dev-to-uploads.s3.amazonaws.com/i/jo5hilg22dc00grbmq0k.png)
   Follow the instructions and update the command word like `pick` 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 as `pick`, and change the rest to `squash` or `s`. 
3. And save. If successful, I will see such kind of message to confirm the squash:
   ![git rebase -i](https://dev-to-uploads.s3.amazonaws.com/i/age907oidhbywno375n6.png)

### 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.

![git merge](https://dev-to-uploads.s3.amazonaws.com/i/u2ziaqp0pp0xdkft7co5.gif)

To merge my upstream branch, can use `git pull`:

```bash
git pull
// In Oh-my-zsh:
ggl
```

Or more often, merge the main branch like this:

```bash
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":

![VSCode git conflicts](https://dev-to-uploads.s3.amazonaws.com/i/pa6l987ml54rs7ehactx.png)

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](https://marketplace.visualstudio.com/items?itemName=eamodio.gitlens) is super helpful here.

![GitLens](https://dev-to-uploads.s3.amazonaws.com/i/1waxfiw9emhm8c0et73f.png)

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:

![gitlens](https://dev-to-uploads.s3.amazonaws.com/i/aiq8ebnpoytwdmk9af3e.png)

## 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:

```bash
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:

```bash
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`:

```bash
gbda
```