常用 Git 使用技巧,收藏了~
點擊關(guān)注公眾號,Java干貨及時送達(dá)
作者: Escape
來源: https://www.escapelife.site/posts/f6ffe82b.html
只有在遇到問題的時候,才體會到技巧帶來的好處!
常見企業(yè)工作流程
主要介紹,企業(yè)中常用的 Git 工作流程!
Git Flow
主干分支 穩(wěn)定分支 開發(fā)分支 補丁分支 修改分支

Github Flow
創(chuàng)建分支 添加提交 提交 PR 請求 討論和評估代碼 部署檢測 合并代碼

Gitlab Flow
帶生產(chǎn)分支 帶環(huán)境分支 帶發(fā)布分支

日常使用最佳實踐
總結(jié)日常工作中應(yīng)該遵循的 Git 使用方式和方法!
使用命令行代替圖形化界面
使用命令行來操作,簡潔且效率高 提交應(yīng)該盡可能的表述提交修改內(nèi)容
區(qū)分 subject 和 body 內(nèi)容,使用空行隔開 subject 一般不超過 50 個字符 body 每一行的長度控制在 72 個字符 subject 結(jié)尾不需要使用句號或者點號結(jié)尾 body 用來詳細(xì)解釋此次提交具體做了什么
使用 .gitignore 文件來排除無用文件
可使用模板文件,然后根據(jù)項目實際進行修改
基于分支或 fork 的開發(fā)模式
不要直接在主干分支上面進行開發(fā) 在新建的分支上進行功能的開發(fā)和問題的修復(fù)
使用 release 分支和 tag 標(biāo)記進行版本管理
使用 release 分支發(fā)布代碼和版本維護(release/1.32) 使用 tag 來標(biāo)記版本(A-大feature功能.B-小feature功能.C-只修bug)
常用命令匯總整理
日常使用只要記住 6 個命令就可以了。

