Git

Anh-Thi Dinh
Quick references for basic tasks with git.
For easier tasks, just use Gitkraken to make things visually. However, it's paid.

Installation & Tools

Rules to be more effective

  • Do commit early and often. (ref)
  • Do make useful commit messages.
  • Create a new branch for every new feature.
  • Use pull requests to merge code to master.
  • For temporary branches, name them starting with _.

Settings on local machine

1# SET GLOBAL INFO
2git config --global user.name "Anh-Thi DINH"
3git config --global user.email "me@dinhanhthi.com"
1# SPECIFIC REPO
2git config user.name "Thi"
3git config user.email "me@dinhanhthi.com"
1# Verify your configuration
2cat .git/config
1# IF: `Could not resolve host: github.com`
2git config --global --unset http.proxy
3git config --global --unset https.proxy
1# SAVE GITHUB AS DEFAULT
2# (don't need to log in again every time we use)
3git config credential.helper store
4git pull
1# FORMATTING DISPLAY
2git config color.ui true # add colors to the results
3git config format.pretty oneline # display only one line of each commit

Check the status

1# CHECK STATUS
2git status
3git status -s # modified files
1# Get remote list
2git remote -v
1# WITH COLORS
2git log --oneline --graph --color --all --decorate
3# --graph: draw text-based branches
4# --decorate: display names and tags
Check some commit:
1git log -- <file> # check commits containing <file>
2git log --prep='abc' # look for commits containing "abc" in their name
3git log <from>..<to> # display commints from <from> to <to> (commit's id, branch's name,...)
Check current HEAD
1git log -1
2# something likes HEAD -> <branch-name>
Check the change of some file,
1git diff file_name.abc
Check the list of files in the last commit,
1# get the last commit id
2git log --format="%H" -n 1
3# list of files
4git diff-tree --no-commit-id --name-only -r <commit_id>
Check all commits w.r.t. a specific file
1# with `--follow`, it works even if the file's name has changed!
2git log --follow -- filename
💡 GitKraken: Ctrl + P > Type "History" > Enter the file path.

Git ignore

