Git 笔记本

本篇博客介绍 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

回档

1
git reset --hard 版本号

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 参数用于下载子模块

1
git clone --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
diff 和 show 和 log -p 的区别

有兴趣的读者可以试试如下命令的区别:

远端与本地分支

创建新分支时,可直接指定上游分支,这样在 push 时就不用指定远端分支了。通常情况下,远端分支与本地分支同名。

1
git checkout -b new_branch origin/new_branch

查看所有本地分支对应的上游分支信息:

1
git branch -vv

指定一个已存在的分支的上游分支:

1
git branch --set-upstream-to=origin/branch_name branch_name(local)

git 同步远端主干分支

建议直接在当前开发分支上执行 git pull origin master,这样可以直接拉取合并远端主干分支的代码。不要直接切换到主干分支再合并,这样会导致分支关系错乱。

情景案例

1. master 分支更新了,如何同步主干最新代码到本地

开发分支同步 master 代码,实际操作有两种方式:

  1. 当前在开发分支,直接 git pull origin master (推荐方式,commit 清晰简洁高效)
  2. 在本地 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
0%