# 工作區(qū) -> 暫存區(qū)$ git add <file/dir># 暫存區(qū) -> 本地倉庫$ git commit -m "some info"# 本地倉庫 -> 遠(yuǎn)程倉庫$ git push origin master # 本地master分支推送到遠(yuǎn)程origin倉庫
# 工作區(qū) <- 暫存區(qū)
$ git checkout -- <file> # 暫存區(qū)文件內(nèi)容覆蓋工作區(qū)文件內(nèi)容
# 暫存區(qū) <- 本地倉庫
$ git reset HEAD <file> # 本地倉庫文件內(nèi)容覆蓋暫存區(qū)文件內(nèi)容
# 本地倉庫 <- 遠(yuǎn)程倉庫
$ git clone <git_url> # 克隆遠(yuǎn)程倉庫
$ git fetch upstream master # 拉取遠(yuǎn)程代碼到本地但不應(yīng)用在當(dāng)前分支
$ git pull upstream master # 拉取遠(yuǎn)程代碼到本地但應(yīng)用在當(dāng)前分支
$ git pull --rebase upstream master # 如果平時使用rebase合并代碼則加上
# 工作區(qū) <- 本地倉庫
$ git reset <commit> # 本地倉庫覆蓋到工作區(qū)(保存回退文件內(nèi)容修改)
$ git reset --mixed <commit> # 本地倉庫覆蓋到工作區(qū)(保存回退文件內(nèi)容修改)
$ git reset --soft <commit> # 本地倉庫覆蓋到工作區(qū)(保留修改并加到暫存區(qū))
$ git reset --hard <commit> # 本地倉庫覆蓋到工作區(qū)(不保留修改直接刪除掉)
配置實用參數(shù)選項
雖然配置比較簡單,但是非常有用!另外,關(guān)注公眾號Java技術(shù)棧,在后臺回復(fù):面試,可以獲取我整理的 Java 系列面試題和答案,非常齊全。
全局配置
# 用戶信息
$ git config --global user.name "your_name"
$ git config --global user.email "your_email"
# 文本編輯器
$ git config --global core.editor "nvim"
# 分頁器
$ git config --global core.pager "more"
# 別名
$ git config --global alias.gs "git status"
# 糾錯
$ git config --global help.autocorrect 1
個人配置
# 不加--global參數(shù)的話,則為個人配置
$ git config --list
$ git config user.name
$ git config user.name "your_name"
# 如果在項目中設(shè)置,則保存在.git/config文件里面
$ cat .git/config
[user]
name = "your_name"
......
合并和變基的選擇
到底什么時候使用 merge 操作,什么時候使用 rebase 操作呢?
使用 merge 操作 - Python 中的 Requests 庫在使用
支持使用 merge 的開發(fā)者,他們認(rèn)為倉庫的提交歷史就是記錄實際發(fā)生過什么,它是針對于歷史的一個文檔,本身其實是有價值的,我們不應(yīng)該隨意修改。我們改變歷史的話,就相當(dāng)于使用“謊言”來掩蓋實際發(fā)生過的事情,而這些痕跡是應(yīng)該被保留的。可能,這樣并不是很好。
# 3rd的兩個分支的commit修改相同內(nèi)容
* 62a322d - (HEAD->master) Merge branch 'hotfix3' into master
|\
| * 6fa8f4a - (hotfix3) 3rd commit in hotfix3
* | 548d681 - 3rd commit in master
|/
* 6ba4a08 - 2nd commit
* 22afcc1 - 1st commit
使用 rebase 操作 - Python 中的 Django 庫在使用
支持使用 rebase 的開發(fā)者,他們認(rèn)為提交歷史是項目過程中發(fā)生過的事情,需要項目的主干非常的干凈。而使用 merge 操作會生成一個 merge 的 commit 對象,讓提交歷史多了一些非常多余的內(nèi)容。
當(dāng)我們后期,使用 log 命令參看提交歷史的話,會發(fā)現(xiàn)主干的提交歷史非常的尷尬。比如,同樣的修改內(nèi)容重復(fù)提交了兩次,這顯然是分支合并導(dǎo)致的問題。
# 3rd的兩個分支的commit修改相同內(nèi)容
* 697167e - (HEAD -> master, hotfix) 3rd commit
* 6ba4a08 - 2nd commit (2 minutes ago)
* 22afcc1 - 1st commit (3 minutes ago)
兩者的使用原則
總的原則就是,只對尚未推送或分享給其他人的本地修改執(zhí)行變基操作清理歷史,從不對已經(jīng)推送到倉庫的提交記錄執(zhí)行變基操作,這樣,你才可能享受到兩種方式帶來的便利。
更新倉庫提交歷史
Git 提供了一些工具,可以幫助我們完善版本庫中的提交內(nèi)容,比如:
合并多個 commit 提交記錄
日常開發(fā)中,我們?yōu)榱送瓿梢粋€功能或者特性,提交很多個 commit 記錄。但是在最后,提交 PR 之前,一般情況下,我們是應(yīng)該整理下這些提交記錄的。有些 commit 需要合并起來,或者需要將其刪除掉,等等。
# 調(diào)整最近五次的提交記錄
$ git rebase -i HEAD~5
$ git rebase -i 5af4zd35 # 往前第六次的commit值
reword c2aeb6e 3rd commit
squash 25a3122 4th commit
pick 5d36f1d 5th commit
fixup bd5d32f 6th commit
drop 581e96d 7th commit
# 查看提交歷史記錄
$ git log
* ce813eb - (HEAD -> master) 5th commit
* aa2f043 - 3rd commit -> modified
* 6c5418f - 2nd commit
* c8f7dea - 1st commit

