Git 是一個開源的分佈式版本控制系統。html
版本控制是一種記錄一個或若干文件內容變化,以便未來查閱特定版本修訂狀況的系統。git
介紹分佈式版本控制系統前,有必要先了解一下傳統的集中式版本控制系統。程序員
集中化的版本控制系統,諸如 CVS,Subversion 等,都有一個單一的集中管理的服務器,保存全部文件的修訂版本,而協同工做的人們都經過客戶端連到這臺服務器,取出最新的文件或者提交更新。github
這麼作最顯而易見的缺點是中央服務器的單點故障。若是宕機一小時,那麼在這一小時內,誰都沒法提交更新,也就沒法協同工做。要是中央服務器的磁盤發生故障,碰巧沒作備份,或者備份不夠及時,就會有丟失數據的風險。最壞的狀況是完全丟失整個項目的全部歷史更改記錄。web
分佈式版本控制系統的客戶端並不僅提取最新版本的文件快照,而是把代碼倉庫完整地鏡像下來。這麼一來,任何一處協同工做用的服務器發生故障,過後均可以用任何一個鏡像出來的本地倉庫恢復。由於每一次的提取操做,實際上都是一次對代碼倉庫的完整備份。shell
Git 是分佈式的。這是 Git 和其它非分佈式的版本控制系統,例如 svn,cvs 等,最核心的區別。分佈式帶來如下好處:數據庫
工做時不須要聯網緩存
首先,分佈式版本控制系統根本沒有「中央服務器」,每一個人的電腦上都是一個完整的版本庫,這樣,你工做的時候,就不須要聯網了,由於版本庫就在你本身的電腦上。既然每一個人電腦上都有一個完整的版本庫,那多我的如何協做呢?比方說你在本身電腦上改了文件 A,你的同事也在他的電腦上改了文件 A,這時,大家倆之間只需把各自的修改推送給對方,就能夠互相看到對方的修改了。安全
更加安全服務器
集中式版本控制系統,一旦中央服務器出了問題,全部人都沒法工做。
分佈式版本控制系統,每一個人電腦中都有完整的版本庫,因此某人的機器掛了,並不影響其它人。
Debian/Ubuntu 環境安裝
若是你使用的系統是 Debian/Ubuntu , 安裝命令爲:
$ apt-get install libcurl4-gnutls-dev libexpat1-dev gettext \ > libz-dev libssl-dev $ apt-get install git-core $ git --version git version 1.8.1.2
Centos/RedHat 環境安裝
若是你使用的系統是 Centos/RedHat ,安裝命令爲:
$ yum install curl-devel expat-devel gettext-devel \ > openssl-devel zlib-devel $ yum -y install git-core $ git --version git version 1.7.1
Windows 環境安裝
在Git 官方下載地址下載 exe 安裝包。按照安裝嚮導安裝便可。
建議安裝 Git Bash 這個 git 的命令行工具。
Mac 環境安裝
在Git 官方下載地址下載 mac 安裝包。按照安裝嚮導安裝便可。
Git 自帶一個 git config
的工具來幫助設置控制 Git 外觀和行爲的配置變量。 這些變量存儲在三個不一樣的位置:
/etc/gitconfig
文件: 包含系統上每個用戶及他們倉庫的通用配置。 若是使用帶有 --system
選項的 git config
時,它會今後文件讀寫配置變量。\~/.gitconfig
或 \~/.config/git/config
文件:只針對當前用戶。 能夠傳遞 --global
選項讓 Git 讀寫此文件。config
文件(就是 .git/config
):針對該倉庫。每個級別覆蓋上一級別的配置,因此 .git/config
的配置變量會覆蓋 /etc/gitconfig
中的配置變量。
在 Windows 系統中,Git 會查找 $HOME
目錄下(通常狀況下是 C:\Users\$USER
)的 .gitconfig
文件。 Git 一樣也會尋找 /etc/gitconfig
文件,但只限於 MSys 的根目錄下,即安裝 Git 時所選的目標位置。
當安裝完 Git 應該作的第一件事就是設置你的用戶名稱與郵件地址。 這樣作很重要,由於每個 Git 的提交都會使用這些信息,而且它會寫入到你的每一次提交中,不可更改:
$ git config --global user.name "John Doe" $ git config --global user.email johndoe@example.com
再次強調,若是使用了 --global
選項,那麼該命令只須要運行一次,由於以後不管你在該系統上作任何事情, Git 都會使用那些信息。 當你想針對特定項目使用不一樣的用戶名稱與郵件地址時,能夠在那個項目目錄下運行沒有 --global
選項的命令來配置。
不少 GUI 工具都會在第一次運行時幫助你配置這些信息。
.gitignore
文件可能從字面含義也不難猜出:這個文件裏配置的文件或目錄,會自動被 git 所忽略,不歸入版本控制。
在平常開發中,咱們的項目常常會產生一些臨時文件,如編譯 Java 產生的 *.class
文件,又或是 IDE 自動生成的隱藏目錄(Intellij 的 .idea
目錄、Eclipse 的 .settings
目錄等)等等。這些文件或目錄實在不必歸入版本管理。在這種場景下,你就須要用到 .gitignore
配置來過濾這些文件或目錄。
配置的規則很簡單,也沒什麼可說的,看幾個例子,天然就明白了。
這裏推薦一下 Github 的開源項目:https://github.com/github/gitignore
在這裏,你能夠找到不少經常使用的模板,如:Java、Nodejs、C++ 的 .gitignore
模板等等。
我的認爲,對於 Git 這個版本工具,再不瞭解原理的狀況下,直接去學習命令行,可能會一頭霧水。因此,本文特地將原理放在命令使用章節以前講解。
當你一個項目到本地或建立一個 git 項目,項目目錄下會有一個隱藏的 .git
子目錄。這個目錄是 git 用來跟蹤管理版本庫的,千萬不要手動修改。
Git 中全部數據在存儲前都計算校驗和,而後以校驗和來引用。 這意味着不可能在 Git 不知情時更改任何文件內容或目錄內容。 這個功能建構在 Git 底層,是構成 Git 哲學不可或缺的部分。 若你在傳送過程當中丟失信息或損壞文件,Git 就能發現。
Git 用以計算校驗和的機制叫作 SHA-1 散列(hash,哈希)。 這是一個由 40 個十六進制字符(0-9 和 a-f)組成字符串,基於 Git 中文件的內容或目錄結構計算出來。 SHA-1 哈希看起來是這樣:
24b9da6552252987aa493b52f8696cd6d3b00373
Git 中使用這種哈希值的狀況不少,你將常常看到這種哈希值。 實際上,Git 數據庫中保存的信息都是以文件內容的哈希值來索引,而不是文件名。
在 GIt 中,你的文件可能會處於三種狀態之一:
與文件狀態對應的,不一樣狀態的文件在 Git 中處於不一樣的工做區域。
git clone
一個項目到本地,至關於在本地克隆了項目的一個副本。工做區是對項目的某個版本獨立提取出來的內容。 這些從 Git 倉庫的壓縮數據庫中提取出來的文件,放在磁盤上供你使用或修改。國外網友製做了一張 Git Cheat Sheet,總結很精煉,各位不妨收藏一下。
本節選擇性介紹 git 中比較經常使用的命令行場景。
克隆一個已建立的倉庫:
# 經過 SSH $ git clone ssh://user@domain.com/repo.git #經過 HTTP $ git clone http://domain.com/user/repo.git
建立一個新的本地倉庫:
$ git init
添加修改到暫存區:
# 把指定文件添加到暫存區 $ git add xxx # 把當前全部修改添加到暫存區 $ git add . # 把全部修改添加到暫存區 $ git add -A
提交修改到本地倉庫:
# 提交本地的全部修改 $ git commit -a # 提交以前已標記的變化 $ git commit # 附加消息提交 $ git commit -m 'commit message'
有時,咱們須要在同一個項目的不一樣分支上工做。當須要切換分支時,恰恰本地的工做尚未完成,此時,提交修改顯得不嚴謹,可是不提交代碼又沒法切換分支。這時,你可使用 git stash
將本地的修改內容做爲草稿儲藏起來。
官方稱之爲儲藏,但我我的更喜歡稱之爲存草稿。
# 1. 將修改做爲當前分支的草稿保存 $ git stash # 2. 查看草稿列表 $ git stash list stash@{0}: WIP on master: 6fae349 :memo: Writing docs. # 3.1 刪除草稿 $ git stash drop stash@{0} # 3.2 讀取草稿 $ git stash apply stash@{0}
撤銷本地修改:
# 移除緩存區的全部文件(i.e. 撤銷上次git add) $ git reset HEAD # 將HEAD重置到上一次提交的版本,並將以後的修改標記爲未添加到緩存區的修改 $ git reset <commit> # 將HEAD重置到上一次提交的版本,並保留未提交的本地修改 $ git reset --keep <commit> # 放棄工做目錄下的全部修改 $ git reset --hard HEAD # 將HEAD重置到指定的版本,並拋棄該版本以後的全部修改 $ git reset --hard <commit-hash> # 用遠端分支強制覆蓋本地分支 $ git reset --hard <remote/branch> e.g., upstream/master, origin/my-feature # 放棄某個文件的全部本地修改 $ git checkout HEAD <file>
刪除添加.gitignore
文件前錯誤提交的文件:
$ git rm -r --cached . $ git add . $ git commit -m "remove xyz file"
撤銷遠程修改(建立一個新的提交,並回滾到指定版本):
$ git revert <commit-hash>
完全刪除指定版本:
# 執行下面命令後,commit-hash 提交後的記錄都會被完全刪除,使用需謹慎 $ git reset --hard <commit-hash> $ git push -f
更新:
# 下載遠程端版本,但不合併到HEAD中 $ git fetch <remote> # 將遠程端版本合併到本地版本中 $ git pull origin master # 以rebase方式將遠端分支與本地合併 $ git pull --rebase <remote> <branch>
推送:
# 將本地版本推送到遠程端 $ git push remote <remote> <branch> # 刪除遠程端分支 $ git push <remote> :<branch> (since Git v1.5.0) $ git push <remote> --delete <branch> (since Git v1.7.0) # 發佈標籤 $ git push --tags
顯示工做路徑下已修改的文件:
$ git status
顯示與上次提交版本文件的不一樣:
$ git diff
顯示提交歷史:
# 從最新提交開始,顯示全部的提交記錄(顯示hash, 做者信息,提交的標題和時間) $ git log # 顯示某個用戶的全部提交 $ git log --author="username" # 顯示某個文件的全部修改 $ git log -p <file>
顯示搜索內容:
# 從當前目錄的全部文件中查找文本內容 $ git grep "Hello" # 在某一版本中搜索文本 $ git grep "Hello" v2.5
增刪查分支:
# 列出全部的分支 $ git branch # 列出全部的遠端分支 $ git branch -r # 基於當前分支建立新分支 $ git branch <new-branch> # 基於遠程分支建立新的可追溯的分支 $ git branch --track <new-branch> <remote-branch> # 刪除本地分支 $ git branch -d <branch> # 強制刪除本地分支,將會丟失未合併的修改 $ git branch -D <branch>
切換分支:
# 切換分支 $ git checkout <branch> # 建立並切換到新分支 $ git checkout -b <branch>
# 給當前版本打標籤 $ git tag <tag-name> # 給當前版本打標籤並附加消息 $ git tag -a <tag-name>
merge 與 rebase 雖然是 git 經常使用功能,可是強烈建議不要使用 git 命令來完成這項工做。
由於若是出現代碼衝突,在沒有代碼比對工具的狀況下,實在太艱難了。
你能夠考慮使用各類 Git GUI 工具。
合併:
# 將分支合併到當前HEAD中 $ git merge <branch>
重置:
# 將當前HEAD版本重置到分支中,請勿重置已發佈的提交 $ git rebase <branch>
Github 做爲最著名的代碼開源協做社區,在程序員圈想必無人不知,無人不曉。
這裏不贅述 Github 的用法,確實有不會用的新手同窗,能夠參考官方教程:https://guides.github.com/
Git 支持三種協議:HTTPS / SSH / GIT
而 Github 上支持 HTTPS 和 SSH。
HTTPS 這種方式要求你每次 push 時都要輸入用戶名、密碼,有些繁瑣。
而 SSH 要求你本地生成證書,而後在你的 Github 帳戶中註冊。第一次配置麻煩是麻煩了點,可是之後就免去了每次 push 須要輸入用戶名、密碼的繁瑣。
如下介紹如下,如何生成證書,以及在 Github 中註冊。
如前所述,許多 Git 服務器都使用 SSH 公鑰進行認證。 爲了向 Git 服務器提供 SSH 公鑰,若是某系統用戶還沒有擁有密鑰,必須事先爲其生成一份。 這個過程在全部操做系統上都是類似的。 首先,你須要確認本身是否已經擁有密鑰。 默認狀況下,用戶的 SSH 密鑰存儲在其 \~/.ssh
目錄下。 進入該目錄並列出其中內容,你即可以快速確認本身是否已擁有密鑰:
$ cd ~/.ssh $ ls authorized_keys2 id_dsa known_hosts config id_dsa.pub
咱們須要尋找一對以 id_dsa
或 id_rsa
命名的文件,其中一個帶有 .pub
擴展名。 .pub
文件是你的公鑰,另外一個則是私鑰。 若是找不到這樣的文件(或者根本沒有 .ssh
目錄),你能夠經過運行 ssh-keygen
程序來建立它們。在 Linux/Mac 系統中,ssh-keygen
隨 SSH 軟件包提供;在 Windows 上,該程序包含於 MSysGit 軟件包中。
$ ssh-keygen Generating public/private rsa key pair. Enter file in which to save the key (/home/schacon/.ssh/id_rsa): Created directory '/home/schacon/.ssh'. Enter passphrase (empty for no passphrase): Enter same passphrase again: Your identification has been saved in /home/schacon/.ssh/id_rsa. Your public key has been saved in /home/schacon/.ssh/id_rsa.pub. The key fingerprint is: d0:82:24:8e:d7:f1:bb:9b:33:53:96:93:49:da:9b:e3 schacon@mylaptop.local
首先 ssh-keygen
會確認密鑰的存儲位置(默認是 .ssh/id_rsa
),而後它會要求你輸入兩次密鑰口令。若是你不想在使用密鑰時輸入口令,將其留空便可。
如今,進行了上述操做的用戶須要將各自的公鑰發送給任意一個 Git 服務器管理員(假設服務器正在使用基於公鑰的 SSH 驗證設置)。 他們所要作的就是複製各自的 .pub
文件內容,並將其經過郵件發送。 公鑰看起來是這樣的:
$ cat ~/.ssh/id_rsa.pub ssh-rsa AAAAB3NzaC1yc2EAAAABIwAAAQEAklOUpkDHrfHY17SbrmTIpNLTGK9Tjom/BWDSU GPl+nafzlHDTYW7hdI4yZ5ew18JH4JW9jbhUFrviQzM7xlELEVf4h9lFX5QVkbPppSwg0cda3 Pbv7kOdJ/MTyBlWXFCR+HAo3FXRitBqxiX1nKhXpHAZsMciLq8V6RjsNAQwdsdMFvSlVK/7XA t3FaoJoAsncM1Q9x5+3V0Ww68/eIFmb1zuUFljQJKprrX88XypNDvjYNby6vw/Pb0rwert/En mZ+AW4OZPnTPI89ZPmVMLuayrD2cE86Z/il8b+gw3r3+1nKatmIkjn2so1d01QraTlMqVSsbx NrRFi9wrf+M7Q== schacon@mylaptop.local
在你的 Github 帳戶中,依次點擊 Settings > SSH and GPG keys > New SSH key
而後,將上面生成的公鑰內容粘貼到 Key
編輯框並保存。至此大功告成。
後面,你在克隆你的 Github 項目時使用 SSH 方式便可。
若是以爲個人講解還不夠細緻,能夠參考:https://help.github.com/articles/adding-a-new-ssh-key-to-your-github-account/
詳細內容,能夠參考這篇文章:Git 在團隊中的最佳實踐--如何正確使用 Git Flow
Git 在實際開發中的最佳實踐策略 Git Flow 能夠概括爲如下:
master
分支 - 也就是咱們常用的主線分支,這個分支是最近發佈到生產環境的代碼,這個分支只能從其餘分支合併,不能在這個分支直接修改。develop
分支 - 這個分支是咱們的主開發分支,包含全部要發佈到下一個 release 的代碼,這個分支主要是從其餘分支合併代碼過來,好比 feature 分支。feature
分支 - 這個分支主要是用來開發一個新的功能,一旦開發完成,咱們合併回 develop 分支進入下一個 release。release
分支 - 當你須要一個發佈一個新 release 的時候,咱們基於 Develop 分支建立一個 release 分支,完成 release 後,咱們合併到 master 和 develop 分支。hotfix
分支 - 當咱們在 master 發現新的 Bug 時候,咱們須要建立一個 hotfix, 完成 hotfix 後,咱們合併回 master 和 develop 分支,因此 hotfix 的改動會進入下一個 release。若是你用 git commit -a
提交了一次變化(changes),而你又不肯定到底此次提交了哪些內容。 你就能夠用下面的命令顯示當前HEAD
上的最近一次的提交(commit):
(master)$ git show
或者
$ git log -n1 -p
若是你的提交信息(commit message)寫錯了且此次提交(commit)尚未推(push), 你能夠經過下面的方法來修改提交信息(commit message):
$ git commit --amend
這會打開你的默認編輯器, 在這裏你能夠編輯信息. 另外一方面, 你也能夠用一條命令一次完成:
$ git commit --amend -m 'xxxxxxx'
若是你已經推(push)了此次提交(commit), 你能夠修改此次提交(commit)而後強推(force push), 可是不推薦這麼作。
若是這只是單個提交(commit),修改它:
$ git commit --amend --author "New Authorname <authoremail@mydomain.com>"
若是你須要修改全部歷史, 參考 'git filter-branch'的指南頁.
經過下面的方法,從一個提交(commit)裏移除一個文件:
$ git checkout HEAD^ myfile $ git add -A $ git commit --amend
這將很是有用,當你有一個開放的補丁(open patch),你往上面提交了一個沒必要要的文件,你須要強推(force push)去更新這個遠程補丁。
若是你須要刪除推了的提交(pushed commits),你可使用下面的方法。但是,這會不可逆的改變你的歷史,也會搞亂那些已經從該倉庫拉取(pulled)了的人的歷史。簡而言之,若是你不是很肯定,千萬不要這麼作。
$ git reset HEAD^ --hard $ git push -f [remote] [branch]
若是你尚未推到遠程, 把 Git 重置(reset)到你最後一次提交前的狀態就能夠了(同時保存暫存的變化):
(my-branch*)$ git reset --soft HEAD@{1}
這隻能在沒有推送以前有用. 若是你已經推了, 惟一安全能作的是 git revert SHAofBadCommit
, 那會建立一個新的提交(commit)用於撤消前一個提交的全部變化(changes); 或者, 若是你推的這個分支是 rebase-safe 的 (例如: 其它開發者不會從這個分支拉), 只須要使用 git push -f
; 更多, 請參考 the above section。
一樣的警告:不到萬不得已的時候不要這麼作.
$ git rebase --onto SHA1_OF_BAD_COMMIT^ SHA1_OF_BAD_COMMIT $ git push -f [remote] [branch]
或者作一個 交互式 rebase 刪除那些你想要刪除的提交(commit)裏所對應的行。
To https://github.com/yourusername/repo.git ! [rejected] mybranch -> mybranch (non-fast-forward) error: failed to push some refs to 'https://github.com/tanay1337/webmaker.org.git' hint: Updates were rejected because the tip of your current branch is behind hint: its remote counterpart. Integrate the remote changes (e.g. hint: 'git pull ...') before pushing again. hint: See the 'Note about fast-forwards' in 'git push --help' for details.
注意, rebasing(見下面)和修正(amending)會用一個新的提交(commit)代替舊的, 因此若是以前你已經往遠程倉庫上推過一次修正前的提交(commit),那你如今就必須強推(force push) (-f
)。 注意 – 老是 確保你指明一個分支!
(my-branch)$ git push origin mybranch -f
通常來講, 要避免強推. 最好是建立和推(push)一個新的提交(commit),而不是強推一個修正後的提交。後者會使那些與該分支或該分支的子分支工做的開發者,在源歷史中產生衝突。
若是你意外的作了 git reset --hard
, 你一般能找回你的提交(commit), 由於 Git 對每件事都會有日誌,且都會保存幾天。
(master)$ git reflog
你將會看到一個你過去提交(commit)的列表, 和一個重置的提交。 選擇你想要回到的提交(commit)的 SHA,再重置一次:
(master)$ git reset --hard SHA1234
這樣就完成了。
(my-branch*)$ git commit --amend
通常來講, 若是你想暫存一個文件的一部分, 你可這樣作:
$ git add --patch filename.x
-p
簡寫。這會打開交互模式, 你將可以用 s
選項來分隔提交(commit); 然而, 若是這個文件是新的, 會沒有這個選擇, 添加一個新文件時, 這樣作:
$ git add -N filename.x
而後, 你須要用 e
選項來手動選擇須要添加的行,執行 git diff --cached
將會顯示哪些行暫存了哪些行只是保存在本地了。
git add
會把整個文件加入到一個提交. git add -p
容許交互式的選擇你想要提交的部分.
這個有點困難, 我能想到的最好的方法是先 stash 未暫存的內容, 而後重置(reset),再 pop 第一步 stashed 的內容, 最後再 add 它們。
$ git stash -k $ git reset --hard $ git stash pop $ git add -A
$ git checkout -b my-branch
$ git stash $ git checkout my-branch $ git stash pop
若是你只是想重置源(origin)和你本地(local)之間的一些提交(commit),你能夠:
## one commit (my-branch)$ git reset --hard HEAD^ ## two commits (my-branch)$ git reset --hard HEAD^^ ## four commits (my-branch)$ git reset --hard HEAD~4 ## or (master)$ git checkout -f
重置某個特殊的文件, 你能夠用文件名作爲參數:
$ git reset filename
若是你想丟棄工做拷貝中的一部份內容,而不是所有。
簽出(checkout)不須要的內容,保留須要的。
$ git checkout -p ## Answer y to all of the snippets you want to drop
另一個方法是使用 stash
, Stash 全部要保留下的內容, 重置工做拷貝, 從新應用保留的部分。
$ git stash -p ## Select all of the snippets you want to save $ git reset --hard $ git stash pop
或者, stash 你不須要的部分, 而後 stash drop。
$ git stash -p ## Select all of the snippets you don't want to save $ git stash drop
這是另一種使用 git reflog
狀況,找到在此次錯誤拉(pull) 以前 HEAD 的指向。
(master)$ git reflog ab7555f HEAD@{0}: pull origin wrong-branch: Fast-forward c5bc55a HEAD@{1}: checkout: checkout message goes here
重置分支到你所需的提交(desired commit):
$ git reset --hard c5bc55a
完成。
先確認你沒有推(push)你的內容到遠程。
git status
會顯示你領先(ahead)源(origin)多少個提交:
(my-branch)$ git status ## On branch my-branch ## Your branch is ahead of 'origin/my-branch' by 2 commits. ## (use "git push" to publish your local commits) #
一種方法是:
(master)$ git reset --hard origin/my-branch
在 master 下建立一個新分支,不切換到新分支,仍在 master 下:
(master)$ git branch my-branch
把 master 分支重置到前一個提交:
(master)$ git reset --hard HEAD^
HEAD^
是 HEAD^1
的簡寫,你能夠經過指定要設置的HEAD
來進一步重置。
或者, 若是你不想使用 HEAD^
, 找到你想重置到的提交(commit)的 hash(git log
可以完成), 而後重置到這個 hash。 使用git push
同步內容到遠程。
例如, master 分支想重置到的提交的 hash 爲a13b85e
:
(master)$ git reset --hard a13b85e HEAD is now at a13b85e
簽出(checkout)剛纔新建的分支繼續工做:
(master)$ git checkout my-branch
假設你正在作一個原型方案(原文爲 working spike (see note)), 有成百的內容,每一個都工做得很好。如今, 你提交到了一個分支,保存工做內容:
(solution)$ git add -A && git commit -m "Adding all changes from this spike into one big commit."
當你想要把它放到一個分支裏 (多是feature
, 或者 develop
), 你關心是保持整個文件的完整,你想要一個大的提交分隔成比較小。
假設你有:
solution
, 擁有原型方案, 領先 develop
分支。develop
, 在這裏你應用原型方案的一些內容。我去能夠經過把內容拿到你的分支裏,來解決這個問題:
(develop)$ git checkout solution -- file1.txt
這會把這個文件內容從分支 solution
拿到分支 develop
裏來:
## On branch develop ## Your branch is up-to-date with 'origin/develop'. ## Changes to be committed: ## (use "git reset HEAD <file>..." to unstage) # ## modified: file1.txt
而後, 正常提交。
Note: Spike solutions are made to analyze or solve the problem. These solutions are used for estimation and discarded once everyone gets clear visualization of the problem. ~ Wikipedia.
假設你有一個master
分支, 執行git log
, 你看到你作過兩次提交:
(master)$ git log commit e3851e817c451cc36f2e6f3049db528415e3c114 Author: Alex Lee <alexlee@example.com> Date: Tue Jul 22 15:39:27 2014 -0400 Bug #21 - Added CSRF protection commit 5ea51731d150f7ddc4a365437931cd8be3bf3131 Author: Alex Lee <alexlee@example.com> Date: Tue Jul 22 15:39:12 2014 -0400 Bug #14 - Fixed spacing on title commit a13b85e984171c6e2a1729bb061994525f626d14 Author: Aki Rose <akirose@example.com> Date: Tue Jul 21 01:12:48 2014 -0400 First commit
讓咱們用提交 hash(commit hash)標記 bug (e3851e8
for #21, 5ea5173
for #14).
首先, 咱們把master
分支重置到正確的提交(a13b85e
):
(master)$ git reset --hard a13b85e HEAD is now at a13b85e
如今, 咱們對 bug #21 建立一個新的分支:
(master)$ git checkout -b 21 (21)$
接着, 咱們用 cherry-pick 把對 bug #21 的提交放入當前分支。 這意味着咱們將應用(apply)這個提交(commit),僅僅這一個提交(commit),直接在 HEAD 上面。
(21)$ git cherry-pick e3851e8
這時候, 這裏可能會產生衝突, 參見交互式 rebasing 章 衝突節 解決衝突.
再者, 咱們爲 bug #14 建立一個新的分支, 也基於master
分支
(21)$ git checkout master (master)$ git checkout -b 14 (14)$
最後, 爲 bug #14 執行 cherry-pick
:
(14)$ git cherry-pick 5ea5173
一旦你在 github 上面合併(merge)了一個 pull request, 你就能夠刪除你 fork 裏被合併的分支。 若是你不許備繼續在這個分支裏工做, 刪除這個分支的本地拷貝會更乾淨,使你不會陷入工做分支和一堆陳舊分支的混亂之中。
$ git fetch -p
若是你按期推送到遠程, 多數狀況下應該是安全的,但有些時候仍是可能刪除了尚未推到遠程的分支。 讓咱們先建立一個分支和一個新的文件:
(master)$ git checkout -b my-branch (my-branch)$ git branch (my-branch)$ touch foo.txt (my-branch)$ ls README.md foo.txt
添加文件並作一次提交
(my-branch)$ git add . (my-branch)$ git commit -m 'foo.txt added' (my-branch)$ foo.txt added 1 files changed, 1 insertions(+) create mode 100644 foo.txt (my-branch)$ git log commit 4e3cd85a670ced7cc17a2b5d8d3d809ac88d5012 Author: siemiatj <siemiatj@example.com> Date: Wed Jul 30 00:34:10 2014 +0200 foo.txt added commit 69204cdf0acbab201619d95ad8295928e7f411d5 Author: Kate Hudson <katehudson@example.com> Date: Tue Jul 29 13:14:46 2014 -0400 Fixes #6: Force pushing after amending commits
如今咱們切回到主(master)分支,‘不當心的’刪除my-branch
分支
(my-branch)$ git checkout master Switched to branch 'master' Your branch is up-to-date with 'origin/master'. (master)$ git branch -D my-branch Deleted branch my-branch (was 4e3cd85). (master)$ echo oh noes, deleted my branch! oh noes, deleted my branch!
在這時候你應該想起了reflog
, 一個升級版的日誌,它存儲了倉庫(repo)裏面全部動做的歷史。
(master)$ git reflog 69204cd HEAD@{0}: checkout: moving from my-branch to master 4e3cd85 HEAD@{1}: commit: foo.txt added 69204cd HEAD@{2}: checkout: moving from master to my-branch
正如你所見,咱們有一個來自刪除分支的提交 hash(commit hash),接下來看看是否能恢復刪除了的分支。
(master)$ git checkout -b my-branch-help Switched to a new branch 'my-branch-help' (my-branch-help)$ git reset --hard 4e3cd85 HEAD is now at 4e3cd85 foo.txt added (my-branch-help)$ ls README.md foo.txt
看! 咱們把刪除的文件找回來了。 Git 的 reflog
在 rebasing 出錯的時候也是一樣有用的。
刪除一個遠程分支:
(master)$ git push origin --delete my-branch
你也能夠:
(master)$ git push origin :my-branch
刪除一個本地分支:
(master)$ git branch -D my-branch
首先, 從遠程拉取(fetch) 全部分支:
(master)$ git fetch --all
假設你想要從遠程的daves
分支簽出到本地的daves
(master)$ git checkout --track origin/daves Branch daves set up to track remote branch daves from origin. Switched to a new branch 'daves'
(--track
是 git checkout -b [branch] [remotename]/[branch]
的簡寫)
這樣就獲得了一個daves
分支的本地拷貝, 任何推過(pushed)的更新,遠程都能看到.
你能夠合併(merge)或 rebase 了一個錯誤的分支, 或者完成不了一個進行中的 rebase/merge。 Git 在進行危險操做的時候會把原始的 HEAD 保存在一個叫 ORIG_HEAD 的變量裏, 因此要把分支恢復到 rebase/merge 前的狀態是很容易的。
(my-branch)$ git reset --hard ORIG_HEAD
不幸的是,若是你想把這些變化(changes)反應到遠程分支上,你就必須得強推(force push)。 是因你快進(Fast forward)了提交,改變了 Git 歷史, 遠程分支不會接受變化(changes),除非強推(force push)。這就是許多人使用 merge 工做流, 而不是 rebasing 工做流的主要緣由之一, 開發者的強推(force push)會使大的團隊陷入麻煩。使用時須要注意,一種安全使用 rebase 的方法是,不要把你的變化(changes)反映到遠程分支上, 而是按下面的作:
(master)$ git checkout my-branch (my-branch)$ git rebase -i master (my-branch)$ git checkout master (master)$ git merge --ff-only my-branch
更多, 參見 this SO thread.
假設你的工做分支將會作對於 master
的 pull-request。 通常狀況下你不關心提交(commit)的時間戳,只想組合 全部 提交(commit) 到一個單獨的裏面, 而後重置(reset)重提交(recommit)。 確保主(master)分支是最新的和你的變化都已經提交了, 而後:
(my-branch)$ git reset --soft master (my-branch)$ git commit -am "New awesome feature"
若是你想要更多的控制, 想要保留時間戳, 你須要作交互式 rebase (interactive rebase):
(my-branch)$ git rebase -i master
若是沒有相對的其它分支, 你將不得不相對本身的HEAD
進行 rebase。 例如:你想組合最近的兩次提交(commit), 你將相對於HEAD\~2
進行 rebase, 組合最近 3 次提交(commit), 相對於HEAD\~3
, 等等。
(master)$ git rebase -i HEAD~2
在你執行了交互式 rebase 的命令(interactive rebase command)後, 你將在你的編輯器裏看到相似下面的內容:
pick a9c8a1d Some refactoring pick 01b2fd8 New awesome feature pick b729ad5 fixup pick e3851e8 another fix ## Rebase 8074d12..b729ad5 onto 8074d12 # ## Commands: ## p, pick = use commit ## r, reword = use commit, but edit the commit message ## e, edit = use commit, but stop for amending ## s, squash = use commit, but meld into previous commit ## f, fixup = like "squash", but discard this commit's log message ## x, exec = run command (the rest of the line) using shell # ## These lines can be re-ordered; they are executed from top to bottom. # ## If you remove a line here THAT COMMIT WILL BE LOST. # ## However, if you remove everything, the rebase will be aborted. # ## Note that empty commits are commented out
全部以 #
開頭的行都是註釋, 不會影響 rebase.
而後,你能夠用任何上面命令列表的命令替換 pick
, 你也能夠經過刪除對應的行來刪除一個提交(commit)。
例如, 若是你想 單獨保留最舊(first)的提交(commit),組合全部剩下的到第二個裏面, 你就應該編輯第二個提交(commit)後面的每一個提交(commit) 前的單詞爲 f
:
pick a9c8a1d Some refactoring pick 01b2fd8 New awesome feature f b729ad5 fixup f e3851e8 another fix
若是你想組合這些提交(commit) 並重命名這個提交(commit), 你應該在第二個提交(commit)旁邊添加一個r
,或者更簡單的用s
替代 f
:
pick a9c8a1d Some refactoring pick 01b2fd8 New awesome feature s b729ad5 fixup s e3851e8 another fix
你能夠在接下來彈出的文本提示框裏重命名提交(commit)。
Newer, awesomer features ## Please enter the commit message for your changes. Lines starting ## with '#' will be ignored, and an empty message aborts the commit. ## rebase in progress; onto 8074d12 ## You are currently editing a commit while rebasing branch 'master' on '8074d12'. # ## Changes to be committed: # modified: README.md #
若是成功了, 你應該看到相似下面的內容:
(master)$ Successfully rebased and updated refs/heads/master.
安全合併(merging)策略
--no-commit
執行合併(merge)但不自動提交, 給用戶在作提交前檢查和修改的機會。 no-ff
會爲特性分支(feature branch)的存在過留下證據, 保持項目歷史一致。
(master)$ git merge --no-ff --no-commit my-branch
我須要將一個分支合併成一個提交(commit)
(master)$ git merge --squash my-branch
我只想組合(combine)未推的提交(unpushed commit)
有時候,在將數據推向上游以前,你有幾個正在進行的工做提交(commit)。這時候不但願把已經推(push)過的組合進來,由於其餘人可能已經有提交(commit)引用它們了。
(master)$ git rebase -i @{u}
這會產生一次交互式的 rebase(interactive rebase), 只會列出沒有推(push)的提交(commit), 在這個列表時進行 reorder/fix/squash 都是安全的。
檢查一個分支上的全部提交(commit)是否都已經合併(merge)到了其它分支, 你應該在這些分支的 head(或任何 commits)之間作一次 diff:
(master)$ git log --graph --left-right --cherry-pick --oneline HEAD...feature/120-on-scroll
這會告訴你在一個分支裏有而另外一個分支沒有的全部提交(commit), 和分支之間不共享的提交(commit)的列表。 另外一個作法能夠是:
(master)$ git log master ^feature/120-on-scroll --no-merges
這個 rebase 編輯屏幕出現'noop'
若是你看到的是這樣:
noop
這意味着你 rebase 的分支和當前分支在同一個提交(commit)上, 或者 領先(ahead) 當前分支。 你能夠嘗試:
HEAD\~2
或者更早有衝突的狀況
若是你不能成功的完成 rebase, 你可能必需要解決衝突。
首先執行 git status
找出哪些文件有衝突:
(my-branch)$ git status On branch my-branch Changes not staged for commit: (use "git add <file>..." to update what will be committed) (use "git checkout -- <file>..." to discard changes in working directory) modified: README.md
在這個例子裏面, README.md
有衝突。 打開這個文件找到相似下面的內容:
<<<<<<< HEAD some code ========= some code >>>>>>> new-commit
你須要解決新提交的代碼(示例裏, 從中間==
線到new-commit
的地方)與HEAD
之間不同的地方.
有時候這些合併不是常複雜,你應該使用可視化的差別編輯器(visual diff editor):
(master*)$ git mergetool -t opendiff
在你解決完全部衝突和測試事後, git add
變化了的(changed)文件, 而後用git rebase --continue
繼續 rebase。
(my-branch)$ git add README.md (my-branch)$ git rebase --continue
若是在解決完全部的衝突事後,獲得了與提交前同樣的結果, 能夠執行git rebase --skip
。
任什麼時候候你想結束整個 rebase 過程,回來 rebase 前的分支狀態, 你能夠作:
(my-branch)$ git rebase --abort
$ git clone --recursive git://github.com/foo/bar.git
若是已經克隆了:
$ git submodule update --init --recursive
$ git tag -d <tag_name> $ git push <remote> :refs/tags/<tag_name>
若是你想恢復一個已刪除標籤(tag), 能夠按照下面的步驟: 首先, 須要找到沒法訪問的標籤(unreachable tag):
$ git fsck --unreachable | grep tag
記下這個標籤(tag)的 hash,而後用 Git 的 update-ref:
$ git update-ref refs/tags/<tag_name> <hash>
這時你的標籤(tag)應該已經恢復了。
若是某人在 GitHub 上給你發了一個 pull request, 可是而後他刪除了他本身的原始 fork, 你將無法克隆他們的提交(commit)或使用 git am
。在這種狀況下, 最好手動的查看他們的提交(commit),並把它們拷貝到一個本地新分支,而後作提交。
作完提交後, 再修改做者,參見變動做者。 而後, 應用變化, 再發起一個新的 pull request。
(master)$ git mv --force myfile MyFile
(master)$ git rm --cached log.txt
在 OS X 和 Linux 下, 你的 Git 的配置文件儲存在 \~/.gitconfig
。我在[alias]
部分添加了一些快捷別名(和一些我容易拼寫錯誤的),以下:
[alias] a = add amend = commit --amend c = commit ca = commit --amend ci = commit -a co = checkout d = diff dc = diff --changed ds = diff --staged f = fetch loll = log --graph --decorate --pretty=oneline --abbrev-commit m = merge one = log --pretty=oneline outstanding = rebase -i @{u} s = status unpushed = log @{u} wc = whatchanged wip = rebase -i @{u} zap = fetch -p
你可能有一個倉庫須要受權,這時你能夠緩存用戶名和密碼,而不用每次推/拉(push/pull)的時候都輸入,Credential helper 能幫你。
$ 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)
你把事情搞砸了:你 重置(reset)
了一些東西, 或者你合併了錯誤的分支, 亦或你強推了後找不到你本身的提交(commit)了。有些時候, 你一直都作得很好, 但你想回到之前的某個狀態。
這就是 git reflog
的目的, reflog
記錄對分支頂端(the tip of a branch)的任何改變, 即便那個頂端沒有被任何分支或標籤引用。基本上, 每次 HEAD 的改變, 一條新的記錄就會增長到reflog
。遺憾的是,這隻對本地分支起做用,且它只跟蹤動做 (例如,不會跟蹤一個沒有被記錄的文件的任何改變)。
(master)$ git reflog 0a2e358 HEAD@{0}: reset: moving to HEAD\~2 0254ea7 HEAD@{1}: checkout: moving from 2.2 to master c10f740 HEAD@{2}: checkout: moving from master to 2.2
上面的 reflog 展現了從 master 分支簽出(checkout)到 2.2 分支,而後再籤回。 那裏,還有一個硬重置(hard reset)到一個較舊的提交。最新的動做出如今最上面以 HEAD@{0}
標識.
若是事實證實你不當心回移(move back)了提交(commit), reflog 會包含你不當心回移前 master 上指向的提交(0254ea7)。
$ git reset --hard 0254ea7
而後使用 git reset 就能夠把 master 改回到以前的 commit,這提供了一個在歷史被意外更改狀況下的安全網。
最後,放一張我總結的腦圖總結一下以上的知識點。
(若是圖片不清晰,能夠在公衆號回覆【Git圖片】下載高清圖)
免費Java資料須要本身領取,涵蓋了Java、Redis、MongoDB、MySQL、Zookeeper、Spring Cloud、Dubbo高併發分佈式等教程,一共30G。
傳送門: https://mp.weixin.qq.com/s/JzddfH-7yNudmkjT0IRL8Q