- Git tutorial: man gittutorial
- Pro Git: https://git-scm.com/docs
- Reset Demystified: https://git-scm.com/book/en/v2/Git-Tools-Reset-Demystified
- Git Branching: https://git-scm.com/book/en/v2/Git-Branching-Branches-in-a-Nutshell
Working Directory <------+------+------+ | | | | | diff HEAD | | V | | | git add | | git diff | | | | | | | | V | | | Index <-----+------|------|------+ | | | | | diff --cached| | V | | | git commit | | | | | | | | | | | V | | | HEAD <-----+------+ | | | | diff HEAD^ V | previous "git commit" | | | | | V | HEAD^ <-------------------+
# reset - https://git-scm.com/blog/2011/07/11/reset.html +----------------------------+-------+------+--------------------+ | | HEAD | Index | Work Dir | WD Safe | +----------------------------+------+-------+----------+---------+ | Commit Level | | | | | +----------------------------+------+-------+----------+---------+ | reset --soft [commit] | REF | NO | NO | YES | | reset [commit] | REF | YES | NO | YES | | reset --hard [commit] | REF | YES | YES | NO | | checkout [commit] | HEAD | YES | YES | YES | | restore -s [commit] | HEAD | YES | YES | YES | +----------------------------+----+-------+----------+-----------+ | File Level | | | | | +----------------------------+------+-------+----------+---------+ | reset (commit) [file] | No | YES | NO | YES | | checkout (commit) [file] | No | YES | YES | NO | | restore -s (commit) [file] | No | YES | YES | NO | +----------------------------+------+------+-----------+---------+
git swtich is newly added to replace the branch switch functions of git checkout
- git revert : creates a new commit that undoes changes from a previous commit; adds new history ;
- git switch : (previously git checkout) checks out content from the repo and puts it under working directory; does not impact history;
- git reset : modifies the index (staging area), or changes which commit a branch head is point at; may impact history;
- common rules :
- if a commit has led to a change, and it is incorrect: "git revert" undoes the change, and record the action in history;
- if files have been changed but have not been committed, "git restore" check out a fresh from repo copy of the filess;
- if a commit has been made but has not been shared to anyone, "git reset" rewrites the history so that it seems nothing has been changed.
ref~ is shorthand for ref~1 and means the commit's first parent. ref~2 means the commit's first parent's first parent......
ref^ is shorthand for ref^1 and means the commit's first parent. ref^2 means the commit's second parent......
diagram as below:
HEAD ------->+ Fifth commit on master | HEAD~1 or HEAD^1 -->+ Merge branch |\ HEAD~1^2 -----|>+ First commit on branch | | HEAD~2 or HEAD~1^1 ->+ | Fourth commit on master | | HEAD~3 or HEAD~2^1 ->+/ Third commit on master | etc. + Second commit on master | + First commit on master | + ...etc.
man gitrevisions
# leverage <refname>@{<date>} of gitrevisions git diff master@{0} master@{1 day ago} git log <commit 1>..<commit 2> git log <commit 1>...<commit 2>
Below options are recommended before using git(without global for per repository based configuration):
# show all options: git config -l --show-origin git config --global user.name "<First Name> <Second Name>" git config --global user.email <email> git config --global http.sslVerify false git config --global core.editor vim git config --global credential.helper cache git config --global credential.useHttpPath true git config --global format.pretty format:"%Cred%h%Creset -%C(yellow)%d%Creset %s %Cgreen(%cd) %C(blue)<%aE>%Creset" git config --global init.defaultBranch main git config --global core.quotepath false git config --global -l
Git configuration can also be edited with vim as below:
git config --global --edit
export GIT_TRACE_PACKET=1 export GIT_TRACE=1 export GIT_CURL_VERBOSE=1
git switch is the newly operation added recently, which foucses on branch switch ops in order to replace git checkout
- git remote update origin --prune ---> update the local list of remote branches
- git branch -a[v]
- git branch -a --sort=-committerdate ---> list all branches which have recent updates
- git branch <name> ---> Create a branch
- git branch -d <name> ---> Delete a branch
- git branch -m <nmae> ---> Rename a branch
- git checkout <name> ---> Checkout a branch(deprecated)
- git checkout -b <name> == git branch <name> + git checkout <name>(deprecated)
- git switch <name> ---> Switch to a branch (equals git checkout <name>)
- git switch -c <name> ---> Create and switch to the branch
- git pull
- git pull -t ---> tags won't get updated sometimes, use -t to fetch them again
- git log [--graph] [--decorate] [--date=relative] [branch name]
- git log [--graph] [--oneline] [--decorate] [branch name]
- git log --graph --oneline --decorate --all
- git log --since '2 days ago'
- git log --since '1 hour ago'
- git log --since='Jan 1 2024' --until='Jan 7 2024'
- git log --pretty=short --stat
- git log --format=full
- git log --format='%H %an %s' --graph
- git log --graph --oneline --decorate --author="[Aa]aron"
- git log --graph --oneline --decorate --author="[email protected]" -i
- git log --grep xxx # show commits whose commit messages contain xxx
- git show [--format=full] <sha1 hash>
- git log -S <string> [-p] [file]
- git log -G <regex> [-p] [file]
- git log -S <string> <commit range starting>..<commit range ending>
- git log --all
- git log --all -i --grep='xxx yyy' # find the commit with message xxx yyy
- git log [--stat | --name-status | --name-only] # show what file have been changed
- git log --follow tempest # show changes on a file/folder
- git log -p # changes with patch
Generally speakcing, 'git mergetool' will show conflicts in below format:
+--------------------------------+ | LOCAL | BASE | REMOTE | +--------------------------------+ | MERGED | +--------------------------------+
Usage:
git config merge.tool vimdiff git config merge.conflictstyle diff3 git merge <branch/commit/etc.> git mergetool Then: 1. solve/edit conflicts between <<< and >>> -> delete <<<, === and >>> -> :wq 2. git add *; git commit -m '<message>' --- OR --- git merge --continue
Merge conflict markers:
<<<<<<< foo ======= bar >>>>>>>
- Normal Merge:
- Top(between <<< and ===): local changes
- Bottom(between === and >>>): upstream/remote changes
- Rebase Merge:
- Top: upstrea/remote changes
- Bottom: local changes
During merge operations, there are situations only some files are supposed to be included.
Keep local files:
# git checkout <local branch name> -- <file names>(deprecated, using git restore) git restore -s <local branch name> <file names> # OR for current branch git restore <file names>
Remove files added by the merge operations:
git rm --cached <files>
Continue merge:
git merge --continue
Store credential on disk in plaintext
git config [--global] credential.helper store
Cache in memory only
# Cache for 15 x minutes by default git config --global credential.helper cache # Specify timeout git config --global credential.helper 'cache --timeout=3600'
Tag is used as a mechanism for version release: each time a tag is created, a release (on github) is created.
Lightweight tag
git tag [-m <message>] <name> [commit]
Annotated tag: recommended, it stores extra meta data for a tag
git tag -a [-m <message>] <name> [commit]
git tag # list tags with additional information such as date git tag --list --format='%(creatordate:short): %(refname:short)' # show annotation lines, w/o -n, annotation won't be shown git tag -n git tag -n100 git tag -l -n
git checkout tags/<tag name> # Checkout the tag and create a new branch to avoid overwritten git checkout tags/<tag name> -b <branch name>
git push will not push tags by default, hence it needs to be explicitly specified.
git push origin <tag name>
# Delete local git tag -d <tag> # Delete remote git push --delete origin <tag>
git fetch --tags --prune [--all]
git log tag1..tag2 | wc -l
Origin status:
# git log --oneline --graph --all * 3c02ffb add passwd * 58c8cf7 add resolve file * 80bebdb add host file
# git reset --hard 58c8cf7 (or git reset --hard HEAD^) HEAD is now at 58c8cf7 add resolve file # git log --oneline --graph --all * 58c8cf7 add resolve file * 80bebdb add host file
# git reflog 58c8cf7 HEAD@{2}: reset: moving to 58c8cf7 3c02ffb HEAD@{3}: commit: add passwd ...... # git reset --hard 3c02ffb HEAD is now at 3c02ffb add passwd # git log --oneline --graph --all * 3c02ffb add passwd * 58c8cf7 add resolve file * 80bebdb add host file
- git blame: Show what revision and author last modified each line of a file
- git blame <file>
- git blame -s <file>
- git grep: Print lines matching a pattern
- git grep 'string pattern'
- git log: search contents of commit or commit message
- git log -S [-p] 'text string' [file]
- git log -G [-p] 'regex' [file]
- git log --grep xxx # show commits whose commit messages contain xxx
git commit -a --amend
git show-ref --tags git show-ref --abbrev=7 --tags git show <tag name>
diff -x '.git*' -Naur --no-dereference <repo1 directory> <repo2 directory>
git push -d origin <branch name> git branch -d <branch name>
git remote update origin --prune
This should only be used when there are too many conflicts to solve during a normal merge operation.
git fetch --all git reset --hard <FETCH_HEAD | branch name, such as origin/master> git pull
git clean -f # Remove file git clean -df # Remove both files and directories git clean -xdf # Remove files, directories, and ignored files and directories git clean -f -e abc -x # Remove files and excluding pattern abc
git show -m <commit id> git show --first-parent <commit id> git diff <commit id>^ <commit id>
# Deprecated command: git checkout <branch name> -- <file name> git restore -s <branch name> <file name> (Note: prefix, such as origin/<branch name>, is needed when you want to checkout files from a remote branch)
git fetch --dry-run git show <from> -> <to>
# Deprecated command: git checkout <commit hash or HEAD~n> -- <file 1> <file 2> ... git restore -s <commit hash or HEAD~n> <file 1> <file 2> ...
git fetch --all git reset --hard origin/master git clean -dn git clean -df
Error as below will be triggered when switch to a branch which exists on several remote refs:
error: pathspec 'unity_solaris' did not match any file(s) known to git.
Solution: switch with --track option as below:
# git remote update # git branch -a master * master remotes/origin/HEAD -> origin/master remotes/origin/master remotes/origin/unity_solaris remotes/upstream/master remotes/upstream/unity_solaris # git switch --track origin/unity_solaris
git difftool -t vimdiff [-y] [--cached]
git diff master origin/master
git diff <branch name1>..<branch name2> -- <abs/rel path to a file or file glob like target-i386/cpu.[ch]> --- OR --- git diff <branch name1>:<abs path(./) to a file> <branch name2>:<abs path to the same file>
git remote add upstream <url of the original upstream branch> git fetch upstream --- OR --- git remote update git branch -a ---> the original upstream branch will be shown git diff master upstream/master ---> compare local(forked) and the upstream --- OR --- git log master..upstream/master git merge upstream/master ---> merger original upstream differences to local
# without using glob, a patch containing all changes can be created git diff <commit x>..<commit y> > test.patch # all .c and .h files under current directory git diff <commit x id> <commit y id> -- *.[ch] # all .c and .h files under current directory and all subdirectories recursely(globstar) git diff <commit x id>..<commit y id> -- **/*.[ch]
After running git fetch, it is good to have a look at what will be changed after merge. Under such condition, below commands help:
git diff HEAD...origin/master --- OR FOR SHORT --- git diff ...origin/master
Refer to man gitrevisions for how to specify date time info.
git diff HEAD 'HEAD@{3 weeks ago}' -- <file/dir name> git diff "master@{0}" "master@{25 hours ago}"
git branch [-r] --contains <commit hash> --- OR --- git branch -a --contains <commit hash>
git add -u
git rm --cached <file path>
This is similar as doing "Rebase and merge" with github:
git merge --no-commit --no-ff
With a branch, lots of commits may be made. But it will pollute the master branch(or other branch to merge into) history, which will make the project history not friendly enough for tracking and maintenance.
For example:
branch 'feature': + -> D -> E / branch 'master' : A -> B -> C
When merge branch 'feature' into 'master', you will get below history:
branch 'master' : A -> B -> C -> D -> E
Commits 'D' and 'E' will both be populated into the master branch history. When there are a lots of commits(say hundres of) from different branches merged into the master branch, the master branch's history is not readable at all.
To avoid that, 'rebase' at branch level before merge is recommended(rebase at the master branch directly is dangerous).
Steps
Here is the init status of 'master' and 'feature' branches:
~ $ git branch -a feature * master ~ $ git log --oneline --graph --decorate * 98a2cca (HEAD -> master) init ~ $ git switch feature Switched to branch 'feature' ~ $ git log --oneline --graph --decorate * 5935f6d (HEAD -> feature) delete handler dir * 419297f delte vars dir * dca50ce delete meta dir * 98a2cca (master) init
We want to consolidate the 3 x commits(5935f6d, 419297f, dca50ce) from 'feature' branch into one:
~ $ git rebase -i HEAD~3
git will open a window/file(with config option core.editor) to let you edit how to rebase the three commits(HEAD~3):
pick dca50ce delete meta dir pick 419297f delte vars dir pick 5935f6d delete handler dir
Change it as below:
r dca50ce delete meta dir f 419297f delte vars dir f 5935f6d delete handler dir
Explanations:
- r/reword: use the commit, but edit the commit message
- f/fixup : merge this commit into the previous one and discard commit message
After quiting the file(vim :wq), you can change the commit message. Quite(:wq) again
~ $ git rebase -i HEAD~3 [detached HEAD 35302fe] delete meta/default/handler dirs Date: Sat Sep 23 20:12:34 2017 +0800 1 file changed, 57 deletions(-) delete mode 100644 meta/main.yml [detached HEAD 4847d34] delete meta/default/handler dirs Date: Sat Sep 23 20:12:34 2017 +0800 3 files changed, 61 deletions(-) delete mode 100644 handlers/main.yml delete mode 100644 meta/main.yml delete mode 100644 vars/main.yml Successfully rebased and updated refs/heads/feature. ~ $ git log --oneline --graph --decorate * 4847d34 (HEAD -> feature) delete meta/default/handler dirs * 98a2cca (master) init
Then the 'feature' branch can be merged into 'master' elegantly:
~ $ git switch master Switched to branch 'master' ~ $ git log --oneline --graph --decorate --all * 4847d34 (feature) delete meta/default/handler dirs * 98a2cca (HEAD -> master) init ~ $ git merge feature Updating 98a2cca..4847d34 Fast-forward handlers/main.yml | 2 -- meta/main.yml | 57 --------------------------------------------------------- vars/main.yml | 2 -- 3 files changed, 61 deletions(-) delete mode 100644 handlers/main.yml delete mode 100644 meta/main.yml delete mode 100644 vars/main.yml ~ $ git log --oneline --graph --decorate * 4847d34 (HEAD -> master, feature) delete meta/default/handler dirs * 98a2cca init ~ $ git log --oneline --graph --decorate --all * 4847d34 (HEAD -> master, feature) delete meta/default/handler dirs * 98a2cca init
The 'feature' branch can be deleted:
~ $ git branch -d feature Deleted branch feature (was 4847d34). ~ $ git branch -a * master
Clone a repo including its submodules:
git clone --recursive <repo url>
If a repository has already been cloned without --recursive:
git submodule update --init --recursive
# "git submodule update" checks out a commit directly but not a symbolic reference to HEAD, hence # "detached head" issue will be triggered. This can be worked around by specifying the branch to # track while adding a submodule # git submodule add <git external repo url to the submodule> [local path of the local repo] git submodule add -b master <git external repo url to the submodule> [local path of the local repo] git submodule init
git submodule update --rebase --remote # OR git submodule foreach git pull origin master
- Delete the relevant section from .gitmodules file;
- git add .gitmodules;
- Delete the relevant section from .git/config;
- git rm --cached path_to_submodule;
- rm -rf .git/modules/path_to_submodule;
- git commit -m message;
- rm -rf path_to_submodule.
Pull all changes including changes in submodules:
git pull --recurse-submodule
Pull all changes for the submodules:
git submodule update --remote [--recursive] [--merge]
Examples:
# the whole command will fail if the inner command hit an error, # to work around the issue, use the ":" command git submodule foreach 'command | :' git submodule foreach [--recursive] 'git reset --hard' git submodule foreach 'git pull origin master | :'
git stash temporarily shelves (or stashes) changes you've made to your working copy so you can work on something else, and then come back and re-apply them later on.
- Command:
- git stash [push [-u] [-a] [-m <message>]]
Options:
- -u: include untracked files
- -a: include ignored files
Example:
git status git stash push -a -m stash1 git list
There are several options to re-apply stashed changes:
Re-apply the latest stashed changes, and remove the changes from the stash:
git stash pop
Re-apply the latest stashed changes but keep the changes in the stash:
git stash apply
Re-apply a specified stashed changes:
git stash list git stash <pop|apply> <stash name, such as stash@1>
git stash show -p stash@{0}
git stash drop [stash name] --- OR to clean all stashes --- git stash clear
git stash branch abc stash@{1} git switch abc # check files at the timestamp when the stash is created git switch -f master git branch -D abc
Apply the changes introduced by some existing commits. Always used to apply commits from one branch to another.
Sometimes, there will be conflicts, which need to be solved just like using merge. After solving the conflicts, use "git cherry-pick --continue" to continue the application, otherwise, use "git cherry-pick --abort" to bail of the step.
Sample:
❯ ls a1.txt a2.txt ❯ git branch -a * features master ❯ git log --oneline --graph --decorate * 4e8ecbc (HEAD -> features) add a2.txt * 3b5695a (master) add a1 ❯ git switch master Switched to branch 'master' ❯ ls -l total 0 -rw-r--r-- 1 kc kc 0 Jun 29 09:13 a1.txt ❯ git cherry-pick -x 4e8ecbc [master 182e923] add a2.txt Date: Fri Jun 29 09:13:43 2018 +0800 1 file changed, 0 insertions(+), 0 deletions(-) create mode 100644 a2.txt ❯ ls a1.txt a2.txt ❯ git log --oneline --graph --decorate * 182e923 (HEAD -> master) add a2.txt * 3b5695a add a1 ❯ git show 182e923 commit 182e923d0682490649487213086c1554b191834f (HEAD -> master) Author: KC Date: Fri Jun 29 09:13:43 2018 +0800 add a2.txt (cherry picked from commit 4e8ecbc09f58d67e4d1802424832e7155decaf5c) diff --git a/a2.txt b/a2.txt new file mode 100644 index 0000000..e69de29
Pull just the latest commits, not the entire repo history which improve clone performance(but w/o history):
git clone --depth 1 https://gitlab.gnome.org/GNOME/glib.git git clone --depth X ...
git reflog git checkout -b <branch> <sha>
git reflog git reflog show --all git reflog show <branch name> git reflog
# use https.proxy for https git config [--global] http.proxy http://proxyuser:[email protected]:8080 git config [--global] http.proxy 'socks5://127.0.0.1:8080' git config [--global] --unset http.proxy # use env vars, use https_proxy for https export http_proxy=http://proxyuser:[email protected]:8080 export http_proxy=socks5://127.0.0.1:8080 export no_proxy="localhost,127.0.0.1,localaddress,.localdomain.com" unset http_proxy
git delta is a powerful pager for git diff/show/log. Refer to https://github.com/dandavison/delta for configuration. If it is not provided within the distribution repositories, install it with homebrew.
git config -l git -c delta.side-by-side=false show <commit id>
Terminal UI for git, great for viewing git log.
go install github.com/jesseduffield/lazygit@latest lazygit