引言
本文整理自工作多年以來遇到的所有 Git 問題匯總,之前都是遺忘的時(shí)候去看一遍操作,這次重新整理了一下,發(fā)出來方便大家收藏以及需要的時(shí)候查找答案。
一、必備知識(shí)點(diǎn)


倉庫
- Remote:遠(yuǎn)程主倉庫;
- Repository/History:本地倉庫;
- Stage/Index:Git追蹤樹,暫存區(qū);
- workspace:本地工作區(qū)(即你編輯器的代碼)
二、git add 提交到暫存區(qū),出錯(cuò)怎么辦
一般代碼提交流程為: 工作區(qū) -> git status 查看狀態(tài) -> git add . 將所有修改加入 暫存區(qū) -> git commit -m "提交描述" 將代碼提交到 本地倉庫 -> git push 將本地倉庫代碼更新到 遠(yuǎn)程倉庫
場(chǎng)景1:
當(dāng)你改亂了工作區(qū)某個(gè)文件的內(nèi)容,想直接丟棄工作區(qū)的修改時(shí),用命令 git checkout -- file 。
git checkout
// 丟棄工作區(qū)的修改 git checkout -- <文件名>
場(chǎng)景2:
當(dāng)你不但改亂了工作區(qū)某個(gè)文件的內(nèi)容,還添加到了暫存時(shí),想丟棄修改,分兩步,第一步用命令 git reset HEAD file ,就回到了場(chǎng)景1,第二步按場(chǎng)景1操作。
三、git commit 提交到本地倉庫,出錯(cuò)怎么辦?
1. 提交信息出錯(cuò)
更改 commit 信息
git commit --amend -m“新提交消息”
2. 漏提交
commit 時(shí),遺漏提交部分更新,有兩種解決方案:
- 方案一:再次 commit
git commit -m“提交消息”
- 此時(shí),git 上會(huì)出現(xiàn)兩次 commit
- 方案二:遺漏文件提交到之前 commit 上
git add missed-file // missed-file 為遺漏提交文件 git commit --amend --no-edit
- --no-edit 表示提交消息不會(huì)更改,在 git 上僅為一次提交
3. 提交錯(cuò)誤文件,回退到上一個(gè) commit 版本,再 commit
git reset
刪除指定的 commit
// 修改版本庫,修改暫存區(qū),修改工作區(qū) git reset HEAD <文件名> // 把暫存區(qū)的修改撤銷掉(unstage),重新放回工作區(qū)。 // git版本回退,回退到特定的commit_id版本,可以通過git log查看提交歷史,以便確定要回退到哪個(gè)版本(commit 之后的即為ID); git reset --hard commit_id //將版本庫回退1個(gè)版本,不僅僅是將本地版本庫的頭指針全部重置到指定版本,也會(huì)重置暫存區(qū),并且會(huì)將工作區(qū)代碼也回退到這個(gè)版本 git reset --hard HEAD~1 // 修改版本庫,保留暫存區(qū),保留工作區(qū) // 將版本庫軟回退1個(gè)版本,軟回退表示將本地版本庫的頭指針全部重置到指定版本,且將這次提交之后的所有變更都移動(dòng)到暫存區(qū)。 git reset --soft HEAD~1
git revert
撤銷 某次操作,此次操作之前和之后的commit和history都會(huì)保留,并且把這次撤銷
作為一次最新的提交
// 撤銷前一次 commit git revert HEAD // 撤銷前前一次 commit git revert HEAD^ // (比如:fa042ce57ebbe5bb9c8db709f719cec2c58ee7ff)撤銷指定的版本,撤銷也會(huì)作為一次提交進(jìn)行保存。 git revert commit
git revert 是提交一個(gè)新的版本,將需要 revert 的版本的內(nèi)容再反向修改回去,
版本會(huì)遞增,不影響之前提交的內(nèi)容
`git revert` 和 `git reset` 的區(qū)別
- git revert 是用一次新的commit來回滾之前的commit, git reset 是直接刪除指定的commit。
- 在回滾這一操作上看,效果差不多。但是在日后繼續(xù)merge以前的老版本時(shí)有區(qū)別。因?yàn)?git revert 是用一次逆向的commit“中和”之前的提交,因此日后合并老的branch時(shí),導(dǎo)致這部分改變不會(huì)再次出現(xiàn),但是 git reset 是之間把某些commit在某個(gè)branch上刪除,因而和老的branch再次merge時(shí),這些被回滾的commit應(yīng)該還會(huì)被引入。
- git reset 是把HEAD向后移動(dòng)了一下,而 git revert 是HEAD繼續(xù)前進(jìn),只是新的commit的內(nèi)容和要revert的內(nèi)容正好相反,能夠抵消要被revert的內(nèi)容。
四、常用命令
1. 初始開發(fā) git 操作流程
- 克隆最新主分支項(xiàng)目代碼 git clone 地址
- 創(chuàng)建本地分支 git branch 分支名
- 查看本地分支 git branch
- 查看遠(yuǎn)程分支 git branch -a
- 切換分支 git checkout 分支名 (一般修改未提交則無法切換,大小寫問題經(jīng)常會(huì)有,可強(qiáng)制切換 git checkout 分支名 -f 非必須慎用)
- 將本地分支推送到遠(yuǎn)程分支 git push <遠(yuǎn)程倉庫> <本地分支>:<遠(yuǎn)程分支>
2. git fetch
將某個(gè)遠(yuǎn)程主機(jī)的更新,全部/分支 取回本地(此時(shí)之更新了Repository)它取回的代碼對(duì)你本地的開發(fā)代碼沒有影響,如需徹底更新需合并或使用 git pull
3. git pull
拉取遠(yuǎn)程主機(jī)某分支的更新,再與本地的指定分支合并(相當(dāng)與fetch加上了合并分支功能的操作)
4. git push
將本地分支的更新,推送到遠(yuǎn)程主機(jī),其命令格式與 git pull 相似
5. 分支操作
- 使用 Git 下載指定分支命令為: git clone -b 分支名倉庫地址
- 拉取遠(yuǎn)程新分支 git checkout -b serverfix origin/serverfix
- 合并本地分支 git merge hotfix :(將 hotfix 分支合并到當(dāng)前分支)
- 合并遠(yuǎn)程分支 git merge origin/serverfix
- 刪除本地分支 git branch -d hotfix :(刪除本地 hotfix 分支)
- 刪除遠(yuǎn)程分支 git push origin --delete serverfix
- 上傳新命名的本地分支: git push origin newName ;
- 創(chuàng)建新分支: git branch branchName :(創(chuàng)建名為 branchName 的本地分支)
- 切換到新分支: git checkout branchName :(切換到 branchName 分支)
- 創(chuàng)建并切換分支: git checkout -b branchName :(相當(dāng)于以上兩條命令的合并)
- 查看本地分支: git branch
- 查看遠(yuǎn)程倉庫所有分支: git branch -a
- 本地分支重命名: git branch -m oldName newName
- 重命名遠(yuǎn)程分支對(duì)應(yīng)的本地分支: git branch -m oldName newName
- 把修改后的本地分支與遠(yuǎn)程分支關(guān)聯(lián): git branch --set-upstream-to origin/newName
五、優(yōu)化操作
1. 拉取代碼 pull --rebase
在團(tuán)隊(duì)協(xié)作過程中,假設(shè)你和你的同伴在本地中分別有各自的新提交,而你的同伴先于你 push 了代碼到遠(yuǎn)程分支上,所以你必須先執(zhí)行 git pull 來獲取同伴的提交,然后才能 push 自己的提交到遠(yuǎn)程分支。
而按照 Git 的默認(rèn)策略,如果遠(yuǎn)程分支和本地分支之間的提交線圖有分叉的話(即不是 fast-forwarded),Git 會(huì)執(zhí)行一次 merge 操作,因此產(chǎn)生一次沒意義的提交記錄,從而造成了像上圖那樣的混亂。
其實(shí)在 pull 操作的時(shí)候,,使用 git pull --rebase 選項(xiàng)即可很好地解決上述問題。加上 --rebase 參數(shù)的作用是,提交線圖有分叉的話,Git 會(huì) rebase 策略來代替默認(rèn)的 merge 策略。
假設(shè)提交線圖在執(zhí)行 pull 前是這樣的:
A---B---C remotes/origin/master / D---E---F---G master
如果是執(zhí)行 git pull 后,提交線圖會(huì)變成這樣:
A---B---C remotes/origin/master / D---E---F---G---H master
結(jié)果多出了 H 這個(gè)沒必要的提交記錄。如果是執(zhí)行 git pull --rebase 的話,提交線圖就會(huì)變成這樣:
remotes/origin/master | D---E---A---B---C---F'---G' master
F G 兩個(gè)提交通過 rebase 方式重新拼接在 C 之后,多余的分叉去掉了,目的達(dá)到。
小結(jié)
大多數(shù)時(shí)候,使用 git pull --rebase 是為了使提交線圖更好看,從而方便 code review。
不過,如果你對(duì)使用 git 還不是十分熟練的話,我的建議是 git pull --rebase 多練習(xí)幾次之后再使用,因?yàn)?nbsp;rebase 在 git 中,算得上是『危險(xiǎn)行為』 。
另外,還需注意的是,使用 git pull --rebase 比直接 pull 容易導(dǎo)致沖突的產(chǎn)生,如果預(yù)期沖突比較多的話,建議還是直接 pull。
注意:
git pull = git fetch + git merge
git pull --rebase = git fetch + git rebase
2. 合代碼 merge --no-ff
上述的 git pull --rebase 策略目的是修整提交線圖,使其形成一條直線,而即將要用到的 git merge --no-ff <branch-name> 策略偏偏是反行其道,刻意地弄出提交線圖分叉出來。
假設(shè)你在本地準(zhǔn)備合并兩個(gè)分支,而剛好這兩個(gè)分支是 fast-forwarded 的,那么直接合并后你得到一個(gè)直線的提交線圖,當(dāng)然這樣沒什么壞處,但如果你想更清晰地告訴你同伴: 這一系列的提交都是為了實(shí)現(xiàn)同一個(gè)目的 ,那么你可以刻意地將這次提交內(nèi)容弄成一次提交線圖分叉。
執(zhí)行 git merge --no-ff <branch-name> 的結(jié)果大概會(huì)是這樣的:

