/

【Git 权威指南】读书笔记 - 和声

主要内容:【Git 协议与工作协同】、【冲突解决】、【Git 里程碑】、【Git 分支】、【远程版本库】、【补丁文件交互】

Git 协议与工作协同

Git 支持的协议

SSH、GIT、HTTP、HTTPS、FTP、FTPS、RSYNC 及前面已经看到的本地协议。

SSH 协议:

ssh://[user@]example.com[:port]/path/to/repo.git/

[user@]example.com:path/to/repo.git/

GIT 协议,最常用的只读协议:

git://example.com[:port]/path/to/repo.git/

HTTP[S] 协议:

http[s]://example.com[:port]/path/to/repo.git/

强制非快进式推送

$ git push -f

强制推送,会强制刷新服务器中的版本。

禁止非快进式推送

$ git --git-dir=/path/to/repos/shared.git config receive.denyNonFastForwards true

冲突解决

拉回操作中的合并

git pull = git fetch + git merge

合并策略

Merge Strategis

Git 合并操作支持很多合并策略,默认会选择最适合的合并策略。例如,和一个分支进行合并时会选择 recursive 合并策略,当和两个或两个以上的其他分支进行合并时采用 octopus 合并策略。

git merge [-s <strategy>] [-X <strategy-option>] [<commit>...]

This option forces conflicting hunks to be auto-resolved cleanly by favoring our version.

$ git merge -s recursive -X ours [<commit>...]

Merge branch obsolete into the current branch, using ours merge strategy:

$ git merge -s ours obsolete

Git 里程碑

显示里程碑

$ git tag

显示说明。-n<num> 显示最多 <num> 行里程碑的说明:

$ git tag -n1

使用统配:

$ git tag -l v1.*

查看提交对应的里程碑及其他引用:

$ git log --oneline --decorate

创建里程碑

当创建了里程碑 mytag 后,会在版本库的 .git/refs/tags 目录下创建一个新文件。实际上指向的是一个提交:

$ git tag mytag

$ git cat-file -t mytag
commit

带说明的 tag 指向的不再是一个提交,而是一个 tag 对象:

$ git tag -m "My first annotated tag." mytag2

$ git cat-file -t mytag2
tag

为里程碑对象添加 GnuPG 签名:

$ git tag -s -m "My first GPG-signed tag." mytag3

删除里程碑

$ git tag -d mytag
Deleted tag 'mytag' (was 60a2f4f)

共享里程碑

创建的里程碑,默认只在本地版本库中可见,不会因为对分支执行推送而将里程碑也推送到远程版本库。

将 mytag 里程碑共享到上游版本库:

$ git push origin mytag

所有里程碑全部推送到远程版本库:

