Git 從入門到精通,這篇包教包會!

簡介
git

Git 是什麼?

Git 是一個開源的分佈式版本控制系統。程序員

什麼是版本控制?

版本控制是一種記錄一個或若干文件內容變化,以便未來查閱特定版本修訂狀況的系統。github

什麼是分佈式版本控制系統?

介紹分佈式版本控制系統前,有必要先了解一下傳統的集中式版本控制系統。web

集中化的版本控制系統,諸如 CVS,Subversion 等,都有一個單一的集中管理的服務器,保存全部文件的修訂版本,而協同工做的人們都經過客戶端連到這臺服務器,取出最新的文件或者提交更新。shell

這麼作最顯而易見的缺點是中央服務器的單點故障。若是宕機一小時,那麼在這一小時內,誰都沒法提交更新,也就沒法協同工做。要是中央服務器的磁盤發生故障,碰巧沒作備份,或者備份不夠及時,就會有丟失數據的風險。最壞的狀況是完全丟失整個項目的全部歷史更改記錄。數據庫

分佈式版本控制系統的客戶端並不僅提取最新版本的文件快照,而是把代碼倉庫完整地鏡像下來。這麼一來,任何一處協同工做用的服務器發生故障,過後均可以用任何一個鏡像出來的本地倉庫恢復。由於每一次的提取操做,實際上都是一次對代碼倉庫的完整備份。後端

爲何使用 Git?

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 外觀和行爲的配置變量。這些變量存儲在三個不一樣的位置:

  1. /etc/gitconfig 文件: 包含系統上每個用戶及他們倉庫的通用配置。若是使用帶有 --system 選項的 git config 時,它會今後文件讀寫配置變量。

  2. \~/.gitconfig 或 \~/.config/git/config 文件:只針對當前用戶。能夠傳遞 --global 選項讓 Git 讀寫此文件。

  3. 當前使用倉庫的 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 工具都會在第一次運行時幫助你配置這些信息。

推薦:太讚了,SpringBoot+Vue先後端分離完整入門教程!

.gitignore

.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 中,你的文件可能會處於三種狀態之一:

  • 已修改(modified) - 已修改表示修改了文件,但還沒保存到數據庫中。

  • 已暫存(staged) - 已暫存表示對一個已修改文件的當前版本作了標記,使之包含在下次提交的快照中。

  • 已提交(committed) - 已提交表示數據已經安全的保存在本地數據庫中。

工做區域