Create a file .gitignore.
1# ignore
2.jekyll-cache
3.git
4__pycache__/
1__pycache__/
2
3# ignore all except
4/*
5
6# whitelist
7!.gitignore
8!/docker/
1# add a file/folder to .gitignore
2echo file.txt >> .gitignore
3echo folder >> .gitignore
Ignore local changes on some files from pull/push
1# they are assumed to be unchanged
2git update-index --assume-unchanged file_1 file_2
3
4# undo
5git update-index --no-assume-unchanged file_1 file_2
6
7# To get a list of dir's/files that are assume-unchanged
8git ls-files -v|grep '^h'

Repositories

1# CREATE REPO
2git init <repo-name>
1# CLONE REPO (using https or ssh)
2git clone <repo-link>
Clone without ssh using Personal Access Token.
1# Example: <https://github.com/dinhanhthi/dinhanhthi.com>
2git clone <https://<token>@github.com/dinhanhthi/dinhanhthi.com>
3# Can be applied to organization's repos.

Git GUI

1git gui
2gitk

Staged & Commits & Push & Pull

Staged

1# ADD MODIFICATION (1 FILE)
2git add * # add all the changes
3git add <file-name> # only add the <file-name> to the staged
1# UNSTAGE A FILE
2git reset HEAD <file>
1# UNSTAGED EVERYTHING
2git reset

Commit & Push

1# MAKE COMMIT (FROM STAGED)
2git commit -m '<comment-for-this-commit>'
3git commit -a # commit any files
1# UNCOMMIT (back to before commit)
2git reset --soft HEAD~1
1# PUSH TO REMOTE
2git push origin <branch-name> # push only <branch-name>
3git push --all origin # push all branches
1# CHECK & TEST A COMMIT
2git checkout <commit-id>
3# after testing
4git checkout -- . # discard all changes
5git checkout <current-branch> # back to previous
1# Commit current date
2git commit -m "`date`" # Wed Aug 28 10:22:06 CST 2019
3git commit -m "`date +'%Y-%m-%d'`" # 2019-08-28
4git commit -m "Updated: `date +'%Y-%m-%d %H:%M:%S'`" # Updated: 2019-08-28 10:22:06
1# Commit with a custom date (in the pass)
2date -v -6H # 6 hours ago -> Mon Feb 20 11:12:39 CET 2023
3# then
4GIT_AUTHOR_DATE='Mon Feb 20 11:12:39 CET 2023' GIT_COMMITTER_DATE='Mon Feb 20 11:12:39 CET 2023' git commit -m "Commit message"
5
6# In case you wanna use an alias, put below in .zshrc or .bashrc
7git_pass() {
8  GIT_AUTHOR_DATE="$(date -v -$1H)" GIT_COMMITTER_DATE="$(date -v -$1H)" git commit -m "$2"
9}
10# Then run
11git_pass 4 "commit message" # "4" means 4 hours ago!

Pull & Fetch

1# LIST ALL REMOTE REPOS
2git remote -v
1# UPDATE REMOTE -> LOCAL
2git pull origin <branch-on-remote>
1# Pull (fetch) a branch from remote
2git checkout --track origin/<branch>
1# COPY A COPY FROM REMOTE
2git fetch origin <branch-on-remote>
3# compare current branch to this copy
4git diff --stat FETCH_HEAD
1# Just fetch the changes from remote
2# Appliy to the current branch
3git fetch
1# Fetch a branch without checkout
2# (On branch A but wanna fetch/create branch B)
3git fetch origin branch-b:branch-b
1# Pull from remote and replace COMPLETELY local
2# Checkout the branch you wanna replace first
3git reset --hard origin/<branch>

Branches

Create

1# CREATE A BRANCH
2git branch <branch-name>
1# CREATE AND MOVE TO NEW BRANCH
2# This one is based on the current `HEAD` (git log -1).
3git checkout -b <new-branch>
4# new branch based on another one
5git checkout -b <new-branch> <existing-branch>
1# Create a independent branch
2# (like git init, without any commit history)
3git checkout --orphan <new-branch>

Two branches

1# CHANGE TO ANOTHER BRANCH
2git checkout <branch-name>
3
4# fatal: 'dev' could be both a local file and a tracking branch.
5git checkout <branch_name> --
1# UPDATE ALL REMOTE BRANCHES TO LOCAL
2# (there may be deleted branches on remote)
3git remote update origin --prune
1# LIST ALL LOCAL BRANCHES
2git branch
1# LIST ALL LOCAL + REMOTE
2git branch -a

Comparing

1# compare current branch with other
2git diff <branch>
1# COMPARE 2 BRANCHES
2git diff <source-branch> <compared-branch>
1# a specific file
2git diff mybranch master -- myfile
1# list all diff files: current vs
2git diff --name-status master
1# 2 files vs
2git diff mybranch master --name-status
3# can be "--name-only"
1# LOCAL <-> REMOTE BRANCHES
2git branch -vv
1# save to log file
2git diff --output=log.txt branch_1 branch_2 --name-status
1# CORRESPONDING LOCAL BRANCH <-> REMOTE
2git fetch
3git branch --set-upstream-to=origin/<remote_branch> <local_branch>

Delete

1# DEL A LOCAL BRANCH
2git branch -d <branch-name>
3git branch -D <branch> # force to delete
1# DEL A REMOTE BRANCH
2git push origin :<branch-name>

Merge

Check if there are conflicts before merging?
1# merge without commit -> check the conflicted files
2git merge --no-commit <other-branch>
3
4# list only the name of conflict files
5git diff --name-only --diff-filter=U
6
7# then reset if don't wanna merge
8git reset --hard
1# MERGE <branch> TO CURRENT
2git merge <branch>
1# merge + keep current changes
2git merge --strategy-option ours
3
4# merge + keep incoming changes
5git merge --strategy-option theirs
1# MERGE <sub-branch> TO master + REPLACE master
2git checkout <sub-branch>
3git merge -s ours master
4git checkout master
5git merge <sub-branch>
6# master can be other
1# Merge all files in a folder frm another commit
2git checkout <commit> folder/*
1# MERGE `/link/to/abc.xyz` FROM `<branch-1>` TO `<branch-2>` (can be `master`)
2git checkout branch-2
3git checkout branch-1 /link/to/abc.xyz
1# MERGE ONLY SOME FOLDER
2git checkout <branch>
3git checkout <from-branch> folder1\ folder2\
1# MERGE commit from ONE BRANCH to CURRENT
2git cherry-pick <commit hash>
1# KEEP FILES/FOLDERS FROM MERGE
2# add line to .gitattributes
3echo 'file_name.txt merge=ours' >> .gitattributes

Conflict

If there are changes from both local and remote, there will be conflicts! Something likes that,
1<<<<<< HEAD
2changes on local
3======
4changes from remote
5>>>>>> template/notetheme2
If you use Visual Studio Code, there is a small toolbar above each conflict and you can choose which one you prefer to keep!
Prefer one of them?
1# keep remote changes
2git pull -X theirs <remote-repo>
1# keep local changes
2git pull -X ours <remote-repo>
Keep both? Using Visual Studio Code or,
1# add below line to .gitattributes (on branch being merged)
2echo "*.whatever merge=union" .gitattributes
3# on windows, remove `""`
Already in conflicted state?
1git checkout --theirs path/to/file # remote
2git checkout --ours path/to/file # local
1# Abort the conflicts
2# (go back to before merging)
3git merge --abort

Exclude from merging

Exclude some files from merge (keep ours),
1# ONLY FOR FILES
2# add below line to .gitattributes (on branch being merged)
3echo "file.ext merge=ours" .gitattributes
4# on windows, remove `""`
Exclude some folders (we cannot use git in this case):
  1. If you delete these folders after merging, just commit and later merges will ignore them.
  1. If you meet a conflict, add the folder's name in a file called reset_folders.sh
    1. 1#!/bin/sh
      2echo 'Reset some only-this-branch folders after merging.'
      3git reset folder_1, folder_2
      4git checkout .
      5git add .
      6git commit -m "update from merge (keep local in some folders)"
      Each time,
      1git merge <from-branch> && sh reset_folders.sh

Rename

1# CURRENT BRANCH
2git branch -m <newname>
3git branch -M <newname> # if there are only capitalization changes
1# CURRENT IS ANOTHER BRANCH
2git branch -m <oldname> <newname>
1# RENAME REMOTE BRANCH (delete old + push new)
2# (rename local branch first)
3git push origin :<oldname> <newname>
4# reset the upstream branch for the new-name local branch
5git checkout <newname>
6git push origin -u <newname>

Squash

1# Squash newest 5 commits
2git rebase -i HEAD~5

Others

Add a description (using Vim editor):
1git branch --edit-description
In the case you wanna exit Vim, press <kbd>ESC</kbd> then type :q to quit or :wq to write and quit.

Tags

1# Listing tags
2git tag
3
4# Delete local tags
5git tag -d <tag_name>
6
7# Delete remote tags
8git push --delete origin <tag_name>

Cleaning

Clean all history of a repo,
1git checkout --orphan tmp_repo
2git add -A                      # Add all files and commit them
3git commit -am "Clean Repo"
4git branch -D <repo>            # Deletes the <repo> branch on remote
5git branch -m <repo>            # Rename the current branch to <repo>
6git push -f origin <repo>       # Force push branch <repo> to github

Remove from git

Remove from git, not from system,
1# a file
2git rm --cached <file_name>
1# a folder
2git rm -r --cached <folder>

Discard the changes

1# DISCARD CHANGES ON CURRENT DIR
2git checkout -- . # for all changes
3git checkout -- <file-name> # for a specific file (go back the last commit of this file)
1# DISCARD ALL LOCAL CHANGES
2git reset --hard
1# Get back to some prev commit and ignore all the changes (including the commits)
2git reset --hard <commit-id>
In the case you want to discard the changes but want to make a save before moving to another branch to test. You can use below line.
1git stash
If you wanna get back to the place you saved (and remove it from the stashed list), just go back to the branch you make the save and use
1git stash pop

Restore

1# RESTORE FROM LAST COMMIT
2git checkout -- <file>
1# Revert single file to a commit
2git checkout <commit> -- <file>
1# DISCARD ALL CHANGES ON LOCAL + GET FROM REMOTE
2git fetch origin
3git reset --hard origin/master
1# DISCARD ALL CHANGES + get the last update from remote
2git reset --hard @{u}
1# EREASE ALL COMMITS + BACK TO <commit-id>
2git reset --hard <commit_id>
3# (force) to push
4git push -f origin master

Change remote url

1# check the current remote url
2cat .git/config| grep "url"
3
4# change to the new one
5git remote set-url origin new.git.url/here

Alias

1# use `git br` instead of `git branch`
2git config alias.br branch

Gitlab: Clone a private repo to local

  1. (Windows) Generate a ssh key ssh-keygen -t rsa -b 4096 -C "your_email@example.com" in C:\Users\dinha\.ssh under the name id_rsa (for private) and id_rsa.pub for public. It's important, the name!!!!
  1. Open and copy key in C:\Users\dinha\.ssh\id_rsa.pub
  1. Go to Gitlab > Settings > SSH Keys > paste a copied key and name it.
  1. Clone again the repo and it shoule be working!

Errors

Problem with pre-commit? (Cannot removing it?)
1rm .git/hooks/pre-commit
2# use sudo if needed
1# error: invalid object Error building trees
2git hash-object -w <error-file>
You can also go back to the previous commit (git reset --hard) and you LOSE all uncommit files.
WARNING: REMOTE HOST IDENTIFICATION HAS CHANGED!
Go to ~/.ssh/ (Linux) or C:\Users\dinha\.ssh (Windows), remove the host from known_hosts and re-connect again.

Git submodules

Git submodules allow you to keep a git repository as a subdirectory of another git repository.
1# "git clone" & "git pull" to automatically update submodules.
2git config --global submodule.recurse true
3
4# public repo: github.com/you/blog (clone)
5# private repo: github.com/you/posts
6
7cd blog
8git submodule add <https://github.com/you/posts> # blog/posts
To stop Fetching submodule.... when git pull: git config submodule.recurse false!
To update all submodules,
1# If this is the 1st time you checkout the repo
2git submodule update --init --recursive
3
4# Update submodules
5git submodule update --recursive --remote
6# Don't make change on the folder of submodules!!!!
1# clone a repo with submodules
2git clone <repo> --recursive
3# or
4git clone <repo>
5git submodule init
6git submodule update
1# REMOVE a submodule (suppose that it's in a/)
2# Make a copy (just in case)
30. mv a/submodule a/submodule_tmp
41. git submodule deinit -f -- a/submodule
52. rm -rf .git/modules/a/submodule
63. git rm -f a/submodule
7# Note: a/submodule (no trailing slash)
84. git rm --cached a/submodule

Others

  • fast-forward means that the commits can be applied directly on top of the working tree without requiring a merge. When git pull and get this message, we can be sure that the new update are not confict with our current modifications.