關於 Git 的那些事

本文主要記錄學習 Git 的過程。由於是學習筆記,可能有描述不全面或是不許確的地方,歡迎在評論區指正。前端

本文的思惟導圖以下:git

1. 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

2. Git 亂碼問題解決方案

如下操做基於 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 terminalsetting.json 文件:

"commandline": "D:\\Git\\bin\\bash.exe"
// 改成  "commandline": "D:\\Git\\bin\\bash.exe -li" 複製代碼

-li 參數可讓 git bash 以正確的配置啓動。估計前面全部問題都和這個有關,由於個人 git 沒有安裝在默認路徑,可能致使某些配置讀取不了,出現各類奇葩的問題。

3. 初始配置

顯示配置:

git config -l/--list
git config --global -l/--list 複製代碼

進行配置:

git config --global username 'xxx'
git config --global useremail 'xxx' 複製代碼

這裏的參數能夠是 local(對本倉庫生效),global(對登陸用戶的全部倉庫生效) 或 system(對全部用戶的全部倉庫生效),在優先級上依次遞減。

4. 本地倉庫操做

4.1 工做區相關

生成項目文件夾(本地倉庫),同時生成記錄重要信息的隱藏文件夾 .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 複製代碼

4.2 暫存區相關

提交文件到暫存區
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
複製代碼

4.3 歷史區相關

提交文件到歷史區
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>
複製代碼

4.4 分支相關

在介紹具體的指令以前,首先要搞清楚 HEADmasterdev 三個指針的做用(不考慮版本回退的狀況):

  • 在一開始,默認只有 master 分支(主分支), master 指針始終指向主分支的最近一次 commit,並在每次出現新的 commit 時向前推動;
  • 若是新建分支,則會出現 dev 指針,它始終指向子分支的最近一次 commit,並在每次出現新的 commit 時向前推動;
  • 無論當前處於哪一個分支, HEAD 指針都指向該分支的最近一次 commit

假設最初只有一個主分支,第一次 commit 新建文本文件,第二次 commit 修改文件內容。下面結合例子和示意圖介紹指令。

查看全部分支
git branch -av
複製代碼

此時只有一個主分支,HEADmaster 指針都指向最近一次 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 指針指向主分支最近一次 commitdev 指向子分支最近一次 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> 效果同樣。

4.5 Git 的三個重要對象(commit、tree、blob)

① 每一次提交會造成一個 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 對象。

5. 遠程倉庫操做

5.1 公私鑰配置

查看本地公私鑰配置狀況:

ls -al ~/.ssh
複製代碼

生成公私鑰:

ssh-keygen -t rsa -b 4096 -C 'your_email@example.com'
複製代碼

以後本地保存私鑰,並在 GitHub 我的設置裏配置公鑰。

5.2 新建項目

第一步,先到 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

5.3 克隆項目

在前面的第二步,能夠直接 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 
複製代碼

6. 多人開發

假設老大(項目負責人)新建遠程倉庫 X,並添加 AB 兩人做爲倉庫的 contributor,那麼 AB 就能夠各自 clone 造成一個本地倉庫,在作一些修改以後直接 push 到 X 倉庫。

6.1 修改不一樣文件/同一文件不一樣區域

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 的。

6.2 修改同一文件同一區域

由於上面的修改都是針對不一樣區域的修改,因此不存在衝突,Git 能夠幫咱們進行 merge。可是當修改同一文件的同一地方時,狀況就不同了:好比說 A 修改了 a 文件的某個地方,以後 push;B 同時也修改了 a 文件的這個地方,進行 push,以後報錯,因此 B 選擇先 fetch --> mergepush,可是在 merge 的時候還會報錯(Auto merged failed)。

這是由於 A 和 B 都修改了同一個地方,Git 並不知道以誰的修改成準,所以 Git 沒法幫咱們作合併的工做。此時,就須要 B 手動去處理衝突,以後再 add --> commit --> push 才行。

6.3 修改同一文件的文件名

假設如今的場景是:A 修改 a 文件的文件名爲 a1,以後 push;B 修改 a 文件的文件名爲 a2,也想要 push,由於本地和遠程不一樣步,根據以前解釋的,到這裏天然會報錯,因此 B 決定先 pullpush,但實際上,在 pull 這一步的時候會再報一個錯,提示沒法進行 merge

這和 6.2 實際上是同樣的狀況,A 和 B 都想要修改同一個東西,Git 不知道應該以誰爲準。假設 AB 通過協商,最終決定修改成 a2,那麼對於 B 來講,他能夠把本地的 aa1 刪除,再對 a2 進行 add --> commit --> push 的操做。(爲何會有 a1,是由於 pull 操做的 fetch 這一步是沒問題的,遠程拉下來以後本地會多出此前 A 更新的 a1 文件)

6.4 local,origin 和 upstream

在前面,咱們是單人開發或者團隊開發,可能只涉及到本地倉庫和遠程倉庫,即 local 和 origin。假設 A 發現了某個第三方開源項目 ThirdParty/project 存在一些 bug,他想要進行修復,那麼他就須要 fork 別人的倉庫,造成本身的遠程倉庫 A/project,再克隆這個倉庫造成本地倉庫。那麼,對於本地倉庫來講,A/project 就是 origin,而 ThirdParty/project 就是 upstream。

local 和 origin 的同步

這個就是以前講的情形,origin 是 A 本身的遠程倉庫,A 具備自由讀寫的權限,能夠直接 pullpush

local 和 upstream 的同步

若是本地倉庫想要與原始倉庫同步,第一步是先設置上游:

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 和 upstream 的同步

設置上游以後,本地能夠與原始倉庫保持同步,那麼 origin 怎麼與原始倉庫同步呢?咱們須要反向 pull request。這裏能夠以掘金翻譯計劃這個項目爲例。在咱們本身的倉庫點擊 pull request 來到這個界面:

注意這裏的 head 和 base,一開始是 origin 指向 upstream,表示我這邊更新了東西,想要原始倉庫去合併;不過如今的狀況是反過來的,咱們能夠看做是原始倉庫更新了東西,想要咱們去合併。因此將 head 和 base 改成從 upstream 指向 origin:

能夠看到,由於有很長一段時間沒有同步更新了,因此多出了整整 800 多個 commit。到了這個界面,就能夠正式建立 pr,而後咱們本身來 merge 了。這樣,origin 和 upstream 就保持了同步。

參考:

玩轉 Git 三劍客

廖雪峯 Git 教程

最後是我的的公衆號,最近在學習操做系統和編譯原理,關於這方面的筆記會比較多。之後的話則基本和前端相關。我還在持續學習的路上,文章會盡可能以原創和翻譯爲主,若是感興趣的話能夠關注一下~

本文使用 mdnice 排版

相關文章
相關標籤/搜索