Git, the stupid content tracker according to its manpage, is packed with features and some can be very intimidating. So we resort to just using the same few commands we can memorize, over and over again, instead of leveraging all the power that was given to us.

This article is also available in Chinese:
https://www.infoq.cn/article/tgdOxMBqYHoVslvlZqxT


Tip #1: Improve your configuration

Git is highly configurable, on a global, user, and local level.

https://git-scm.com/docs/git-config

Lookup Order

Every setting can be overwritten:

$CWD/.git/config
    ▼ ▼ ▼
$HOME/.gitconfig`
    ▼ ▼ ▼
$HOME/.config/git/config
    ▼ ▼ ▼
/etc/gitconfig

Change Settings

Either edit any of the files with your favorite editor or use the CLI:

# GLOBAL SETTINGS
$ git config --global <keypath> <value>

# LOCAL SETTINGS
$ git config <keypath> <value>

Values need to be quoted if they contain any white space characters.

Show Current Settings

# SHOW CURRENT SETTINGS AND THEIR ORIGIN
$ git config --list --show-origin

Helpful Config Settings

# SET YOUR IDENTITY
$ git config --global user.name "<your name>"
$ git config --global user.email <your email>

# PREFERRED EDITOR  
$ git config --global core.editor <editor, e.g. vim>

# CREDENTIALS CACHE  
# WINDOWS  
$ git config --global credential.helper manager
# LINUX (timeout in seconds)
$ git config --global credential.helper "cache --timeout=3600"
# MACOS
$ git config --global credential.helper osxkeychain

https://git-scm.com/docs/gitcredentials


Tip #2: Aliases

To save commonly used git commands, create an alias:

# CREATE AN ALIAS
$ git config --global alias.<alias-name> "<git command>"

# USE AN ALIAS
$ git <alias-name> <more optional arguments>

Helpful Aliases

# UNDO LAST COMMIT
$ git config --global alias.undo "reset --soft HEAD^"

# AMEND STAGED CHANGES TO LAST COMMIT (keep commit message)
$ git config --global alias.amend "commit --amend --no-edit"

# COMPACTER STATUS OUTPUT
$ git config --global alias.st "status -sb"

# COLORED LOG WITH GRAPH
$ git config --global alias.lg "log --graph --pretty=format:'%Cred%h%Creset -%C(yellow)%d%Creset %s %Cgreen(%cr) %C(bold blue)<%an>%Creset'"

# REMOVE ALL MERGED BRANCHES
$ git config --global alias.rmb "!git branch --merged | grep -v '*' | xargs -n 1 git branch -d"

# RANK CONTRIBUTERS
$ git config --global alias.rank "shortlog -n -s --no-merges"

Tip #3: Finding Commits and Changes

By Commit Message

# BY COMMIT MESSAGE (all branches)
$ git log --all --grep='<search term>'

# BY COMMIT MESSAGE (include reflog)
$ git log -g --grep='<search term>'

By Change

# BY CONTENT OF CHANGES
$ git log -S '<search term>'

By Date

# BY DATE RANGE
$ git log --after='DEC 15 2019' --until='JAN 10 2020'

Tip #4: Adding Hunks

Instead of just adding all changes of a file with git add <filepath>, the argument --patch / -p will stage hunks interactively.

# Patch Commands

y = stage hunk
n = don't stage this hunk
q = quit
a = stage this and all remaining hunks of the current file
d = don't stage this and all remaining hunks of the current file
/ = search for hunk (regex)
s = split into smaller hunks
e = manually edit hunk
? = print help

g = select a hunk to go to
j = leave hunk undecided, see next undecided hunk
J = leave hunk undecided, see next hunk
k = leave hunk undecided, see previous undecided hunk
J = leave hunk undecided, see previous hunk

https://git-scm.com/docs/git-add#Documentation/git-add.txt–i


Tip #5: Stash changes without committing

A stash is a temporary shelve of the current changes. With their help, we can return to the current state of the index and can reapply the stashed changes later on.

By default, only changes in currently tracked files will be stashed, new files will be ignored.

Multiple stashes can be created and applied independently.

https://git-scm.com/docs/git-stash

Creating

# CREATE NEW STASH
$ git stash

# CREATE NEW STASH (include untracked changes)
$ git stash -u/--include-untracked

# CREATE NEW STASH WITH NAME
$ git stash save "<stash name>"

# INTERACTIVE STASHING
$ git stash -p

Listing

# LIST ALL STASHES (providing "n" for other commands)
$ git stash list

Viewing

# VIEWING STASH CONTENT
$ git stash show

# VIEWING STASH DIFFS
$ git stash show -p

Applying

# APPLY LAST STASH (delete stash afterwards)
$ git stash pop

# APPLY LAST STASH (keep stash)
$ git stash apply

# APPLY SPECIFIC STASH (n = stash list number)
$ git stash pop/apply stash@{n}

# CREATE NEW BRANCH FROM STASH (n = stash list number)
$ git stash branch <new branch name> stash@{n}

# APPLY SINGLE FILE FROM STASH (n = stash list number)
$ git checkout stash@{n} -- <filepath>

Cleaning

# DROP SPECIFIC STASH (n = stash list number)
$ git stash drop stash@{n}

# DROP ALL STASHES
$ git stash clear

Tip #6: Dry Run

Many git operations can be quite destructive. For example, a git clean -f will remove all untracked files, without any chance to bring them back.

To avoid such a catastrophic result, many commands support a dry-run to check the result before it actually happens. Sadly, the option used isn’t always the same:

git clean -n/--dry-run
git add -n/--dry-run
git rm -n/--dry-run

# GIT MERGE PSEUDO DRY-RUN
git merge --no-commit --no-ff <branch>
git diff --cached
git merge --abort

Be aware that git commit -n isn’t dry-run at all!

It’s actually the option --no-verify, ignoring any pre-commit/commit-msg githooks.


Tip #7: Safer Force Push

It’s easy to mess up a branch by working on an older commit, creating a new head, etc. The remote changes could be overridden with git push --force, but they shouldn’t!

git push --force is a destructive and dangerous operations, because it works unconditionally, and destroys any commits already push by other committers. This doesn’t have to be fatal for the repository of others, but changing history and affecting others is not a good idea.

A better option is usinggit push --force-with-lease instead.

Instead of unconditionally overwriting the upstream remote, git checks for any remote changes that aren’t available locally. If so, it fails with a “stale info” message and wants us to git fetch first.

https://git-scm.com/docs/git-push#Documentation/git-push.txt—force-with-leaseltrefnamegt


Tip #8: Changing Commit messages

Commit are immutable, and can’t be changed. But we can amend an existing commit with a new message, instead. This will replace the original commit, so don’t use it on already pushed commits.

git commit --amend -m "<new commit message>"

https://git-scm.com/docs/git-commit#Documentation/git-commit.txt—amend


Tip #9: Changing history even more

Changing the history of a repository doesn’t stop at the last commit message. With git rebase multiple commits can changed:

# RANGE OF COMMITS
git rebase -i/--interactive HEAD~<number of commits>

# ALL COMMITS AFTER <commit-hash>
git rebase -i/--interactive <commit hash>

A reverse-ordered list of commits is presented in the configured editor. Something like this:

# <command> <commit hash> <commit message>
pick 5df8fbc revamped logic
pick ca5154e README typos fixed
pick a104aff added awesome new feature

By changing the actual content of the editor, we can give git a blueprint what/how to rebase:

# p, pick   = use commit without changes
# r, reword = change commit message
# e, edit   = edit commit
# s, squash = meld into commit
# f, fixup  = like "squash", but discard commit message
# x, exec   = run command (rest of the line)
# d, drop   = remove commit

After saving the editor, git will run this blueprint to rewrite history.

The e, edit will pause the rebase, to edit the current state of the repository. To finish up, run git rebase --continue.

If any problems occur, like merge conflicts, and we want to start over, there’s always git rebase --abort.

https://git-scm.com/docs/git-rebase


Tip #10: Archive Tracked Files

We can compress the tracked files for a specific ref in different formats (zip or tar):

git archive --format <format> --output <filename> <ref>

<ref> can be a branch, commit hash, or a tag.

https://git-scm.com/docs/git-archive


Bonus Tip: The Single Dash

There is a shortcut representing the previously used branch: a single dash-

git checkout my-branch
# Current branch: my-branch
<do some git operations, e.g. adding/commiting>

git checkout develop
# Current branch: develop
git merge -
# Merges my-branch into develop

The single dash is equal to @{-1}.

https://git-scm.com/docs/git-checkout#Documentation/git-checkout.txt-ltbranchgt


Resources