刪除意外調(diào)試的測試代碼
有時候提交之后,我們才發(fā)現(xiàn)提交的歷史記錄中存在這一些問題,而這個時候我們又不想新生成一個 commit 記錄,且達(dá)到一個修改的目錄。即,修改之前的 commit 提交記錄。
# 不使用分頁器
$ git --no-pager log --oneline -1
d5e96d9 (HEAD -> master) say file
# 改變提交信息并加入暫存區(qū)
$ echo "hello" > say.txt
$ git add -u
# 改變當(dāng)前最新一次提交記錄
$ git commit --amend
# 改變且息不改變提交信
$ git commit --amend --no-edit
# 改變當(dāng)前最新一次提交記錄并修改信息
$ git commit --amend -m "some_info"
# 不使用分頁器
$ git --no-pager log --oneline -1
9e1e0eb (HEAD -> master) say file
取消多個 commit 中的部分提交
我們開發(fā)了一個功能,而在上線的時候,產(chǎn)品經(jīng)理說這個功能的部分特性已經(jīng)不需要了,即相關(guān)特性的提交記錄和內(nèi)容就可以忽略/刪除掉了。
# 回滾操作(可多次執(zhí)行回滾操作)
# 徹底上次提交記錄;也可是PR的提交記錄
# 默認(rèn)會生成一個類型為reverts的新commit對象
$ git revert 3zj5sldl
[4] 合并某些特定的 commit 提交
我們不希望合并整個分支,而是需要合并該分支的某些提交記錄就可以了。
bash
# 摘櫻桃
$ git cherry-pick -x z562e23d
使用引用日志記錄
如何找回我們丟失的內(nèi)容和記錄?
我們之前說過,使用下面命令回退內(nèi)容、強制推送代碼、刪除本地分支,都是非常危險的操作,因為重置之后我們就沒有辦法在找到之前的修改內(nèi)容了。
# 回退
$ git reset --hard <commit>
# 推送
$ git push origin master -f
# 分支
$ git branch -D <branch_name>
其實 Git 給我們留了一個后門,就是使用 relflog 命令來找回之前的內(nèi)容,只不過是相對來說麻煩一些。而原理也很簡答,就是在我們使用 Git 命令操作倉庫的時候,Git 偷偷地幫助我們把所有的操作記錄了下來。
# 查看日志記錄
$ git --no-pager log --oneline -1
4bc8703 (HEAD -> master) hhhh
# 回退到上次提交
$ git reset --hard HEAD~1
# 查看引用日志記錄
$ git reflog
6a89f1b (HEAD -> master) HEAD@{0}: reset: moving to HEAD~1
4bc8703 HEAD@{1}: commit (amend): hhhh
# 找回內(nèi)容
$ git cherry-pick 4bc8703
批量修改歷史提交
批量修改歷史提交雖然不常用,但是理解的話可以省下很多時間!
之前我們學(xué)習(xí)到的命令都是針對于一個或者多個 commit 提交信息進行修改的,如果我們需要全局修改歷史提交呢?當(dāng)然,Git 中也是支持全局修改歷史提交的,比如全局修改郵箱地址,或者將一個文件從全局歷史中刪除或修改。
開源項目中使用了公司郵箱進行提交了 提交文件中包含隱私性的密碼相關(guān)信息 提交時將大文件提交到了倉庫代碼中了
這里我們可以使用 filter-brach 的方式進行修改,但是建議在使用之前,新建一個分支,在上面進行測試沒有問題之后,再在主干上操作,防止出現(xiàn)問題,背個大鍋在身上。
# 創(chuàng)建分支
$ git branch -b testing
# 修改郵箱地址
$ git filter-branch --commit-filter '
if [ "$GIT_AUTHOR_EMAIL" == "[email protected]" ]; then
GIT_AUTHOR_NAME="escape";
GIT_AUTHOR_EMAIL="[email protected]";
git commit-tree "$@"
else
git commit-tree "$@"
fi' HEAD
靈活使用鉤子函數(shù)
主要介紹.git/hooks 目錄下面的示例鉤子函數(shù)!
在 Git 里面有兩類,分別對應(yīng)客戶端和服務(wù)端鉤子函數(shù)。客戶端的鉤子函數(shù),是在執(zhí)行提交和合并之類的操作時調(diào)用的。而服務(wù)端鉤子函數(shù),就是當(dāng)服務(wù)端收到代碼提交之后,可以出發(fā)代碼檢查和持續(xù)集成的步驟。作為開發(fā)者我們并不會搭建 Git 服務(wù)器,所以基本不會涉及。
下面就是 Git 自帶的鉤子腳本,但是自帶的都以 .sample 作為后綴,表示并沒有啟用,表示為一個示例。如果需要啟用的話,將 .sample 作為后綴刪除掉,即可。而其鉤子腳本的對應(yīng)內(nèi)容,都是使用 Shell 語法進行編寫的。
? ll .git/hooks
total 112
-rwxr-xr-x applypatch-msg.sample
-rwxr-xr-x commit-msg.sample
-rwxr-xr-x fsmonitor-watchman.sample
-rwxr-xr-x post-update.sample
-rwxr-xr-x pre-applypatch.sample
-rwxr-xr-x pre-commit.sample
-rwxr-xr-x pre-merge-commit.sample
-rwxr-xr-x pre-push.sample # 不會推送包含WIP的commit提交
-rwxr-xr-x pre-rebase.sample
-rwxr-xr-x pre-receive.sample
-rwxr-xr-x prepare-commit-msg.sample
-rwxr-xr-x update.sample
其實,鉤子腳本使用任何語言編寫都是可以的,只要你讓程序返回對應(yīng)的退出碼就可以了。
正常的代碼合入流程就是,我們本地修改之后,提一個 PR 請求并通過 Github 的 CI 檢查,接下來進行代碼評審,最后被合并入主干。但是,好的一個習(xí)慣就是,在代碼提交之前就應(yīng)該保證代碼不會出現(xiàn)語法錯誤等基礎(chǔ)問題,比如通過 flake8 和 PEP8 標(biāo)準(zhǔn)等。
這個時候我們就可以使用 pre-commit 這個 Github 的開源項目了,其本質(zhì)就是給項目添加鉤子函數(shù)的一個腳本,可以保證我們在提交代碼或者推送代碼之前,先檢查代碼的質(zhì)量。
而 pre-commit-hooks 這個項目里面包含的就是,現(xiàn)在所支持的鉤子腳本,即開箱即用的鉤子腳本集合。而其鉤子腳本的對應(yīng)內(nèi)容,都是使用 Python 語法進行編寫的。
# 安裝方式
$ pip install pre-commit
# 指定hook類型(即在哪里檢查)
$ pre-commit install -f --hook-type pre-push
# 配置需要執(zhí)行的檢查
$ cat .pre-commit-config.yaml
repos:
- repo: https://github.com/pre-commit/pre-commit-hooks
rev: v2.9.2
hooks:
- id: trailing-whitespace
- id: flake8
# 執(zhí)行push操作時檢查
$ git push origin master
快速克隆大型項目
在大項目中工作中,拉取代碼非常占時間!
我們?nèi)绻霝?Linux 或 Python 這樣的大型項目貢獻(xiàn)提交的時候,首先遇到的問題就是,如果快速的 clone 該項目到本地。因為改項目提交歷史超多且倉庫巨大,加了國內(nèi)網(wǎng)絡(luò)的問題,可能等項目完全拉下來的時候,我們的熱情都消減下去了。
好在 Git 也幫我們想到了這樣的問題,我們可以使用 --depth 參數(shù)值拉取遠(yuǎn)程倉庫上面最新一次的提交歷史,并不包含項目歷史記錄,即 .git/objects/ 目錄下的對象只是本地的,并不包含之前的多次修改產(chǎn)生的對象。
# 克隆不包含之前歷史
$ git clone http://xxx.xx.xxx/xxx --depth=1
但是,有時間我們可能會需要 clone 倉庫中的某個 tag 版本對應(yīng)下的內(nèi)容。如果我們直接使用 clone 命令是無法做到的,需要執(zhí)行如下操作,即可完美解決。
# 克隆特定版本代碼
$ git init xxx-15-0-1
$ git remote add origin http://xxx.xx.xxx/xxx
$ git -c protocol.version=2 fetch origin 15.0.1 --depth=1
$ git checkout FETCH_HEAD
上面的效果已經(jīng)基本可以滿足我們?nèi)粘J褂眯枨罅耍遣恍业氖牵悻F(xiàn)在接受了一個機器學(xué)習(xí)的項目,里面包含了大量的 lfs 文件,現(xiàn)在 clone 又會變得非常慢。可以使用如下操作來避免,Git 工具主動拉去 lfs 文件,來達(dá)到目錄。
# 克隆不包含LFS數(shù)據(jù)
$ GIT_LFS_SKIP_SMUDGE=1 git clone http://xxx.xx.xxx/xxx
如何處理工作中斷
如果在多路運轉(zhuǎn)的時候,還能夠高效的進行開發(fā)!
比如,我們現(xiàn)在正在一個分支為項目添加一個小的功能,此時,產(chǎn)品經(jīng)理找到你說是線上環(huán)境現(xiàn)在有一個 bug 需要讓你來修復(fù)下。但是,此時我們添加的小功能并沒有完成。
如果此時,我們直接切換到主干分支的話,會將之前分支沒有來得及提交的內(nèi)容全部都帶到了主干分支上來,這是我們不想看到的情況。此時,我們需要保存上個分支的工作狀態(tài),在我們修改完成線上 bug 之后,再繼續(xù)工作。
好在 Git 也幫我們想到了這樣的問題,我們可以使用 stash 子命令幫助我們將當(dāng)前工作區(qū)、暫存區(qū)當(dāng)中的修改都保存到堆棧之中。等到需要處理的時候,再彈出堆棧中的內(nèi)容,我們再次進行開發(fā)。
? git stash -h
usage: git stash list [<options>]
or: git stash show [<options>] [<stash>]
or: git stash drop [-q|--quiet] [<stash>]
or: git stash ( pop | apply ) [--index] [-q|--quiet] [<stash>]
or: git stash branch <branchname> [<stash>]
or: git stash clear
or: git stash [push [-p|--patch] [-k|--[no-]keep-index] [-q|--quiet]
[-u|--include-untracked] [-a|--all] [-m|--message <message>]
[--pathspec-from-file=<file> [--pathspec-file-nul]]
[--] [<pathspec>...]]
or: git stash save [-p|--patch] [-k|--[no-]keep-index] [-q|--quiet]
[-u|--include-untracked] [-a|--all] [<message>]
# 存儲當(dāng)前的修改但不用提交commit
$ git stash
# 保存當(dāng)前狀態(tài)包括untracked的文件
$ git stash -u
# 展示所有stashes信息
$ git stash list
# 回到某個stash狀態(tài)
$ git stash apply <stash@{n}>
# 刪除儲藏區(qū)
$ git stash drop <stash@{n}>
# 回到最后一個stash的狀態(tài)并刪除這個stash信息
$ git stash pop
# 刪除所有的stash信息
$ git stash clear
# 從stash中拿出某個文件的修改
$ git checkout <stash@{n}> -- <file-path>
其實比較保險的做法就是,將當(dāng)前的所有修改進行 push 并保存到遠(yuǎn)程倉庫里面。這樣的好處在于,可以遠(yuǎn)端備份我們的修改,不會害怕本地文件丟失等問題。等到我們需要繼續(xù)開發(fā)的時候,拉下對應(yīng)內(nèi)容,再想辦法進行補救,比如使用 --amend 或者 reset 命令。
# 將工作區(qū)和暫存區(qū)覆蓋最近一次提交
$ git commit --amend
$ git commit --amend -m "some_info"
# 回退到指定版本并記錄修改內(nèi)容(--mixed)
# 本地倉庫覆蓋到工作區(qū)(保存回退文件內(nèi)容修改)
$ git reset a87f328
$ git reset HEAD~
$ git reset HEAD~2
$ git reset <tag>~2
$ git reset --mixed <commit/reference>
# 本地倉庫覆蓋到工作區(qū)(不保留修改直接刪除掉)
$ git reset --soft <commit/reference>
# 本地倉庫覆蓋到工作區(qū)(保留修改并加到暫存區(qū))
$ git reset --hard <commit/reference>






關(guān)注Java技術(shù)棧看更多干貨


