git

Opening Files with Conflicts

After a merge, I would usually do git status to see which files have conflicts to be resolved. Then, I would manually open the files listed under unmerged paths one by one with Vim to edit them.

$ git status
On branch develop
Your branch is ahead of 'origin/develop' by 2 commits.
  (use "git push" to publish your local commits)

You have unmerged paths.
  (fix conflicts and run "git commit")
  (use "git merge --abort" to abort the merge)

Unmerged paths:
  (use "git add <file>..." to mark resolution)

    both modified:   package.json
    both modified:   pages/main.js

no changes added to commit (use "git add" and/or "git commit -a")

Actually, we can get that list of files with git diff --name-only --diff-filter=U.

$ git diff --name-only --diff-filter=U
package.json
pages/main.js

To make it easier to edit all conflicted files, you can add a git alias in .gitconfig, so you can do git conflicts.

[alias]
    conflicts = !vim `git diff --name-only --diff-filter=U`

Alternatively, you can also use a shell alias, so you can do conflicts.

alias conflicts='vim $(git diff --name-only --diff-filter=U)'

Tags: git

Quicker Branch Merging

Assume I’m on the feature/hello branch. I want to checkout to the staging branch, merge feature/hello into that, and checkout back to feature/hello.

Instead of doing this:

$ git checkout staging
$ git merge feature/hello
$ git checkout feature/hello

We can do this:

$ git checkout staging
$ git merge -
$ git checkout -

Basically, - refers to the previous branch.

Tags: git

Git Alias for Pull Request

My company uses Bitbucket to host our remote repositories. It becomes annoying when I need to manually visit the Bitbucket page for the repository to create a pull request. So, to make life easier, we can add the pr alias.

[alias]
  pr = \
    !repo=$(git ls-remote --get-url | cut -f 2 -d "@" | cut -f 2 -d ":" | cut -f 1 -d ".") && \
    branch=$(git symbolic-ref --short HEAD) && \
    open "https://bitbucket.org/${repo}/pull-requests/new?source=${branch}"

We use git ls-remote --get-url to get the remote url for the repository, then use cut to get the remote repository name. Note that the cut is meant for ssh urls like [email protected]:chunkhang/apple_sauce.git. https urls will need a different cut.

On the other hand, we use git symbolic-ref --short HEAD to get the current local branch name. With that, we can construct the url to create the pull request, and open it in the web browser.

Tags: git

Git Housekeeping

I just got tired of seeing the huge list of branches whenever I do git branch. 99% of these branches are already merged and the remote branch already gone. So, once in a while, it’s good to do some housekeeping with your repositories. Note that everything below does not affect the remote repository at all. It only cleans up your local repository.


1. Prune local branches

Delete all local branches that have been merged, except for develop, master or staging

git branch --merged | grep -vE 'develop|master|staging' | xargs git branch -d

2. Prune remote branches

Delete all remote branches you still keep track of, which no longer exist in the remote origin

git fetch --prune

3. Clear reflog

Clear all reflog entries

git reflog expire --all --expire=now

4. Garbage collection

Run garbage collection process aggressively

git gc --prune=now --aggressive

To make it easy to run all the actions above, we can add everything under an alias in .gitconfig.

[alias]
  housekeeping = \
    !git branch --merged | grep -vE 'develop|master|staging' | xargs git branch -d && \
    git fetch --prune && \
    git reflog expire --all --expire=now && \
    git gc --prune=now --aggressive

Now, we can run git housekeeping every once in a while to feel good while your repository gets cleansed thoroughly.

$ git housekeeping
Deleted branch feature/cross_region_orders (was 62bfae1).
Deleted branch feature/cross_region_quotation (was 4d823d9).
Deleted branch feature/out_of_service_area_orders (was 80de5c3).
Deleted branch feature/remove_dhl_hardcode (was 265f644).
Deleted branch feature/store_google_response (was f9f8b71).
From bitbucket.org:pick-up/address_service
 - [deleted]         (none)     -> origin/feature/close_redis
 - [deleted]         (none)     -> origin/feature/cross_region_orders
 - [deleted]         (none)     -> origin/feature/discard_google_parts
 - [deleted]         (none)     -> origin/feature/remove_dhl_hardcode
 - [deleted]         (none)     -> origin/feature/th-region
 - [deleted]         (none)     -> origin/hotfix/2.25.2
 - [deleted]         (none)     -> origin/hotfix/2.29.1
 - [deleted]         (none)     -> origin/release/2.27.0
 - [deleted]         (none)     -> origin/release/2.28.0
 - [deleted]         (none)     -> origin/release/2.28.1
Counting objects: 2194, done.
Delta compression using up to 4 threads.
Compressing objects: 100% (2171/2171), done.
Writing objects: 100% (2194/2194), done.
Total 2194 (delta 1473), reused 614 (delta 0)

If you have multiple repositories, you can even consider using gita. With gita, we can do gita super housekeeping to perform housekeeping on all our repositories in one go.

