Skip to main content

Git Tips

Tutorials
全域設定檔
# Using git to edit the configuration
git config global --edit

# List the global configurations
git config --global --list

# Using vi/cat to edit the configuration
vi ~/.gitconfig

# Set the author's email address and name
git config --global user.email "alang.hsu@gmail.com"
git config --global user.name "Alang Hsu"

# Set default editor
git config --global core.editor "vi"

# Set default branch name
git config --global init.defaultBranch "main"

.gitconfig

[user]
	email = alang.hsu@gmail.com
	name = Alang Hsu
[core]
	editor = vi
[init]
	defaultBranch = main
建立新專案
mkdir test-git-push
cd test-git-push
git config --global user.name "<user-name>"
git config --global user.email "<your-email-addr>"
git init
echo "Test Git Push only" > README.md
git add .
git commit -m "Initial commit"
git remote add origin https://<user-name>@github.com/a-lang/test-git-push.git
git remote -v
git push -u origin master
目錄更名

專案目錄下的檔案或子目錄的更名動作,必須使用 git 指令。

git mv <old-folder> <new-folder>
Commit 訊息

簡單的 commit 訊息

git commit -m "Fixed a typo in somewhere"

免 add 快速 commit (僅限 edited & deleted)

git commit -am "<commit-message>"

比較複雜的訊息

# 設定 Vim 作為編輯器
git config --global core.editor "vim"

# 不要加 -m 參數
git commit 

Commit 訊息編寫原則

  1. 用一行空白行分隔標題與內容
  2. 限制標題最多只有 50 字元,簡單描述更動的內容
  3. 標題開頭要大寫
  4. 標題不以句點結尾
  5. 以祈使句撰寫標題
  6. 內文每行最多 72 字,可以多行,詳細描述更動的內容
  7. 用內文解釋 what 以及 why vs. how

檢視 Comments

git log --graph --pretty=format:'%Cred%h%Creset -%C(yellow)%d%Creset %s %Cgreen(%cr) %C(bold blue)<%an>%Creset' --abbrev-commit --date=relative

修改最後一筆 Comment

git commit -v --amend
Clone for specified branch version
# Specified version
git clone -b 8.3.0 https://github.com/OpenSIPS/opensips-cp.git /var/www/opensips-cp
SSH Key Authentication
# 建立 ssh-key
# 預設路徑: ~/.ssh/id_rsa (private key) , ~/.ssh/id_rsa.pub (public-key)
ssh-keygen -t rsa -b 4096 -C "alang@my-linux-desktop"

# Gitlab 網站匯入 public ssh-key
# 測試 ssh key authentication
# 如果輸出 Welcome to GitLab, @alang! 表示連線認證成功
ssh -T git@gitlab.shurafom.eu

# 下載專案
# NOTE: 位址必須是 git@XXX.xxx.xxx 開頭。
# 如果使用 https:// 則必須改用 Personal Token 認證方式  
git clone git@gitlab.shurafom.eu:myproject/myprog.git
diff
# 還沒執行 git add 前,比對目前檔案與最近一次 commit 版本的內容差異
git diff file

# 比對兩個檔案的內容差異
git diff --word-diff file1 file2

# 檢視兩個 Commit 版本的內容差
git log --graph --pretty=format:'%Cred%h%Creset -%C(yellow)%d%Creset %s %Cgreen(%cr) %C(bold blue)<%an>%Creset' --abbrev-commit --date=relative
git diff <old-commit-id> <new-commit-id>
Undo in Git

Local unstaged changes (還沒有 add)

git status
git restore <filename>

Local staged changes (已經 add 還沒有 commit)

git status
git restore --staged <filename>
git restore <filename>

Local committed changes (已經 commit 還沒有 push)

git status
git log
git reset --soft HEAD~
git log

NOTE: 上述指令是回復最新的 commit,如果是更早的 commit,可以執行 git reset <commit-id>,而 commit-id 可以從 git log --oneline 找到。

不過,如果 commit 已經 push 到遠端庫,必須改用 git revert

Public committed changes (已經 push) 

git log --oneline
git revert <last-commit-id> --no-edit
git push
git log

NOTE: 在執行 git revert後,log 會多一條 Revert "XXXX" 的 commit 紀錄,原先的 commit 紀錄也會保留。

Git Prompt with bash

.bashrc:

# Kali-like Prompt with Git status

git_stats() {
  local STATUS=$(git status -s 2> /dev/null)
  local UNTRACKED=$(echo "$STATUS" | grep '^??' | wc -l)
  local STAGED=$(($(echo "$STATUS" | grep '^M ' | wc -l) + $(echo "$STATUS" | grep '^D ' | wc -l) + $(echo "$STATUS" | grep '^R ' | wc -l) + $(echo "$STATUS" | grep '^C ' | wc -l)+$(echo "$STATUS" | grep '^A ' | wc -l)))
  local DRC=$(($(echo "$STATUS" | grep '^ D' | wc -l) + $(echo "$STATUS" | grep '^ R' | wc -l) + $(echo "$STATUS" | grep '^ C' | wc -l)))
  local MODIFIED=$(echo "$STATUS" | grep '^ M' | wc -l)
  local STATS=''
  if [ $UNTRACKED != 0 ]; then
    STATS="\e[43m untr: $UNTRACKED "
  fi
  if [ $MODIFIED != 0 ]; then
    STATS="$STATS\e[43m mod: $MODIFIED "
  fi
  if [ $DRC != 0 ]; then
    STATS="$STATS\e[43m drc: $DRC "
  fi
  if [ $STAGED != 0 ]; then
    STATS="$STATS \e[42m staged: $STAGED "
  fi
  if [ ! -z "$STATS" ]; then
  echo -e "\e[30m $STATS\e[0m"
  fi
}

function origin_dist {
 local STATUS="$(git status 2> /dev/null)"
 local DIST_STRING=""
 local IS_AHEAD=$(echo -n "$STATUS" | grep "ahead")
 local IS_BEHIND=$(echo -n "$STATUS" | grep "behind")
 if [ ! -z "$IS_AHEAD" ]; then
  local DIST_VAL=$(echo "$IS_AHEAD" | sed 's/[^0-9]*//g')
  DIST_STRING="$DIST_VAL AHEAD"
 elif [ ! -z "$IS_BEHIND" ]; then
  local DIST_VAL=$(echo "$IS_BEHIND" | sed 's/[^0-9]*//g')
  DIST_STRING="BEHIND $DIST_VAL"
 fi
 if [ ! -z "$DIST_STRING" ]; then
  echo -en "\e[97;45m $DIST_STRING"
 fi
}

__PS1_GIT_BRANCH='`__git_ps1` '
__PS1_GIT_DIST='`origin_dist`'
__PS1_GIT_STATS='`git_stats` '

if $(__git_ps1 2>/dev/null);then
    PS1="\[\033[38;5;209m\]┌──[\[\033[38;5;141m\]\u\[\033[38;5;209m\]@\[\033[38;5;105m\]\h\[\033[38;5;231m\]:\w\[\033[38;5;209m\]]\[\033[33m\]${__PS1_GIT_BRANCH}${__PS1_GIT_DIST}${__PS1_GIT_STATS}\[\033[00m\]\n\[\033[38;5;209m\]└─\\[\033[38;5;209m\]\\$\[\033[37m\] "
else
    source /usr/share/git-core/contrib/completion/git-prompt.sh
    PS1="\[\033[38;5;209m\]┌──[\[\033[38;5;141m\]\u\[\033[38;5;209m\]@\[\033[38;5;105m\]\h\[\033[38;5;231m\]:\w\[\033[38;5;209m\]]\[\033[33m\]${__PS1_GIT_BRANCH}${__PS1_GIT_DIST}${__PS1_GIT_STATS}\[\033[00m\]\n\[\033[38;5;209m\]└─\\[\033[38;5;209m\]\\$\[\033[37m\] "
fi
Git Rebase
Git Alias
git config --global alias.st status
git config --global alias.c commit
git config --global alias.br branch

~/.gitconfig

[alias]
    st = status
    c = commit
    loo = log --oneline
    # 重新修改最後一筆 commit 的 comment
    onemore = commit -a --amend --no-edit
    # 刪除最後一筆 commit, 保留文件的修改
    undo = reset --soft HEAD^
    # 刪除最後一筆 commit, 不保留文件
    cancel = reset --hard HEAD^
Gitignore

Git 對於專案庫目錄內的所有資料夾與檔案都會納入版本控制的範圍,然而,在大部分情況,專案目錄裡有些檔案或資料夾是不需要做版控的

不需要版控的檔案或資料夾:

  1. 編譯後的檔案,例如 .o .so

  2. 如果是 Node.js 專案,目錄 node_modules 內的檔案都是從外部的軟體庫下載,與自己的程式碼無關。

  3. 與 Log 有關的檔案或資料夾,例如 .log

  4. 系統環境變數檔,例如 .env

  5. 開發工具 (IDE) 自動產生的設定檔。

.gitignore

# ignore all directories with the name test
test/

.env
log/
node_modules/
make/*.o
make/*.so

# ignores all .md files
.md

# does not ignore the README.md file
!README.md