本篇博客介绍 Git 的基本使用,争取按规范的使用 Git 使开发流程更加规范。
.git 文件结构
以下是一个 .git 文件的结构。
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
| .git
├── branches
├── COMMIT_EDITMSG
├── config
├── description
├── HEAD
├── hooks
│ ├── applypatch-msg.sample
│ ├── commit-msg.sample
│ ├── fsmonitor-watchman.sample
│ ├── post-update.sample
├── index
├── info
│ └── exclude
├── logs
│ ├── HEAD
│ └── refs
│ ├── heads
│ │ └── main
│ └── remotes
│ └── origin
│ ├── HEAD
│ └── main
├── modules
│ └── themes
│ └── FixIt
│ ├── branches
│ ├── config
│ ├── description
│ ├── HEAD
│ ├── hooks
│ │ ├── applypatch-msg.sample
│ │ ├── commit-msg.sample
│ │ ├── fsmonitor-watchman.sample
│ │ ├── post-update.sample
│ ├── index
│ ├── info
│ │ └── exclude
│ ├── logs
│ │ ├── HEAD
│ │ └── refs
│ │ ├── heads
│ │ │ └── master
│ │ └── remotes
│ │ └── origin
│ │ └── HEAD
│ ├── objects
│ │ ├── info
│ │ └── pack
│ │ ├── pack-3f2759e5d239cc34913467329d51643b48f3e651.idx
│ │ └── pack-3f2759e5d239cc34913467329d51643b48f3e651.pack
│ ├── packed-refs
│ └── refs
│ ├── heads
│ │ └── master
│ ├── remotes
│ │ └── origin
│ │ └── HEAD
│ └── tags
├── objects
│ ├── 0e
│ │ └── f1525d81bad33f58a45e7a6c8018056e98bd02
│ ├── 12
│ │ └── 4aa45213eb54a6b7c7d5b715b88837086ee27f
│ ├── 1e
│ │ └── 14dfb2d0fa8c159232825937cb68013f268154
│ ├── 33
│ │ └── 5c6256c5ce8569a84743471d92376488b8595e
│ ├── 3c
│ │ └── 8f38a136aa07edbd81c8aa92562b1b16139b36
│ ├── info
│ └── pack
│ ├── pack-4b3a92177530dc3b688096fe7081564903e6e4bb.idx
│ └── pack-4b3a92177530dc3b688096fe7081564903e6e4bb.pack
├── packed-refs
└── refs
├── heads
│ └── main
├── remotes
│ └── origin
│ ├── HEAD
│ └── main
└── tags
|
- HEAD 存储当前指向的分支
- config git 配置信息
- hooks 配置 hook
- object 存储一些文件信息 (如git add 后会生成暂存区文件)
- refs 存储分支信息
object 文件
1
2
3
4
5
| ├── objects
│ ├── 0e
│ │ └── f1525d81bad33f58a45e7a6c8018056e98bd02
│ ├── 12
│ │ └── 4aa45213eb54a6b7c7d5b715b88837086ee27f
|
object 有三种类型: commit, tree, blob 和一个 tag 的 object 类型。
- blob 存储文件内容(git add 后产生)
- tree 存储文件目录信息
- commit 存储文件提交信息,对应唯一的版本代码
获取当前版本代码:会根据 commit 中的 tree ID 寻找 tree 信息,通过 tree 存储信息获取对应目录树信息,再从 tree 中找到 blob ID 获取文件内容。
如文件头 0e
加上后面的 f1525d81bad33f58a45e7a6c8018056e98bd02
为文件的 object id。
该文件进行了加密,但可以使用 git 命令查看文件信息
1
| git cat-file -p 0ef1525d81bad33f58a45e7a6c8018056e98bd02
|
如果当修改了一个历史的 commit 可能会造成悬空 object,即历史的 commit 没有 refs 指向。
1
2
3
4
5
| # 查看悬空的 obj
git fsck --lost-found
# 删除不需要的 obj
git gc
|
Refs
存储文件内容,实际是一个指针指向对应 commit ID,一般指向最新的 commit。他有不同种类 refs/heads
表示分支,refs/tags
表示标签。
- git reflog 查看本地更新过 HEAD 的 commit 记录。
git tag 表示一个稳定的不常更新的分支。
1
2
3
4
| git tag v0.0.1
# 添加附注标签 Annotation Tag
git tag -a v0.0.2 -m "bla bla bla"
|
refs/tags
文件中存储了 object 信息和标签信息,包括那个用户打了标签。
git 配置
git 配置分为三个级别从高到低为 --system
, --global
, --local
。每个级别的配置可能重复,低级别会覆盖高级别配置。
--system
系统配置存储在 /etc/gitconfig
--global
全局配置存储在 ~/.gitconfig
--local
本地配置存储在 .git/config
http 和 ssh 远端区别
- http 远端走 http 的认证方式(token)
- ssh 走 ssh 的认证方式(公钥私钥)出于安全推荐使用 ed25519 加密
go 开发依赖库很多时经常会 pull 一些远端仓库,因此有时验证是十分麻烦的。
http 免密配置方式:
1
2
3
4
5
| # 内存配置免密
git config --global credential.helper 'cache --timeout=3600'
# 硬盘配置免密 默认路径 ~/.git-credential
git config --global credential.helper "store --file /path/to/credential-file"
|
常用的配置指令
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
| # 修改信息
git config --global user.name "xxx"
git config --global user.email "xxx"
# Instead of 配置(替换 git 传输方式,比如下面把 ssh 替换为 https 传输)
git config --global url.git@github.com:.insteadOf https://github.com/
# git 自定义命令别名(下面把 commit --amend --no-edit 命令替换为 cin)
git config --global alias.cin "commit --amend --no-edit"
##---远端配置---##
# 查看远端
git remote -v
# 设置 fetch 和 push 不同的远端(下面我单独设置了 push 远端)
git remote set--url --add --push origin git@github.com:my_repo/git.git
|
git gc
删除一些不需要的 object 与打包 object 减少仓库体积。
使用该指令需要进行一些前置操作才可执行。
- reflog 用于记录操作日志,防止误操作。
- git gc prune=now 指定修改多长时间的对象,默认两周前
1
2
| git reflog expire --expire=now --all
git gc --prune=now
|
gitignore
Git 会自动忽略空文件夹,因此在想要忽略某个文件夹下的全部文件,但还想保留上传该父文件夹时需要在父文件夹目录下创建 .gitignore
。
忽略规则相关常用命令
1
2
3
4
5
| 清除已经持久化文件的缓存
git rm --cached filename
检测文件忽略规则
git check-ignore -v filename
|
我的 git 常用指令辞典
;
: 使用分号分隔多个指令可在同一行顺序执行多个 git 指令&&
: 使用 && 分隔多个指令可在同一行顺序执行多个 git 指令,但是如果前一个指令执行失败,后面的指令不会执行- git log –oneline –graph –decorate –all 查看所有分支的提交记录
初始化仓库相关
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
| # 添加远程仓库
git remote add origin xxxx
# 查看本地配置文件
git config --global -l
#修改信息
git config --global user.name "xxx"
git config --global user.email "xxx"
# 提交
git push origin +分支名称
# 设置代理,端口自定义,我用的 clash 默认端口 7890
git config --global http.proxy http://127.0.0.1:7890
git config --global https.proxy https://127.0.0.1:7890
# 取消代理
git config --global --unset http.proxy
git config --global --unset https.proxy
|
修改历史版本
1
2
3
4
5
| # 修改最近一次 commit信息
commit --amend
# rebase 修改最近三个 commit
git rebase -i HEAD~3
|
分支相关
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
| # 查看当前分支
git branch
# -a 查看所有分支
git branch -a
# 删除分支,注意需要切换到其他分支再删除
git branch -d 分支名
# 切换分支
git checkout 分支名
# 从当前所在分支下创建并进入 dev 分支
git checkout -b dev
# push 该分支
git push origin dev
# 更新远端分支到本地(不合并)
git fetch origin dev
# 合并远端分支
git merge origin/dev
git pull = git fetch + git merge
# 远端新建分支
git push origin nikuliu(本地):nikuliu(远端)
|
删除
1
2
3
4
5
6
7
| # 删除暂存区或分支上的文件, 但本地又需要使用, 只是不希望这个文件被版本控制
git rm --cached file_path
git commit -m 'delete remote somefile'
git push
# 放弃修改复写缓存区
git reset --hard branch
|
回档
HEAD
关键字指的是当前分支最末梢最新的一个提交。也就是版本库中该分支上的最新版本。
submodule 使用
克隆一个带有 submodule 的仓库。
1
2
3
4
5
6
7
8
9
10
| git clone url
cd 进入仓库路径
# --recursive 对嵌套 submodule 也可执行
git submodules update --init --recursive
# 查看当前子模块信息
git submodule status
# 更新 submodule 到最新提交版本
git submodule update --remote
|
--recursive
参数用于下载子模块
创建克隆后,使用默认设置初始化其中的所有子模块。这是 相当于在克隆完成后立即运行 git submodule update –init –recursive 完成的。
更新 submodule 到指定 tag/commit
1
2
3
4
5
6
| cd submodule_directory
git checkout v1.0
cd ..
git add submodule_directory
git commit -m "moved submodule to v1.0"
git push
|
Git rebase
可以整合多个提交记录,注意不要合并已经 push 的记录。
merge 操作会生成一个新的节点,之前提交分开显示。 而 rebase 操作不会生成新的节点,是将两个分支融合成一个线性的操作。
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
| # 从当前提交位置开始,合并最近三条 commit 记录
git rebase -i HEAD~3
GNU nano 4.8 /home/niku/code/git-text/test-clone/.git/rebase-merge/git-rebase-todo
pick b21522a rebase2
# 变基 54b6e35..b21522a 到 54b6e35(1 个提交)
#
# 命令:
# p, pick <提交> = 使用提交
# r, reword <提交> = 使用提交,但修改提交说明
# e, edit <提交> = 使用提交,进入 shell 以便进行提交修补
# s, squash <提交> = 使用提交,但融合到前一个提交
# f, fixup <提交> = 类似于 "squash",但丢弃提交说明日志
修改 s 参数表示将下面的 commit 合并到上面的版本
|
Git 使用中常见问题
换行符问题 LF/CRLF
在 windows 下使用 Git 一定会碰到的一个问题,在我们用 git add .
命令时会提示 warning: LF will be replaced by CRLF
。
CR(CarriageReturn),LF(LineFeed),CR/LF
是不同操作系统上使用的换行符,具体如下:
Windows 平台: 使用回车 CR
和换行 LF
两个字符来结束一行,即回车+换行 \r\n
;
Mac 和 Linux平台:只使用换行 LF
一个字符来结束一行,即 \n
;
解决方法:
1
| git config --global core.autocrlf true
|
这样在你提交时自动地把回车 CRLF
转换成换行 LF
,而在检出代码时把换行 LF
转换成回车 CRLF
。
多主机一个分支
多主机公用一个仓库是可能会遇到这个问题,直接按照提示使用如下指令做关联即可。或者每次都这样提交 git push origin branch
。
1
| git push --set-upstream origin main
|
常见问题
- 分离 HEAD 情况下更改代码时,最好预先创建新分支。
git commit message 规范
type
说明 commit 的类型。
- feat:新功能(feature)
- fix:修补 bug
- docs:文档(documentation)
- style: 格式(不影响代码运行的变动)
- refactor:重构(即不是新增功能,也不是修改 bug 的代码变动)
- test:增加测试
- chore:构建过程或辅助工具的变动
- build:改变构建流程,新增依赖库、工具、构造工具的或者外部依赖的改动
- perf:提高性能的改动
- ci:自动化流程配置修改、与CI(持续集成服务)有关的改动
- revert:回滚到上一个版本,执行git revert打印的message
subject
commit 内容简要描述,字数不宜过长。第一人称,动词现在时开头。
cherry pick
指从一个分支中选择一个提交,并将其应用到另一个分支。例如 merge 和 rebase 通常会将许多提交应用到另一个分支。
git 提效小技巧 & 规范
善用 diff
工作中经常会遇到一个场景,每天到工位后快速查看昨天工作分支的代码改动,每次打开浏览器查看 gitlab 或者 github 的提交记录,这样效率很低。
- 单
git diff
对比的是工作区和暂存区的差异 git diff --staged
对比的是暂存区和最后一次 commit 的差异git diff HEAD
将已暂存和未暂存的改动与上次提交的差异进行比较git diff <branch_name1> <branch_name2>
对比两个分支的差异,改变分支顺序结果可能不同git diff <commit_hash> <commit_hash>
对比两个 commit 的差异- 指定文件,在上述命令后加上文件名即可
git diff <commit_hash> <commit_hash> file_name
git diff mycommit^ mycommit
某次提交和其父提交的差异,^
表示父提交git diff HEAD~n HEAD
当前提交和 n 次提交前的差异git diff --name-only
只查看更改的文件路径
使用 delta 替代默认的 diff 工具,更加清晰。
delta
如下是我的 delta 配置,退出后会将 diff 记录保留到终端
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
| [core]
pager = delta --dark -n --navigate --pager 'less -iRFX'
[interactive]
diffFilter = delta --color-only --features=interactive
[delta]
features = decorations
[delta "interactive"]
keep-plus-minus-markers = false
[delta "decorations"]
commit-decoration-style = blue ol
commit-style = raw
file-style = omit
hunk-header-decoration-style = blue box
hunk-header-file-style = red
hunk-header-line-number-style = "#067a00"
hunk-header-style = file line-number syntax
|
远端与本地分支
创建新分支时,可直接指定上游分支,这样在 push 时就不用指定远端分支了。通常情况下,远端分支与本地分支同名。
1
| git checkout -b new_branch origin/new_branch
|
查看所有本地分支对应的上游分支信息:
指定一个已存在的分支的上游分支:
1
| git branch --set-upstream-to=origin/branch_name branch_name(local)
|
git 同步远端主干分支
建议直接在当前开发分支上执行 git pull origin master
,这样可以直接拉取合并远端主干分支的代码。不要直接切换到主干分支再合并,这样会导致分支关系错乱。
情景案例
1. master 分支更新了,如何同步主干最新代码到本地
开发分支同步 master 代码,实际操作有两种方式:
- 当前在开发分支,直接
git pull origin master
(推荐方式,commit 清晰简洁高效) - 在本地 master 分支(不推荐)多处理一次。
a. 先 pull origin master
(如果有冲突处理冲突,进行一次 commit)
b. 再将本地 master merge
到本地开发分支
开发分支同步完 master 需 push 到远端一次,以防协作开发由于代码同步不及时导致的问题。
2. 修改文件名
情景:不小心把文件名写错了或者文件放错了目录,并且已经进行了提交。
方法一:通过 git mv 命令修改(git mv 会同时修改本地文件名和 git 中记录的文件)
1
2
| git mv old_filename new_filename
git commit -m "fix: Renamed file from old_filename to new_filename"
|
方法二:手动修改错误文件再 git rm
手动修改本地文件名
1
2
| git add new_filename
git rm old_filename
|
3. 撤回合并提交:
情景:不小心将 release 分支合并到本地开发分支。
如果在本地,还没有推到远程仓库
git reset –hard HEAD~1 // 丢弃所有未提交的更改和暂存区的内容
–hard // (1)同时丢弃工作区改动
–soft // (2)只丢弃暂存区,不改动当前文件
4. 查看某个文件的详细提交历史与代码 diff
git log -p xxx.go
-p 是 –patch 的缩写,会显示该文件每个提交的代码 diff
5. git stash 临时保存未提交的代码
情景:
假设你正在开发一个功能 f1,突然来了个更急的需求 f2,现在这个功能的代码仅仅开发了一半,此时不完整且没测试,不能commit,但是这个代码又确实有用,这个时候就可以把代码放到堆中。
具体流程:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
| # 1. 当前开发分支 feat/A,git stash 临时保存未提交的代码
git stash save "WIP: f1 partial implementation"
# (默认只会保存被追踪过的文件改动,如果有新增的文件可使用 git stash -u )
# 这时候工作区会和上一次的 commit 相同,代码已经保存到 stash 里。
git stash list # 可以查看所有存储的点
# 2. 切到其他分支工作
git checkout feat/B
git add、git commit、git push
# 3. 开发完成切回源分支
git checkout feat/A
# 恢复之前没写完的代码,并删除 stash 存储的内容
git stash pop
# 4. 继续开发
|
6. 删除或修改分支名
比如某个分支名写错了,或者不想用了。
1
2
3
4
5
| git branch -d <branch_name> // (1)删除本地分支
git push origin -d <branch_name> // (2)删除远程分支
git branch -m new-branch-name // (1)修改当前所在的分支名
git branch -m old-branch-name new-branch-name // (2)修改其他分支名
|
7. 撤回未 add 的代码(未添加到暂存区)
1
2
| git checkout -- . // (1)撤回全部改动
git checkout -- <file_path> //(2)撤回某个文件的改动
|
8. 撤回已经 add 但未提交文件改动
1
2
3
| git restore --staged filename // (1)从暂存区移除,不撤回工作区的改动
git restore --staged --worktree filename // (2)从暂存区移除,同时将工作区改动也撤回
|
9. 修改上一次的 commit message & 向上一次提交追加新的改动
使用场景:
- 还没有推送提交到仓库
- 打算修改 commit 信息
- 发现写错了打算将新的改动加到上次的 commit 中,不新增 commit
1
2
3
4
| git commit --amend // (1) 在启动的编辑器里修改 commit 信息
git add file1 file2
git commit --amend // (2) 追加这两个文件的改动到删一次 commit
|
10. 查看当前分支某个文件和 master 最新提交的 diff
1
2
| git fetch origin // 拉取远端最新变更
git diff origin/master -- internal/biz/accountmonitor/repository/panel_metric.go
|