Git 分支操作
为 Git 命令设置别名
上一节课程中的操作,有些命令的重复度极高,比如 git status 和 git branch -avv 等,Git 可以对这些命令设置别名,以便简化对它们的使用,设置别名的命令是 git config --global alias.[别名] [原命令],如果原命令中有选项,需要加引号。别名是自定义的,可以随意命名,设置后,原命令和别名具有同等作用。操作如下:
git config --global alias.st status
自己设置的别名要记住,也可以使用 git config -l 命令查看配置文件。下面文档中的命令将使用这些别名。
Git 分支管理
下面介绍 Git 作为分布式版本控制器最强大的功能:分支管理。
Git fetch 刷新本地分支信息
在介绍分支前,先讲解另一个命令 git fetch,它的作用是将远程仓库的分支信息拉取到本地仓库,注意,仅仅是更新了本地的远程分支信息,也就是执行 git branch -avv 命令时,查看到的 remotes 开头的行的分支信息。
举例说明一下,首先我们在 GitHub 页面上对 one.txt 文件进行修改并增加一次提交。
提交完成后,提交数变成 2 个,点下历史按钮可以看到提交记录:
在实验环境中执行 git fetch 命令,然后执行 git branch -avv 查看分支信息:
# 将远程仓库的分支信息拉取到本地仓库 git fetch # 查看本地分支信息 git branch -avv
可以看到,本地分支 main 的版本号无变化,而远程分支已经更新。所以,fetch 命令的作用是刷新保存在本地仓库的远程分支信息,此命令需要联网。
此时若想使本地 main 分支的提交版本为最新,可以执行 git pull 命令来拉取远程分支到本地,pull 是拉取远程仓库的数据到本地,需要联网,而由于前面执行过 git fetch 命令,所以也可以执行 git rebase 「账户」/「分支名称」 命令来实现 “使本地 main 分支基于远程仓库的 main 分支”,rebase 命令在后面还会经常用到,这里只需按部就班操作即可:
git rebase origin/main
可以看到,远程仓库 main 分支、本地仓库的 origin/main 分支、本地仓库的 main 分支是一致的。
创建新的本地分支
分支在项目开发中作用重大,多人协作时尤其不可或缺。例如一个项目上线了 1.0 版本,研发部门需要开发 1.1、1.2 两个测试版,增加不同的新功能,测试版的代码显然不能在正式版所在的分支上,此时需要新的分支来存放不同版次的代码。再例如实验楼的课程团队在维护课程仓库时,每个人都有各自的分支,在自己的分支上进行修改,然后向 master 分支提 PR(pull request),最后从 master 分支推送到线上。
首先,克隆远程仓库到本地,进入仓库主目录,执行 git br 查看分支信息
执行 git branch [分支名] 可以创建新的分支:
git branch dev
此命令创建新分支后并未切换到新分支,还是在 main 分支上,执行 git checkout [分支名] 切换分支,checkout 也是常用命令,先给它设置别名,然后切换分支:
# 更换别名 git config --global alias.ch checkout # 更换分支 git ch dev
创建新分支还要手动切换太麻烦,介绍另一个常用的命令 git checkout -b [分支名] 创建分支并切换到新分支:
git ch -b dev1
如上图所示的分支信息,前两行是新建的本地分支信息,它们的版本号与主分支 master 一致,这是因为在哪个分支上创建新分支,新分支的提交记录就与哪个分支一致。新建分支并无跟踪任何远程分支,所以没有 master 分支中的中括号和括号内的蓝色远程分支名。
假设我们要在当前分支 dev1 上开发一个新的功能,需要增加一个文件 new_func1,然后生成一个新的提交:
将新分支中的提交推送至远程仓库
好,新功能已经写好并提交到了版本区,现在要推送了,推送到哪里呢?正常逻辑当然要推送到远程仓库的同名分支,不过现在远程仓库里只有一个分支:
上图紫色框中是一个下拉按钮,点击后显示仓库中的全部分支,按钮上显示的是当前所在分支。
执行 git push [主机名] [本地分支名]:[远程分支名] 即可将本地分支推送到远程仓库的分支中,通常冒号前后的分支名是相同的,如果是相同的,可以省略 :[远程分支名],如果远程分支不存在,会自动创建:
git push origin dev1:dev1
上图命令可以简写为 git push origin dev1 。注意哦,这是我们创建 SSH 关联后第一次执行 push 命令,可以看到传输速度有明显的提高,更重要的是,不再需要重复输入用户名和密码了,另外打印信息的第一行是警告信息,因为是这个分支的第一次推送嘛,下次执行推送就不会再出现了。现在执行 git br 查看一下分支情况:
可以看到,远程分支 origin/dev1 的信息已经在本地存在,且与本地同名分支一致。再看下 GitHub 页面的情况:
很好,与预期毫无二致。
本地分支跟踪远程分支
现在有个问题,当我们再次在 dev1 分支上修改并提交,推送到远程仓库时还是要输入上面的那个长长的命令,好不方便。如果能和 master 分支一样跟踪远程同名分支,就可以直接使用 git push 命令推送了。有办法的,执行这个命令 git branch -u [主机名/远程分支名] [本地分支名] 将本地分支与远程分支关联,或者说使本地分支跟踪远程分支。如果是设置当前所在分支跟踪远程分支,最后一个参数本地分支名可以省略不写:
git branch -u origin/dev1
这个命令的 -u 选项是 --set-upstream 的缩写。可不可以让本地分支跟踪远程非同名分支呢?
可以的,尽管几乎遇不到这种自找麻烦的需求。
可不可以撤销本地分支对远程分支的跟踪呢?也是可以的,执行 git branch --unset-upstream [分支名] 即可撤销该分支对远程分支的跟踪,同样地,如果撤销当前所在的分支的跟踪,分支名可以省略不写:
git branch --unset-upstream dev1
问题又来了,前面的操作是先将本地分支推送到远程仓库,使远程仓库创建新分支,然后再执行命令使本地分支跟踪远程分支,有没有办法在推送时就自动跟踪远程分支呢?有的,在推送的时候,加个 --set-upstream 或其简写 -u 选项即可,现在切换到 dev 分支试一下这个命令:
删除远程分支
接下来,介绍一下删除分支的方法。
首先,删除远程分支,使用 git push [主机名] :[远程分支名] ,如果一次性删除多个,可以这样:git push [主机名] :[远程分支名] :[远程分支名] :[远程分支名] 。此命令的原理是将空分支推送到远程分支,结果自然就是远程分支被删除。另一个删除远程分支的命令:git push [主机名] --delete [远程分支名]。删除远程分支的命令可以在任意本地分支中执行。两个命令分别试一下:
git push origin :dev :dev1 # 或者 --delete 可以缩减为 -D git push origin --delete dev dev1
可以看到本地仓库已经没有远程分支 dev 和 dev1 的分支信息。查看 GitHub 仓库页面:
也只剩 master 一个分支。操作成功。
本地分支的更名与删除
回到实验环境,使用 git branch -D [分支名] 删除本地分支,同样地,此命令也可以一次删除多个,将需要删除的分支名罗列在命令后面即可。在此之前,先介绍一个极少用到的命令:给本地分支改名 git branch -m [原分支名] [新分支名] ,若修改当前所在分支的名字,原分支名可以省略不写:
git branch -m dev1 newdev
好,现在要一次性删除本地分支 dev 和 newdev。需要注意的一点:当前所在的分支不能被删除。切换到 master 分支,然后执行 git branch -D dev newdev 命令:
git branch -D ved dev newdev
很好,一切都回到了开始时的样子,就像什么都没有发生。
多人协作 GitHub 部分
准备
本节将介绍 GitHub 多人协作与相关 Git 的操作,所有操作全部在浏览器页面上完成,内容相对较少。
建议大家准备两个浏览器和两个 GitHub 账号以便模拟场景。
创建仓库
首先,在组长账号中创建一个仓库,名为 work,在创建仓库时,需要说明第一节中提到的两个下拉框
左边的忽略文件下拉框:我们在写代码时,总会出现一些不需要上传到仓库的垃圾文件、缓存文件、备份文件、环境文件等等,可以创建一个忽略文件将这些不需要被上传到远程仓库的文件忽略掉。忽略文件的名字是 .gitignore,它被放置在仓库主目录下,将不需上传的文件的名字写入其中,Git 就会自动忽略它们。比如这个仓库是用来 Windows 开发的,就在下拉框中选择 Windows,如果这是一个保存 Java 项目的仓库,就选择 Java。这样,在仓库创建成功后,忽略文件就自动出现了,这个忽略文件中有对应的语言或工具中绝大部分通用的忽略规则。当然了,你也可以自己手动增删改。
如果在创建仓库时忘记了选择忽略文件,几个提交后突然想起来,怎么办?GitHub 上有人把忽略文件都做好了,打开链接 github / gitignore ,这个仓库里有很多忽略文件,选择你需要的放到自己的仓库即可。
右边的开源许可下拉框:关于开源许可证,不属于本课程所述范围,如有需要大家可以自行搜索。我们的仓库不需要选择这一项。选择这个之后,仓库中会出现相对应的图标,比如上面提到的忽略文件仓库
对上图右上角三个按钮进行说明:
Watch:这是一个下拉按钮,可以选择对此仓库关注、不关注、忽略等。
Star:如果觉得这个仓库很好,就点击这个按钮送一颗星,在淘宝提供刷星业务之前,仓库获得的星越多表示该项目越优秀。
Fork:在别人的仓库中点此按钮会克隆一个完全一样的仓库到你自己的账号中,包括所有分支、提交等,但不会克隆 issue(本节后面会讲到),当此仓库发生版本变化,不会自动同步到你克隆的仓库里,反之亦然。
添加合作者
现在在组长账号中增加该仓库的合作者,也就是组员
在输入框中写入组员 GitHub 账号的用户名,选择正确的用户,点击右侧按钮就会发送一封邀请邮件给组员
点击邮箱中绿色按钮接受邀请,会跳转到组员访问组长仓库的页面
点击 Fork 按钮,克隆组长的仓库到组员的账号中,完成后自动跳转到组员的仓库页面
添加 issue
切换到组长的 GitHub 页面,在仓库中添加一些项目任务或待解决问题,这些任务就是 issue
写好任务标题后,可以在右侧指派一位或多位项目参与者来完成,同样 GitHub 也会给被指派者发邮件的(可以在自己的 GitHub 账号上设置拒收哪类邮件)
写好两个 issue,前面说过的,组长仓库里的 issue 不会出现在组员仓库中
多人协作Git 部分
克隆仓库到本地
以组员的身份克隆自己的 work 仓库到实验环境,由于之前已经设置了实验环境的 SSH 公钥到 GitHub,所以我们使用 git 开头的地址来克隆
链接的结尾 .git 是不需要的
完成任务并推送到自己的仓库
现在我们要完成组长仓库的一个 issue,注意每个 issue 在创建后都会生成一个编号,我们首先完成 1 号 issue:
创建文件,添加到暂存区,提交,查看本地仓库分支状态:
注意在执行 commit 命令时,备注信息里有个 “fix #1”,这是必要的,当备注信息中含有此字样的 commit 出现在组长仓库,仓库中编号为 #1 的 issue 就会自动关闭。类似的字样还有 “fixes #xxx、fixed #xxx、closes #xxx、close #xxx、closed #xxx”,这些并不重要,选择字母最少的 fix 就可以了。当然偶尔忘记写这个字样也不要紧的,issue 可以手动关闭,甚至关掉的 issue 还能再开。
完成以上操作,组员的 GitHub 仓库会发生变化,新增一个版本号为 b374 的提交:
提PR & 检查合并 PR
接下来,怎么把修改从组员的仓库添加到组长的仓库呢?这就用到了 pull request 方法,简称 PR。这个词组比较费解,两个词都有动词属性,字面意思是 “拉,请求”,可以理解为这是一个名词性词组,意为 “允许被拉取的请求”,创建一个 PR 就是从甲分支向乙分支提一个请求,该请求中有一个或多个提交,对方觉得可以、没问题,就合并(merge) 这个请求,也就是把请求中所有提交的修改增加到乙分支上,整个过程简称 “提 PR”、“检查合并 PR”。提 PR 既可以在仓库内,也可以跨用户跨仓库。
好,现在我们从组员的 work 仓库 master 分支给组长的 work 仓库 master 分支提一个 PR:
之后检查,然后提交即可
完成后,页面自动跳转到组长的 work 仓库 PR 的合并页面:
该页面只有参与项目协作的成员有权限进入,当前 GitHub 的登录用户是组员,所以可见,且对这个仓库有完全的管理权限,除了删除仓库。当然了,检查合并 PR 的权限也是有的。
重要的一点:提了 PR 之后,一定要求参与项目的其他成员来检查合并,不要自己来,尽管自己有权限。
上图中绿色按钮是个下拉按钮,合并 PR 的方法有三种,分别解释一下:
Create a merge commit :
这种方式会在组长仓库的 master 分支上生成一个新的提交,且保留 PR 中的所有提交信息。这是一种常规操作,用得最多。
Squash and merge
:压缩合并,它会把 PR 中的全部提交压缩成一个。此方法的优点就是让提交列表特别整洁。一个 PR 里有很多提交,每个提交都是很细小的改动,保留这些提交没什么意义,这种情况就使用此方法合并 PR。
Rebase and merge
:这种方法不会生成新的提交,例如 PR 中有 6 个提交,用此方法合并后,组长仓库也会新增 6 个提交。注意,这些提交的版本号与组员的提交不同,此外完全一样。
现在切换到另一个登录组长账号的浏览器,打开合并 PR 的页面,用第一种方法合并:
这就是第一种方式合并的结果,生成了一个新的提交,这个提交里没有修改。因为样子不太美观,这是我最不喜欢用的方式。仔细看上图的 issue,变成了 1 个,也就是说在合并 PR 后,#1 issue 被关闭了。
以上就是一次完整的修改、提交、推送、提 PR、合并 PR 的过程。
需要注意的一点:从 A 向 B 提 PR 后,在 PR 合并或关闭前,A 上所有新增的提交都会出现在 PR 里。
同步主仓库
因为组长的 master 分支新增了一个空提交,所以需要让组员的仓库同步组长的仓库,使它们的提交版本一致。作为组员,要时刻保持自己的 master 分支与组长的一致,以避免在下次提 PR 时出现冲突,该操作叫做 “同步主仓库”,组长的仓库就是主仓库。
提 PR、合并 PR 只能在 GitHub 页面上操作。同步主仓库是要用 Git 操作的。首先,使用 remote
系列命令来增加一个关联主机,执行 git remote add [主机名] [主仓库的地址]
,注意,主仓库的地址使用 https 开头的:
如上图所示,主机名是随意定义的,只要不是 origin 就可以,因为自己的仓库地址对应的主机名是 origin,
主仓库的主机名通常定义为 up 或 upstream,这个主机名其实就是一个变量,它的值就是仓库地址,例如 git push origin master
完全等于 git push git@github.com:uiuing/work master 。
如此说来,关联主仓库后也没什么变化嘛,确实如此,即使地址写错也不会报出来。现在可以使用前面课程介绍过的 fet
ch
命令来拉取主仓库的全部分支信息到本地仓库了,我有时使用这个命令看上一个命令是否有拼写错误
如何同步主仓库哩?方法有二,一是执行 git pull --rebase up master
,此命令需联网,二是执行 git rebase up/master,
此命令不联网,因为前面已经执行了 git fetch up
这个需要联网的命令,本地已经有了最新的主仓库 master 分支信息,所以可以这么操作。
总结一下:git pull --rebase = git fetch + git rebase
。现在使用方法二来同步:
同步主仓库已完成。现在可以继续修改提交自己的 master 分支了。然后一并推送到自己的远程仓库。
以上是在自己 Fork 的仓库里进行修改的过程。还有一种常用的方式,就是不用 Fork,直接克隆组长的仓库到本地,然后各自创建自己的分支,在自己的分支上进行修改提交,最后从自己的分支向 master 分支提 PR。方式不同,原理一样。
关于多人协作的 Git 操作就到这里了。
Git tag 和 GitHub releases
Git 标签的作用
在一个项目中,我们可能需要阶段性地发布一个版本,比如 V1.0、V1.0.2、V3.2 Beta 之类的,Git 的标签可以满足这个需求。在一个长期大型项目中,可能会有数千个提交版本,我们可能需要对重要的节点性提交打个记号,这时也可以使用 Git 的标签功能。在一些项目相关的书籍中,我们会看到 “执行 xxx 命令签出这个版本以查看对应的代码” ,这也是使用 Git 的标签功能做到的。本节实验将详细讲解此功能的具体操作。
创建标签
前面的内容提到过 GitHub 的 issue 功能,issue 是仓库拥有者在 GitHub 上手动创建的,仓库被 Fork 时 issue 不会跟随。Tags 通常在本地使用 git 命令创建后推送到 GitHub 上,与 issue 相同的一点,它也只存在于项目仓库内,Fork 或提 PR 都不会带上它。在多人协作项目中,通常由组长对主仓库设置 Tags,单人项目自然就是自己说了算。
重要的一点,我们创建标签是给具体的某次提交创建的,跟分支无关。创建标签使用 git tag [标签名] -m [备注信息] [提交版本号] 这个命令。其中 -m [备注信息] 可以省略不写,但建议不要省略。[提交版本号] 可以省略,如果是给当前分支最新的提交创建标签的话。
给当前分支当前版本创建一个标签:
git tag v1.0 -m "项目正式版本 1.0"
这样一个本地标签就创建完成了。
查看标签
执行 git tag 命令显示仓库中的全部标签列表,执行 git show [标签名] 查看标签详情:
# 仓库标签列表 git tag # 查看标签详情 git show v1.0 | cat
前文已提到,标签是在提交的基础上创建的,如果仓库的多个分支中都有这个提交版本,那么这些分支上就有关于这个提交的相同的标签。
删除本地标签
当我们执行 git tag [标签名] 创建本地标签后,在仓库主目录的 .git/refs/tags 目录下就会生成一个标签文件:
tree .git/refs/tags
执行 git tag -d [标签名] 删除本地标签,标签文件也会被删除:
git tag -d v1.0
将本地标签推送到远程仓库
首先对两个提交版本创建对应的标签:
执行 git push origin [标签名] 推送标签到远程仓库,注意前面的命令都只涉及本地操作不需要联网,此命令需要联网:
git push origin 001
我们到浏览器上打开仓库主目录,可以查看 releases 和 tags :
点 Tags 按钮查看标签:
关于 releases 是什么,下文会介绍。
如果你一口气创建了 6 个标签,当然啦,这种情况很少发生,可以使用 git push origin --tags 命令将全部本地标签推送至远程仓库:
删除远程仓库标签
如果标签废弃不用或者写错了,可以使用 git push origin :refs/tags/[标签名] 删除远程仓库的标签,命令中的标签名其实也就是文件名:
git push origin :refs/tags/001
好,删除成功。以上就是关于 Git 标签的创建、查看、推送、删除的操作流程。
查看本地仓库的标签列表:
咦,001 标签还在呢?是的,本地标签需要另外手动删除,上文已演示。
签出版本
现在介绍一下关于 “签出版本” 的操作,我们会见到类似这种说明:“如果你从 GitHub 上克隆了这个程序的仓库,那么可以在仓库主目录下执行 git checkout xxx 签出程序的这个版本。” 其实签出版本就是指定某个提交版本创建一个新的分支。
假定当前的 work 仓库就是一个程序,我们要签出 001 版本,执行以下步骤即可。
首先执行 git checkout [标签名] 切换到之前的某个提交版本,然后执行 git checkout -b [新的分支名] 将此提交版本固定到一个新分支上并切换到此分支:
git checkout 001 git checkout -b 001
GitHub 的 releases
GitHub 的 releases 是 2013 年发布的新功能,旨在协助软件开发者分发新版本给用户,关于这个功能这里仅作简单介绍。
当项目组织宣布发布一个软件产品的版本,发布过程就是一个将软件交付给最终用户的工作流。版本是具有修改日志和二进制文件的一类对象,它们提供了 Git 工作流之外的完整项目历史,它们也可以从存储库的主页上被访问。发布版 release 附带发布说明和下载软件或源代码的链接。按照许多 Git 项目的约定,发布版本与 Git 的标签 tag 绑定。您可以使用现有的标签,或者让 release 在发布时创建标签。这就是上面查看 GitHub 仓库中标签信息时出现的场景。
标签是 Git 中的概念,而 releases 则是 Github、码云等源码托管商所提供的更高层的概念。Git 本身是没有 releases 这个概念,只有 tag。两者之间的关系则是,release 基于 tag,为 tag 添加更丰富的信息,一般是编译好的文件。