git merge --no-ff
中間的分叉線路圖很清晰的顯示這些提交都是為了實(shí)現(xiàn) complete adjusting user domains and tags
更進(jìn)一步
往往我的習(xí)慣是,在合并分支之前(假設(shè)要在本地將 feature 分支合并到 dev 分支),會(huì)先檢查 feature 分支是否『部分落后』于 遠(yuǎn)程 dev 分支 :
git checkout dev git pull # 更新 dev 分支 git log feature..dev
如果沒有輸出任何提交信息的話,即表示 feature 對(duì)于 dev 分支是 up-to-date 的。如果有輸出的話而馬上執(zhí)行了 git merge --no-ff 的話,提交線圖會(huì)變成這樣:
git-merge
所以這時(shí)在合并前,通常我會(huì)先執(zhí)行:
git checkout feature git rebase dev
這樣就可以將 feature 重新拼接到更新了的 dev 之后,然后就可以合并了,最終得到一個(gè)干凈舒服的提交線圖。
再次提醒:像之前提到的,rebase 是『危險(xiǎn)行為』,建議你足夠熟悉 git 時(shí)才這么做,否則的話是得不償失啊。
總結(jié)
使用 git pull --rebase 和 git merge --no-ff 其實(shí)和直接使用 git pull git merge 得到的代碼應(yīng)該是一樣。
使用 git pull --rebase 主要是為是將提交約線圖平坦化,而 git merge --no-ff 則是刻意制造分叉。
六、SSH
1. 查看是否生成了 SSH 公鑰
$ cd ~/.ssh $ ls id_rsa id_rsa.pub known_hosts
其中 id_rsa 是私鑰,id_rsa.pub 是公鑰。
2. 如果沒有那就開始生成,設(shè)置全局的user.name與user.email
git config --list // 查看是否設(shè)置了user.name與user.email,沒有的話,去設(shè)置 // 設(shè)置全局的user.name與user.email git config --global user.name "XX" git config --global user.email "XX"
3. 輸入 ssh-keygen 即可(或`ssh-keygen -t rsa -C "email"`)
$ ssh-keygen Generating public/private rsa key pair. Enter file in which to save the key (/Users/schacon/.ssh/id_rsa): Enter passphrase (empty for no passphrase): Enter same passphrase again: Your identification has been saved in /Users/schacon/.ssh/id_rsa. Your public key has been saved in /Users/schacon/.ssh/id_rsa.pub. The key fingerprint is:
4. 生成之后獲取公鑰內(nèi)容,輸入 cat ~/.ssh/id_rsa.pub 即可, 復(fù)制 ssh-rsa 一直到 .local這一整段內(nèi)容
$ 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== [email protected]
5. 打開 GitLab 或者 GitHub,點(diǎn)擊頭像,找到設(shè)置頁
6. 左側(cè)找到 SSH keys 按鈕并點(diǎn)擊,輸入剛剛復(fù)制的公鑰即可
七、暫存
git stash 可用來暫存當(dāng)前正在進(jìn)行的工作,比如想 pull 最新代碼又不想 commit , 或者另為了修改一個(gè)緊急的 bug ,先 stash,使返回到自己上一個(gè) commit,,改完 bug 之后再 stash pop , 繼續(xù)原來的工作;
- 添加緩存棧: git stash ;
- 查看緩存棧: git stash list ;
- 推出緩存棧: git stash pop ;
- 取出特定緩存內(nèi)容: git stash Apply stash@{1} ;
八、文件名過長(zhǎng)錯(cuò)誤
Filename too long warning: Clone succeeded, but checkout failed.
git config --system core.longpaths true
九、郵箱和用戶名
查看
git config user.name git config user.email
修改
git config --global user.name "username" git config --global user.email "email"
十、.gitignore 更新后生效:
git rm -r --cached . git add . git commit -m ".gitignore is now working”
十一、同步Github fork 出來的分支
1、配置remote,指向原始倉庫
git remote add upstream https://github.com/InterviewMap/InterviewMap.git
2、上游倉庫獲取到分支,及相關(guān)的提交信息,它們將被保存在本地的 upstream/master 分支
git fetch upstream # remote: Counting objects: 75, done. # remote: Compressing objects: 100% (53/53), done. # remote: Total 62 (delta 27), reused 44 (delta 9) # Unpacking objects: 100% (62/62), done. # From https://github.com/ORIGINAL_OWNER/ORIGINAL_REPOSITORY # * [new branch] master -> upstream/master
3、切換到本地的 master 分支
git checkout master # Switched to branch 'master'
4、把 upstream/master 分支合并到本地的 master 分支,本地的 master 分支便跟上游倉庫保持同步了,并且沒有丟失本地的修改。
git merge upstream/master # Updating a422352..5fdff0f # Fast-forward # README | 9 ------- # README.md | 7 ++++++ # 2 files changed, 7 insertions(+), 9 deletions(-) # delete mode 100644 README # create mode 100644 README.md
5、上傳到自己的遠(yuǎn)程倉庫中
git push
原文:https://mp.weixin.qq.com/s?__biz=MzIwMDY0Nzk2Mw==&mid=2650320580&idx=1&sn=1a1f258eca7da3457aa9b79dde91d868&utm_source=tuicool&utm_medium=referral