❝本文主要記錄學習 Git 的過程。由於是學習筆記,可能有描述不全面或是不許確的地方,歡迎在評論區指正。前端
❞
本文的思惟導圖以下:git
安裝應該不用多說了,主要講一下升級。Linux 和 Mac 都有包管理器,升級是很方便的,關鍵是 windows 並無這類東西,那麼怎麼升級呢?github
這裏首先要經過 git version
查看你當前的 Git 版本是多少,若是版本:web
<= 2.14.1
:不要多想,老老實實卸載舊版本,安裝新版本吧。。。
2.14.2 ~ 2.16.1
:直接
git update
升級
>= 2.16.1(2)
:直接
git update-git-for-windows
升級
經過命令升級的時候可能半天沒反應,最後提示你 using proxy as per lookup
,如圖所示:json
那麼不要猶豫了,你須要科學工具,不然速度絕對讓你崩潰。若是沒有的話,那也只能卸載再安裝了,不過官網下載的速度也是至關之慢......windows
如下操做基於 win10。bash
這個問題有點奇怪,直接使用 Git Bash
是不會出現亂碼的,可是使用 windows terminal
後,在 git log
的時候中文會顯示爲八進制。你的問題可能和我不同,也許是 git commit
或者 git status
的時候亂碼。總之,咱們能夠統一設置:app
git config --global gui.encoding utf-8
git config --global core.quotepath false git config --global i18n.commitencoding utf-8 git config --global i18n.logoutputencoding utf-8 複製代碼
最後執行:ssh
export LESSCHARSET=utf-8
複製代碼
而後新建一個環境變量,鍵名 LESSCHARSET
,鍵值 utf-8
。(這一步很關鍵,不然你新開窗口仍是會亂碼的)編輯器
注意:具體的設置可能由於系統(我是 win10)或默認編碼不一樣而不一樣。我在解決這個問題的時候查看了不少博客,有的地方會說 git config --global i18n.logoutputencoding utf-8
這一步應該是設置爲 gbk
,不過我這樣設置以後連 Git bash
也亂碼了......(儘管 win10 的默認編碼應該是 gbk
沒錯),因此在設置上仍是得看你本身系統的狀況,可能得多試幾回。
❝2020-05-27 更
❞
這個 windows terminal
果真不讓人省心,今天發現 ls
指令也會有亂碼的狀況,並且目錄不會高亮。最後在這個 issue 下找到了答案,兩種解決方法:
① 編輯 git/etc/bash/bashrc
文件,末尾添加:
export LANG="zh_CN.UTF-8"
export LC_ALL="zh_CN.UTF-8" 複製代碼
② 編輯 windows terminal
的 setting.json
文件:
"commandline": "D:\\Git\\bin\\bash.exe"
// 改成 "commandline": "D:\\Git\\bin\\bash.exe -li" 複製代碼
-li
參數可讓 git bash
以正確的配置啓動。估計前面全部問題都和這個有關,由於個人 git
沒有安裝在默認路徑,可能致使某些配置讀取不了,出現各類奇葩的問題。
顯示配置:
git config -l/--list git config --global -l/--list 複製代碼
進行配置:
git config --global username 'xxx'
git config --global useremail 'xxx' 複製代碼
這裏的參數能夠是 local
(對本倉庫生效),global
(對登陸用戶的全部倉庫生效) 或 system
(對全部用戶的全部倉庫生效),在優先級上依次遞減。
生成項目文件夾(本地倉庫),同時生成記錄重要信息的隱藏文件夾 .git
:
git init project
複製代碼
查看項目文件狀態,紅色表示文件還在工做區,綠色表示文件還在暫存區,不顯示錶示都到了歷史區:
git status
複製代碼
撤銷本地文件的修改,使之回退到最近一次 git add
的狀態:
git checkout -- <file>
複製代碼
❝上面這個命令和切換分支很是像(多了
❞--
),容易混淆。爲此,git 2.23
以後引入了專門的回退命令和切換分支命令,用來分離git checkout
的職責。
咱們能夠用下面命令進行工做區文件的回退:
git restore <file>
複製代碼
有時候咱們在工做區作一些修改的時候,可能臨時接手了其它更緊急的任務,那麼這時候就得把當前所作的更改作一個狀態保存:
git stash
複製代碼
在任務完成後,想要恢復以前保存的狀態,則能夠:
git stash apply // 或者 git stash pop 複製代碼
git add <file> git add . git add -A 複製代碼
git rm --cached <file> git rm --cached -r . git rm <file> // 刪除工做區和暫存區的文件 複製代碼
git mv <file1> <file2> git commit -m'Change the file name' 複製代碼
當咱們修改文件並進行了 git add
以後,就不能再經過 git restore <file>
直接撤銷本地文件的修改了。但能夠先經過 git reset HEAD <file>
撤銷對暫存區的文件提交(某個文件不提交了),再 git restore <file>
。
git reset HEAD <file>
複製代碼
一樣的,咱們能夠用新命令代替:
git restore --staged <file>
複製代碼
這兩個命令藉助於 HEAD
進行恢復,所以運行在「至少提交了一次」的前提下(若沒有提交則不存在 HEAD
,使用命令是會報錯的)。不過忘了也不要緊,Git 會根據操做給咱們相關提示的。
當咱們不加 <file>
參數的時候,會撤銷暫存區全部的文件提交(全部文件都不提交了),其實至關於恢復到了最近一次 commit
的狀態:
git diff HEAD
複製代碼
在急着將工做區的文件提交到暫存區以前,能夠先比對兩個區的差別:
git diff
複製代碼
若是目標是具體文件,也能夠加上參數:
git diff --<file>
複製代碼
一樣,在急着將暫存區的文件提交到歷史區以前,可能須要先將暫存區與最近一次 commit
進行比對,看看修改了什麼東西(--cached
表示暫存區):
git diff --cached
複製代碼
git commit -m'xxxxx' git commit 複製代碼
commit
信息git log git log --oneline // 查看簡略的提交信息 git log --graph // 查看圖示提交信息 git log --n4 // 查看最近四次的提交信息 git log --all // 查看全部分支上的提交信息 git reflog // 查看包含回退在內的提交信息 複製代碼
commit
信息git commit --amend
複製代碼
commit
信息若是要修改之前的 commit
信息,就須要變基(rebase
)了。找到要修改的 commit
的前一次 commit id
:
git rebase -i <commit id>
複製代碼
咱們要修改的是 3291b88
這個 commit
的信息,將前面的 pick
指令改成 reword
指令,並保存:
來到另外一個界面,在這裏進行修改,以後保存退出便可:
commit
:假設想要合併中間的多個連續 commit
(2.txt 到 6.txt):
基於這幾個 commit
的前一次 commit id
進行變基:
git rebase -i ec7eeb3
複製代碼
在這裏會列出第一個 commit
日後的全部 commit
,將想要合併的 commit
前面的指令改成 squash
指令,並保存:
編輯 commit
信息,這個信息會成爲合併後的總 commit
信息,保存退出:
能夠看到,commit
合併成功了:
commit
:針對上面合併以後的結果,若是咱們想要合併第一個和最後一個 commit
,那麼能夠基於第一個 commit
進行變基:
git rebase -i ec7eeb3
複製代碼
在 rebase
的交互式界面中,只會顯示第一個 commit
日後的 commit
,可是咱們這裏須要用到第一個 commit
,因此手動寫入一個 pick ec7eeb3
,並把第三個 commit
調整放到它後面,squash 有壓入、塞入的意思,這裏能夠理解爲把第三個 commit
塞入第一個中:
保存後會彈出一些提示,經過 git rebase --continue
再次回到交互式界面中。後面的步驟就和以前同樣了:
再來打印看看,發現合併成功了:
git reset --hard HEAD^ / HEAD~n git reset --hard <commit id> 複製代碼
git diff <commit id1> <commit id2>
複製代碼
在介紹具體的指令以前,首先要搞清楚 HEAD
,master
和 dev
三個指針的做用(不考慮版本回退的狀況):
master
分支(主分支),
master
指針始終指向主分支的最近一次
commit
,並在每次出現新的
commit
時向前推動;
dev
指針,它始終指向子分支的最近一次
commit
,並在每次出現新的
commit
時向前推動;
HEAD
指針都指向該分支的最近一次
commit
假設最初只有一個主分支,第一次 commit
新建文本文件,第二次 commit
修改文件內容。下面結合例子和示意圖介紹指令。
git branch -av
複製代碼
此時只有一個主分支,HEAD
和 master
指針都指向最近一次 commit
:
PS:這裏必定要注意,判斷當前處於哪一個分支看右邊括號的內容便可,HEAD -> master
表示的並非 HEAD
指針指向主分支,而是像示意圖那樣。
參數 -a
表明查看本地和遠程全部分支,-v
表明查看分支的同時顯示最後一次 commit
的相關信息。若是隻想查看本地分支,能夠:
git branch -v
複製代碼
commit
建立並切換分支git branch new_branch git checkout new_branch /* 或者 */ git checkout -b new_branch 複製代碼
此時有了子分支,因爲它是基於主分支的最近一次 commit
建立的,因此三個指針指向同一個東西,即 HEAD -> new_branch,master
commit
建立並切換分支git branch new_branch <commit id> git checkout new_branch /* 或者 */ git checkout -b new_branch <commit id> 複製代碼
若是咱們是基於第一次 commit
建立分支的,則指針的變更以下:
git checkout -b new_branch old_branch
複製代碼
git switch new_branch
複製代碼
在 git 2.23
以後有了專門的切換分支命令。
git merge new_branch
複製代碼
直接刪除指定分支:
git branch -D new_branch
複製代碼
刪除前會先讓用戶肯定分支是否已經合併:
git branch -d new_branch
複製代碼
假設當前提交狀況以下:
要比較兩個分支的差別,天然而然想到:
git diff master new_branch
複製代碼
不過,前面咱們知道,master
指針指向主分支最近一次 commit
,dev
指向子分支最近一次 commit
,正是這兩個指針不一樣才得以將分支區別開來,而指針又是指向 commit
,所以其實能夠經過比對兩個分支最近一次 commit
的差別,進而比對兩個分支的差別:
能夠看到,雖然兩次 diff
的參數不一樣,可是結果是同樣,這是由於「比對分支的差別,在本質上就是比對 commit
的差別」。
commit
切換到「分離頭指針」狀態git checkout <commit id>
複製代碼
此時,咱們會到達某次 commit
以後的狀態,咱們能夠今後次 commit
的狀態出發,進行一些試探性的修改。因爲處在 'detached HEAD' state
(「分離頭指針」狀態),因此修改不是基於任何具體分支的,一旦切換到某個具體分支,全部的修改都會消失。這適用於實驗性的修改,能夠避免影響其它任何分支。
其實,Git 給出的文字提示也很清楚。咱們還能夠注意括號裏的內容,一開始是 master
分支,後來直接變成了某一次 的 commit id
。同時能夠看到,HEAD
再也不像此前同樣指向某個指針,而是「分離了」:
對應的示意圖以下:
固然,咱們能夠執行下面指令:
git checkout -b new_branch
複製代碼
此時就會真正建立一個分支,咱們的修改得以保留。實際上就和以前的 git checkout -b new_branch <commit id>
效果同樣。
① 每一次提交會造成一個 commit
對象,根據 id 查看該 commit
對象的類型和內容:
git cat-file -t 'xxxxxx' git cat-file -p 'xxxxxx' 複製代碼
② 該 commit
對象的 tree
對象是一棵文件結構樹,它記錄了提交時的文件結構快照(有哪些文件夾、哪些文件),根據 id 查看該 tree
對象的類型和內容:
git cat-file -t 'xxxxxx' // tree git cat-file -p 'xxxxxx' 複製代碼
③tree
對象包含了 tree
對象和 blob
對象,分別指代文件夾和文件,咱們能夠進一步查看其類型和內容:
git cat-file -t 'xxxxxx' // tree or blob git cat-file -p 'xxxxxx' 複製代碼
「注意最外層、最初始的 tree
是文件結構樹,此後的 tree
是文件夾樹」。比方拿下面這張圖來看,第一棵樹是文件結構樹,內容是各個文件夾和文件,文件直接指代 blob
對象,而文件夾則指代另外一棵樹/ tree
對象。
查看本地公私鑰配置狀況:
ls -al ~/.ssh
複製代碼
生成公私鑰:
ssh-keygen -t rsa -b 4096 -C 'your_email@example.com'
複製代碼
以後本地保存私鑰,並在 GitHub 我的設置裏配置公鑰。
第一步,先到 GitHub 建立一個遠程倉庫,拿到倉庫對應的 ssh
或者 http
地址
第二步,建立本地倉庫:
git init // 初始化當前文件夾爲本地倉庫 git remote add origin <Address> // 本地倉庫與遠程倉庫創建關聯 git remote -v // 查看與本地倉庫關聯的遠程倉庫 複製代碼
最後,本地提交一些修改,並推送到遠程:
git add -A git commit -m'xxxx' git push origin master 複製代碼
PS:注意,若是以前創建遠程倉庫的時候勾選新建 README.md
文件,而且後來採用的不是 git clone
的方法,那麼在這裏是不能直接推送到遠程的,由於本地與遠程內容不同(本地沒有 README.md
文件),直接推送的話會報錯。必須得先 git pull
把遠程的東西拉下來同步,以後再 git push
。
在前面的第二步,能夠直接 git clone
,這樣就不須要手動初始化本地倉庫、創建遠程鏈接以及同步等:
git clone <Address>
複製代碼
不過,這隻會克隆遠程倉庫的 master 分支。「之後」若是咱們想要克隆遠程的其它分支,能夠:
git branch remote_branch_name origin/remote_branch_name
複製代碼
或者是直接切換到其它分支,Git 默認會幫咱們創建一個追蹤(track)遠程倉庫對應分支的分支:
git switch remote_branch_name
複製代碼
若是一開始就只想要克隆某個分支,能夠:
git clone -b remote_branch_name <Address>
複製代碼
還有一種狀況是,咱們直接在本地創建了一個分支,忘記將它和對應的遠程分支創建關聯,這時候怎麼辦呢?能夠這樣:
git branch --set-upstream-to=origin/remote_branch remote_branch
複製代碼
假設老大(項目負責人)新建遠程倉庫 X,並添加 AB 兩人做爲倉庫的 contributor,那麼 AB 就能夠各自 clone 造成一個本地倉庫,在作一些修改以後直接 push 到 X 倉庫。
A 和 B 在同一個分支 remote_branch
上進行開發,A 修改 a 文件,B 修改 b 文件。在 A push
以後,B 是不能直接 push
的,由於本地倉庫與遠程倉庫對應分支的內容還沒進行同步。必須先執行下面的操做進行同步:
git pull origin/remote_branch
複製代碼
這個指令其實作了兩件事,第一件事是把遠程已被 A 更新的 remote_branch
分支拉到本地:
git fetch origin/remote_branch
複製代碼
第二件事是將這個目前最新的分支與本地 B 本身的分支進行合併:
git merge remote_branch origin/remote_branch
複製代碼
最後再進行 push
就不會再報錯了。
還有一種狀況是修改同一文件的不一樣區域,由於修改的地方不同,Git 仍是能夠幫咱們進行 merge
的。
由於上面的修改都是針對不一樣區域的修改,因此不存在衝突,Git 能夠幫咱們進行 merge
。可是當修改同一文件的同一地方時,狀況就不同了:好比說 A 修改了 a 文件的某個地方,以後 push;B 同時也修改了 a 文件的這個地方,進行 push
,以後報錯,因此 B 選擇先 fetch --> merge
再 push
,可是在 merge
的時候還會報錯(Auto merged failed)。
這是由於 A 和 B 都修改了同一個地方,Git 並不知道以誰的修改成準,所以 Git 沒法幫咱們作合併的工做。此時,就須要 B 手動去處理衝突,以後再 add --> commit --> push
才行。
假設如今的場景是:A 修改 a 文件的文件名爲 a1,以後 push
;B 修改 a 文件的文件名爲 a2,也想要 push
,由於本地和遠程不一樣步,根據以前解釋的,到這裏天然會報錯,因此 B 決定先 pull
再 push
,但實際上,在 pull
這一步的時候會再報一個錯,提示沒法進行 merge
。
這和 6.2 實際上是同樣的狀況,A 和 B 都想要修改同一個東西,Git 不知道應該以誰爲準。假設 AB 通過協商,最終決定修改成 a2,那麼對於 B 來講,他能夠把本地的 a
和 a1
刪除,再對 a2
進行 add --> commit --> push
的操做。(爲何會有 a1
,是由於 pull
操做的 fetch
這一步是沒問題的,遠程拉下來以後本地會多出此前 A 更新的 a1
文件)
在前面,咱們是單人開發或者團隊開發,可能只涉及到本地倉庫和遠程倉庫,即 local 和 origin。假設 A 發現了某個第三方開源項目 ThirdParty/project
存在一些 bug,他想要進行修復,那麼他就須要 fork 別人的倉庫,造成本身的遠程倉庫 A/project
,再克隆這個倉庫造成本地倉庫。那麼,對於本地倉庫來講,A/project
就是 origin,而 ThirdParty/project
就是 upstream。
這個就是以前講的情形,origin 是 A 本身的遠程倉庫,A 具備自由讀寫的權限,能夠直接 pull
和 push
。
若是本地倉庫想要與原始倉庫同步,第一步是先設置上游:
git remote add upstream <remoteAddress>
複製代碼
查看當前配置的遠程倉庫(將包括 origin 和 upstream):
git remote -v
複製代碼
以後就能夠與原始倉庫進行同步了:
git pull upstream master
複製代碼
不過這裏要注意,A 不是項目參與者,對原始倉庫是沒有寫入權限的,所以沒法直接進行 git push
操做。那麼 A 怎麼作呢?他能夠先在本地把 bug 修復好,以後先 push 到 orgin 上去,再對原始倉庫發起 pull request,等待項目負責人進行 merge。
設置上游以後,本地能夠與原始倉庫保持同步,那麼 origin 怎麼與原始倉庫同步呢?咱們須要反向 pull request。這裏能夠以「掘金翻譯計劃」這個項目爲例。在咱們本身的倉庫點擊 pull request 來到這個界面:
注意這裏的 head 和 base,一開始是 origin 指向 upstream,表示我這邊更新了東西,想要原始倉庫去合併;不過如今的狀況是反過來的,咱們能夠看做是原始倉庫更新了東西,想要咱們去合併。因此將 head 和 base 改成從 upstream 指向 origin:
能夠看到,由於有很長一段時間沒有同步更新了,因此多出了整整 800 多個 commit。到了這個界面,就能夠正式建立 pr,而後咱們本身來 merge 了。這樣,origin 和 upstream 就保持了同步。
參考:
最後是我的的公衆號,最近在學習操做系統和編譯原理,關於這方面的筆記會比較多。之後的話則基本和前端相關。我還在持續學習的路上,文章會盡可能以原創和翻譯爲主,若是感興趣的話能夠關注一下~
本文使用 mdnice 排版