Git

Quick references for basic tasks with git.

👉 Note: Github.
👉 Note: Gitkraken.

For easier tasks, just use Gitkraken to make things visually.

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 #

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

Check the status #

# CHECK STATUS
git status
git status -s # modified files
# Get remote list
git remote -v
# WITH COLORS
git log --oneline --graph --color --all --decorate
# --graph: draw text-based branches
# --decorate: display names and tags

Check some commit:

git log -- <file> # check commits containing <file>
git log --prep='abc' # look for commits containing "abc" in their name
git log <from>..<to> # display commints from <from> to <to> (commit's id, branch's name,...)

Check current HEAD

git log -1
# something likes HEAD -> <branch-name>

Check the change of some file,

git diff file_name.abc

Check the list of files in the last commit,

# get the last commit id
git log --format="%H" -n 1
# list of files
git diff-tree --no-commit-id --name-only -r <commit_id>

Check all commits w.r.t. a specific file

# with `--follow`, it works even if the file's name has changed!
git log --follow -- filename

💡 GitKraken: Ctrl + P > Type "History" > Enter the file path.

Git ignore #

Create a file .gitignore.

# ignore
.jekyll-cache
.git
__pycache__/
__pycache__/

# ignore all except
/*

# whitelist
!.gitignore
!/docker/
# add a file/folder to .gitignore
echo file.txt >> .gitignore
echo folder >> .gitignore

Ignore local changes on some files from pull/push,

# they are assumed to be unchanged
git update-index --assume-unchanged file_1 file_2

# undo
git update-index --no-assume-unchanged file_1 file_2

# To get a list of dir's/files that are assume-unchanged
git ls-files -v|grep '^h'

Repositories #

# CREATE REPO
git init <repo-name>
# CLONE REPO (using https or ssh)
git clone <repo-link>

Git GUI #

git gui
gitk

Staged & Commits & Push & Pull #

Staged #

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

Commit & Push #

# MAKE COMMIT (FROM STAGED)
git commit -m '<comment-for-this-commit>'
git commit -a # commit any files
# UNCOMMIT (back to before commit)
git reset --soft HEAD~1
# PUSH TO REMOTE
git push origin <branch-name> # push only <branch-name>
git push --all origin # push all branches
# CHECK & TEST A COMMIT
git checkout <commit-id>
# after testing
git checkout -- . # discard all changes
git checkout <current-branch> # back to previous
# Commit current date
git commit -m "`date`" # Wed Aug 28 10:22:06 CST 2019
git commit -m "`date +'%Y-%m-%d'`" # 2019-08-28
git commit -m "Updated: `date +'%Y-%m-%d %H:%M:%S'`" # Updated: 2019-08-28 10:22:06

Pull & Fetch #

# LIST ALL REMOTE REPOS
git remote -v
# UPDATE REMOTE -> LOCAL
git pull origin <branch-on-remote>
# Pull (fetch) a branch from remote
git checkout --track origin/<branch>
# COPY A COPY FROM REMOTE
git fetch origin <branch-on-remote>
# compare current branch to this copy
git diff --stat FETCH_HEAD
# just fetch the changes from remote
git fetch

Difference between git pull --rebase and git pull --ff-only[ref],

  • --rebase: remote changes (C) will be applied before local changes (D) resulting in the following tree

    A -- B -- C -- D
  • --ff-only: fail if there are conflicts, otherwise, it will work!

Branches #

Create #

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

Two branches #

# CHANGE TO ANOTHER BRANCH
git checkout <branch-name>

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

Comparing #

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

Delete #

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

Merge #

Check if there are conflicts before merging?

# merge without commit -> check the conflicted files
git merge --no-commit <other-branch>

# list only the name of conflict files
git diff --name-only --diff-filter=U

# then reset if don't wanna merge
git reset --hard

# MERGE <branch> TO CURRENT
git merge <branch>
# merge + keep current changes
git merge --strategy-option ours

# merge + keep incoming changes
git merge --strategy-option theirs
# MERGE <sub-branch> TO master + REPLACE master
git checkout <sub-branch>
git merge -s ours master
git checkout master
git merge <sub-branch>
# master can be other
# Merge all files in a folder frm another commit
git checkout <commit> folder/*
# MERGE `/link/to/abc.xyz` FROM `<branch-1>` TO `<branch-2>` (can be `master`)
git checkout branch-2
git checkout branch-1 /link/to/abc.xyz
# MERGE ONLY SOME FOLDER
git checkout <branch>
git checkout <from-branch> folder1\ folder2\
# MERGE commit from ONE BRANCH to CURRENT
git cherry-pick <commit hash>
# KEEP FILES/FOLDERS FROM MERGE
# add line to .gitattributes
echo 'file_name.txt merge=ours' >> .gitattributes

Conflict #

If there are changes from both local and remote, there will be conflicts! Something likes that,

<<<<<< HEAD
changes on local
======
changes from remote
>>>>>> 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?

# keep remote changes
git pull -X theirs <remote-repo>
# keep local changes
git pull -X ours <remote-repo>

Keep both? Using Visual Studio Code or,

# add below line to .gitattributes (on branch being merged)
echo "*.whatever merge=union" .gitattributes
# on windows, remove `""`

Already in conflicted state?

git checkout --theirs path/to/file # remote
git checkout --ours path/to/file # local
# Abort the conflicts
# (go back to before merging)
git merge --abort

Exclude from merging #

Exclude some files from merge (keep ours),

# ONLY FOR FILES
# add below line to .gitattributes (on branch being merged)
echo "file.ext merge=ours" .gitattributes
# 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.

  2. If you meet a conflict, add the folder's name in a file called reset_folders.sh,

    #!/bin/sh
    echo 'Reset some only-this-branch folders after merging.'
    git reset folder_1, folder_2
    git checkout .
    git add .
    git commit -m "update from merge (keep local in some folders)"

    Each time,

    git merge <from-branch> && sh reset_folders.sh

Rename #

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

Others #

Add a description (using Vim editor):

git branch --edit-description

In the case you wanna exit Vim, press ESC then type :q to quit or :wq to write and quit.

Cleaning #

Clean all history of a repo,

git checkout --orphan tmp_repo
git add -A # Add all files and commit them
git commit -am "Clean Repo"
git branch -D <repo> # Deletes the <repo> branch on remote
git branch -m <repo> # Rename the current branch to <repo>
git push -f origin <repo> # Force push branch <repo> to github

Remove from git #

Remove from git, not from system,

# a file
git rm --cached <file_name>
# a folder
git rm -r --cached <folder>

Discard the changes #

# DISCARD CHANGES ON CURRENT DIR
git checkout -- . # for all changes
git checkout -- <file-name> # for a specific file (go back the last commit of this file)
# DISCARD ALL LOCAL CHANGES
git reset --hard
# Get back to some prev commit and ignore all the changes (including the commits)
git 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.

git 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

git stash pop

Restore #

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

Change remote url #

# check the current remote url
cat .git/config| grep "url"

# change to the new one
git remote set-url origin new.git.url/here

Alias #

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

Gitlab: Clone a private repo to local #

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

Errors #

Problem with pre-commit? (Cannot removing it?)

rm .git/hooks/pre-commit
# use sudo if needed

# error: invalid object Error building trees
git 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.

# "git clone" & "git pull" to automatically update submodules.
git config --global submodule.recurse true

# public repo: github.com/you/blog (clone)
# private repo: github.com/you/posts

cd blog
git submodule add https://github.com/you/posts # blog/posts
# update submodules
git submodule update --remote
# don't make change on the folder of submodules!!!!
# clone a repo with submodules
git clone <repo> --recursive
# or
git clone <repo>
git submodule init
git submodule update
# REMOVE a submodule
0. mv a/submodule a/submodule_tmp

1. git submodule deinit -f -- a/submodule
2. rm -rf .git/modules/a/submodule
3. git rm -f a/submodule
# Note: a/submodule (no trailing slash)

# or, if you want to leave it in your working tree and have done step 0
3. git rm --cached a/submodule
3bis mv a/submodule_tmp 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.
  • Find big files / commits: Using this scripts and this answer.