$ git push origin refs/tags/*
  • 里程碑共享,必须显式的推送。
  • 执行获取或拉回操作,自动从远程版本库获取新里程碑,并在本地版本库中创建。
  • 如果本地已有同名的里程碑,默认不会从上游同步里程碑,即使两者里程碑的指向是不同的。

删除远程版本库的里程碑

$ git push origin :mytag2

里程碑命名规范

语义化版本 2.0.0

版本格式:主版本号.次版本号.修订号,版本号递增规则如下:

  • 主版本号:当你做了不兼容的 API 修改,
  • 次版本号:当你做了向下兼容的功能性新增,
  • 修订号:当你做了向下兼容的问题修正。

先行版本号及版本编译信息可以加到 主版本号.次版本号.修订号 的后面,作为延伸。

Git 分支

分支是代码管理的利器。如果没有有效的分支管理,代码管理就适应不了复杂的开发过程和项目的需要。

分支命令概述

# 显示
1. git branch

# 创建
2. git branch <branchname>
3. git branch <branchname> <start-point>

# 删除,-d 在删除分支 <branchname> 时会检查所要删除的分支是否已经合并到其他分支中,否则拒绝删除。
4. git branch -d <branchname>
5. git branch -D <branchname>

# 重命名,-m 如果版本库中已经存在名为 <newbranch> 的分支,拒绝执行重命名,而 7 会强制执行。
6. git branch -m <oldbranch> <newbranch>
7. git branch -M <oldbranch> <newbranch>

创建并切换到新分支:

git checkout -b <new_branch> [<start_point>]

分支变基

  • master
  • dev(开发完成,领先 master)
# 先切换到 dev
$ git checkout dev

# 变基操作
$ git rebase master

# 遇到冲突,解决冲突

# 添加到暂存区
$ git add -u

# 继续变基
$ git rebase --continue

# 直接将 dev 推送到远程 master
$ git push origin dev:master

# 切换到 master
$ git checkout master

# 拉取最新代码
$ git pull

# 删除 dev 分支
$ git branch -d dev

远程版本库

远程分支

查看远程分支:

$ git branch -r

在向远程版本库执行获取操作时,不是把远程版本库的分支原封不动地复制到本地版本库的分支中,而是复制到另外的命名空间。

远程分支不是真正意义上的分支,是类似于里程碑一样的引用。如果针对远程分支执行检出命令,会看到大段的错误警告。

远程分支类似于里程碑,如果检出就会使得头指针 HEAD 处于分离头指针状态。实际上除了以 refs/heads 为前缀的引用之外,如果检出任何其他引用,都将使工作区处于分离头指针状态。如果对远程分支进行修改就需要创建新的本地分支。

分支追踪

为了能够在远程分支 origin/hello-1.x 上进行工作,需要基于该远程分支创建本地分支:

$ git checkout hello-1.x

从远程分支创建本地分支,自动建立了分支间的跟踪,而从一个本地分支创建另外一个本地分支则没有。

hello-1.x 分支中创建新的本地分支 hello-jx,并与远程建立联系。

$ git checkout -b hello-jx hello-1.x
cat .git/config

远程版本库

# 设置
$ git remote add new-remote file:///path/to/repos/hello-user1.git

# 修改 url
$ git remote set-url new-remote file:///path/to/repos/hello-user2.git

# 单独修改 push 地址
$ git remote set-url --push new-remote /path/to/repos/hello-user2.git

# 修改 版本库名称
$ git remote rename new-remote user2

# 当注册了多个远程版本库并希望获取所有远程版本库的更新
$ git remote update

# 如果某个远程版本库不想在执行 git remote update 时获得更新
$ git config remote.user2.skipDefaultUpdate true

# 删除
$ git remote rm

PUSH 和 PULL 操作与远程版本库

在执行 git pull 操作的时候可以通过参数 --rebase 设置使用变基而非合并操作,将本地分支的改动变基到跟踪分支上。为了避免因为忘记使用 --rebase 参数导致分支的合并,可进行设置:

$ git config branch.<branchname>.rebase true

这样在遇到冲突(本地和远程分支出现偏离)的情况下,会采用变基操作,而不是默认的合并操作。

如果为本地版本库设置参数 branch.autosetuprebase ,值为 true,则在基于远程分支建立本地追踪分支时,会自动配置 branch.<branchname>.rebase 参数。

分支和里程碑的安全性

  • reflog 记录对分支的操作历史。默认创建的带工作区的版本库都会包含 core.logallrefupdatestrue 的配置,这样在版本库中建立的每个分支都会创建对应的 reflog。但是创建的裸版本库默认不包含这个设置,也就不会为每个分支设置 reflog。可能因为分支误操作导致数据丢失,可以考虑为裸版本库添加此配置。

  • 关闭非快进式提交。配置 receive.denyNonFastForwards 设置为 true,则禁止一切非快进式推送。更好的方法是通过架设基于 SSH 协议的 Git 服务器,配置强制提交的用户权限。

  • 关闭分支删除功能。配置 receive.denyDeletes 设置为 true,则禁止删除分支。更好的方法是:配置分支删除的用户权限。

补丁文件交互

将最近三个提交转换为补丁文件:

$ git format-patch -s HEAD~3..HEAD

-s 会在导出的补丁文件中添加当前用户的签名。

应用补丁:

$ git am user1-mail-archive

git apply 可以应用一般格式的补丁文件,但是不能执行提交,也不能保持补丁中的作者信息。

Reference: