Git tips

tags: git

How to Write a Git Commit Message

首先是推薦文章內的重點:好的 commit message 的七個規則
The seven rules of a great Git commit message

  1. Separate subject from body with a blank line
    標題與內文以一個空白行分隔

這樣可以在使用 $ git log 閱讀時叫容易分辨出哪行是標題

  1. Limit the subject line to 50 characters
    標題字數最多 50

因為標題字數超過 50 在 github 上會被隱藏到 裡面
需要額外點開才能完整知道這個 commit 在做什麼
超過 50 字可能就不是一個精簡的標題了

  1. Capitalize the subject line
    標題首字大寫

  2. Do not end the subject line with a period
    標題結尾不要句點

  3. Use the imperative mood in the subject line
    標題用命令口吻

  4. Wrap the body at 72 characters
    內文一行字數不超過 72

這樣在 $ git log 裡面看會比較清楚,排版比較好看

  1. Use the body to explain what and why vs. how
    內文說明是什麼、為什麼而不是如何做

因為看 commit 的人是想知道你為什麼要做這件事,而不是要知道你如何做這件事的

ref from:
how to write git message


index and working tree

Image Not Showing Possible Reasons
  • The image file may be corrupted
  • The server hosting the image is unavailable
  • The image path is incorrect
  • The image format is not supported
Learn More →


Git 工具 - 使用 Git 做 Debug

git blame [file]查看該file適合人在何時修改的

-L [start,end]:限制範圍在該file的第幾行到第幾行
-C :找出原始程式碼的出處

Image Not Showing Possible Reasons
  • The image file may be corrupted
  • The server hosting the image is unavailable
  • The image path is incorrect
  • The image format is not supported
Learn More →

Git flow

git flow 初始化

git flow init

開始新功能設計

新功能的開發是由 'develop' 分支的最後一個版本發展出來的
git flow feature start [branch name]
完成新功能
當你在新功能分支完成新功能後,使用下列指令來結束新功能開發:
git flow feature finish [branch name]
這個指令會替你完成下列動作:

  • 合並(Merge) MYFEATURE 分支回到 'develop'
  • 刪除這個新功能分支
  • 切換回 'develop' 分支

發佈新功能分支
讓遠端其他人也可使用這個分支
git flow feature publish [branch name]
取得新功能分支
取得遠端上的新分支
git flow feature pull [branch name]
追蹤遠端上的分支
git flow feature track [branch name]

推出新版本

使用下列指令準備推出(release)新版本分支:
git flow release start RELEASE [BASE]
完成推出新版本
完成 release 版本是一個很大的 git 分支操作。但你只許要執行下列指令:
git flow release finish RELEASE
git flow 會幫你執行下面幾個步驟:

  • 合並 release 分支到 'master'
  • 在 master 加上版本號標籤(Tag)
  • 合並 release 分支到 'develop'
  • 移除 release 分支

快速修復hotfix

開始快速修復
git flow hotfix start VERSION [BASENAME]
你必須填寫一個 VERSION 參數,這代表當你完成 hotfix 時發佈的版本號碼。
此外你可以自由挑選修復開始的分支名稱**[BASENAME]**,分支名稱如果不填寫的狀況下,預設是 'master'。
完成快速修復
git flow hotfix finish VERSION

  • 使用 git reflog 來查看 HEAD 的修改紀錄
  • 使用 git log -g 查看 reflog 的詳細內容

git reset

比較 **git reset HEAD^** 的參數

指令
效果

git reset HEAD^ 

回到前一個 patch,且恢復檔案的狀態

git reset --soft HEAD^ 

回到前一個 patch,但保持檔案狀態為 Changes to be committed

git reset --mixed

回到前一個patch,狀態為unstage

git reset --hard HEAD^

回到前一個 patch,且強制清除檔案的修改內容

  • reset merge
always discards the index (staged changes);
aborts if unstaged and staged changes present on any file
  • reset keep
keeps, but unstages, the index; 
aborts if the reset target touches the same file

簡單來說--keep只要有修改到相同檔案部份,reset就會abort,但是--merge只有在修改要reset的檔案或是新增要reset時沒有的檔案,殘會abort,--merege域社會清除所有在index中的檔案,需要特別注意,而--keep會將期轉為unstage。
https://stackoverflow.com/questions/24728543/git-reset-merge-vs-git-reset-keep

git commit amend 修改提交訊息

因為凡事只要做了 commit 的動作,git 就會重新產生一組新的 commit id
而 git commit amend 後面也可以加 -m 的參數帶入新的 commit message

​​​​$ git commit --amend -m <title>                             # 只要提交 title
​​​​$ git commit --amend -m <title> -m <message>                # 提交 title 以及 message
​​​​$ git commit --amend -m <title> -m <msg1> -m <msg2> ....    # 提交多段 messages

若不小心和上一個patch合併3則可利用下指令將兩者分離
git reset soft HEAD@{1}

使用 git cherry-pick <commit id> 挑入 patch

https://zlargon.gitbooks.io/git-tutorial/content/patch/remove.html

git revert 還原指定的patch

  • 使用 git revert <commit id> 還原指定的 patch
  • 使用 git revert --continue 告知 git 已經解完衝突
  • 使用 git revert --abort 來要放棄這次 revert

git 只提交檔案部份內容

  • 使用 git add -p 提交檔案部分的內容
  • 使用 git checkout -p 回復檔案部分的內容

git patch

生成patch:

git format-patch -n

or

git format-patch <commit>  從某一個 commit 開始往後生成patch
git format-patch -n <commit> 從某一個 commit 開始從前先成 n 個patch

打包最新的三個 commit

git format-patch -3

套用patch:

git apply --check patch #套用patch時確認能否直接套用
git am xxx.patch

Make some changes, create a patch

git diff > patch-issue-1.patch

Add a file and create a patch

git add newfile
git diff staged > patch-issue-2.patch

Add a file, make some changes, and create a patch

git add newfile
git diff HEAD > patch-issue-2.patch

Make a patch for a commit

git format-patch COMMIT_ID

Make patches for the last two commits

git format-patch HEAD~2

Make patches for all non-pushed commits

git format-patch origin/master

Create patches that contain binary content

git format-patch binary full-index origin/master

track remote

git checkout --track(-t) origin/serverfix

remove remote branch

git push [遠程名] :[分支名]
git push origin :serverfix

delete origin branch

git push origin --delete [branch]

pull all repository in directory

ls | xargs -P10 -I{} git -C {} pull

git rev-list

Lists commit objects in reverse chronological order

$git checkout `git rev-list -1 --before="$DATE" master`
Note: checking out '79ad22cfb7d1ea950f4ffa2860f63bd4d0f31692'.

You are in 'detached HEAD' state. You can look around, make experimental
changes and commit them, and you can discard any commits you make in this
state without impacting any branches by performing another checkout.

If you want to create a new branch to retain commits you create, you may
do so (now or later) by using -b with the checkout command again. Example:

  git checkout -b <new-branch-name>

HEAD is now at 79ad22c... Need to update validate_sync with new column

效果等同於

$ DATE='Sep 3 2014'
$ git rev-list -1 --before="$DATE" master | xargs -Iz git checkout z
# 找出所有git object中,data<Sep 3 2014的onject,只取最接近現在的一個,然後checkout至該object。

之後就可以對該節點進行操做

$ git checkout `git rev-list -1 --before="$DATE" master`
## 用一個tag標注,方便以後checkout
$ git tag bug_23142
## go back master
$ git checkout master
## go to the mark tag
$ git checkout bug_23142
## 也可以直接建立一個新的branch
$ git co -b "bug23142"

利用file size尋找

$ SIZE=167092
$ git rev-list --all \
  | while read commit
 do if git ls-tree -l -r $commit \
  | grep -q -w $SIZE
 then echo $commit
 break
 fi
 done
d91807d59a6326e48077311e96e4d5730f24304c

Finding a git commit by error message

git log --format=%h \
  | xargs -n 1 -I sh -c \
  "echo -n {}; git show {}:bucardo | head -8627 | tail -1" \
  | less
## About 35 lines down:
379c9006     $dbh = DBI->connect($BDSN, 'bucardo'...

View remote urls

git remote -v

Change origin url

git remote set-url origin http//github.com/repo.git

See the files changed in a specific commit

git diff-tree --no-commit-id --name-only -r 
COMMIT_ID
or

git show --pretty="format:" --name-only COMMIT_ID
source: http://stackoverflow.com/a/424142/1391963

See diff before push

git diff --cached origin/master

See commit history for just the current branch

git cherry -v master
(master is the branch you want to compare)

Delete all untracked files

git clean -f
Including directories:

git clean -f -d
Preventing sudden cardiac arrest:

git clean -n -f -d
Source: http://stackoverflow.com/q/61212/1391963

Rebase

rebase is like merge,but it will merge your branch after the specific branch.

將整個feature branch移動到master branch之後

git co feature
git rebase master

交互式rebase

可以指定要被merge的commit
讓user可以edit list before rebasing.

git checkout feature
git rebase -i master
  1 pick c01b36d hello commit                                                                                                                                                                 
  2 pick 30fcc2a commit test
  3 pick d15923d test2
  4 pick 8cd6a3f test3
  5 
  6 # Rebase 9d960fb..8cd6a3f onto 9d960fb (4 command(s))
  7 #
  8 # Commands:
  9 # p, pick = use commit
 10 # r, reword = use commit, but edit the commit message
 11 # e, edit = use commit, but stop for amending
 12 # s, squash = use commit, but meld into previous commit
 13 # f, fixup = like "squash", but discard this commit's log message
 14 # x, exec = run command (the rest of the line) using shell
 15 # d, drop = remove commit
 16 #
 17 # These lines can be re-ordered; they are executed from top to bottom.
 18 #
 19 # If you remove a line here THAT COMMIT WILL BE LOST.
 20 #
 21 # However, if you remove everything, the rebase will be aborted.
 22 #
 23 # Note that empty commits are commented out

絕對不要在公用的branch 上使用rebase

如果沒有把握,可以先創立一個暫時的rebase分支,確認沒問題後才merge回主要分支
git checkout feature
git checkout -b temporary-branch
git rebase -i master
# [清理目录]
git checkout master
git merge temporary-branch

可以參考 https://blog.yorkxin.org/2011/07/29/git-rebase

git bisect

git bisect start
git bisect bad
git bisect good <some tag or rev that knew was working>
git bisect run [unittest runner of choice]

git bisect reset # to exit and put code back to state before git bisect start

分離某個子目錄到新的repository

git clone xxx
cd mcplugins
git checkout origni/master -b mylib
git filter-branch --prune-empty --subdirectory-filter plugins/mylib mylib
git push xxx/mylib.git mylib:master

衝突發生,但是知道哪個檔案正確

git merge y
# CONFLICT ...
git checkout --theirs conflict.txt # conflict.txt is the conflict file
git add conflict.txt
git commit

N.b. the function is there as hack to get $@ doing
what you would expect it to as a shell user.
Add the below to your .gitconfig for easy ours/theirs aliases.
ours = "!f() { git checkout ours $@ && git add $@; }; f"
theirs = "!f() { git checkout theirs $@ && git add $@; }; f"

git merge-base

git merge-base找出兩分之開始分叉的base

git remove sensitive data

  1. remove
 git filter-branch --force --index-filter \
'git rm --cached --ignore-unmatch [PATH-TO-YOUR-FILE-WITH-SENSITIVE-DATA]' \
--prune-empty --tag-name-filter cat -- --all
  • Force Git to process, but not check out, the entire history of every branch and tag
  • Remove the specified file, as well as any empty commits generated as a result
  • Overwrite your existing tags
  1. Add your file with sensitive data to .gitignore to ensure that you don't accidentally commit it again.
echo "YOUR-FILE-WITH-SENSITIVE-DATA" >> .gitignore
$ git add .gitignore
$ git commit -m "Add YOUR-FILE-WITH-SENSITIVE-DATA to .gitignore"
  1. push to original repository
git push origin --force --all
  1. Tell your collaborators to rebase, not merge, any branches they created off of your old (tainted) repository history. One merge commit could reintroduce some or all of the tainted history that you just went to the trouble of purging.
  2. If you are confidient that git filter branch have no side effect that you can clean up your repository
git for-each-ref --format='delete %(refname)' refs/original | git update-ref --stdin
git reflog expire --expire=now --all
git gc --prune=now

git remove commit

假設我們的版控歷史紀錄如下:

R–A–B–C–D–E–HEAD
接下來要移除 B 跟 C 的 commit tree,變成

R–A–D’–E–HEAD
依序執行下列指令可以移除 B & C

# detach head and move to D commit
git checkout <SHA-for-D>
 
# move HEAD to A, but leave the index and working tree as for D
git reset --soft <SHA-for-A>
 
# Redo the D commit re-using the commit message, but now on top of A
git commit -C <SHA-for-D>
 
# Re-apply everything from the old D onwards onto this new place 
git rebase --onto HEAD <SHA-for-D> master
 
# push it
git push --force

or use cherry-pick

git rebase --hard a
git cherry-pick d
git cherry-pick e

config

指定編輯器

git config --global core.editor vim

檢查設定

git config --list
git config <key>

tig

git source tree

同步不同remote server的repository

  1. 查看remote 來源
    • git remote -v
  2. set upstream
    • git remote add upstream <upstram-url>
  3. git remote -v
    • 檢查是否多一組remote
  4. 和第二組server同步
    • git pull upstream master
  5. 合併
    • git pull rebase upstream master
  6. push 回自己的repository
    5. git push origin master

git submodule

  • git submodule add <repo>

  • git submodule init

  • git submodule update

  • git submodule foreach git pull origin master

  • Clone all submodules

    • git clone --recursive git://xxxx
    • if already cloned:
      • git submodule update --init --recursive
  • Remove submodule
    Creating a submodule is pretty straight-forward, but deleting them less so. The commands you need are:

$ git submodule deinit submodulename
$ git rm submodulename
$ git rm --cached submodulename
$ rm -rf .git/modules/submodulename
  • reset hard for all submodules
    • git submodule foreach git reset --hard
    • git submodule foreach --recursive git reset --hard

creating a bare repo

  • git init bare

git hooks

git hook help

https://git-scm.com/book/en/v2/Customizing-Git-Git-Hooks

Rewrite branches

git filter-branch

merge remote pull request to local

git fetch origin pull/ID/head:BRANCHNAME

and

git checkout BRANCHNAME

If the PR code changes and you want to update:

# Do this while in the pr37 branch
$ git pull upstream pull/37/head

Merging the PR

You'll only do this the first time it creates the local upstream_master branch, tracks it to upstream_master, and switches to the branch:

$ git checkout -t -b upstream_master upstream/master

After the first time you'll just do:

$ git checkout upstream_master

Now merge the PR:

$ git merge pr37

NOTE: You should edit the merge commit message to reference the PR (using, say #37 in it).

Now push:

$ git push upstream HEAD:master

在repository中更改使用者

eg:A->B->C->D
更改B以後的commit的使用者

  1. git rebase -i A
  2. 將pick改為edit
  3. git commit amend author="Author Name email@address.com"
  4. git rebade continue
  5. 重複1直到rebase結束

徹底清除

git filter-branch --env-filter '
WRONG_EMAIL="wrong@example.com"
NEW_NAME="New Name Value"
NEW_EMAIL="correct@example.com"

if [ "$GIT_COMMITTER_EMAIL" = "$WRONG_EMAIL" ]
then
    export GIT_COMMITTER_NAME="$NEW_NAME"
    export GIT_COMMITTER_EMAIL="$NEW_EMAIL"
fi
if [ "$GIT_AUTHOR_EMAIL" = "$WRONG_EMAIL" ]
then
    export GIT_AUTHOR_NAME="$NEW_NAME"
    export GIT_AUTHOR_EMAIL="$NEW_EMAIL"
fi
' --tag-name-filter cat -- --branches --tags

局部config

git config user.name "YOUR NAME"
git config user.email "YOUR EMAIL"

Finding

  • git log -S "string to find"
    • Commons parameters:
    • --source:顯示在命令行上給出的每次引用的引用名稱
    • --all:start from every branch
    • reverse:prints in reverse order

find author committer

git log --author="name or email"
git log --committer="name or email"

list commits containing specific files

git log -- <path to file>
git log -- **/*.js
git log --name-status -- **/*.js

view commit history for a specific function

git log -L :FunctionName:FilePath

Find a tag where a commit is referenced

git tag --contains <commitid>

Copy a folder or file from one branch to another

$ git checkout <branch-you-want-the-directory-from> -- <folder-name or file-name>

Restore a deleted file

First find the commit when the file last existed:

$ git rev-list -n 1 HEAD -- filename

Then checkout that file:

git checkout deletingcommitid^ -- filename

Delete tag

$ git tag -d <tag_name>
$ git push <remote> :refs/tags/<tag_name>

Exporting a repository as a Zip file

$ git archive --format zip --output /full/path/to/zipfile.zip master

Tracking files

I want to change a file name's capitalization, without changing the contents of the file

(master)$ git mv --force myfile MyFile

I want to overwrite local files when doing a git pull

(master)$ git fetch --all
(master)$ git reset --hard origin/master

I want to remove a file from Git but keep the file

(master)$ git rm --cached log.txt

I want to revert a file to a specific revision

Assuming the hash of the commit you want is c5f567:

(master)$ git checkout c5f567 -- file1/to/restore file2/to/restore

If you want to revert to changes made just 1 commit before c5f567, pass the commit hash as c5f567~1:

(master)$ git checkout c5f567~1 -- file1/to/restore file2/to/restore

I want to list changes of a specific file between commits or branches

Assuming you want to compare last commit with file from commit c5f567:

$ git diff HEAD:path_to_file/file c5f567:path_to_file/file

Same goes for branches:

$ git diff master:path_to_file/file staging:path_to_file/file

I want Git to ignore changes to a specific file

This works great for config templates or other files that require locally adding credentials that shouldn't be committed.

$ git update-index --assume-unchanged file-to-ignore

Note that this does not remove the file from source control - it is only ignored locally. To undo this and tell Git to notice changes again, this clears the ignore flag:

$ git update-index --no-assume-unchanged file-to-stop-ignoring

I want to cache a username and password for a repository

You might have a repository that requires authentication. In which case you can cache a username and password so you don't have to enter it on every push and pull. Credential helper can do this for you.

$ git config --global credential.helper cache

Set git to use the credential memory cache

$ git config --global credential.helper 'cache --timeout=3600'

Set the cache to timeout after 1 hour (setting is in seconds)

To find a credential helper:

$ git help -a | grep credential
# Shows you possible credential helpers

I want to make Git ignore permissions and filemode changes

$ git config core.fileMode false

If you want to make this the default behaviour for logged-in users, then use:

$ git config --global core.fileMode false

revert file after commit and push

  • git reset HEAD^ path/to/file/to/revert

Git 2.23:

  • git restore source=HEAD^ staged path/to/file

shorter:

  • git restore -s@^ -S path/to/file

view commit files:

  • git diff name-only HEAD^

undo changes one file:

git checkout master                            # first get back to master
git checkout experiment -- app.js  # then copy the version of app.js 
                                                                   # from branch "experiment"

Git 2.23: git switch/restore

git switch master
git restore -s experiment -- app.js

Push 到其他人的遠端分支

$ git push git@github.com:owner/repo.git HEAD:target-branch

pull specific branch from github PR

git fetch origin pull/ID/head:BRANCHNAME

10 new Git commands you should start using today