Git

Git Introduction

Core Concept

The Three areas

  • Working Tree - files you are currently working on. It is a tree because it implies the entire directory structure. It is used to be called ‘working directory’ until git v2.9.
  • Index - contains changes about what will go into the next commit. Technical name in Git is “index”. It is called “Staging Area” sometimes.
  • Repository(.git folder) - where git stores the metadata and object database for the project.
Working directory, staging area, and Git directory. Image from [https://git-scm.com/book/en/v1/Getting-Started-Git-Basics](https://git-scm.com/book/en/v1/Getting-Started-Git-Basics)

HEAD - This is a pointer normally points to a named branch(e.g. master). When we use a checkout command to checkout
a specific commit, HEAD is in Detached state.

Tilde vs caret

  • HEAD^ - first parent of HEAD. same as HEAD^1.
  • HEAD^2 - second parent of HEAD.
  • HEAD^^ - first parent of the first parent of HEAD. Not the same as HEAD^2. Equivalent of HEAD~2.
  • HEAD~ - parent of HEAD
  • HEAD~2 - 2 levels up the hierarchy.
  • see Tilde vs caret

Basic Commands

These are the most commonly used commands.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
# create a git repo
git init

# Clone existing repository into a new directory
git clone <repository_url>

# check status
git status

# check short status
git status -s
git status --short

# stage file(s). changes in working tree are moved to the index for the next commit
git add <file>
git add *.java
git add .

# commit changes in the index
git commit -m "message"

# push current branch to remote
git push

# push current branch to remote and keeps track of the change from the remote branch. Note: You usually use this command in a new local branch.
git push -u <remote> <branch>

# Update the remote-tracking branches for the repository you cloned from,
# then merge one of them into your current branch
# If conflict exist, it will abort
# use `git stash` to save changes, or use `git reset --hard origin/master` to abort changes
# then continue with `git pull`
git pull

Config

The global configuration can be manually modified in ~/.gitconfig

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
git config --global user.name 'First Last'
git config --global user.email 'me@company.com'

# set default branch name. Default is 'master'
git config --global init.defaultBranch

# configure 'push.default' to push only current branch to remote branch. This is prefered
git config --global push.default simple

# configure 'push.default' to push all local branches to remote branches. Do not use!
# git config --global push.default matching

# set core editor
# For vim - git config --global core.editor "vim"
git config --global core.editor "code --wait"

# set a diff tool
git config --global diff.tool gvimdiff

# git push config
git config --global --add push.autoSetupRemote true

# set pull behavior
git config pull.ff only # fast forward only
git config pull.rebase true # when use git pull, it will rebase

To view configuration

1
2
3
4
5
# view settings
git config --list

# view settings and where they are coming from
git config --list --show-origin

Alias

config alias s for status

1
git config --global alias.s status

Now you can use git s to show status.

You can also add alias in ~/.gitconfig file directly. Here an example:

1
2
3
4
5
6
7
8
9
10
11
12
[alias]
# one-line log
l = log --pretty=format:"%C(yellow)%h\\ %ad%Cred%d\\ %Creset%s%Cblue\\ [%cn]" --decorate --date=short
a = add
cm = commit -m
cam = commit -a -m
s = status
co = checkout
cob = checkout -b
p = push
pl = pull
pr = pull --rebase

You can also add alias to ~/.bashrc or ~/.zshrc to cut down keystrokes.

1
2
3
4
5
# one-line log
alias glo='git log --pretty=format:"%C(yellow)%h %ad%Cred%d %Creset%s%Cblue [%cn]" --decorate --date=short'

# use 'g' to replace 'git'
alias g='git'

Line Ending Conversions

There are 3 config options regarding how git handles line ending conversions.

  • Checkout Windows-style, commit Unix-style line endings. Git converts LF to CRLF when checking out text files. Recommended for windows. (“core.autocrlf” is set to “true”)
  • Checkout as-is, commit Unix-style line endings. Recommended for Unix. (“core.autocrlf” is set to “input”)
  • Checkout as-is, commit as-is. Not recommended for cross-platform projects. (“core.autocrlf” is set to “false”)

if “core.autocrlf” is set to “input”, a warning will be displayed when adding text files to index in Windows.

1
2
warning: CRLF will be replaced by LF in foo.txt.
The file will have its original line endings in your working directory.

Set “safecrlf” to be “false” to hide the warning message. So these two commands can go together.

1
2
git config --global core.autocrlf input
git config --global core.safecrlf false

git add

git add updates the index using the current content found in the working tree, to prepare the content staged for the next commit.

1
2
3
4
5
6
7
8
9
10
11
12
# Stage all files (that are not listed in the .gitignore) in the entire repository
git add .

# add a file to index
git add file2

# Interactively stage hunks of changes
git add -p .

# Interactively stage hunks of changes for a file
git add -p file2

use --patch or -p option if you want to stage part of a file for commit. Git will ask you what you want to do with each hunk;

1
Stage this hunk [y,n,q,a,d,e,?]? 

here are the options you can select
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
e - manually edit the current hunk
? - print help

git checkout

This command can be used to switch branch. Can also be used to revert file to a commit or to restore file.

Git 2.23 separates git checkout into two commands:

  • git switch to switch branches
  • git restore to restore working tree files
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
# switch branch
git checkout <branch>

# switch to local master branch
git checkout master

# create a new branch and switch to it
git checkout -b <newbranch>

# checkout a remote branch
git checkout feature

# if you have multiple remote, then
git checkout -b feature origin/feature

# Example to revert Makefile to two revisions back,
git checkout master~2 Makefile # checkout a file out of a commit

# delete hello.c by mistake, and then restore it back from index
rm -f hello.c
git checkout hello.c # undo the delete by restoring hello.c


# 'detached HEAD' state
# you enter detached head state by checking out a specific commit
# git checkout <commit>
# see git checkout --help for more info about detached HEAD
# see https://git-scm.com/docs/git-checkout#_detached_head for the visual
git checkout c6c8007 # or
git checkout master^^

git switch

git switch - Switch branches

1
2
3
4
5
6
7
8
# switch to main branch
git switch main

# switch to a new branch 'newfeature'
git switch -c newfeature

# switch to previous branch
git switch -

git restore

git restore - Restore working tree files or files in the index.

Restore location - by default, the restore location is the working tree. --staged will restore the index only. To restore both working tree and index, use --staged --worktree.

Source - if --staged is given, the contents are restored from HEAD, otherwise from the index. Use --source to restore from a different commit.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
# restore all files in the current directory to match the version in the index.
git restore .

# restore a file to match the version in the index.
git restore <file>

# To restore files in the index to match the version in HEAD
git restore --staged <file1> <file2> ...

# To restore all files in the index to match the version in HEAD
git restore --staged .

# To restore both staged and workingtree.
git restore --staged --worktree .

# to restore a file from master branch
git restore --source master <file1>

git reset

git-reset - Reset current HEAD to the specified state. The git reset command is a complex and versatile tool for undoing changes.

git reset is similar in behavior to git checkout. Where git checkout solely operates on the HEAD ref pointer, git reset will move the HEAD ref pointer and the current branch ref pointer.

Options:

  • --hard - resets the index and working tree.
  • --mixed - resets the index. This is the default.
  • --soft - doesn’t touch the idnex file or the working tree at all.
1
2
3
4
5
6
7
8
9
10
11
# back out all the changes since last commit, both working tree and the index.
# this command is DANGEROUS because changes in working tree and the index are lost.
git reset --hard

# same as `git reset --mixed`
# resets the index but not the working tree. changes are preserved but not in the index anymore
git reset

# Unstage files in Index.
# same as `git restore --staged <file1> <file2> ...`
git reset HEAD <file1> <file2> ...

Reset to a Commit
If you haven’t push the commit, you can fix the commit locally. DO NOT use these command if you have already pushed the changes.

1
2
3
4
5
6
7
8
9
# reset to the target commit. Both change in the working tree and the index will be kept
git reset --soft commitID

# reset to the target commit. Throw away change in working tree and index.
# be careful with this command
git reset --hard commitID

# hard reset to origin/master.
git reset --hard origin/master

For more information on git reset, see https://www.atlassian.com/git/tutorials/undoing-changes/git-reset


git branch

git branch command - List, create or delete branch

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
# List branches
git branch -l

# List remote branches
git branch -r

# Create a branch
# this command do not automatically switch to the new branch
git branch new-branch-name

# Create a branch from a tag
git branch my-new-branch v1.2

# Create a branch from a commit
git branch my-new-branch c0a9b60

# Delete a branch
git branch -d branch-name

# Delete a remote branch
git branch -d -r origin/branch-name

git revert

git revert will create new commits to undo the previous commit. git revert require your working tree to be clean. use using git reset --hard to throw away all uncommitted changes if needed.

To revert a pushed commit, first get the commit ID

1
git log --oneline

Then execute git revert <commit-id> to revert the commit. This will create a commit that undos the commit to be revert. You can also use this command to undo a range of commits

1
2
3
4
5
6
7
8
9
10
11
# Revert a commit
git revert 901300d

# Revert multiple commits, start with 901300d, then 7806790 and 01926a4
git revert 901300d 7806790 01926a4

# Revert the last two commits
git revert HEAD~2..HEAD

# Revert a range of commits. The first hash is exclusive. The last hash is inclusive
git revert 874845a..4dd4b4e

If you don’t want to create a commit, add -n or --no-commit option. Changes to revert the commit will be in stage area. No commit is created.

1
git revert -n 874845a..4dd4b4e

Revert a merge commit

1
2
3
4
5
6
# To revert a merge commit. Merge commit has two parents. You must use -m flag to specify the mainline. 
# Parent number starts with 1. The other commit that is not consider mainline will be un-merge.
git revert -m 1 8f937c6

# To revert a merge commit without commit use -n or --no-commit flag
git revert -m 1 -n 8f937c6

If there is conflict, you can choose to resolve the conflict or use git revert --abort to abort the operation.

1
2
# abort the revert operation
git revert --abort

see git revert man page for more information

Upstream

The term upstream and downstream refers to the repository. Generally, upstream is from where you clone the repository, and downstream is any project that integrates your work with other works.

set the upstream when pushing a new local branch

1
git push -u origin new-branch

You can set the upstream for an existing branch

1
git branch --set-upstream-to=origin/some-branch some-branch

You can also unset a upstream

1
git branch --unset-upstream

find out which remote branch a local branch is tracking

1
git branch -vv

Reference:

Remote

Pro Git working with Remote

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
# show remote URLs
git remote -v

# add a remote
git remote add <name> <url>

# favor SSH protocol to HTTPS
# HTTPS requires you to enter user name and password
# setting remote url to use SSH protocol
git remote set-url <name> <newUrl>
git remote set-url origin git@gitlab.com:xinghua24/GitCommands.git

# remove and add origin
git remote rm origin
git remote add origin git@gitlab.com:xinghua24/GitCommands.git

# show remote branches
git remote show <remote>
git remote show origin

Branching & Merge


1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
# show local branches. * points to the branch that is checked out
git branch

# show local and remote branch
git branch -a

# rename a branch
git branch -m <oldname> <newname>

# rename current branch
git branch -m <new-branch-name>

# new branch. new branch name can be feature/xfeature
git checkout -b <branch>

# switch branch
git checkout <branch>

# merge a branch. first checkout the branch you wish to merge into and then git merge <branch>
# merge change is made to the current branch(branch to merge into). The branch from parameter is not changed.
git merge <branch>

# no parameter git merge
# if no commit is given, then merge the remote-tracking branch
git merge

# After seeing a conflict, you can abort the merge or resolve the conflicts.
# The easiest way to resolve merge conflict is edit conflict file in VSCode.
# Then add edited files to index, and then run git commit.

# merge origin/develop to current branch
git merge origin/develop

# fast-forward only
git merge newbranch --ff-only

# no fast-forward merge
git merge newbranch --no-ff

# merge but do not create a commit
git merge --no-commit --no-ff origin/develop

# abort the merge due to conflict
git merge --abort

# show branches merged into master
git branch --merged master

# show branches merged into HEAD
git branch --merged

# show branches not merged into HEAD
git branch --no-merged

# delete a local branch. use -D to force an unmerged delete
# can't use this command delete the current branch
git branch -d <branch>
git branch -D <branch>

Fetch and Pull

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
# fetch - only download all branches from remote, do not merge
git fetch <remote>
git fetch

# git pull = git fetch + git merge.
# Better to simply use git fetch and merge explicitly
git pull

# git pull --rebase = git fetch + git rebase
git pull --rebase

# fetch all remotes
git pull --all

# delete a remote branch
git push <remote> --delete <branch>

Push

1
2
3
4
5
6
7
8
9
10
11
# push a new branch to remote. -u is short for --set-upstream
git push -u origin <branch>
git push --set-upstream origin <branch>

# if remote branch is named differently
git push <remote> <local_branch_name>:<remote_branch_name>

# push changes in local branch to remote branch
git push <remote> <branch>
git push origin <branch>
git push

If the current branch only exist in local and not in remote, then the following error will occur. In this case,
Use -u or --set-upstream flag to set the up stream.

1
2
3
4
fatal: The current branch master has no upstream branch.
To push the current branch and set the remote as upstream, use

git push --set-upstream origin <branch-name>

Rebase

git rebase - Reapply commits on top of another base tip. Unlike git merge, Rebase makes history clean.

If rebase is done after the branch is pushed to remote repository, then the next push will be rejected. You have to use force push to push change. command is git push --force or git push -f.

1
2
3
4
5
6
7
8
# this command only make change in the current branch. The branch from parameter is not changed
git rebase <branch>
git rebase main
git rebase origin/main

# Interactive mode. Make a list of the commits which are about to be rebased.
git rebase -i
git rebase --interactive

To learn more about interactive rebase, see How to keep your Git history clean with interactive rebase

Rebase Conflict

Git rebase will stop at the first problematic commit and leave conflict makers in the tree. You need to resolve conflict first, then add files to the index.

1
git add <filename>

after resolving the conflict, continue the rebase process

1
git rebase --continue

You can abort the rebase if you don’t want to continue

1
git rebase --abort

Diff

most common commands are git diff and git diff –cached

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
#  you can use 'difftool' instead of 'diff' if 'tool.diff' is setup

# diff takes two references. it can be hash, pointer(HEAD) or branch name

# Changes in the working tree not yet staged for the next commit.
git diff
git diff -- <file>

# Changes between the index and last commit;
# what you would be committing if you run "git commit" without "-a" option
git diff --cached
git diff --staged HEAD
git diff --cached -- <file>

# Changes in the working tree since last commit;
# what you would be committing if you run "git commit -a"
git diff HEAD
git diff HEAD -- <file>

# comparing with arbitrary commits
git diff HEAD^ HEAD # between before the last commit and the last commit
git diff HEAD HEAD^ # between the last commit and before the last commit
git diff 3c797b7 HEAD # compare using commit hash
git diff 3c797b7 07568da

# Compare two branch
git diff master origin/master

Tagging

Tagging allows you to tag specific points in a repository’s history as being important.

There are two types of tags

  • Lightweight tag - just a pointer to a specific commit
  • Annotated tag - stores as full object in the Git database. They’re checksummed; contain the tagger name, email, and data; have a tagging message.
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
# List tags with optional -l or --list
git tag

# Create a tag
git tag v1.0

# Create a Annotated Tag. -m specifies a tagging message
git tag -a v1.2 -m "my version v1.2"

# show git data
git show v1.2

# Tag a specific commit
git tag -a v1.3 89414fb

# Deleting a tag
git tag -d v1.2

# Sharing Tags
git push origin v1.0

# Checking out Tags
git checkout v1.0

For more information on tagging see


Stash

Stash - Use git stash when you want to record the current state of the working tree and the index, but want to go back to a clean working tree.
Ref Doc: https://git-scm.com/docs/git-stash. Stashing is handy if you need to quickly switch context and work on something else.

One way to use Stash is to stash before running git pull command. after pull is completed,
run git stash apply. this way no merge conflict will occur when pulling files from remote.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
# save a stash
# uncommited work in Working directory and Staging Area are saved to stash
git stash
git stash save <descriptive_message>

# git stash a single file
git stash push -m "message" <file>

# show stashes
git stash list

# show stash change files
git stash show <stash>
git stash show stash@{0}

# show stash in patch form using -p option
git stash show -p stash@{0}
git stash show -p stash@{1}

# apply stash stash@{0}. If there is conflict, edit the conflict
git stash apply stash@{0}

# also applies stash
# apply doesn't remove the stash from queue. but pop does.
git stash pop
git stash pop stage@{0}

# remove a single stash from list of stash entries
git stash drop stash@{0}

# remove all stash entries
git stash clear

In Powershell, curly braces have special meaning. You need to surround the stash name with single quote or escape with backtick. Otherwise you will get error message error: unknown switch 'e'.

1
git stash apply 'stash@{0}'

More stash tutorial: Atlassian Git Stash Tutorial

Subtree

Git subtree allows one repository to exist inside the parent repository as a sub-directory. Useful for maintaining sub-project.

1
2
3
4
5
6
7
8
# Add subtree
git subtree add --prefix=path/to-repo repo-name branch

# Push to subtree's repo
git subtree push --prefix=path/to-repo repo-name branch

# Pull changes from subtree's repo back to parent repo
git subtree pull --prefix=path/to-repo repo-name branch

The main drawback for subtree is you have to specify the directory path for every push/pull.

Reference

git worktree

A git repository can support multiple working trees, allowing you to check out more than one branch at a time.

In its simplest form, git worktree add <path> automatically creates a new branch whose name is the final component of .

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
# `git worktree add <path>`simplest form. create a new branch named hotfix-jira-1234 at path ../hotfix-jira-1234
git worktree add ../hotfix-jira-1234

# work on an existing develop branch in a new worktree
git worktree add ../temp-develop develop

# create a new branch named emergency-fix on path ../temp path based off develop branch.
# -b specifies the new branch name
git worktree add -b emergency-fix ../temp develop

# list worktree
git worktree list

# remove worktree which path is ../temp
git worktree remove ../temp

more on git worktree

.gitignore

The rules for the patterns you can put in the .gitignore file are as follows:

  • Blank lines or lines starting with # are ignored.
  • Standard glob patterns work, and will be applied recursively throughout the entire working tree.
  • You can start patterns with a forward slash (/) to avoid recursivity.
  • You can end patterns with a forward slash (/) to specify a directory.
  • You can negate a pattern by starting it with an exclamation point (!).

Example .gitignore file:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
# ignore all .DS_Store file or directory
.DS_Store

# ignore all .log files. An asterisk is a wildcard that matches zero or more characters.
*.log

# ignore /dist direcotry in the repository root. Prepending a slash matches files only in the repository root.
/dist

# ignore node_modules directory and bin directory at any level
node_modules/
bin/

# ignore doc/notes.txt, but not doc/server/arch.txt
doc/*.txt

# ignore all .pdf files in the doc/ directory and any of its subdirectories
doc/**/*.pdf

To ignore all .DS_Store file in MacOS:

1
2
echo .DS_Store >> ~/.gitignore_global
git config --global core.excludesfile ~/.gitignore_global

More on .gitignore


Gitflow

Gitflow is a popular branching model for Git. it was introduced by Vincent Driessen.

There are two main branches

  • master - branch that is always production-ready and contains the last release version fo source code in production
  • develop - reflects the development changes. This is where feature branches merges to.

Other branches

  • feature - derive from develop branch. used to develop features. Merged to develop branch.
  • release - derived from develop branch that is used for release. merged to master and develop. release is merged to develop branch so that important updates is not lost. After the merge, release branch can be deleted.
  • hotfix - derived from master branch. It is used to fix a bug in production. It is merged to master and develop branch like the release branch.
Author: Vincent Driessen

For more details on Gitflow, see Gitflow workflow by Atlassian

For a practical example of Gitflow workflow, see Git Flow Workflow

Other Stuffs

Other Commands

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
# show files being tracked
git ls-files

# rename or move
git mv <old_file_path> <new_file_path>

# if rename or move using bash command mv, use git add -A to indicate a rename
# git add -A adds all changes in the working tree including rename and delete
git add -A

# remove a file
git rm <file>

# replace commit with a new commit message OR make forgotten changes, stage them and commit again
git commit --amend

# Log
# show logs. top logs are more recent
git log
git log --oneline
git log --all --decorate --oneline --graph
git log -n 20 --decorate --oneline --graph

# show log of one file
git log --oneline -- <file>

# create Alias "graph" to show graph
git config --global alias.graph "log --all --decorate --oneline --graph"


# getting help
git help <verb>
man git-<verb>


# Fix 'detached HEAD' state
git checkout master

# Better checkout only a file, this way you will not go to 'detached HEAD' state
git checkout c6c8007 -- <file>


# quick lookup a Git command
git <verb> -h

# full help document
git <verb> --help

# open gui
git gui

git push -u flag

When you create a local branch and then push it to the remote repository. You can add a -u flag when doing a git push. It adds a tracking reference to the upstream you are pushing to. There is a ‘link’ between local and remote branch. So when you do a git pull without any argument, git will fetch and update local branch. If you do it without -u flag, local branch will not keep track of the change in the remote repository.

-u flag is the same as –set-upstream.

Example

1
git push -u origin new-branch

From git-push documentation

For every branch that is up to date or successfully pushed, add upstream (tracking) reference, used by argument-less git-pull and other commands.

Cherry-Pick

Apply the commit from other branch to the current branch. see Documentation.
If there are multiple commits to cherry-pick, apply the older ones first.
git config –global core.editor “

usage:

1
git cherry-pick <commit sha1>...

Resolving the conflict between Git branches

Pull Request with conflict:

The following steps will resolve conflict regardless if Pull Request is created or not.

Assuming

  • Destination branch is master
  • Source branch is feature
  1. pull the latest changes from destination branch

    1
    2
    git checkout master
    git pull
  2. make sure you are on the source branch(feature in this case). pull if necessary

    1
    2
    git checkout feature
    git pull
  3. merge branch master into feature

    1
    git merge master
  4. resolve the conflicts

  5. add and commit the change, then push

    1
    2
    3
    git add .
    git commit
    git push

Reference: