Git
是一個分佈式版本控制軟件,最初由Linus Torvalds
創做,於2005年以GPL
發佈。最初目的是爲更好地管理Linux
內核開發而設計。node
# 在當前目錄新建一個git倉庫 git init # 打開git倉庫圖形界面 gitk # 顯示全部變動信息 git status # 刪除全部Untracked files git clean -fd # 下載遠程倉庫的全部更新 git fetch remote # 下載遠程倉庫的全部更新,而且Merge git pull romote branch-name # 查看上次commit id git rev-parse HEAD # 將指定分支合併到當前分支 git merge branch-name # 將最近的一次commit打包到patch文件中 git format-patch HEAD^ # 將patch文件 添加到本地倉庫 git am patch-file # 查看指定文件修改歷史 git blame file-name
# 將遠程git倉庫克隆到本地 git clone url # 將遠程git倉庫克隆到本地 git clone -b branch url
# 將修改過,未add到Staging區的文件,暫時存儲起來 git stash # 恢復以前stash存儲的內容 git stash apply # 保存stash 並寫message git stash save "stash test" # 查看stash了哪些存儲 git stash list # 將stash@{1}存儲的內容還原到工做區 git stash apply stash@{1} # 刪除stash@{1}存儲的內容 git stash drop stash@{1} # 刪除全部緩存的stash git stash clear
# 配置git圖形界面編碼爲utf-8 git config --global gui.encoding=utf-8 # 設置全局提交代碼的用戶名 git config --global user.name name # 設置全局提交代碼時的郵箱 git config --global user.email email # 設置當前項目提交代碼的用戶名 git config user.name name
# 顯示全部遠程倉庫 git remote -v # 增長一個新的遠程倉庫 git remote add name url # 刪除指定遠程倉庫 git remote remove name # 獲取指定遠程倉庫的詳細信息 git remote show origin
# 添加全部的修改到Staging區 git add . git add --all # 添加指定文件到Staging區 git add file # 添加多個修改的文件到Staging區 git add file1 file2 # 添加修改的目錄到Staging區 git add dir # 添加全部src目錄下main開頭的全部文件到Staging區 git add src/main*
# 提交Staging區的代碼到本地倉庫區 git commit -m "message" # 提交Staging中在指定文件到本地倉庫區 git commit file1 file2 -m "message" # 使用新的一次commit,來覆蓋上一次commit git commit --amend -m "message" # 修改上次提交的用戶名和郵箱 git commit --amend --author="name <email>" --no-edit
# 列出本地全部分支 git branch # 列出本地全部分支 並顯示最後一次提交的哈希值 git branch -v # 在-v 的基礎上 而且顯示上游分支的名字 git branch -vv # 列出上游全部分支 git branch -r # 新建一個分支,但依然停留在當前分支 git branch branch-name # 刪除分支 git branch -d branch-name # 設置分支上游 git branch --set-upstream-to origin/master # 本地分支重命名 git branch -m old-branch new-branch
# 建立本地分支並關聯遠程分支 git checkout -b local-branch origin/remote-branch # 新建一個分支,且切換到新分支 git checkout -b branch-name # 切換到另外一個分支 git checkout branch-name # 撤銷工做區文件的修改,跟上次Commit同樣 git checkout commit-file
# 建立帶有說明的標籤 git tag -a v1.4 -m 'my version 1.4' # 打標籤 git tag tag-name # 查看全部標籤 git tag # 給指定commit打標籤 git tag tag-name commit-id # 刪除標籤 git tag -d tag-name
# 刪除遠程分支 git push origin :master # 刪除遠程標籤 git push origin --delete tag tag-name # 上傳本地倉庫到遠程分支 git push remote branch-name # 強行推送當前分支到遠程分支 git push remote branch-name --force # 推送全部分支到遠程倉庫 git push remote --all # 推送全部標籤 git push --tags # 推送指定標籤 git push origin tag-name # 刪除遠程標籤(須要先刪除本地標籤) git push origin :refs/tags/tag-name # 將本地dev分支push到遠程master分支 git push origin dev:master
# 將未commit的文件移出Staging區 git reset HEAD # 重置Staging區與上次commit的同樣 git reset --hard # 重置Commit代碼和遠程分支代碼同樣 git reset --hard origin/master # 回退到上個commit git reset --hard HEAD^ # 回退到前3次提交以前,以此類推,回退到n次提交以前 git reset --hard HEAD~3 回退到指定commit git reset --hard commit-id
# 查看文件在工做區和暫存區區別 git diff file-name # 查看暫存區和本地倉庫區別 git diff --cached file-name # 查看文件和另外一個分支的區別 git diff branch-name file-name # 查看兩次提交的區別 git diff commit-id commit-id
# 查看指定標籤的提交信息 git show tag-name # 查看具體的某次改動 git show commit-id
# 指定文件夾 log git log --pretty=format:"%h %cn %s %cd" --author="iisheng\|勝哥" --date=short src # 查看指定用戶指定format 提交 git log --pretty=format:"%h %cn %s %cd" --author=iisheng --date=short # 查看該文件的改動歷史 git log --pretty=oneline file # 圖形化查看歷史提交 git log --graph --pretty=oneline --abbrev-commit # 統計倉庫提交排名前5 git log --pretty='%aN' | sort | uniq -c | sort -k1 -n -r | head -n 5 # 查看指定用戶添加代碼行數,和刪除代碼行數 git log --author="iisheng" --pretty=tformat: --numstat | awk '{ add += $1 ; subs += $2 } END { printf "added lines: %s removed lines : %s \n",add,subs }'
# 將指定分支合併到當前分支 git rebase branch-name # 執行commit id 將rebase 停留在指定commit 處 git rebase -i commit-id # 執行commit id 將rebase 停留在 項目首次commit處 git rebase -i --root
# 恢復第一次add 的文件,同 git rm --cached git restore --staged file # 移除staging區的文件,同 git checkout git restore file
# 撤銷前一次commit git revert HEAD # 撤銷前前一次commit git revert HEAD^ # 撤銷指定某次commit git revert commit-id
我見過有的人使用
Git
別名,反正由於有自動補全的存在,我歷來沒用過Git
別名。不過個人確將個人rm -rf
命令替換成了別的腳本了...git
安裝bash-completion
github
brew install bash-completion
shell
添加 bash-completion 到~/.bash_profile
:數據庫
if [ -f $(brew --prefix)/etc/bash_completion ]; then . $(brew --prefix)/etc/bash_completion fi
shell
有不一樣種類,我這裏使用的是bash
。緩存
暫存未提交的代碼bash
git stash
還原暫存的代碼app
git stash apply
使用cherry-pick
命令ssh
git cherry-pick 指定commit-id
還原未commit
的本地更改的代碼分佈式
git reset --hard
還原包含commit
的代碼,到跟遠程分支相同
git reset --hard origin/master
還原到上次commit
git reset --hard HEAD^
還原到當前以前的幾回commit
git reset --hard HEAD~2
強制推送到遠程分支,確保沒有其餘人在push
,否則可能會丟失代碼
git push origin develop --force
使用git filter-branch
命令。
複製下面的腳本,替換相關變量
OLD_EMAIL
CORRECT_NAME
CORRECT_EMAIL
腳本以下:
#!/bin/sh git filter-branch --env-filter ' OLD_EMAIL="your-old-email@example.com" CORRECT_NAME="Your Correct Name" CORRECT_EMAIL="your-correct-email@example.com" if [ "$GIT_COMMITTER_EMAIL" = "$OLD_EMAIL" ] then export GIT_COMMITTER_NAME="$CORRECT_NAME" export GIT_COMMITTER_EMAIL="$CORRECT_EMAIL" fi if [ "$GIT_AUTHOR_EMAIL" = "$OLD_EMAIL" ] then export GIT_AUTHOR_NAME="$CORRECT_NAME" export GIT_AUTHOR_EMAIL="$CORRECT_EMAIL" fi ' --tag-name-filter cat -- --branches --tags
強制推送替換
git push --force --tags origin 'refs/heads/*'
也是使用git filter-branch
命令。
git filter-branch --force --index-filter \ "git rm --cached --ignore-unmatch FILE-PATH-AND-NAME" \ --prune-empty --tag-name-filter cat -- --all
強制推送覆蓋遠程分支。
git push origin --force --all
強制推送覆蓋遠程tag
。
git push origin --force --tags
這裏想說的是使用git hooks
,通常在項目目錄.git/hooks
,客戶端可使用hooks
,控制團隊commit
提交規範,或者push
以前,自動編譯項目校驗項目可運行。服務端可使用hooks
,控制push以後自動構建項目,merge
等自動觸發單元測試等。
git reset --hard
命令,執行錯了,能恢復嗎?查看當前commit log
誤操做git reset --hard 8529cb7
執行git reflog
還原到以前的樣子
編輯 ~/.ssh/config
文件 沒有就建立
# github Host github.com Port 22 HostName github.com PreferredAuthentications publickey AddKeysToAgent yes IdentityFile ~/.ssh/github_id_rsa UseKeychain yes User iisheng # gitlab Host gitlab.iisheng.cn Port 22 HostName gitlab.iisheng.cn PreferredAuthentications publickey AddKeysToAgent yes IdentityFile ~/.ssh/gitlab_id_rsa UseKeychain yes User iisheng
多用git rebase
。
好比,開發分支是feature
,主幹分支是master
。咱們在進行代碼合併的時候,能夠執行下面的命令。
# 切換當前分支到feature git checkout feature # 將當前分支代碼變基爲基於master git rebase master
而後咱們再切換到master
分支,執行git merge feature
,就能夠進行快進式合併了,commmits
歷史就不會有交叉了。後文咱們會詳細講解。
git rebase
會更改commit歷史,請謹慎使用。
下面的圖是Guava
項目的commit
記錄。
原始Git
提交記錄是這樣的
執行git rebase -i 070943d
,對指定commitId以前的提交,進行修改
修改後Git
提交記錄變成了這樣
git rebase -i
很是實用,還能夠將多個commit合併成一個等不少事情,務必要記下。
git stash clear
怎麼辦?git fsck --lost-found
執行以後,能夠找到相關丟失的commit-id
,而後merge
一下便可。
該命令上能夠找回git add
以後被弄丟的文件。
啥?你沒執行過
git add
代碼就丟了?別怕,通常編譯器有Local History
趕忙去試試吧。
git merge
咱們執行git merge
命令的時候,常常會看到Fast-forward
字樣,Fast-forward
究竟是個什麼東西?
其實,git merge
通常有三種場景。
舉個栗子,假如初始存在master
和hotfix
分支是這樣的。
而後咱們在hotfix
分支加了些代碼,分支變成這樣了。
這個時候,咱們將hotfix
分支,merge
到master
,即執行git merge hotfix
。
因爲的分支hotfix
所指向的提交C3
是C2
的直接後繼, 所以Git
會直接將指針向前移動。換句話說,若是順着一個分支走下去可以到達另外一個分支,那麼Git
在合併二者的時候, 只會簡單的將指針向前推動(指針右移),由於這種狀況下的合併操做沒有須要解決的分歧——這就叫作 快進(fast-forward)。
再舉個栗子,假如初始存在feature
和master
分支狀況是這樣的。
而後咱們在feature
分支加了些代碼,而master
分支也有人加了代碼,如今分支變成這樣了。
這個時候,咱們將feature
分支,merge
到master
,即執行git merge feature
。
和以前將分支指針向前推動所不一樣的是,Git
將這次三方合併的結果作了一個新的快照而且自動建立一個新的提交指向它。這個被稱做一次合併提交,它的特別之處在於他有不止一個父提交。
因此咱們也知道了,爲何有的時候merge以後會產生新的commit,而有的時候沒有。
若是在兩個分支分別對同一個文件作了改動,Git
就無法直接合並他們。當遇到衝突的時候,Git
會自動停下來,等待咱們解決衝突。就像這樣
$ git merge dev Auto-merging 111.txt CONFLICT (content): Merge conflict in 111.txt Automatic merge failed; fix conflicts and then commit the result.
咱們能夠在合併衝突後的任意時刻使用git status
命令來查看那些因包含合併衝突而處於未合併unmerged
狀態的文件。
$ git status On branch master You have unmerged paths. (fix conflicts and run "git commit") (use "git merge --abort" to abort the merge) Unmerged paths: (use "git add <file>..." to mark resolution) both modified: 111.txt no changes added to commit (use "git add" and/or "git commit -a")
待解決衝突的文件Git
會以未合併的狀態標識出來,出現衝突的文件會出現一些特殊的區段,看起來像下面的樣子。
<<<<<<< HEAD 111aaa ======= 111b >>>>>>> dev
<<<<<<<
後面的是當前分支的引用,咱們的例子中,就表明master
分支。>>>>>>>
後面表示的是要合併到當前分支的分支,即dev
分支。=======
的上半部分,表示當前分支的代碼。下半部分表示dev
分支的代碼。
咱們能夠把上面的測試內容改爲下面的樣子來解決衝突
111aaa
在解決了全部文件裏的衝突以後,對每一個文件使用git add
命令來將其標記爲衝突已解決。
解決衝突的過程當中,每一步均可以執行git status
查看當前狀態,Git
也會給出相應提示,進行下一步操做。當咱們全部的文件都暫存以後時,執行git status
時,Git
會給咱們看起來像下面的這種提示
$ git status On branch master All conflicts fixed but you are still merging. (use "git commit" to conclude merge)
而後,咱們根據提示執行git commit
。
Merge branch 'dev' # Conflicts: # 111.txt # # It looks like you may be committing a merge. # If this is not correct, please remove the file # .git/MERGE_HEAD # and try again. # Please enter the commit message for your changes. Lines starting # with '#' will be ignored, and an empty message aborts the commit. # # On branch master # All conflicts fixed but you are still merging. #
而後,咱們保存此次提交就完成了此次衝突合併。
git rebase
舉個栗子。咱們一樣用剛纔merge
的場景。
若是不用rebase
,使用merge
是下面這樣的,合併分支的時候會產生一個合併提交,並且會有分支交叉的狀況。
使用rebase
是下面這樣的。
而後,切換到master
分支,進行一次快進式合併。
變基實際上就是基於其餘分支重塑當前分支。變基以後,當前分支就至關因而基於最新的其餘分支新加了一些commit,這樣的話就能夠進行快進式合併了。
它的原理是首先找到這兩個分支(即當前分支 dev
、變基操做的目標基底分支master
)的最近共同祖先 C2
,而後對比當前分支相對於該祖先的歷次提交,提取相應的修改並存爲臨時文件, 而後將當前分支指向目標基底C3
, 最後以此將以前另存爲臨時文件的修改依序應用,也就是在C3
後面添加C4'
、C5'
。
提到Git
,總有人會說快照,快照是個什麼鬼?
實際上,Git
是一個內容尋址文件系統,其核心部分是一個簡單的鍵值對數據庫。將Git
中的對象,存儲在.git/objects
目錄下。
Git
對象主要分爲,數據對象(blob object)、樹對象(tree object)、提交對象(commit object)、標籤對象(tag object)。
咱們新建一個目錄,而後在該目錄下執行git init
初始化一個Git
項目。
而後,查看.git/objects
目錄下都有什麼。
$ find .git/objects .git/objects .git/objects/pack .git/objects/info
接着,咱們寫一個文件echo '1111' > 111.txt
,並執行git add
以後,再查看。
$ find .git/objects .git/objects .git/objects/5f .git/objects/5f/2f16bfff90e6620509c0cf442e7a3586dad8fb .git/objects/pack .git/objects/info
咱們發現.git/objects
目錄下,多了個文件和目錄。實際上,Git
會將咱們的文件數據外加一個頭部信息header
一塊兒作SHA-1
校驗運算而獲得校驗和。而後,校驗和的前2個字符用於命名子目錄,餘下的38個字符則用做文件名。
咱們可使用下面的命令,顯示在Git
對象中存儲的內容。
$ git cat-file -p 5f2f16bfff90e6620509c0cf442e7a3586dad8fb 1111
這就是咱們在上文寫入的文件內容。
上述類型的對象稱之爲數據對象(blob object)。數據對象,僅保存了文件內容,而文件名字沒有被保存。
數據對象大體對應UNIX
中的inodes
或文件內容,樹對象則對應了UNIX
中的目錄項。一個樹對象包含了一條或多條樹對象記錄(tree entry),每條記錄含有一個指向數據對象或者子樹對象的SHA-1
指針,以及相應的模式、類型、文件名信息。
一般,Git
根據某一時刻暫存區(即index
區域)所表示的狀態建立並記錄一個對應的樹對象。
當咱們執行過git add
以後,暫存區就有內容了,咱們能夠經過Git
底層命令,生成樹對象。
$ git write-tree b716c7b049ccd9048b0566a57cfd516c17c1e39f
查看該樹對象的內容。
$ git cat-file -p b716c7b049ccd9048b0566a57cfd516c17c1e39f 100644 blob 5f2f16bfff90e6620509c0cf442e7a3586dad8fb 111.txt
數據對象保存了數據的內容,樹對象能夠表示當前目錄的快照。可是,若想重用這些快照,必須記住樹對象的SHA-1
哈希值。並且,咱們也不知道是誰保存了這些快照,在什麼時刻保存的,以及爲何保存這些快照。而以上這些,正是
提交對象(commit object)
能保存的基本信息。
咱們對當前暫存區進行一次提交,git commit -m "first commit"
。
而後查看一下log
找到該次提交的commit
哈希值。
$ git log --oneline 5281f7e (HEAD -> master) first commit
接着,咱們查看一下該提交對象的內容。
$ git cat-file -p 5281f7e tree b716c7b049ccd9048b0566a57cfd516c17c1e39f author iisheng <***@gmail.com> 1596073568 +0800 committer iisheng <***@gmail.com> 1596073568 +0800 first commit
提交對象的格式很簡單:它先指定一個頂層樹對象,表明當前項目快照;而後是可能存在的父提交(前面描述的提交對象並不存在任何父提交);以後是做者/提交者信息(依據你的user.name
和user.email
配置來設定,外加一個時間戳);留空一行,最後是提交註釋。
標籤對象(tag object) 很是相似於一個提交對象——它包含一個標籤建立者信息、一個日期、一段註釋信息,以及一個指針。主要的區別在於,標籤對象一般指向一個提交對象,而不是一個樹對象。它像是一個永不移動的分支引用——永遠指向同一個提交對象,只不過給這個提交對象加上一個更友好的名字罷了。
實際上
Git
中的各類對象都是相似的,只不過由於各類對象自身功能不一樣,存儲結構不一樣而已。
Git
引用至關因而Git
中特定哈希值的別名。一長串的哈希值不是很友好,可是起個別名,咱們就能夠像這樣git show master
、git log master
的去使用他們。
Git
中的引用存儲在.git/refs
目錄下。咱們能夠執行find .git/refs/
查看當前Git
項目中都存在哪些引用。
在.git
目錄下有一個名字叫作HEAD
的文件,HEAD
文件一般是一個符號引用(symbolic reference)指向目前所在的分支。所謂符號引用,表示它是一個指向其餘引用的指針。
若是咱們在工做區checkout
一個SHA-1
值,HEAD
引用也會指向這個包含Git
對象的SHA-1
值。
Git
標籤分爲,附註標籤和輕量標籤。輕量標籤,使用git tag v1.0
便可建立。附註標籤須要使用-a
選項,即git tag -a v1.0 -m "my version 1.0"
這種。
輕量標籤就是一個固定的引用。附註標籤須要建立標籤對象,並記錄一個引用來指向該標籤對象。
不熟悉Git
的同窗,可能會犯這樣一個錯誤。其餘同窗讓他拉取一下遠程最新的master
分支代碼,他可能直接用IDE
找到本地的遠程分支的引用,也就是origin/master
,直接checkout
一個本地分支。
其實,origin/master
只是遠程分支的一個引用,不必定跟遠程分支代碼同步,咱們能夠用git fetch
或者git pull
來讓origin/master
和遠程分支同步。