本文主要为教材的笔记,目前已拜读到
Rerere
。
TIPS: 文中的PR
代表pull request
;fork等于‘派生’。
一些重要概念
一些语法
杂项
<branch>@{<筛选条件>}
, 见
指定提交
我们有许多引用(HEAD,branch,tag,commitID)来找到这些引用所指向的提交,而有更多的历史提交该如何索引到呢?
下面的例子我都将用HEAD
代表引用
- 指定单个提交:
^3
是第三个爸爸(前提是对应提交有多个爸爸,0是自己,1和置空
是爸爸,2是二爸,…);
~3
是第三层长辈(0是自己,1和置空
是爸爸,2是爷爷,…)。
- 举例:
HEAD^
, 指向该引用的父提交
。HEAD^2
, 指向第二个父提交。 - 举例:
HEAD~2
代表“第一父
提交的第一父
提交”,也就是“祖父提交” - 举例:
HEAD~3^2
HEAD的曾祖父的第二个父亲。
- 指定提交区间
- 双点:选出在一个分支中而不在另一个分支中的提交
git log experiment..master
显示在 master 分支中而不在 experiment 分支中的提交- 如果你留空了其中的一边, Git 会默认为 HEAD
- 常用: 查看你即将推送到远端的内容 $
- 多点:在任意引用前加上 ^ 字符或者 –not 来指明你不希望提交被包含其中的分支。
三者等价 git log refA..refB
git log ^refA refB
git log refB --not refAgit log refA refB ^refC
所有被 refA 或 refB 包含的但是不被 refC 包含的提交。 - 三点:选择出被两个引用 之一 包含但又不被两者同时包含的提交。
git log master...experiment
比较’后者分支’和’两者公共祖先
分支’之间的差异。
三棵树
典型的Git 的思维框架。commit, clone, checkout
等命令都会使这三棵树的内容一致。
Working Directory
Index
- 即
暂存区
,预期的下一次提交的快照。
- 即
HEAD
- 该分支的
最后一次
提交的快照,下一次提交的父结点。本地仓库(commit快照的集合、版本库)。
- 该分支的
引用
是指向 Git 数据库中某个提交(commit)的指针
。查看引用的命令见。
· 本地的引用:
- HEAD类
- 【HEAD】:(必有!
.git/HEAD
)是指向当前工作目录所在分支的指针。一般HEAD指向分支,所以它是指向指针的指针
。 - detached HEAD: (可有)意味着 HEAD 指向的是一个特定的提交(commit),而不是分支。所以它是
指向对象的指针
。在这种状态下的提交将不属于任何分支。为此建议新建一个分支来承接这个提交对象。 - FETCH_HEAD:(可有,
.git/FETCH_HEAD
)指向最近一次从远程存储库获取的提交。 - ORIG_HEAD:(可有,
.git/ORIG_HEAD
)保存某些操作前的 HEAD 的值,用于回滚。它通常用于在执行危险的操作(例如git reset)之前保存 HEAD 的位置。
- 【HEAD】:(必有!
- 分支(branch): (如
.git/refs/heads/master
)当本地分支追踪着一个远程分支,则此时本地分支叫
跟踪分支
,远程的叫上游分支
。 - 标签(tag):(如
.git/refs/tags/1.0.0
) - PR型分支: 本地不会存在这种分支
· 远程的引用:(注意,一个版本库可以连接多个remote)
(HEAD不会存在于远程)
远程分支:(如
.git/refs/remotes/master
)详见远程标签。
命令图解:
Pull-Request
Git初始化和配置
初始化
初始化就是把一个目录变成git仓库
- 方法1:
git init
- 方法2:
git clone <url>.git
使用前的配置
git config
有3个级别:--system
系统级;--global
用户级;--local
本地级。
- 显示所有三级配置:
git config --list --show-origin
- 配置用户:
git config --global user.name "John Doe"
,git config --global user.email [email protected]
忽略文件详解
其他有用配置
- 设置git别名:简化常用git命令的输入复杂性, 如,
- 撤销已暂存的文件
git reset HEAD -- <file>
->git unstage <file>
:$ - 查看最后一次提交日志:
git log -1 HEAD
->git last
:$ - 查看图形化、简化日志:
git log --graph --oneline --all
->git sim-log
:$ - 外部命令变成git子命令(叹号):
gitk
->git visual
:$
- 撤销已暂存的文件
- 暂存密码几分钟:用于push时Git服务器询问用户密码的临时缓存。
git config --global credential.helper cache
- pull时默认使用变基:
git config --global pull.rebase true
Git命令
更多命令移步官方文档
基础类
git add
功能:跟踪新文件,把已跟踪的文件放到暂存区,合并时把有冲突的文件标记为已解决状态等。
本质:精确地将内容添加到下一次提交中
git commit
提交动作的意义:是对你的工作目录作一次快照
,以后可以回到这个状态,或者进行比较。
命令 | 说明 |
---|---|
git commit -m "..." |
提交 |
git commit -a -m "..." |
提交并跳过缓存,即省略git add 。但要小心一些并不需要的文件。 |
git commit --amend |
追加提交,而不新增快照。详见 |
git rm
- git删除文件的原理:仓库的快照是没有删除一说的,只有更新。因此git的删除实际执行手段是:
从暂存区移除,然后提交
。 - git删除文件的效果:工作区、暂存区都会被删掉。如果接着commit后,新快照里相应文件也就没有了。
- 注意事项:当文件在
暂存区
和最新快照
的内容有不同,则会报错并建议--cached
或-f
命令 | 说明 |
---|---|
git rm log/\*.log |
工作区、暂存区都会被删掉。这里的文件是glob模式匹配 |
git rm --cached <file> |
只删除暂存区 |
git rm -f <file> |
强制删除工作区、暂存区 |
git mv
-功能:移动文件、重命名文件。
-原理:等效于三个命令mv fileA fileB && git rm fileA && git add fileB
, 即实现了在暂存区
内的文件移动。
查看类
git status -s
git show
git show <commit/branch/tag> --stat
git show <brancn>@{<筛选条件>}
用来筛选查看结果,如HEAD@{5}
是前5次的HEAD历史;master@{yesterday}
是你的 master 分支在昨天的时候指向了哪个提交。
git reflog
查看HEAD所指向的历史
引用日志只存在于本地仓库,它只是一个记录你在 自己
的仓库里做过什么的日志。
git diff
命令 | 说明 |
---|---|
git diff |
比较的是文件在暂存区和工作目录的差异, a/ 是暂存区,b/ 是工作目录。 |
git diff --staged |
比对文件在最后一次提交和暂存区的差异, a/ 是本地仓库的最后一次提交,b/ 是暂存区 |
git diff master [feat1] |
比较两个分支/提交 之间的差异(分支名也可换成提交对象哈希值), 若没有第二个参数,则默认第二个参数是当前分支 |
git diff master..feat1 |
比较两个分支之间的差异, 等价于上行命令。详见 |
git diff master...feat1 |
比较’后者分支’和’两者公共祖先 分支’之间的差异,等价于git diff $(git merge-base feat1 master) feat1 |
git difftool --tool-help |
看你的系统支持哪些 Git Diff 插件(包括命令行形式的、窗口形式的 TODO:) |
其他参数:
--cached
--check
检查代码错误
git log
git log --oneline --decorate --graph --all
查看各个分支当前所指的提交对象
、分叉历史。git log b1 --not master
查看b1分支下不与master重合的提交对象。等价于git log master..b1
- 其他参数
- –abbrev-commit 显示
提交对象
的前7位 - -g 来查看类似于 git log 输出格式的
引用日志信息
。 --no-merges
不要显示合并提交
, 即三方合并场景下自动生成的新提交对象
。--patch/-p
显示每次提交所引入的差异/具体修改。--show-signature
显示签名信息-2
,--since=2.weeks
限制输出选项,更多--stat
看到每次提交的简略统计信息--pretty=oneline/ short/ full/ fuller/ format:"%h - %an, %ar : %s"
可以使用不同于默认格式的方式展示提交历史。具体格式占位符见
- 更多常用选项
- –abbrev-commit 显示
- log中的搜索功能,见
git grep
在工作目录中搜索。详见搜索
git blame
显示每一行是由谁提交的。
查看各种指针
指针即引用,包含branch、tag等。
查看分支
git branch -a
, 列出本地和远程的所有分支,即.git/refs/heads/
和.git/refs/remotes/
下的所有文件。
更详细的branch使用见。
查看所有引用
git ls-remote [<url>]
,列出远程url下的所有引用。
低级命令,显示任意一个仓库(url)中的所有引用。其中url也可换成url-shortname。获取到的列表形如:
|
解释:
HEAD
指向工作目录所在的提交对象;refs/heads/
下都是分支;refs/tags/
下都是标签;- (注:为什么没有
refs/remote/
, 毕竟本地的.git/refs/
下一般都有remote/
目录?因为ls-remote
命令的对象是远程仓库,不是本地仓库,远程仓库当然没有remote
啦。) refs/pull/
下都是PR分支refs/pull/<pr#>/head
: 指向PR过程中派生仓库里合并请求分支
中的最后一个提交快照。refs/pull/<pr#>/merge
: 它表示如果你在网站上按下“合并”按钮,将产生的提交。这可以让你在点击按钮之前测试合并。
清理和重置类
清理 clean
贮藏 stash
重置 reset
!危险的命令, reset加'--hard'更危险。重置的原理
- 它让HEAD在一个分支内的提交快照之间进行切换;
- 有能力改变分支的提交历史;
- 有能力恢复工作区的文件:从
暂存区
恢复,或从历史提交
恢复。 - 可实现
压缩提交
的效果。
git reset HEAD~
==git reset --mixed HEAD~
重置整个提交,即HEAD和Index区都回退到了指定提交,工作区不变。git reset HEAD [--] <path/to/file>
==git reset --mixed HEAD <path/to/file>
重置单个文件, 相当于add
的逆向。HEAD也可换成其他任意历史提交。
参数:
--soft
: 轻度作用是只让HEAD切换到
目标提交,暂存区
和工作区
不变。相当于git commit
的逆向。--mixed
: 中度【默认参数】作用是让HEAD不动,并把HEAD更新到
Index区,工作区
不变。相当于add和commit
的逆向。--hard
: !危险 作用是把Index区更新到
工作区。--patch
: 部分地重置
与其他命令的对比
- vs. rebase的压缩提交,可以实现更简单的
压缩提交
git reset --soft HEAD~2
git commit - vs. checkout
- 当操作对象是分支:
git checkout [branch]
类似git reset --hard [branch]
- checkout对工作目录更安全,它会尝试简单合并新变化。
- reset中HEAD会带着branch1一起移动,而 checkout 只会移动 HEAD。
- 当操作对象是文件:
git checkout -- file1
类似git reset --hard [branch] file
(如果 reset 允许你这样运行的话)- checkout对工作目录不安全,它会把工作区也回退了。
- 参考速查表
- 当操作对象是分支:
- vs. rebase的压缩提交,可以实现更简单的
其他重置命令:
git checkout -- <file>
!危险,撤销工作区的文件所做过的修改。另参考git checkout HEAD <file>
从最新提交恢复文件
撤销 revert
git revert
命令是一个安全的撤销操作,它不会改变提交历史。相反,它会在提交历史中创建一个新的提交,用来描述撤销的操作。
标签类
git tag
- 查看标签:
git tag
,git tag -l "v1.8.5*"
git show
看到标签信息和与之对应的提交信息 - 打标签:给仓库
历史中
的某一个提交打上标签,以示重要。分类如下,- 轻量标签(lightweight):只是某个特定提交的引用
git tag v1.4
- 附注标签(annotated):是存储在 Git 数据库中的一个完整对象
git tag -a v1.4 -m "my version 1.4"
- 轻量标签(lightweight):只是某个特定提交的引用
- 推送标签(类似推送分支)
git push <remote> <tagname>
推送特定标签到远程仓库,类似推送分支。git push <remote> --tags
推送远端没有的所有标签
- 删除标签
git tag -d <tagname>
本地的git push <remote> :refs/tags/<tagname>
远程的, 将冒号前面的空值推送到远程标签名,从而高效地删除它git push <remote> --delete <tagname>
远程的, 方法2, 更直观
- 检出标签
git checkout 2.0.0
查看某个标签所指向的文件版本缺点:使你的仓库处于“分离头指针(detached HEAD)”的状态,此时要想将修改提交到标签里是不可能的,得创建新分支来实现
git checkout -b version2 v2.0.0
- 其他参数:
- ☆ 加签名:
git tag -s v1.5 -m '...'
(如何配置与使用签名请参考)
- ☆ 加签名:
git describe <branch>
为提交生成构建号
。由于每个提交的ID是一串不易读的SHA-1值,该命令实现了为提交附上一个可读的名称。
- 构建号的格式:
<最近的标签名>-<自该标签之后的提交数目>-g<8或10位的SHA-1值>
(e.g.v1.6.2-rc1-20-g8c5b85c
)。 - 前提条件:最近的那个tag的生成需要使用 -a 或 -s 选项。
远程相关
见
含remote fetch pull push
等。
分支相关
git branch
git checkout
checkout(检出)的作用是实现在不同的分支或文件状态之间切换
,状态切换意味着工作目录内容的改变。因此一般在检出前,需留意你当前的修改需不需要被提交(commit)、临时保存(stash)等。
checkout能切换分支,恢复文件等。
☆
切换分支
:git checkout [-b branch2] [branch1]
- 形式1:
git checkout branch1
,切换到项目版本库中名为branch1的分支。注意:(先用
git branch -a
查看所有分支的列表)- 当branch1存在于本地分支列表(形如
branch1
),则可以直接切换
过去,和git switch branch1
等价; - 当branch1
只
存在于远程分支列表(形如origin/branch1
),即此时该远程跟踪分支
在本地没有跟踪分支
:
- 若用
git checkout origin/branch1
,则HEAD称为detached HEAD
,因此建议使用下一行的方法; - 若用
git checkout branch1
,则该命令是git checkout -b branch1 origin/branch1
的简化版(见形式3)。
- 当branch1不存在于整个分支列表,则报错。
- 当branch1存在于本地分支列表(形如
- 形式2:
git checkout -b branch2
,基于当前分支,新建并切换到分支branch2。 - 形式3:
git checkout -b branch2 branch1
,基于指定的分支branch2,新建并切换到分支branch1。☆最常用的场景:
git checkout -b branch1 origin/branch1
-> 太过于常用,简化版本:git checkout --track origin/branch1
-> 进一步简化:git checkout branch1
(前提:本地没有名为branch1的分支,而远程仓库刚好有。TODO: 连接的是多个远程仓库怎么办)
- 形式1:
-
- 工作目录中的file1被修改过,此时恢复优先级为:暂存区的file1 -> 提交对象中的file1。
一些重要参数
特性对比:checkout vs. reset,详见
- HEAD的特性:checkout让HEAD在分支之间切换;reset让HEAD在一个分支里的
提交快照之间
切换。
- HEAD的特性:checkout让HEAD在分支之间切换;reset让HEAD在一个分支里的
git switch
switch是对checkout的切换分支
方面的功能的改进。git switch <branch-name>
切换到一个已有的本地分支;或切换到一个本地没有但远程有的分支,它会自动创建并设定好远程追踪。
- 更多参数
-c <branch>
创建并切换到一个新分支。
git merge
常用
请移步。
高级:冲突场景下的
冲突场景形如:,针对该场景,我们有以下解决手段:
手段1-不想处理
git merge --abort
简单地退出合并。它会尝试恢复到你运行合并前的状态。记得事先把工作区stash或commit。
手段2-忽略空白
git merge -Xignore-space-change branch1
如果你的团队中的某个人可能不小心重新格式化空格为制表符或者相反的操作,这会是一个救命稻草。
手段3-手动处理再合并
- step1 进入合并冲突状态
- step2 拿到3个版本的文件的拷贝:你的版本、对方的版本、你们最近共同祖先的版本
背景知识:Git 在索引中存储了所有这些版本,在 “stages” 下每一个都有一个数字与它们关联。 Stage 1 是它们共同的祖先版本(“commin”),stage 2 是你的版本(“ours”),stage 3 来自于 MERGE_HEAD,即你将要合并入的版本(“theirs”)
- git show+特殊语法
git show :1:hello.rb > hello.common.rb
git show :2:hello.rb > hello.ours.rb
git show :3:hello.rb > hello.theirs.rb - ls-files -u 底层命令
$ git ls-files -u
100755 ac51efdc3df4f4fd328d1a02ad05331d8e2c9111 1 hello.rb
100755 36c06c8752c78d2aff89571132f3bf7841a7b5c3 2 hello.rb
100755 e85207e04dfdd5eb0a1e9febbc67fd837c44a1cd 3 hello.rb
- git show+特殊语法
- step3 手动修复我的、或对方的版本的文件
- step4 针对指定文件的合并
git merge-file -p hello.ours.rb hello.common.rb hello.theirs.rb > hello.rb
- 其他步骤:查看3个版本文件内容的差异
- 查看合并的结果与他们那边有什么不同,可以运行
git diff --theirs [-b]
。这里’-b’是去除空白。 - 查看合并的结果与你们这边有什么不同,可以运行
git diff --ours
- 查看文件在两边是如何改动的,
git diff --base
- 查看合并的结果与他们那边有什么不同,可以运行
- step5 清理不用的文件
git clean -f
清理我们为手动合并而创建但不再有用的额外文件。
手段4-检出冲突
常见于两个分支距离共同祖先很远的情况下。
见
查看手段1-合并日志
git log --oneline --left-right --merge
显示合并提交的历史记录,并标记每个提交来自哪个分支。
查看手段2-组合式差异格式
- 产生合并冲突后:
直接运行的git diff
会给你一个相当独特的输出格式,见。 - 冲突修改后:
运行git diff
, 或git show
, 或git log --cc -p -1
, 可以查看冲突是如何解决的。
高级:撤销合并
法1:修复引用。
git reset --hard HEAD~
原理是回退分支指针。
缺点是它会重写历史,在一个共享的仓库中这会造成问题的。法2:还原提交。
git revert -m 1 HEAD
这里
1
表示合并行为中的主线分支
,如果是2
则表示并入分支
。
其效果为:(这里^M
等同于C6
)原理是不重写历史,只生成新提交,而这个新提交与合并前的
主线分支
一模一样。
缺点是在接下来无法正常合并,因此需要进一步处理:还原再还原
,即在上图的基础上执行git revert HEAD
,得到^^M
,他和M
是一模一样,然后才能再次执行正常的合并git merge topic
。如图所示:
高级:偏好型合并
使用参数-Xours 或 -Xtheirs
, 让Git 简单地选择特定的一边并忽略另外一边而不是让你手动解决冲突。
更严格的做法:git merge -s ours branch1
(用到再研究)
高级:子树合并
子树合并的思想是你有两个项目,并且其中一个映射到另一个项目的一个子目录,或者反过来也行。
用到再学。
git rebase
发布相关
其他类
git submodule: 管理 Git 子模块。
git fsck: 检查对象文件和引用的完整性。
管道命令
git中更加底层、低级的命令, 属于幕后的命令。
cat-file
git cat-file -p HEAD^
check-ignore
checkout-index
commit-tree
count-objects
diff-index
for-each-ref
hash-object
ls-files
git ls-files -s
# 显示出索引当前的样子。显示文件的SHA-1
-u
ls-tree
git ls-tree -r HEAD
merge-base
merge-file
git merge-file -p hello.ours.rb hello.common.rb hello.theirs.rb > hello.rb
更多参数:
--ours
--theirs
, 有冲突时直接指定选择一边。
read-tree
rev-list
rev-parse 查看branch1的SHA-1
show-ref
symbolic-ref
update-index
update-ref
verify-pack
write-tree
Git远程
一个本地仓库
可连接多个远程仓库
。
命令:查看、增删远程仓库
git remote -v
显示本地仓库连接的所有
远程仓库git remote show <remote>
查看某一个
远程仓库的更多信息git ls-remote <remote>
查看某一个
远程仓库下的所有远程引用(HEAD, tag, branch)的完整列表
git remote add <shortname> <url>
添加远程仓库git remote rename <shortname1> <shortname2>
修改一个远程仓库的简写名git remote remove <shortname>
移除一个远程仓库
命令:下载、拉取
(以下假设remote=origin
, branch=dev1
)
git fetch <remote> [<branch>]
git pull
等于fetch+merge。使用前提是已经设置好了跟踪的上游分支
,见这里的方法2。
更多命令:git pull <url> <branch>
临时把url下的branch进行拉取合并,避免了‘添加remote’等配置。
命令:推送
(下面假设remote=origin
, branch=dev1
)
git push <remote> <branch>
- 其原始命令为:
git push origin refs/heads/dev1:refs/heads/dev2
, 或git push origin dev1:dev2
, 即推送本地的dev1分支来更新/成为
远程的dev2分支牢记:冒号前是本地分支,冒号后是远程仓库;
注意:两个分支名字可同可不同。 - 注意【集中式工作流】:当多人推送到同一远程分支时,你必须先
抓取
他们的工作,再将其合并
进你的工作(这里抓取+合并
也可换成pull
),然后才能push
。
- 其原始命令为:
- push的更多参数
git push origin --delete dev1
: 删除远程的dev1分支git push -u origin dev1
:- -u即
--set-upstream-to
, 会将本地的“dev1”分支和指定了远程仓库的“dev1”分支关联
起来(即手动设置远程跟踪分支
的概念, 联系branch-u)。 - 以后在
dev1
环境下就可以用简化的“git push”命令来推送了(pull也是),不需要指定远程分支名字。 - 这样方便在一个仓库连接了
多个远程仓库
时,对push的默认配置。 - TODO: 引用规范
- -u即
- push标签
git push <remote> <tagname>
推送特定标签到远程仓库,类似推送分支。git push <remote> --tags
推送远端没有的所有标签
★ Git分支管理!
概述
- 原理解读:用对象的逻辑理解git原理:
- Git的分支,是术语
引用
的其中一种。其本质上仅仅是指向提交对象的可变指针
。 - HEAD是一个指针,指向当前所在的本地分支(可理解为
当前分支的别名
)
- Git的分支,是术语
一些命令
git branch
查看:
git branch
本地所有分支git branch -a
本地+远程所有分支。(查看当前仓库有哪些远程仓库,执行git remote -v
)-v
每个分支的最后一次提交的信息-vv
在-v
基础上有更多简笔信息:跟踪的上游分支、与上游的版本差异。
常用:$-u
设置上游分支,详见--merged [branch]
,--no-merged [branch]
只显示已经
/没有
合并到当前分支的分支--merged
下显示的非当前分支,可尽情删除,毕竟都合并过了。其他情况,也可强制删除-D
新建分支:
git branch <branch1> [base-branch]
- 当base-branch缺失,则默认基于当前branch来新建。
- 可为分支设置
命名空间
, 如branch1可写为james/branch1
。
更多命令:
git branch -h
git checkout
: 切换分支或还原工作目录中的文件。见git merge
: 将一个分支的更改合并到另一个分支。见git rebase
: 变基,将提交从一个分支移动到另一个分支。见
场景:分支的新建与合并
切换/检出命令的机理:让HEAD指向另一个的分支指针
,同时工作目录也恢复成对应的快照版本。
一般情况下,这些分支命令也可分开操作:
- 查看(
git branch
) - 新建(
git branch <dev1>
) - 切换/检出(
git checkout <dev1>
),当dev1在本地不存在时,该命令实际是把远程分支检出到本地新建的同名分支,同时设置好跟踪
- 先把
dev1
分支打扫干净:尽量把所有修改都形成快照,即commit。实在不行也可贮藏(stashing)和修补(amending)。 - 切换到主分支:
git checkout master
- 新建并切换到修复分支:
git checkout -b hotfix
- 修改、测试、提交
- 把修复分支合并到master:
git checkout master
,git merge hotfix
- 删除重复分支:
git branch -d hotfix
(因为此时master指针和hotfix指针,指向同一个提交对象。 )
- 修改、测试、提交
- 把开发分支合并到master:
git checkout master
,git merge dev1
此时,若master指向的快照不是dev1指向的快照的
直接祖先
,则git后台会寻找他俩的公共祖先
,做一个三方合并
, 三方合并会自动创建一个新提交对象C6
,如下图: - 删除开发分支:
git branch -d dev1
merge后,如果某文件有冲突,会提示合并失败。
- 定位产生冲突的文件:
git status
。 - 进入文件手动解决冲突:冲突代码会有明显的状态标识,我们需二选一、或自定义处理代码。
- 删除标识:
<<<<<<< , ======= , 和 >>>>>>>
- 标记文件为
冲突已解决
:git add file
- 最终合并提交。
分支开发工作流
长期分支
主题分支
远程分支
(下面假设remote=origin, branch=dev1)
★
远程跟踪分支
:
是远程分支状态的本地引用,无法移动(即无法在该分支基础上产生新的提交快照
)。形式为<remote>/<branch>
, 另外用git branch -a
命令可得到形式remotes/<remote>/<branch>
。- 如何产生:
git fetch <remote>
后抓取到了本地没有的新分支
就是。 - 想在远程跟踪分支上工作怎么办,有两种方法:
- 把
远程追踪分支
合并到本地当前分支:git merge origin/dev1
。 - 基于
远程追踪分支
在本地新建一个分支:git checkout -b dev1 origin/dev1
(超级常用的命令,具体使用见里的形式3。)此时,本地的dev1叫
跟踪分支
, 对应的那个远程的dev1叫上游分支
。
- 把
- 如何产生:
★
跟踪分支
/上游分支
:
从一个远程跟踪分支(上游分支)
检出一个本地分支(跟踪分支)
时自动创建的分支。有了’上游’和’跟踪’两个分支的对应,git pull
才能自动识别抓取、合并的对象; 且可用@{upstream}
或@{u}
来代替上游分支名,如git merge @{u}
->git merge origin/dev1
。
如何生成跟踪分支(及其上游分支):
变基与合并
同:都是整合分支的方法,两者执行结果是相同的。
异:merge下的提交历史复杂(多线),因为所有commit都保留了;rebase下的提交历史简洁(单线),因为会删除部分历史commit。
变基-rebase
- 变基的定义:即提取在
当前主题分支
experiment中引入的补丁和修改,移动到目标基底分支
master进行应用和提交。 - 变基的实质:
丢弃
一些现有的提交
,然后相应地新建
一些内容一样但实际上不同的提交
。 - 变基的原理:
一开始experiment指向C4,master指向C3。
-> 找到最近共同祖先
C2
-> 对比experiment对C2的所有修改(即一个/多个快照)并存为临时文件
;
-> 将experiment指向master所在的C3;
-> 在C3基础上依序应用临时文件
的修改,生成新快照C4'
(多个快照时,就是新快照链), experiment指向最新的那个快照;
-> master进行快进合并
完成变基。 - 命令解释:
git rebase master
, 把当前分支融入到master里。 - 操作步骤:
- 先在
主题分支
里实施变基:$ git checkout experiment
,$ git rebase master
这两个命令等价于
$ git rebase master(base分支) experiment(topic分支)
- 后在
目标分支
里完成变基:$ git checkout master
,$ git merge experiment
- 最后删除不用的分支:
$ git branch -d experiment
- 先在
- 优点:对比’merge’, 变基使得提交历史更加整洁——没有分叉。实际上,rebase就是把merge的
三方合并
情况改造成快进合并
。 - ☆风险:鉴于
变基
有丢弃提交的特性,如果提交存在于你的仓库之外,而别人可能基于这些提交进行开发,那么不要执行变基。- 风险后果:仓库中会有多个完全重复的commit;别人丢弃的一些commit会因为你的push再次回到仓库;仓库版本混乱。
- 出现风险的解决办法:用变基解决变基
- 也就是当你在fetch了‘别人push的经过变基的commit’后,不要用‘merge’了,而是用‘rebase’命令,将当前主题分支变基到远程跟踪分支上,如:
git rebase teamone/master [master]
,然后执行【操作步骤】中的2-3步。 - 上述过程
fetch + rebase
命令可整合为:git pull --rebase
, 简便实现主题分支
变基到远程跟踪分支
。 - 下图是用merge和用rebase的效果对比:
- 该办法的前提:确保别人的变基中 C4’ 和 C4 是几乎一样的。
- 也就是当你在fetch了‘别人push的经过变基的commit’后,不要用‘merge’了,而是用‘rebase’命令,将当前主题分支变基到远程跟踪分支上,如:
- 更多功能:
- 【–onto】当分支有嵌套,只想让
子子分支
并入指定分支
,同时不让子分支
并入:- 图示场景:让
server
分支下的client
分支并入master
分支 - 方法:
$ git rebase --onto master server client
->$ git checkout master
->$ git merge server
- 解释:“取出 client 分支,找出它从 server 分支分叉之后的补丁,然后把这些补丁在 master 分支上重放一遍,让 client 看起来像直接基于 master 修改一样”
- 图示场景:让
- 【-i】用 rebase -i 将工作压缩成一个单独的提交
- 【–onto】当分支有嵌套,只想让
拣选-cherry-pick
变基是把一个分支上的多个提交
打成一个补丁,更新到目标分支上,生成新提交;
拣选是把一个提交
的更改,直接更新到目标分支上,生成新提交。
这种方式在你只想引入主题分支中的某个提交,或者主题分支中只有一个提交,而你不想运行变基时很有用。git cherry-pick e43a6
合并-merge–常用场景
使用场景见。
- 命令解释:
git merge dev1
, 将b1分支的内容合并到当前分支中。 - 合并的类型:
- 快进合并:dev1要并入master,而master的快照是dev1的快照的
直接祖先
,因此master指针可直接快进到dev1处。 - 三方合并:不是
直接祖先
,则要找俩分支的公共祖先
快照(分叉点),对这三个快照进行整合,生成一个新快照(汇合点)。此时master和dev1都指向这个新快照。
- 快进合并:dev1要并入master,而master的快照是dev1的快照的
- 更多参数:
- 延迟生成新快照:
git merge --squash dev1
,在需要三方合并的场景下,该参数会做合并过程的几乎所有工作,除了生成新快照。这些改动将在你下一次手动提交
时被写入。 - 另一种延迟:
--no-commit
- 延迟生成新快照:
Git服务器(公共仓库)
为了git的协作功能,你的仓库需要转变成公共仓库/远程仓库/git服务器
。
4种协议
git服务器使用的通信协议支持:本地、SSH、HTTP、Git,详见。
一般地,用ssh://
进行授权访问,用git://
进行无授权访问,用https://
可同时实现。
- Git协议
- 其数据访问特点:快速且无需授权,因此仓库
只能是公开的
。 - 优点:对只读的项目很友好,省去逐一配置 SSH 公钥的麻烦。
…
- 其数据访问特点:快速且无需授权,因此仓库
用SSH协议搭建Git服务
比起HTTP,借助SSH协议
的搭建更加容易!但其难点在于用户管理
——如何设置数量不可知的用户的访问权限
、读写权限
?
- 方法1:在Git服务寄宿的主机上创建一个’git’用户供所有用户使用。
- 方法2:让 SSH 服务器通过某种
集中授权机制
(e.g. LDAP服务)来授权。
下面将借助方法1进行搭建:
准备一个ssh可连接的远程主机:
我有一个云服务器、一个域名,因此我将云服IP添加到了域名解析mygit.taddream.site
中,
访问云服方式为:ssh -p 10361 [email protected]
创建
git
用户,建立.ssh
目录:sudo adduser git
sudo usermod -aG sudo git # 将 git 用户添加到 sudo 组中
su git
cd
mkdir .ssh && chmod 700 .ssh
touch .ssh/authorized_keys && chmod 600 .ssh/authorized_keys接收来自一些用户的
公钥
妥善存放:echo "粘贴公钥内容" >> ~/.ssh/authorized_keys
- 裸仓库目录名一定是
.git
结尾;- 安装git服务的远程主机中,一般所有的裸仓库都放在
/srv/git/
下, 其权限改为777
。
创建一个裸仓库可有多种场景:
场景1:
远程主机管理者
手动将别处的裸仓库上传到远程主机- 先在别处将目标仓库导出为裸仓库:
$ git clone --bare my_project my_project.git
上述命令等价于把原仓库的
.git/
目录复制过来:$ cp -Rf my_project/.git my_project.git
- 然后将裸仓库上传到远程主机:
$ scp -P 10361 -r my_project.git [email protected]:/srv/git/
- 先在别处将目标仓库导出为裸仓库:
场景2:
远程主机管理者
建空的裸仓库 +项目贡献者
推送初始版本- 先在远程主机
创建
空的公共仓库:$ sudo git init --bare /srv/git/pro2.git
- 然后从任一用户本地
推送
第一个项目版本:cd project
git init
git add .
git commit -m 'initial commit'
# git remote add origin [email protected]:/srv/git/project.git # 若出现端口权限报错,则用下面的命令
git remote add origin ssh://[email protected]:10361/srv/git/project.git
git push origin master
- 先在远程主机
一旦裸仓库有了初始快照
,用户们就可以(用ssh协议借助git账户
和放置好的公钥
)克隆了。
- clone:
- 当访问端口是默认的22,可用:
git clone [email protected]:/srv/git/project.git
- 当访问端口非22(e.g. 10361),可用:
git clone ssh://[email protected]:10361/srv/git/project.git
- 当访问端口是默认的22,可用:
- push:
git push origin master
如果push时出现
写入权限报错
,可对该公共仓库目录使用git init --bare --shared
,或直接chmod 775
,都能修改其组权限
为所有用户可写。
目前所有(获得授权的)开发者用户都能以系统用户 git 的身份登录服务器从而获得一个普通 shell。
但远程主机系统管理员并不想让开发者对公共仓库
之外有权限。
- 约束1:将
git-shell
(Git软件包中自带的受限shell工具)设置为用户 git 的登录 shell。- 约束方法:
sudo chsh git -s $(which git-shell)
- 效果:放置了公钥的开发者执行
ssh [email protected]
再也不能成功了,但git clone/push
等仍有效。
- 约束方法:
- 约束2:禁用端口转发
- 约束原因:约束1并不能阻止开发者通过
SSH端口转发
获取git身份下的普通shell,因此需要对git身份禁用端口转发。 - 方法:在
~/.ssh/authorized_keys
中,在指定的公钥条目开始位置,把ssh-rsa
改为:txt
- 约束原因:约束1并不能阻止开发者通过
- 更多的约束设置:查看
git help shell
用Git协议搭建
通过 “Git” 协议建立一个基于守护进程的仓库。TODO: 用到再研究。搭建方法见
用 smart HTTP搭建
设置 Smart HTTP: 一般只需要在服务器上启用git-http-backend
的CGI(命令网关接口)脚本。
步骤:
系统环境准备
- 安装 Apache 作为
web服务器
及CGI服务器
:sudo apt-get install apache2 apache2-utils # 安装Apache
a2enmod cgi alias env headers ssl # 启用相关模块
systemctl restart apache2 # 重启 - 确保主机的80或443端口开放。如有防火墙限制、需要端口转发等问题,请事先处理好。
改权限
- 允许web服务器对
仓库目录
的读写权限:chgrp -R www-data /srv/git
- 新建虚拟主机配置文件并设定权限:
cd /etc/apache2/sites-available
sudo touch git-http.conf
sudo chgrp www-data git-http.conf # 若要递归,用`-R`
配置
- 在
git-http.conf
写入:<VirtualHost *:80>
ServerName git.taddream.site
DocumentRoot /var/www/gitweb
...
</VirtualHost> - VirtualHost标签中添加git相关配置:让
git-http-backend脚本
作为 Web 服务器对/git 路径请求
的CGI处理器。SetEnv GIT_PROJECT_ROOT /srv/git
SetEnv GIT_HTTP_EXPORT_ALL
ScriptAlias /git/ /usr/lib/git-core/git-http-backend/如果留空 GIT_HTTP_EXPORT_ALL 这个环境变量,Git 将只对无授权客户端提供带 git-daemon-export-ok 文件的版本库,就像 Git 守护进程一样。
- VirtualHost标签中添加
授权验证
(当对仓库有写入时)<Files "git-http-backend">
AuthType Basic
AuthName "Git Access"
AuthUserFile /srv/git/.htpasswd
Require expr !(%{QUERY_STRING} -strmatch '*service=git-receive-pack*' || %{REQUEST_URI} =~ m#/git-receive-pack$#)
Require valid-user
</Files> 其他配置
- 对拉取操作也要
授权验证
:<LocationMatch "^/git/.*/git-receive-pack$">
Options +ExecCGI
AuthType Basic
AuthName "Git Access"
AuthUserFile /etc/apache2/.htpasswd_project1
Require valid-user
</LocationMatch> - 其他
- 对拉取操作也要
授权
因为配置中用到了.htpasswd
文件,所以需要添加 Apache 授权用户$ htpasswd -c /srv/git/.htpasswd yourname
如果已经创建过 .htpasswd 文件,去掉
-c
命令执行后,终端会让你设置密码。完成后.htpasswd
中会将密码加密存放。
重启
启动虚拟主机,重启 Apache
|
bingo!测试
- 开发者已经可以用http来clone、fetch了:
git clone http://git.taddream.site:10580/git/project.git
注:API中的
/git/
即服务器上的/srv/git/
目录 - 开发者也能push的,如果此前对push操作(
git-receive-pack
)设置了授权验证,那么push过程会让你输入Apache的用户密码。
建立GitWeb
GitWeb是一个 Git 服务器的简易 web 界面,使用 GitWeb 来显示仓库的信息。
查看临时web
使用轻量web服务器
。见
持续维护的web
使用gitweb软件包
,包内有CGI脚本。
【准备软件包】
- 先获取gitweb目录:用
whereis gitweb
查看系统是否自带,若自带,一般存在于/usr/share/gitweb/
; 若不自带,则用下述命令手动安装,得到./gitweb/
目录。git clone git://git.kernel.org/pub/scm/git/git.git
cd git/
make GITWEB_PROJECTROOT="/srv/git" prefix=/usr gitweb - 后移动目录:放到Apache默认网页目录下:
sudo cp -Rf <path of gitweb> /var/www/
- 最后配置gitweb: 为了指定git仓库目录,需在
/etc/gitweb.conf
(没有则创建)中写入$projectroot = "/home/git/repositories";
- 先获取gitweb目录:用
【配置虚拟主机】
还记得用smart HTTP搭建
Git服务器里的配置环节吗?当时只实现了用http://git.taddream.site:10580
来传输仓库(SetEnv GIT_PROJECT_ROOT
), 而没有用这个url来同时实现仓库的网页查看(DocumentRoot
)。
为此我们配置如下:- 修改
VirtualHost
标签里的DocumentRoot
属性; 若用的系统自带的更新,不必配置该环境变量,只需配置gitweb包
,还需配置环境变量GITWEB_PROJECTROOT
;/etc/gitweb.conf
即可;- 并增加
Directory
子标签。在重启Apache(DocumentRoot /var/www/gitweb
SetEnv GITWEB_PROJECTROOT /srv/git
<Directory /var/www/gitweb>
Options +ExecCGI +FollowSymLinks +SymLinksIfOwnerMatch
AllowOverride All
order allow,deny
Allow from all
AddHandler cgi-script cgi
DirectoryIndex gitweb.cgi
</Directory>systemctl restart apache2
)后,我们就可以访问http://git.taddream.site:10580
来查看仓库了。
- 修改
建立GitLab (better GitWeb)
是比GitWeb更现代,功能更全的 Git 服务器。GitLab 是一个数据库支持的 web 应用,安装也更复杂。
资料:git->gitlab
TODO: 用到再研究该模块
Git的协作方式
Git是分布式的,它可以很好地支持多人协作。
分布式工作流程
集中式工作流
属于单点协作模型。使用广泛。所有开发者都需要与仓库对齐。
工作特点:不支持非快进式(non-fast-forward)
推送,即push
前须对仓库可能存在的更新进行fetch+merge
。
集成管理者工作流
GitHub/GitLab中使用最广泛的。项目是只读
的,开发者只能fork,想推送到master
分支时只能呼叫请求管理者进行push。
优点之一: 你可以持续地工作,而主仓库的维护者可以随时拉取你的修改。
流程见
主管与副主管工作流
多见于大型项目, 如Linux内核。
副主管(lieutenant),即各个集成管理者
分别负责集成项目中的特定部分。
主管(dictator),即总集成管理者
负责统筹,维护参考仓库
。
整合-管理者工作流
类似于主管-副主管
,但这里没有主管,而是副主管们形成集中式工作流。然后开发者和副主管之间的关系则来自集成管理者工作流
。
贡献项目
贡献原则
- 【纠错】:提交前要检查错误,特别是空白错误:
git diff --check
- 【切小块】:每一次提交的变更,应该只针对一个小问题的解决,而不应该是多个小问题攒起来后再提交。
- 如果一个文件有大量修改,而想切小块多次提交怎么办:用
交互式暂存
,即git add --patch
- 如果一个文件有大量修改,而想切小块多次提交怎么办:用
- 【优质的提交信息】:
git commit -m "<msg>"
中,msg
= 少于50字节的摘要性描述(标题
) + 一个空白行 + 详细的变更描述(正文
)。正文中可以有:
空行
来分段,项目符号
如数字和 - *,悬挂缩进
等。
在不同级别的团队中
在公开项目-用fork-PR
特点:没有权限直接更新项目的master分支,需要用到fork
和pull request
。
工作流程:
git clone <project-url>
: 本地仓库将有一个origin
连接project-urlgit checkout -b featureA
:新建分支开始自己的工作- …work: 新增,修改,多次提交等
fork
:去GitHub的项目主页派生git remote add myfork <forked-project-url>
: 添加remote
,即你的派生仓库urlgit push -u myfork featureA
origin没有权限,只能推送到
派生仓库
myfork;
这里不是把featureA合并到master再推送到myfork/master,而是保留主分支,直接推送featureA分支到新分支myfork/featureA。拉取请求(Pull Request)
:通知原项目的维护者你有想要他们合并的工作。详见- 等待管理者审查、讨论、合并,最终实现你的贡献被更新到原仓库的master上。
关于Pull-Request
方法1:在GitHub网页上进行 – 相当方便
- PR流程:进入派生仓库的刚刚push的分支页面,会自动出现
Compare & PR
按钮,进入按提示创建PR即可。如图: - 一些重要的知识点:
- 【PR前不必总是 Fork】:当你对源项目有写权限,那就可以直接推送
特性分支
过去,然后手动创建一个master分支的PR页面
,进行代码审查和讨论等协作。 - 【PR后仍可提交】:当你对
特性分支
发起PR后,讨论过程中仍可以在特性分支上继续提交。最终PR的merge会整合‘从PR前到讨论后’的所有改动。 - 【发起PR的场所】:任何分支上均可,甚至可在PR上发起PR
- 【派生仓库的master不要动】:建议你的工作在派生仓库的
特性分支
上进行,派生仓库的master多用来与源仓库的master对齐。 - 【不干净合并情况的处理】:
不干净合并
就是在PR时报错存在合并冲突
。解决冲突有两种方法:变基
:(不推荐)派生仓库中,把你的特性分支,变基到master分支中。由于变基容易搅乱提交历史的性质。反向合并
: (推荐!)拉取源仓库的master,将其合并到你派生仓库的特性分支上,修复提示的冲突,推送回远程派生仓库的特性分支,再次发起PR。该过程类似于派生仓库的更新
- 【引用】:GitHub的评论中可以引用PR、issue、commit。
议题引用
: 项目中所有的PR和Issue(议题)都会生成一个独一无二的编号
以供引用。引用方式有三种:- 直接引用当前仓库的PR/Issue:
#123
- 引用别人派生仓库的Issue:
<username>#123
- 引用完全不同项目的PR/Issue:
<username>/<projectName>#123
- 直接引用当前仓库的PR/Issue:
提交引用
:必须完整的写出 40 位长的 SHA-1
- 【PR前不必总是 Fork】:当你对源项目有写权限,那就可以直接推送
- PR流程:进入派生仓库的刚刚push的分支页面,会自动出现
方法2:用
git request-pull
命令git request-pull origin/master myfork
:- 命令解释:’origin/master’是
基础分支
,即希望管理者在原项目的该分支上拉取我的贡献;’myfork’是给管理者指明应该去哪个url拉取内容。 - 命令输出:一个请求拉取的所有修改的摘要。这个摘要可以用来通过邮件发给项目管理员。
- 命令解释:’origin/master’是
在公开项目-用邮件
【不常用】
该方法区别于fork+pull request
: 这里是直接把每一次提交变成补丁文件
,然后用邮件发给项目管理者。
命令:git format-patch -M origin/master
,得到每个提交生成的.patch
文件,然后可以用git send-email *.patch
命令发送,或手动发送那些文件。
维护项目
如何成为一个项目的管理者。一般流程是:为将要整合的新东西新开分支 -> 导入补丁到新分支 -> 检查贡献内容 -> 合并到原项目的目标分支(e.g. master) -> 想发布时,可为发布打标签 -> 为发布生成构建号 -> 发布 -> 制作提交简报。具体如下。
1、导入大家的贡献
场景1:贡献者邮件传来.patch
【该场景多用于贡献频率较低的开发者】
工作流程为,①在原始仓库中开启新分支,用以测试补丁;②把补丁内容应用到工作目录,用以查看、检查、测试;③手动
暂存并提交补丁所引入的更改;④按需把新补丁合并到master。
其中第二步有两种情况:
- 情况1,.patch文件由
git diff
生成,可使用命令git apply /path/*.patch
:
该命令优于patch -p1
。
在执行前还可先检查补丁:git apply --check /path/*.patch
。 - 情况2,.patch文件由
git format-patch
生成,可使用命令git am /path/*.patch
:- git am 是为了读取 mbox 文件而构建的, mbox 是一种用来在单个文本文件中存储一个或多个电子邮件消息的简单纯文本格式,.patch文件开头就是Mbox格式。
- 一个mbox 文件可以包含多个补丁文件。生成方法:开发者用
git send-email
发来所有补丁,你将其下载成mbox格式即可。
- 手动解决所有标记出来的冲突(就和merge时一样),然后暂存文件,然后用
git am --resolved
告诉系统冲突解决,继续下一个文件。 - 也可用参数智能应对:
git am -3 /path/*.patch
,尝试三方合并
的方法。前提是用于创建补丁的提交对象(即公共祖先
)在你的版本库内。
场景2:贡献者用fork+PullRequest
你此刻处于本地源仓库
, 有多种方法对新补丁进行导入、审查、合并。
方法1:添加
remote
(多用于长期贡献的开发者)git remote add devlpr1 <developer-forked-project-url> # 添加开发者邮件中指定的`remote`
git fetch devlpr1
git checkout -b featureA devlpr1/featureA # 把开发者在派生仓库的特性分支,检出到本地的特性分支,同时设置好了`追踪`。审核后没问题就可以合并了,具体怎么合并还要看你的工作流。
方法2:用
临时抓取合并
命令(多用于临时开发者的PR)git pull <developer-forked-project-url> <featureA>
。
审核后没问题就可以合并了,具体怎么合并还要看你的工作流。方法3:
PR页面
上操作PR页面
的merge pull request
按钮会指导我们进行琐碎的拉取合并过程。如:方法4:使用
合并请求分支
(多用于有大量PR时使用)
PR分支的概述
该方法的核心是,把PR引用的路径
从对本地源仓库的隐身状态
,变为可拉取可合并的显式分支
。- 处理单个PR时:
-> 获取PR编号如#372
-> 在git ls-remote origin
中获取编号对应的索引路径,如refs/pull/372/head
-> 直接抓取该分支:git fetch origin refs/pull/372/head
-> 该分支合并到本地源仓库的测试分支
:git checkout -b test; git merge FETCH_HEAD
-> 审查后就可以合并到master,伺机push了。 - 处理大量PR时:
- 先配置
origin
:
打开./git/config
,在[remote "origin"]
模块下添加txt
其作用是让远程源仓库的所有看起来像refs/pull/123/head
的引用应该在本地版本库像refs/remotes/origin/pr/123
一样存储。当然如果你有多个远程仓库,这里‘origin’也可换成你想要的‘shortname’。
- 后执行
git fetch origin
, 用fetch更新后,一次性把所有的PR分支
都变成了显式分支
- 后检出一个
PR分支
:git checkout pr/123
- 审查后就可以合并到master,伺机push了。
- 先配置
- 处理单个PR时:
2、检查贡献内容
- 查看贡献者独有的提交:
git log featureA --not master
(即master之后,featureA之前的提交) - 查看贡献代码的具体差异:
git diff master [featureA]
: 用于master是featureA的直接祖先
时;git diff $(git merge-base featureA master) [featureA]
: 用于master有分叉后,应该手动寻找master与featureA的公共祖先
后,再与贡献者的分支比较差异。简化命令:
git diff master...featureA
, 该方法下对比的两个主体是:后者分支、两者的公共祖先
分支。
3、合并贡献内容
常见合并工作流–两阶段
一旦本地源仓库
有新内容的分支审核完毕,则将其合并入稳定的master分支,如此反复操作。
在重要项目中,考虑使用两阶段合并循环
:
- 你会维护两个长期分支,分别是 master 和 develop,master 分支只会在一个非常稳定的版本发布时才会更新,而所有的新代码会首先整合进入 develop 分支。
- 你定期将这两个分支推送到公共版本库中。
- 每次需要
合并新的主题分支
时, 应该合并进入 develop 分支; - 当
打标签发布
的时候,你会将 master 分支快进到已经稳定的 develop 分支。
大项目合并工作流–四阶段
资料: Git 维护者手册
四个长期分支:
- master:稳定版本库。特点,始终在进行
快进
。 - next:特点,偶尔会被
变基
。 - pu(proposed updates):用于新工作。特点,
变基
比较频繁。 - maint(maintenance backports):用于维护性向后移植工作
工作流程: - master分出不同的
主题分支
(理解为项目模块); - 贡献者们在不同的主题下工作,成果被收入各个主题分支,方法可参考fork+PR;
- 测试评估一个主题分支是否能合并,或者仍需要更多工作。
- 若可验收,将被并入 next 分支;
- 若需更多工作,将被并入 pu 分支;
- 之后当next或pu完全稳定,则再次并入 master 分支(实际就是master快进到next或pu分支)。
- 一个主题并入master后,便会被从版本库中删除掉。
- 在需要的时候,将发布一版新的master。同时上一版master会派生出
maint分支
,提供向后移植过来的补丁以供发布维护更新。
变基与拣选工作流
有些维护者更喜欢保持线性的提交历史
。
具体使用参考
4、为发布打标签
5、生成构建号(可选)
6、准备一次发布
创建一个最新的快照归档,封装成压缩包进行发布。
gzip
格式:$zip
格式:$注:这里操作的分支是master;整个项目被放进了
project
目录再压缩的;因为使用了describe,所以要保证master此前有tag(with -a/-S)。
7、制作提交简报
shortlog可以快速生成一份包含从上次发布
之后项目新增内容的修改日志(changelog)类文档,即给定范围内的所有提交的摘要的总结。git shortlog --no-merges master --not v1.0.1
( v1.0.1以来的所有提交的总结。)
GitHub
贡献项目
常用的工作流程:
- 贡献者(fork > clone > work > push >PR,详见),
- 管理者(审查、讨论、合并、关闭PR,都在网页上进行)。
每个分支都有自己的PR
GitHub风格的Markdown
它增加了一些基础的 Markdown 中做不到的东西。 它在创建拉取请求和议题中的评论和描述
时十分有用。
任务列表
方法:
|
效果:只需要点击复选框,就能更新评论 —— 你不需要直接修改 Markdown。在PR的总览页面上还能看到它的进度条。
引用别人评论的部分句子
方法:你的评论中复制那些句子,在每行前添加 >
符号即可。
表情符号
方法:句首用:
开头(e.g. :eye
),句中则在冒号前加空格(e.g. :eye
)。
让派生仓库保持更新
- 方法1:进入
本地派生仓库
的master > 把远程的源仓库
的master拉取合并到本地master > 更新后的本地master推送到远程派生仓库
git checkout master
git pull <url>
git push origin master - 方法2: 方法1的简化版,配置本地仓库
remote
里的fetch-url
为项目源仓库的url、push-url
保持为项目派生仓库的url。最后使用简化的命令组合即可:git remote add forked_origin <url> # 添加源仓库的url
git branch --set-upstream-to=forked_origin/master master # 通过修改追踪分支,来改变fetch-url
git config --local remote.pushDefault origin # 保持push-url$ - 方法3: 直接在远程派生仓库的GitHub网页点击
Sync fork
即可。
维护项目
· 1、创建新的版本库:其中,当分享url的时候,推荐HTTP(s)的,因为SSH的需要GitHub账号秘钥等。
· 2、添加合作者:给他们push权限。
· 4、PR中的提醒、通知和订阅。
· 5、网页通知VS邮件通知。
3、管理合并请求
(一些PR的知识点)
先用邮件或PR页面
对发起的一个PR
进行通知、审查、讨论和改进。(邮件和评论是同步的)。
然后当你决定要合并了,有这4种方法
6、特殊文件
- README
- 贡献 CONTRIBUTING:指出对于你的项目开启的合并请求你想要的/不想要的各种事情。 这样别人在开启合并请求之前可以读到这些指导方针。
管理组织
组织帐户
代表了一组共同拥有多个项目的人,同时也提供一些工具用于对成员进行分组管理。
TODO: 需要再学。
脚本GitHub
介绍GitHub 钩子系统与 API 接口,使 GitHub 按照我们的设想来工作。
钩子
TODO: 需要再学。https://docs.github.com/zh/webhooks
GitHub API
- 针对不需要授权的接口,发送一个简单的 GET 请求即可;
- 需要授权的接口,使用访问令牌
示例场景: - 对一个特定议题写评论
curl -H "Content-Type: application/json" \
-H "Authorization: token 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" \
--data '{"body":"写下你的评论"}' \
https://api.github.com/repos/JamesRay0713/blog-comments/issues/1/comments - 修改PR的状态
TODO: 需要再学。 https://git-scm.com/book/zh/v2/GitHub-%E8%84%9A%E6%9C%AC-GitHub#_修改_pull_request_的状态
Git工具
交互式暂存
交互式
帮助你将文件的特定部分组合成提交,确保提交是逻辑上独立的变更集。
git add -i
其中,$ git add -i
staged unstaged path
1: unchanged +0/-1 TODO
2: unchanged +1/-1 index.html
3: unchanged +5/-1 lib/simplegit.rb
*** Commands ***
1: [s]tatus 2: [u]pdate 3: [r]evert 4: [a]dd untracked
5: [p]atch 6: [d]iff 7: [q]uit 8: [h]elp
What now>2
让你选择要暂存的文件;5
让你选择要暂存某个文件的哪些行。- 更多‘文件的部分暂存’的命令:
git add -p
相当于上面的5
git reset --patch
部分重置文件- git stash save –patch 部分暂存文件
- git checkout –patch 部分检出文件
贮藏与清理
贮藏
· 使用场景:当前分支的工作有点难以理清,因此不能有效commit,此刻又需要另开分支做更要紧的事。
· 作用:跟踪文件的修改与暂存的改动——然后将未完成的修改保存到一个栈上,并将工作目录恢复到最后一次提交的状态。 而你可以在任何时候重新应用这些改动(甚至在不同的分支上)。
贮藏也可当做有回退功能的清理工具
· 使用方法:
- 在一个‘暂存区有未提交、工作目录有未暂存’的分支中
- 开启贮藏
git stash [push]
,git status
会看到一个干净的分支。贮藏的所有文件都是
已跟踪
状态。 - 查看历史贮藏
git stash list
- 在当前分支中恢复贮藏
git stash apply [stash@{2}]
(若不指定宾语,则默认恢复stash@{0})除非使用
--index
,上面命令不会重新暂存
。 - 在新建分支中恢复贮藏
git stash branch <branch1> [<stash>]
恢复成功后自动丢弃贮藏。
- 移除:
git stash drop [stash@{0}]
- 应用+移除:
git stash pop [stash@{0}]
· 更多参数:
--keep-index
-u
贮藏任何未跟踪文件
--patch
交互式指定需要贮藏的内容
清理
它被设计为从工作目录中移除没有忽略的未跟踪的
文件。 如果你改变主意了,你也不一定能找回来那些文件的内容。
安全期间,决定清理时,先用-n
,再改成-f
。
- 安全清理的替代方式:
git stash --all
- 移除工作目录中所有未追踪的文件以及空的子目录-d:
git clean -df
- 演习式清理–dry-run 或 -n :
git clean -d -n
- 包含忽略文件:
-x
签名工具
GPG参考。
可以签名/验证签名的一些命令:-S/-s
- 签:
git tag -s v1.5 -m 'my signed 1.5 tag'
- 签:
git commit -a -S -m 'signed commit'
- 签:
git merge --verify-signatures -S <branch1>
- 看:
git log --show-signature -1
,git log --pretty="format:%h %G? %aN %s"
(%G) - 验:
git merge --verify-signatures <branch1>
检查并拒绝没有携带可信 GPG 签名的提交
搜索
搜文件
git grep <string>
从提交历史、工作目录、甚至索引中查找一个’字符串’或者’正则表达式’。
参数:
- -n 或 –line-number 选项数来输出 Git 找到的匹配行的行号。
- -c 或 –count 只输出匹配数。
- -p 或 –show-function 显示每一个匹配的字符串所在的方法或函数
- –and 确保了多个匹配出现在同一文本行中
搜提交
git log -S <string/常量名>
显示新增和删除该字符串的提交-L
展示代码中一行或者一个函数的历史。git log -L :git_deflate_bound:zlib.c
: 查看 zlib.c 文件中 git_deflate_bound 函数的每一次变更.
重写历史
即修改历史提交,其原则是:你要修改的提交,一定不能是已经push了的提交!!!
有以下两种使用场景:
修改最后一次提交
【可修改内容、也可修改提交信息】
步骤:先在工作目录中修改、增删你的文件 -> git add
-> git commit --amend
-> 自动进入编辑器,写入新的‘commit message’ -> 完成。
效果:你的最新一次的commit实现了变更,commit的校验和也刷新了,但不会新增一个提交。
--no-edit
选项可以跳过编辑提交信息。
同时处理多个提交
· 该场景下我们有多种目的:批量修改提交信息、重排序多个提交、压缩多个提交、拆分任意一个提交等。
· 实现它们的核心是git rebase -i HEAD~3
,基于交互式的变基工具
中的自定义脚本,从你在命令行中指定的提交(HEAD~3)开始,从上到下的依次重演每一个提交引入的修改。
注意:变基范围的选择易搞错。例如想重写最近的
3次提交
(HEAD, HEAD, HEAD2),则应该把’最远的那个提交(HEAD~2
)的父提交(^
)’传给命令,即HEAD~3
。
· 【总体步骤】是:
- 执行
git rebase -i HEAD~3
,自动进入文本编辑器,可以看到有多个pick行
,对应你指定的HEAD~3后的每一个提交。形如rebase-i的编辑器
pick 993162d update
pick e790f14 iafter reba
pick b7b18ae uuu---update
# Rebase 4acbfd9..b7b18ae onto 4acbfd9 (3 commands)
#
# Commands:
# p, pick <commit> = use commit
# r, reword <commit> = use commit, but edit the commit message
# e, edit <commit> = use commit, but stop for amending
# s, squash <commit> = use commit, but meld into previous commit
# f, fixup [-C | -c] <commit> = like "squash" but keep only the previous
# commit's log message, unless -C is used, in which case
# keep only this commit's message; -c is same as -C but
# opens the editor
# x, exec <command> = run command (the rest of the line) using shell
# b, break = stop here (continue rebase later with 'git rebase --continue')
# d, drop <commit> = remove commit
# l, label <label> = label current HEAD with a name
# t, reset <label> = reset HEAD to a label
# m, merge [-C <commit> | -c <commit>] <label> [# <oneline>]
# create a merge commit using the original merge commit's
# message (or the oneline, if no original merge commit was
# specified); use -c <commit> to reword the commit message
# u, update-ref <ref> = track a placeholder for the <ref> to be updated
# to this position in the new commits. The <ref> is
# updated at the end of the rebase
#
# These lines can be re-ordered; they are executed from top to bottom.
#
# If you remove a line here THAT COMMIT WILL BE LOST.
#
# However, if you remove everything, the rebase will be aborted.
#注释里有脚本的完整教程。
注意:pick行
的顺序是‘历史提交的正向顺序’,即从旧到新,跟git log
的输出相反。 - 编辑该脚本,以满足你重写历史的目的。参考下面的多个子标题的例子。
- 保存退出,会自动运行脚本,并自动给出接下来你需要怎么做的命令提示。同时终端会提示你所处的分支状态和脚本进度:
- 按提示执行命令、解决冲突…(当提示中有
git add <file>
时,你甚至可以在工作区修改部分文件内容再add,当然这会有后序commit的更多冲突让你手动解决),完成!
脚本1:修改一个/多个历史的提交信息
· 编辑脚本:把你想修改的commitID前的pick
改成edit
。
· 脚本执行:会提示你使用git commit --amend
,该命令会进入第一个edit行
的commit的提交信息
编辑器。编辑后执行提示命令git rebase --continue
,运行下一行脚本。逐行应用,直到完成。
脚本2:删除部分历史提交
· 编辑脚本:直接删除那些提交对应的行。
· 脚本执行:按提示即可。(可能用到git add .
等)
· 最终效果:被删commit之后的commitID都将更新。
脚本3:重排序历史提交
· 编辑脚本:直接更换pick行
的顺序,注意从上到下是从旧到新。
· 脚本执行:同脚本2。
· 最终效果:变序了的commitID都将更新。
脚本4:压缩多个提交
· 编辑脚本:把你想修改的commitID前的pick
改成squash
。
· 最终效果:squash行
的提交会并入上一行的提交,从而新生成一个commitID。
脚本5:拆分提交
拆分一个提交会撤消这个提交,然后多次地部分地暂存与提交直到完成你所需次数的提交。
· 编辑脚本:同脚本1。
· 脚本执行:退出脚本后会自动执行到你第一个edit
的提交,接下来你需要:
git reset HEAD^ # 回退到你正在编辑的这个提交的上一个提交,这样所有更改都变成了`未暂存`的状态
# work: 任何修改
git add && git commit # 修改提交可重复执行,从而实现“提交的拆分”
git rebase --continue # 继续脚本中的下一行。
核武器级选项:filter-branch
用于通过脚本的方式改写大量提交,建议不要在已公开项目中使用。
使用场景:
- 从每一个提交中移除一个文件
- 使一个子目录做为新的根目录
- 全局修改邮箱地址
注:git filter-branch 有很多陷阱,不再推荐使用它来重写历史。 请考虑使用 git-filter-repo,TODO: 用到再学。
重置解密
高级合并
Rerere 重用记录的解决方案
Git仓库工具
可视化工具gitk
轻量GitWeb
git instaweb --httpd=lighttpd
: 开启web, 默认1234端口。若不带httpd
,则默认使用lighttpd。也可指定其他web服务器:apache2、webrick等,若系统没有则apt
下载。git instaweb --httpd=lighttpd --stop
: 关闭web。
引用规范:“refspec.” 它是一种把 remote 的名称映射到你本地 .git 目录的方法。