本文主要为教材的笔记,目前已拜读到Rerere
TIPS: 文中的PR代表pull request;fork等于‘派生’。

资料

git常用命令图解

一些重要概念

一些语法

杂项

<branch>@{<筛选条件>},

指定提交

我们有许多引用(HEAD,branch,tag,commitID)来找到这些引用所指向的提交,而有更多的历史提交该如何索引到呢?
下面的例子我都将用HEAD代表引用

  1. 指定单个提交:
    ^3是第三个爸爸(前提是对应提交有多个爸爸,0是自己,1和置空是爸爸,2是二爸,…);
    ~3是第三层长辈(0是自己,1和置空是爸爸,2是爷爷,…)。
  • 举例:HEAD^, 指向该引用的父提交HEAD^2, 指向第二个父提交。
  • 举例:HEAD~2 代表“第一父提交的第一父提交”,也就是“祖父提交”
  • 举例:HEAD~3^2 HEAD的曾祖父的第二个父亲。
  1. 指定提交区间
  • 双点:选出在一个分支中而不在另一个分支中的提交
    • git log experiment..master 显示在 master 分支中而不在 experiment 分支中的提交
    • 如果你留空了其中的一边, Git 会默认为 HEAD
    • 常用: 查看你即将推送到远端的内容
      $
  • 多点:在任意引用前加上 ^ 字符或者 –not 来指明你不希望提交被包含其中的分支。
    三者等价
    git log refA..refB
    git log ^refA refB
    git log refB --not refA
    git 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 的位置。
  • 分支(branch): (如.git/refs/heads/master

    当本地分支追踪着一个远程分支,则此时本地分支叫跟踪分支,远程的叫上游分支

  • 标签(tag):(如.git/refs/tags/1.0.0
  • PR型分支: 本地不会存在这种分支

· 远程的引用:(注意,一个版本库可以连接多个remote)

  • (HEAD不会存在于远程)

  • 远程分支:(如.git/refs/remotes/master详见

  • 远程标签。

  • 合并请求分支/PR分支:(如refs/pull/<pr#>/head

    • 由来:远程源仓库一旦有PR发生,就会生成这样一个分支型引用,指向派生仓库里想要合并的那个分支的最新提交快照。clone和fetch都会忽略这类引用,因此可称作一种假分支
    • 优点:本质来讲,仓库内的一个引用,却指向了仓库外的一个资源,还不用过问这个资源的url,简直就是为高效的pull request量身定做的。
    • 查看这种分支列表的方法:参考
    • 如何使用:参考 方法4。
  • 命令图解:git常用命令图解

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" 可以使用不同于默认格式的方式展示提交历史。

      具体格式占位符

    • 更多常用选项
  • 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。获取到的列表形如:

9c906cc02bf209b8b8823f696fffeac5501a3372        HEAD
9c906cc02bf209b8b8823f696fffeac5501a3372 refs/heads/main
9c906cc02bf209b8b8823f696fffeac5501a3372 refs/heads/npm
6a83107c62950be9453aac297bb0193fd743cd6e refs/pull/1/head
afe83c2d1a70674c9505cc1d8b7d380d5e076ed3 refs/pull/1/merge
3c8d735ee16296c242be7a9742ebfbc2665adec1 refs/pull/2/head
15c9f4f80973a2758462ab2066b6ad9fe8dcf03d refs/pull/2/merge
f6b06a6d6c61bfe81ea99cc55669121a7d6dee8f refs/tags/1.0.0-rc.6
3d98a5a189e0bd680850a5e4c388021c03bf89de refs/tags/1.0.1a

解释:

  • 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也可换成其他任意历史提交。

  • 参数:

    1. --soft: 轻度作用是只让HEAD切换到目标提交,暂存区工作区不变。相当于git commit的逆向。
    2. --mixed: 中度【默认参数】作用是让HEAD不动,并把HEAD更新到Index区,工作区不变。相当于add和commit的逆向。
    3. --hard: !危险 作用是把Index区更新到工作区。
    4. --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对工作目录不安全,它会把工作区也回退了。
      • 参考速查表
  • 其他重置命令:

    • 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"
  • 推送标签(类似推送分支)
    • 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查看所有分支的列表)

      1. 当branch1存在于本地分支列表(形如branch1),则可以直接切换过去,和git switch branch1等价;
      2. 当branch1存在于远程分支列表(形如origin/branch1),即此时该远程跟踪分支在本地没有跟踪分支
      • 若用git checkout origin/branch1,则HEAD称为detached HEAD,因此建议使用下一行的方法;
      • 若用git checkout branch1,则该命令是git checkout -b branch1 origin/branch1的简化版(见形式3)。
      1. 当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: 连接的是多个远程仓库怎么办)

  • 切换/恢复文件git checkout -- file1

    • 工作目录中的file1被修改过,此时恢复优先级为:暂存区的file1 -> 提交对象中的file1。
  • 一些重要参数

    • --conflict

      git checkout --conflict=diff3 hello.rb 使用 diff3 风格(默认风格是merge)的合并标记来解决 hello.rb 文件中的合并冲突。
      diff3 风格会将两个分支的修改以及它们的共同祖先的修改都显示出来,这样可以更清晰地看到冲突的具体内容。
      如果你喜欢这种格式,可以修改默认设置:git config --global merge.conflictstyle diff3
    • --ours ; --theirs
      一种无需合并的快速方式,你可以选择留下一边的修改而丢弃掉另一边修改。
  • 特性对比:checkout vs. reset,详见

    • 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”)

    拿到拷贝文件有2种方法:
    • 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
  • 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 archive
  • git shortlog

其他类

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>]
    • 只会将数据从远程仓库下载/更新到你的本地仓库 —— 它并不会自动合并或修改你当前的工作,需要自己手动merge。原理见
    • fetch中的分支问题详见
  • 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: 引用规范
  • push标签
    • git push <remote> <tagname> 推送特定标签到远程仓库,类似推送分支。
    • git push <remote> --tags 推送远端没有的所有标签

★ Git分支管理!

概述

图解-分支即指针
  • 原理解读:用对象的逻辑理解git原理
    • Git的分支,是术语引用的其中一种。其本质上仅仅是指向提交对象的可变指针
    • HEAD是一个指针,指向当前所在的本地分支(可理解为当前分支的别名

一些命令

  • 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: 变基,将提交从一个分支移动到另一个分支。

场景:分支的新建与合并

1. 新建和切换分支 `git checkout -b `

切换/检出命令的机理:让HEAD指向另一个的分支指针,同时工作目录也恢复成对应的快照版本。
一般情况下,这些分支命令也可分开操作:

  • 查看(git branch)
  • 新建(git branch <dev1>)
  • 切换/检出(git checkout <dev1>),当dev1在本地不存在时,该命令实际是把远程分支检出到本地新建的同名分支,同时设置好跟踪
2. 此时master有紧急错误需要修复
  • 先把dev1分支打扫干净:尽量把所有修改都形成快照,即commit。实在不行也可贮藏(stashing)和修补(amending)
  • 切换到主分支: git checkout master
  • 新建并切换到修复分支:git checkout -b hotfix
  • 修改、测试、提交
  • 把修复分支合并到master: git checkout master, git merge hotfix
  • 删除重复分支:git branch -d hotfix(因为此时master指针和hotfix指针,指向同一个提交对象。 )
3. 继续回到开发分支`dev1`上工作
  • 修改、测试、提交
  • 把开发分支合并到master: git checkout master, git merge dev1

    此时,若master指向的快照不是dev1指向的快照的直接祖先,则git后台会寻找他俩的公共祖先,做一个三方合并, 三方合并会自动创建一个新提交对象C6,如下图:图解-git三方合并

  • 删除开发分支:git branch -d dev1
4. 有冲突怎么合并?

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
    如何生成跟踪分支(及其上游分支):

    • 【克隆时自动生成】:git clone后,会自动获得跟踪分支(master)对远程跟踪分支(origin/master)的跟踪。
    • 【把远程分支检出到本地新分支时自动生成】:具体使用里的形式3。
    • 【手动设置/更改本地已有的分支的上游】:git branch -u origin/dev1 [local_branch](-u即--set-upstream-to联系push-u

变基与合并

同:都是整合分支的方法,两者执行结果是相同的。
异: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的效果对比:图解-fetch了变基后的数据后使用merge和rebase的区别
      • 该办法的前提:确保别人的变基中 C4’ 和 C4 是几乎一样的。
  • 更多功能:
    • 【–onto】当分支有嵌套,只想让子子分支并入指定分支,同时不让子分支并入:
      • 图示场景:让server分支下的client分支并入master分支图解-变基的高级功能
      • 方法:$ git rebase --onto master server client ->
        $ git checkout master ->
        $ git merge server
      • 解释:“取出 client 分支,找出它从 server 分支分叉之后的补丁,然后把这些补丁在 master 分支上重放一遍,让 client 看起来像直接基于 master 修改一样”
    • 【-i】用 rebase -i 将工作压缩成一个单独的提交

拣选-cherry-pick

变基是把一个分支上的多个提交打成一个补丁,更新到目标分支上,生成新提交;
拣选是把一个提交的更改,直接更新到目标分支上,生成新提交。

这种方式在你只想引入主题分支中的某个提交,或者主题分支中只有一个提交,而你不想运行变基时很有用。
git cherry-pick e43a6

合并-merge–常用场景

使用场景

  • 命令解释:git merge dev1, 将b1分支的内容合并到当前分支中。
  • 合并的类型:
    • 快进合并:dev1要并入master,而master的快照是dev1的快照的直接祖先,因此master指针可直接快进到dev1处。
    • 三方合并:不是直接祖先,则要找俩分支的公共祖先快照(分叉点),对这三个快照进行整合,生成一个新快照(汇合点)。此时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进行搭建:
1. 配置远程主机
  1. 准备一个ssh可连接的远程主机:
    我有一个云服务器、一个域名,因此我将云服IP添加到了域名解析mygit.taddream.site中,
    访问云服方式为:ssh -p 10361 [email protected]

  2. 创建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
  3. 接收来自一些用户的公钥妥善存放: echo "粘贴公钥内容" >> ~/.ssh/authorized_keys

2. 准备`裸仓库`到远程主机中
  • 裸仓库目录名一定是.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
3. 开发者开始使用`公共仓库`

一旦裸仓库有了初始快照,用户们就可以(用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
  • push:git push origin master

    如果push时出现写入权限报错,可对该公共仓库目录使用git init --bare --shared,或直接chmod 775,都能修改其组权限为所有用户可写。

4. 权限约束

目前所有(获得授权的)开发者用户都能以系统用户 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
  • 更多的约束设置:查看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`

配置

  1. git-http.conf写入:
    <VirtualHost *:80>
    ServerName git.taddream.site
    DocumentRoot /var/www/gitweb
    ...
    </VirtualHost>
  2. 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 守护进程一样。

  3. 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>
  4. 其他配置
    • 对拉取操作也要授权验证
      <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

cd /etc/apache2/sites-available
a2ensite git-http.conf # 注意,`a2ensite`命令操作的目标文件一定不能带路径。
systemctl restart apache2

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脚本。

  1. 【准备软件包】

    • 先获取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";
  2. 【配置虚拟主机】
    还记得用smart HTTP搭建Git服务器里的配置环节吗?当时只实现了用http://git.taddream.site:10580来传输仓库(SetEnv GIT_PROJECT_ROOT), 而没有用这个url来同时实现仓库的网页查看(DocumentRoot)。
    为此我们配置如下:

    • 修改VirtualHost标签里的DocumentRoot属性;
    • 若用的系统自带的gitweb包,还需配置环境变量GITWEB_PROJECTROOT 更新,不必配置该环境变量,只需配置/etc/gitweb.conf即可;
    • 并增加Directory子标签。
      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>
      在重启Apache(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分支,需要用到forkpull request
工作流程:

  • git clone <project-url>: 本地仓库将有一个origin连接project-url
  • git checkout -b featureA:新建分支开始自己的工作
  • …work: 新增,修改,多次提交等
  • fork:去GitHub的项目主页派生
  • git remote add myfork <forked-project-url>: 添加remote,即你的派生仓库url
  • git 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即可。如图:GitHub上创建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
        • 提交引用:必须完整的写出 40 位长的 SHA-1
  • 方法2:用git request-pull命令

    • git request-pull origin/master myfork:
      • 命令解释:’origin/master’是基础分支,即希望管理者在原项目的该分支上拉取我的贡献;’myfork’是给管理者指明应该去哪个url拉取内容。
      • 命令输出:一个请求拉取的所有修改的摘要。这个摘要可以用来通过邮件发给项目管理员。

在公开项目-用邮件

【不常用】
该方法区别于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:
    1. git am 是为了读取 mbox 文件而构建的, mbox 是一种用来在单个文本文件中存储一个或多个电子邮件消息的简单纯文本格式,.patch文件开头就是Mbox格式。
    2. 一个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了。

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 中做不到的东西。 它在创建拉取请求和议题中的评论和描述时十分有用。

任务列表

方法:

- [X] 编写代码
- [ ] 编写所有测试程序
- [ ] 为代码编写文档

效果:只需要点击复选框,就能更新评论 —— 你不需要直接修改 Markdown。在PR的总览页面上还能看到它的进度条。
github-markdown-任务列表

引用别人评论的部分句子

方法:你的评论中复制那些句子,在每行前添加 > 符号即可。

表情符号

方法:句首用:开头(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即可。github-同步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-rebase-i脚本执行进度
  • 按提示执行命令、解决冲突…(当提示中有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 目录的方法。


Static Badge Static Badge Static Badge Static Badge
Copyright © 2023-2024 Raymond H., All Rights Reserved.