Tags: git

Comparing Two Branches

To compare two branches, just do git diff feature..develop.

Tags: git

Staging Hunks

Usually, we stage files before committing them:

$ git add vimrc
$ git commit -m 'Update vimrc'
[master f4efe1d] Update vimrc
 1 file changed, 1 insertion(+)

However, we can also stage individual hunks in a file if we only want to commit part of the changes in that file:

$ git add --patch vimrc
diff --git a/vimrc b/vimrc
index c493cca..8a95218 100644
--- a/vimrc
+++ b/vimrc
@@ -56,7 +56,7 @@ endif
 " Git
 " -----------------------------------------------------------------------------
 Plug 'tpope/vim-fugitive'
-Plug 'airblade/vim-gitgutter'
+" Plug 'airblade/vim-gitgutter'
 Plug 'rhysd/conflict-marker.vim'

 " -----------------------------------------------------------------------------
Stage this hunk [y,n,q,a,d,/,e,?]?

The git add --patch or git add -p command allows us to select hunks interactively for staging. You can type ? during the process to print the help message to understand what each letter means:

y - stage this hunk
n - do not stage this hunk
q - quit; do not stage this hunk or any of the remaining ones
a - stage this hunk and all later hunks in the file
d - do not stage this hunk or any of the later hunks in the file
g - select a hunk to go to
/ - search for a hunk matching the given regex
j - leave this hunk undecided, see next undecided hunk
J - leave this hunk undecided, see next hunk
k - leave this hunk undecided, see previous undecided hunk
K - leave this hunk undecided, see previous hunk
s - split the current hunk into smaller hunks
e - manually edit the current hunk
? - print help

Tags: git

Logs for One Branch against Another

To view the logs for a specific branch in respect to another branch, we can do git log develop..feature. This will show us only the new commits on feature if it was branched off from develop.

Alternatively, if you are already on the feature branch, you can do git log develop..@ or just git log develop...

Tags: git

Verbose Git Commit

After staging your files, doing git commit will launch your editor, typically Vim, for you to enter the commit message.


# Please enter the commit message for your changes. Lines starting
# with '#' will be ignored, and an empty message aborts the commit.
#
# On branch master
# Your branch is up-to-date with 'origin/master'.
#
# Changes to be committed:
#   modified:   vimrc
#

However, if you do git commit -v, you will get to see the diff for the commit you are about to make as well. This is a useful reminder for when you are writing the commit message.


# Please enter the commit message for your changes. Lines starting
# with '#' will be ignored, and an empty message aborts the commit.
#
# On branch master
# Your branch is up-to-date with 'origin/master'.
#
# Changes to be committed:
#   modified:   vimrc
#
# ------------------------ >8 ------------------------
# Do not touch the line above.
# Everything below will be removed.
diff --git a/vimrc b/vimrc
index cc63644..fb3832c 100644
--- a/vimrc
+++ b/vimrc
@@ -439,6 +439,11 @@ nmap <C-f>f <Plug>CtrlSFPrompt
 nmap <C-f>w <Plug>CtrlSFCwordPath
 nnoremap <C-f>t :CtrlSFToggle<cr>
 
+" -----------------------------------------------------------------------------
+" emmet.snippets
+" -----------------------------------------------------------------------------
+imap <C-e>, <esc>dawae<tab><esc>pa<tab>
+
 " -----------------------------------------------------------------------------
 " ultisnips
 " -----------------------------------------------------------------------------

To make this the default behavior of git commit, you can do git config --global commit.verbose true. This will add the following entry into .gitconfig.

[commit]
  verbose = true

Tags: git

Operating only on changed files

In some occasion, after making some changes to different files, we might want to apply some operations only on the changed files.

Assuming you’re using git for version control, we can use git diff --name-only to get a list of changed files.

In shell, we can use $(<command) to run a command and capture its output.

Combining these two, we can apply an operation on changed files by:

$ <command> $(git diff --name-only)

Use Case

Previously I am not using any code formatter on my client project. Even though, I can run the formatter directly on the whole project, I am afraid that some code might break (since I don’t have a full test coverage).

Hence, I plan to slowly format my code whenever I make any changes with this command:

bundle exec standardrb $(git diff --name-only) --fix

References

Git Patches

You may be familiar with git stash, but patches can also be very useful. Assuming you have changes to the working directory, instead of stashing it, we can create a patch:

git diff > example.patch

With that, we have created a patch file that contains the current diff. Now, instead of stashing, we can just discard all changes. To apply the patch, just do:

git apply example.patch

This is similar to running git stash pop to update the current working directory with the diff from example.patch. With patches, it’s easier to keep track of what each patch does. On top of that, you can distribute patches to other people so they can quickly apply the change to their repository. It’s super useful to create patches for testing a feature for example. Your reviewer can then just apply the patch when they’re reviewing your branch.

To revert the patch, simply do:

git apply -R example.path

You can use either -R or --reverse.

Tags: git