與文件狀態對應的,不一樣狀態的文件在 Git 中處於不一樣的工做區域。

  • 工做區(working) - 當你 git clone 一個項目到本地,至關於在本地克隆了項目的一個副本。工做區是對項目的某個版本獨立提取出來的內容。這些從 Git 倉庫的壓縮數據庫中提取出來的文件,放在磁盤上供你使用或修改。

  • 暫存區(staging) - 暫存區是一個文件,保存了下次將提交的文件列表信息,通常在 Git 倉庫目錄中。有時候也被稱做 `‘索引’',不過通常說法仍是叫暫存區。

  • 本地倉庫(local) - 提交更新,找到暫存區域的文件,將快照永久性存儲到 Git 本地倉庫。

  • 遠程倉庫(remote) - 以上幾個工做區都是在本地。爲了讓別人能夠看到你的修改,你須要將你的更新推送到遠程倉庫。同理,若是你想同步別人的修改,你須要從遠程倉庫拉取更新。

命令

國外網友製做了一張 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 將本地的修改內容做爲草稿儲藏起來。

推薦:太讚了,SpringBoot+Vue先後端分離完整入門教程!

官方稱之爲儲藏,但我我的更喜歡稱之爲存草稿。

# 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 做爲最著名的代碼開源協做社區,在程序員圈想必無人不知,無人不曉。

這裏不贅述 Github 的用法,確實有不會用的新手同窗,能夠參考官方教程:https://guides.github.com/

clone 方式

Git 支持三種協議:HTTPS / SSH / GIT

而 Github 上支持 HTTPS 和 SSH。

HTTPS 這種方式要求你每次 push 時都要輸入用戶名、密碼,有些繁瑣。

而 SSH 要求你本地生成證書,而後在你的 Github 帳戶中註冊。第一次配置麻煩是麻煩了點,可是之後就免去了每次 push 須要輸入用戶名、密碼的繁瑣。

如下介紹如下,如何生成證書,以及在 Github 中註冊。

生成 SSH 公鑰

如前所述,許多 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 Flow

詳細內容,能夠參考這篇文章: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。

常見問題

編輯提交 (editting commits)

我剛纔提交了什麼

若是你用 git commit -a 提交了一次變化 (changes),而你又不肯定到底此次提交了哪些內容。你就能夠用下面的命令顯示當前HEAD上的最近一次的提交 (commit):

(master)$ git show

或者

$ git log -n1 -p

個人提交信息 (commit message) 寫錯了

若是你的提交信息 (commit message) 寫錯了且此次提交 (commit) 尚未推(push), 你能夠經過下面的方法來修改提交信息(commit message):

$ git commit --amend

這會打開你的默認編輯器, 在這裏你能夠編輯信息. 另外一方面, 你也能夠用一條命令一次完成:

$ git commit --amend -m 'xxxxxxx'

若是你已經推 (push) 了此次提交 (commit), 你能夠修改此次提交(commit) 而後強推(force push), 可是不推薦這麼作。

我提交 (commit) 裏的用戶名和郵箱不對

若是這只是單個提交 (commit),修改它:

$ git commit --amend --author "New Authorname <authoremail@mydomain.com>"

若是你須要修改全部歷史, 參考'git filter-branch'的指南頁.

我想從一個提交 (commit) 裏移除一個文件

經過下面的方法,從一個提交 (commit) 裏移除一個文件:

$ git checkout HEAD^ myfile
$ git add -A
$ git commit --amend

這將很是有用,當你有一個開放的補丁 (open patch),你往上面提交了一個沒必要要的文件,你須要強推(force push) 去更新這個遠程補丁。

我想刪除個人的最後一次提交 (commit)

若是你須要刪除推了的提交 (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。

刪除任意提交 (commit)

一樣的警告:不到萬不得已的時候不要這麼作.

$ git rebase --onto SHA1_OF_BAD_COMMIT^ SHA1_OF_BAD_COMMIT
$ git push -f [remote] [branch]

或者作一個 交互式 rebase 刪除那些你想要刪除的提交 (commit) 裏所對應的行。

我嘗試推一個修正後的提交 (amended 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),而不是強推一個修正後的提交。後者會使那些與該分支或該分支的子分支工做的開發者,在源歷史中產生衝突。

我意外的作了一次硬重置 (hard reset),我想找回個人內容

若是你意外的作了 git reset --hard, 你一般能找回你的提交 (commit), 由於 Git 對每件事都會有日誌,且都會保存幾天。

(master)$ git reflog

你將會看到一個你過去提交 (commit) 的列表, 和一個重置的提交。選擇你想要回到的提交 (commit) 的 SHA,再重置一次:

(master)$ git reset --hard SHA1234

這樣就完成了。

暫存 (Staging)

我須要把暫存的內容添加到上一次的提交 (commit)

(my-branch*)$ git commit --amend

我想要暫存一個新文件的一部分,而不是這個文件的所有

通常來講, 若是你想暫存一個文件的一部分, 你可這樣作:

$ git add --patch filename.x

-p 簡寫。這會打開交互模式, 你將可以用 s 選項來分隔提交 (commit);然而, 若是這個文件是新的, 會沒有這個選擇, 添加一個新文件時, 這樣作:

$ git add -N filename.x

而後, 你須要用 e 選項來手動選擇須要添加的行,執行 git diff --cached 將會顯示哪些行暫存了哪些行只是保存在本地了。

我想把在一個文件裏的變化 (changes) 加到兩個提交 (commit) 裏

git add 會把整個文件加入到一個提交. git add -p 容許交互式的選擇你想要提交的部分.

我想把暫存的內容變成未暫存,把未暫存的內容暫存起來

這個有點困難, 我能想到的最好的方法是先 stash 未暫存的內容, 而後重置 (reset),再 pop 第一步 stashed 的內容, 最後再 add 它們。

$ git stash -k
$ git reset --hard
$ git stash pop
$ git add -A

未暫存 (Unstaged) 的內容

我想把未暫存的內容移動到一個新分支

$ git checkout -b my-branch

我想把未暫存的內容移動到另外一個已存在的分支

$ git stash
$ git checkout my-branch
$ git stash pop

我想丟棄本地未提交的變化 (uncommitted changes)

若是你只是想重置源 (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

分支 (Branches)

我從錯誤的分支拉取了內容,或把內容拉取到了錯誤的分支

這是另一種使用 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

完成。

我想扔掉本地的提交 (commit),以便個人分支與遠程的保持一致

先確認你沒有推 (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 下:

(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

我想保留來自另一個 ref-ish 的整個文件

假設你正在作一個原型方案 (原文爲 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.

我把幾個提交 (commit) 提交到了同一個分支,而這些提交應該分佈在不一樣的分支裏

假設你有一個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

我想刪除上游 (upstream) 分支被刪除了的本地分支

一旦你在 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

我想從別人正在工做的遠程分支簽出 (checkout) 一個分支

首先, 從遠程拉取 (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) 的更新,遠程都能看到.

Rebasing 和合並 (Merging)

我想撤銷 rebase/merge

你能夠合併 (merge) 或 rebase 了一個錯誤的分支, 或者完成不了一個進行中的 rebase/merge。Git 在進行危險操做的時候會把原始的 HEAD 保存在一個叫 ORIG_HEAD 的變量裏, 因此要把分支恢復到 rebase/merge 前的狀態是很容易的。

(my-branch)$ git reset --hard ORIG_HEAD

我已經 rebase 過, 可是我不想強推 (force push)

不幸的是,若是你想把這些變化 (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.

我須要組合 (combine) 幾個提交(commit)

假設你的工做分支將會作對於 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)。

推薦:太讚了,SpringBoot+Vue先後端分離完整入門教程!

例如, 若是你想 單獨保留最舊 (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) 過了

檢查一個分支上的全部提交 (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(interactive rebase) 可能出現的問題

這個 rebase 編輯屏幕出現'noop'

若是你看到的是這樣:

noop

這意味着你 rebase 的分支和當前分支在同一個提交 (commit) 上, 或者 領先 (ahead) 當前分支。你能夠嘗試:

  • 檢查確保主 (master) 分支沒有問題

  • rebase 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

雜項 (Miscellaneous Objects)

克隆全部子模塊

$ git clone --recursive git://github.com/foo/bar.git

若是已經克隆了:

$ git submodule update --init --recursive

刪除標籤 (tag)

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

恢復已刪除標籤 (tag)

若是你想恢復一個已刪除標籤 (tag), 能夠按照下面的步驟: 首先, 須要找到沒法訪問的標籤 (unreachable tag):

$ git fsck --unreachable | grep tag

記下這個標籤 (tag) 的 hash,而後用 Git 的 update-ref:

$ git update-ref refs/tags/<tag_name> <hash>

這時你的標籤 (tag) 應該已經恢復了。

已刪除補丁 (patch)

若是某人在 GitHub 上給你發了一個 pull request, 可是而後他刪除了他本身的原始 fork, 你將無法克隆他們的提交 (commit) 或使用 git am。在這種狀況下, 最好手動的查看他們的提交 (commit),並把它們拷貝到一個本地新分支,而後作提交。

作完提交後, 再修改做者,參見變動做者。而後, 應用變化, 再發起一個新的 pull request。

跟蹤文件 (Tracking Files)

我只想改變一個文件名字的大小寫,而不修改內容

(master)$ git mv --force myfile MyFile

我想從 Git 刪除一個文件,但保留該文件

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

配置 (Configuration)

我想給一些 Git 命令添加別名 (alias)

在 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

我想緩存一個倉庫 (repository) 的用戶名和密碼

你可能有一個倉庫須要受權,這時你能夠緩存用戶名和密碼,而不用每次推 / 拉 (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 官網

    • Git Github

  • 模板

    • gitignore 模板 - .gitignore 文件模板

    • gitattributes 模板 - .gitattributes 文件模板

    • github-cheat-sheet - git 命令簡略圖表

  • Git 書

    • Git 官方推薦教程 - Scott Chacon 的 Git 書。

  • Git 教程

    • Git 中文教程

    • 廖雪峯的 Git 教程

    • 有關 git 的學習資源

  • 文章

    • Git Cookbook

    • Git 奇技淫巧

    • Git 風格指南

    • Git 在團隊中的最佳實踐 -- 如何正確使用 Git Flow

  • Git 工具

    • guis - Git 官網展現的客戶端工具列表。

    • gogs - 極易搭建的自助 Git 服務。

    • gitflow - 應用 fit-flow 模型的工具。

    • firstaidgit.io 一個可搜索的最常被問到的 Git 的問題

    • git-extra-commands - 一堆有用的額外的 Git 腳本

    • git-extras - GIT 工具集 -- repo summary, repl, changelog population, author commit percentages and more

    • git-fire - git-fire 是一個 Git 插件,用於幫助在緊急狀況下添加全部當前文件, 作提交 (committing), 和推(push) 到一個新分支(阻止合併衝突)。

    • git-tips - Git 小提示

    • git-town - 通用,高級 Git 工做流支持!http://www.git-town.com

  • GUI 客戶端 (GUI Clients)

    • GitKraken - 豪華的 Git 客戶端 Windows, Mac & Linux

    • git-cola - 另一個 Git 客戶端 Windows & OS X

    • GitUp - 一個新的 Git 客戶端,在處理 Git 的複雜性上有本身的特色

    • gitx-dev - 圖形化的 Git 客戶端 OS X

    • Source Tree - 免費的圖形化 Git 客戶端 Windows & OS X

    • Tower - 圖形化 Git 客戶端 OS X(付費)

  • git cheat sheet

    • github-git-cheat-sheet




本文分享自微信公衆號 - 架構師智庫(beijing-tmt)。
若有侵權,請聯繫 support@oschina.cn 刪除。
本文參與「OSC源創計劃」,歡迎正在閱讀的你也加入,一塊兒分享。

相關文章
相關標籤/搜索