300字范文,内容丰富有趣,生活中的好帮手!
300字范文 > 项目开发-工具-版本控制Git完整系统化使用说明

项目开发-工具-版本控制Git完整系统化使用说明

时间:2021-05-07 22:59:27

相关推荐

项目开发-工具-版本控制Git完整系统化使用说明

Git使用说明

前言1. 起步1.1 关于版本控制1.2 Git 简史1.3 Git 是什么?1.4 命令行1.5 安装 Git1.6 初次运行 Git 前的配置1.7 获取帮助1.8 总结2. Git 基础2.1 获取 Git 仓库2.2 记录每次更新到仓库2.3 查看提交历史2.4 撤消操作2.5 远程仓库的使用2.6 打标签2.7 Git 别名3. Git 分支3.1 分支简介3.2 简单的分支的新建与合并例子3.3 分支管理3.4 本地分支开发工作流3.5 远程分支3.6 变基4. 服务器上的 Git4.1 协议4.2 在服务器上搭建 Git4.3 生成 SSH 公钥4.4 配置服务器4.5 Git 守护进程4.6 Smart HTTP4.7 GitWeb4.8 GitLab4.9 第三方托管的选择5. 分布式 Git5.1 分布式工作流程5.2 向一个项目贡献5.3 维护项目6. GitHub6.1 账户的创建和配置6.2 对项目做出贡献6.3 维护项目6.4 管理组织6.5 脚本 GitHub7. Git 工具7.1 选择修订版本7.2 交互式暂存7.3 贮藏与清理7.4 签署工作7.5 搜索7.6 重写历史7.7 重置揭密7.8 高级合并7.9 Rerere7.10 使用 Git 调试7.11 子模块7.12 打包7.13 替换7.14 凭证存储7.15 总结8. 自定义 Git8.1 配置 Git8.2 Git 属性8.3 Git 钩子8.4 使用强制策略的一个例子8.5 总结9. Git 与其他系统9.1 作为客户端的 Git9.2 迁移到 Git9.3 总结10. Git 内部原理10.1 底层命令与上层命令10.2 Git 对象10.3 Git 引用10.4 包文件10.5 引用规范10.6 传输协议10.7 维护与数据恢复10.8 环境变量10.9 总结11. 一个成功的Git分支模板

前言

对于Git不甚了解,只停步在基础命令阶段,去Git官网学习Git官方文档,以此篇博客记录所学,如有误载,感谢您的指正。

Git命令中文文档:/

Git官方文档链接:https://git-/book/zh/v2/

Git命令官方文档:https://git-/docs

1. 起步

1.1 关于版本控制

版本控制:版本控制是一种记录一个或若干文件内容变化,以便将来查阅特定版本修订情况的系统。分以下三种:

本地版本控制系统。集中化的版本控制系统(Centralized Version Control Systems,简称 CVCS)有CVS、Subversion 以及 Perforce 等。分布式版本控制系统(Distributed Version Control System,简称 DVCS)有 Git、Mercurial、Bazaar 以及 Darcs 等。

1.2 Git 简史

诞生于,沿用至今适合管理大项目。

1.3 Git 是什么?

Git特性简介:

直接记录快照,而非差异比较

基于差异如下图:

基于快照如下图:

近乎所有操作都是本地执行

在本地产生的项目数据均保存在本地。

Git 保证完整性

Git 中所有的数据在存储前都计算校验和,然后以校验和来引用。在传送过程中丢失信息或损坏文件,Git 就能发现。

Git 一般只添加数据

你执行的 Git 操作,几乎只往 Git 数据库中添加数据。

三种状态

1、已提交(committed):已修改表示修改了文件,但还没保存到数据库中。

2、已修改(modified):已暂存表示对一个已修改文件的当前版本做了标记,使之包含在下次提交的快照中。

3、已暂存(staged)。已提交表示数据已经安全地保存在本地数据库中。

Git 项目拥有三个阶段:工作区、暂存区以及 Git 目录:

1.4 命令行

多种使用方式,最好命令行模式。

1.5 安装 Git

官网各终端安装教程:https://git-/book/zh/v2/起步-安装-Git

1.6 初次运行 Git 前的配置

git config,具体使用前往前言中提供的链接。

1.7 获取帮助

git help,具体使用前往前言中提供的链接。

1.8 总结

了解Git是什么,不同版本控制系统的区别,如何安装配置查看帮助。

2. Git 基础

2.1 获取 Git 仓库

1.克隆远程仓库:git clone <远程仓库URL地址或SSH协议地址>

2.将未进行版本控制的本地目录转化为Git仓库:在当前项目下执行git init

2.2 记录每次更新到仓库

工作目录中的所有文件分为两种情况:

已跟踪:指被纳入版本控制的文件,Git已经知道的文件,状态可能是未修改、已修改、已放入暂存区。

未跟踪:工作目录中除已跟踪的文件,不存在于上次快照记录中,也没有被放入暂存区。

1、检查当前文件状态:git status

$ git statusOn branch master \\当前所在分支为默认分支masterYour branch is up-to-date with 'origin/master'.\\与远程库master分支关联nothing to commit, working directory clean \\工作目录下很干净

新增文件后:

$ echo 'My Project' > README \\新建README文件$ git statusOn branch masterYour branch is up-to-date with 'origin/master'.Untracked files: \\未跟踪文件,用户可选择是否跟踪该文件(use "git add <file>..." to include in what will be committed)READMEnothing added to commit but untracked files present (use "git add" to track)

2、跟踪新文件:git add <文件名或目录>

$ git add README$ git statusOn branch masterYour branch is up-to-date with 'origin/master'.Changes to be committed: \\以下文件已暂存(use "git restore --staged <file>..." to unstage)new file: README

3、暂存已修改的文件

在工作目录下修改CONTRIBUTING.md文件后:

$ git statusOn branch masterYour branch is up-to-date with 'origin/master'.Changes to be committed:(use "git reset HEAD <file>..." to unstage)new file: READMEChanges not staged for commit:(use "git add <file>..." to update what will be committed)(use "git checkout -- <file>..." to discard changes in working directory)modified: CONTRIBUTING.md \\CONTRIBUTIN.md文件被更改,且未暂存

CONTRIBUTING.md文件暂存:

$ git add CONTRIBUTING.md$ git statusOn branch masterYour branch is up-to-date with 'origin/master'.Changes to be committed:(use "git reset HEAD <file>..." to unstage)new file: READMEmodified: CONTRIBUTING.md

再次修改CONTRIBUTING.md文件:

$ vim CONTRIBUTING.md$ git statusOn branch masterYour branch is up-to-date with 'origin/master'.Changes to be committed:(use "git reset HEAD <file>..." to unstage)new file: READMEmodified: CONTRIBUTING.mdChanges not staged for commit:(use "git add <file>..." to update what will be committed)(use "git checkout -- <file>..." to discard changes in working directory)modified: CONTRIBUTING.md

CONTRIBUTING.md文件同时出现在暂存区和非暂存区,如果现在提交则提交的是第一次修改后的文件而非再次修改后的文件,因为第二次修改没有add。将最新版本暂存:

$ git add CONTRIBUTING.md$ git statusOn branch masterYour branch is up-to-date with 'origin/master'.Changes to be committed:(use "git reset HEAD <file>..." to unstage)new file: READMEmodified: CONTRIBUTING.md

4、状态简览(文件前的符号代表当前状态):git staus -s

$ git status -sM README \\已修改,未暂存MM Rakefile \\暂存后又做了修改,既有暂存部分又有未暂存部分A lib/git.rb \\新添加到暂存区的文件M lib/simplegit.rb \\已修改且已暂存?? LICENSE.txt \\新添加未暂存

5、忽略文件

正常情况下总会有文件无需纳入Git管理,如日志文件和临时文件等。这种情况下可以创建一个名为.gitignore文件,列出要忽略的文件的模式。格式规范如下:

所有空行或者以 # 开头的行都会被 Git 忽略。

可以使用标准的 glob 模式匹配,它会递归地应用在整个工作区中。

匹配模式可以以(/)开头防止递归。

匹配模式可以以(/)结尾指定目录。

要忽略指定模式以外的文件或目录,可以在模式

-前加上叹号(!)取反。

示例:

$ cat .gitignore*.[oa]*~

第一行告诉 Git 忽略所有以.o.a结尾的文件。

第二行告诉 Git 忽略所有名字以波浪符~结尾的文件。

6、查看已暂存和未暂存的修改:

git diff:比较工作目录中当前文件和暂存区域快照之间的差异。也就是修改后还没有暂存起来的变化内容。git diff --staged:比对已暂存文件与最后一次提交的文件差异。

7、提交更新:

git commit -m "提交注释"

8、跳过使用暂存区,不再需要git add就可提交所有修改:

git commit -a -m "提交注释"

9、移除文件:

git rm 文件名:手动删除该文件后,使用此命令将该未放入暂存区的文件不纳入版本管理。git rm -f 文件名:手动删除该文件后,强制将之前修改过或已经放到暂存区的文件不纳入版本管理。`git rm --cached 文件名:删除该文件的暂存记录,但保留在当前工作目录。git rm \*~:命令会删除所有名字以 ~ 结尾的文件。

10、移动文件

$ git mv file_from file_to

等价于

$ mv README.md README$ git rm README.md$ git add README

2.3 查看提交历史

git log:查看提交历史,常用选项如下:

限制输出的选项:

2.4 撤消操作

1、撤销最新提交,将当前暂存区文件提交代替上次提交:git commit --amend

2、取消暂存的文件:git reset HEAD 文件名

3、撤销对文件的修改(修改将永久消失):git checkout -- 文件名

2.5 远程仓库的使用

1、查看远程仓库:git remote -v

2、添加远程仓库:

git clone <远程仓库URL地址或SSH协议地址>:添加默认名称origin的远程仓库。git remote add 仓库名 <远程仓库URL地址或SSH协议地址>:添加远程仓库并命名为远程“仓库名”。

3、从远程仓库中抓取与拉取:

git fetch <remote>:将远程数据下载到你的本地仓库,并不会自动合并或修改你当前的工作。git pull <remote>:抓取数据并自动尝试合并到当前所在的分支。

4、推送到远程仓库:git push <remote> <branch>

5、查看某个远程仓库:git remote show <remote>

6、远程仓库重命名:git remote rename <remote> <新名称>

7、远程仓库名称:git remote remove <remote>

2.6 打标签

1、列出标签:git tagorgit tag -l "通配模式"

2、创建标签:

附注标签:包含打标签者的名字、电子邮件地址、日期时间, 此外还有一个标签信息,并且可以使用 GNU Privacy Guard (GPG)签名并验证。

//创建git tag -a v1.4 -m "my version 1.4"//查看git show v1.4

轻量标签:只是某个特定提交的引用

//创建git tag v1.4//查看git show v1.4

后期打标签:

//查看提交历史git log --pretty=oneline//为指定提交打标签git tag -a <标签名> <校验和或部分校验和>

共享标签:传送标签到远程仓库服务器上。

git push origin <tagname>删除标签:

本地:git tag -d <tagname>

远程:git push origin --delete <tagname>检出标签:查看某个标签所指向的文件版本。

git checkout <tagname>

2.7 Git 别名

Git 并不会在你输入部分命令时自动推断出你想要的命令。 如果不想每次都输入完整的 Git 命令,可以通过 git config 文件来轻松地为每一个命令设置一个别名。

git config --global alias.<别名> '<命令>'

3. Git 分支

3.1 分支简介

1、Git 的分支,本质上仅仅是指向提交对象的可变指针

在进行提交操作时,Git 会保存一个提交对象(commit object)。 知道了 Git 保存数据的方式,我们可以很自然的想到——该提交对象会包含一个指向暂存内容快照的指针。 但不仅仅是这样,该提交对象还包含了作者的姓名和邮箱、提交时输入的信息以及指向它的父对象的指针。 首次提交产生的提交对象没有父对象,普通提交操作产生的提交对象有一个父对象, 而由多个分支合并产生的提交对象有多个父对象。

为了更加形象地说明,我们假设现在有一个工作目录,里面包含了三个将要被暂存和提交的文件。 暂存操作会为每一个文件计算校验和(使用我们在 起步 中提到的 SHA-1 哈希算法),然后会把当前版本的文件快照保存到 Git 仓库中 (Git 使用 blob 对象来保存它们),最终将校验和加入到暂存区域等待提交:

$ git add README test.rb LICENSE$ git commit -m 'The initial commit of my project'

当使用 git commit 进行提交操作时,Git 会先计算每一个子目录(本例中只有项目根目录)的校验和, 然后在 Git 仓库中这些校验和保存为树对象。随后,Git 便会创建一个提交对象, 它除了包含上面提到的那些信息外,还包含指向这个树对象(项目根目录)的指针。 如此一来,Git 就可以在需要的时候重现此次保存的快照。

现在,Git 仓库中有五个对象:三个 blob 对象(保存着文件快照)、一个 树 对象 (记录着目录结构和 blob 对象索引)以及一个 提交 对象(包含着指向前述树对象的指针和所有提交信息)。首次提交对象及其树结构如下:

分支及其提交历史:

2、分支创建:git branch <分支名称>

$ git branch testing

这会在当前所在的提交对象上创建一个指针,两个指向相同提交历史的分支如图:

HEAD指针:指向当前所在的本地分支,git branch 命令仅仅 创建 一个新分支,并不会自动切换到新分支中去。如图:

git log 命令查看各个分支当前所指的对象:git log --oneline --decorate

3、分支切换:git checkout <分支名>

下面示范切换到test分支,和主分支切换分别开发。

$ git checkout testing

使HEAD 指针切换指向testing分支。如图:

$ vim test.rb$ git commit -a -m 'made a change'

使HEAD 分支随着提交操作自动向前移动

$ git checkout master

使 HEAD 指回 master 分支,同时将工作目录恢复成 master 分支所指向的快照内容。

注意:分支切换会改变你工作目录中的文件

在切换分支时,一定要注意你工作目录里的文件会被改变。 如果是切换到一个较旧的分支,你的工作目录会恢复到该分支最后一次提交时的样子。 如果 Git 不能干净利落地完成这个任务,它将禁止切换分支。

$ vim test.rb$ git commit -a -m 'made other changes'

这个项目产生了分叉。

查看分叉历史:git log --oneline --decorate --graph --all

创建新分支的同时切换过去:git checkout -b <新分支名>

3.2 简单的分支的新建与合并例子

背景:将经历如下步骤:

开发某个网站。

为实现某个问题待解决,创建一个分支。

在这个分支上开展工作。

正在此时,你突然接到一个电话说有个很严重的问题需要紧急修补。 你将按照如下方式来处理:

切换到你的线上分支(production branch)。

为这个紧急任务新建一个分支,并在其中修复它。

在测试通过之后,切换回线上分支,然后合并这个修补分支,最后将改动推送到线上分支。

切换回你最初工作的分支上,继续工作。

实现步骤:

1、初始状态

2、创建解决问题分支iss53

$ git checkout -b iss53Switched to a new branch "iss53"

3、在iss53分支上修改提交

$ vim index.html$ git commit -a -m 'added a new footer [issue 53]'

4、切换到主分支,创建紧急问题分支,解决问题,提交修改。

$ git checkout masterSwitched to branch 'master'$ git checkout -b hotfixSwitched to a new branch 'hotfix'$ vim index.html$ git commit -a -m 'fixed the broken email address'[hotfix 1fb7853] fixed the broken email address1 file changed, 2 insertions(+)

5、切换回主分支,与hotfix分支合并

$ git checkout master$ git merge hotfixUpdating f42c576..3a0874cFast-forwardindex.html | 2 ++1 file changed, 2 insertions(+)

6、删除hotfix分支,进入iss53分支开发,提交修改

$ git branch -d hotfixDeleted branch hotfix (3a0874c).$ git checkout iss53Switched to branch "iss53"$ vim index.html$ git commit -a -m 'finished the new footer [issue 53]'[iss53 ad82d7a] finished the new footer [issue 53]1 file changed, 1 insertion(+)

6、回到主分支,与iss53分支合并

$ git checkout masterSwitched to branch 'master'$ git merge iss53Merge made by the 'recursive' strategy.index.html | 1 +1 file changed, 1 insertion(+)

7、删除iss53分支

$ git branch -d iss53

遇到冲突时的分支合并:

1、合并iss53分支时,如果你对 #53 问题的修改和有关 hotfix 分支的修改都涉及到同一个文件的同一处,在合并它们的时候就会产生合并冲突:

$ git merge iss53Auto-merging index.htmlCONFLICT (content): Merge conflict in index.htmlAutomatic merge failed; fix conflicts and then commit the result.

2、查看哪些文件产生了冲突,使用 git status 命令来查看那些因包含合并冲突而处于未合并(unmerged)状态的文件:

$ git statusOn branch masterYou have unmerged paths.(fix conflicts and run "git commit")Unmerged paths:(use "git add <file>..." to mark resolution)both modified:index.htmlno changes added to commit (use "git add" and/or "git commit -a")

3、打开冲突文件index.html

<<<<<<< HEAD:index.html<div id="footer">contact : email.support@</div>=======<div id="footer">please contact us at support@</div>>>>>>>> iss53:index.html

修改为

<div id="footer">please contact us at email.support@</div>

4、add修改后查看是否所有冲突是否解决完毕:

$ git statusOn branch masterAll conflicts fixed but you are still merging.(use "git commit" to conclude merge)Changes to be committed:modified: index.html

5、git commit完成合并,提交信息反馈如下:

Merge branch 'iss53'Conflicts:index.html## It looks like you may be committing a merge.# If this is not correct, please remove the file#.git/MERGE_HEAD# and try again.# Please enter the commit message for your changes. Lines starting# with '#' will be ignored, and an empty message aborts the commit.# On branch master# All conflicts fixed but you are still merging.## Changes to be committed:#modified: index.html#

3.3 分支管理

1、查看分支列表,*代表HEAD指向的当前所在分支。

$ git branchiss53* mastertesting

2、查看每一个分支的最后一次提交。

$ git branch -viss53 93b412c fix javascript issue* master 7a98805 Merge branch 'iss53'testing 782fd34 add scott to the author list in the readmes

3、查看已经合并的分支, 在这个列表中分支名字前没有 * 号的分支通常可以使用 git branch -d 删除掉。

$ git branch --mergediss53* master

4、查看没有合并的分支。

$ git branch --no-mergedtesting

5、尝试使用 git branch -d 命令删除它时会失败,可以使用可以使用 -D 选项强制删除它。

$ git branch -d testingerror: The branch 'testing' is not fully merged.If you are sure you want to delete it, run 'git branch -D testing'.

3.4 本地分支开发工作流

1、长期分支工作模式

只在 master 分支上保留完全稳定的代码——有可能仅仅是已经发布或即将发布的代码,develop 或者 topic 的平行分支,被用来做后续开发或者测试稳定性——这些分支不必保持绝对稳定,但是一旦达到稳定状态,它们就可以被合并入 master 分支。

想象成流水线:

2、主题分支

拥有多个主题分支的提交历史:

现在,我们假设两件事情:你决定使用第二个方案来解决那个问题,即使用在 iss91v2 分支中方案。 另外,你将 dumbidea 分支拿给你的同事看过之后,结果发现这是个惊人之举。 这时你可以抛弃 iss91 分支(即丢弃 C5 和 C6 提交),然后把另外两个分支合并入主干分支。 合并了 dumbidea 和 iss91v2 分支之后的提交历史:

3.5 远程分支

1、远程分支:

远程引用是对远程仓库的引用(指针),包括分支、标签等等。

git ls-remote <远程仓库名eg:origin>:显示远程引用的完整列表

git remote show <远程仓库名eg:origin>:获得远程分支的更多信息。

远程跟踪分支是远程分支状态的引用。以<remote>/<branch>的形式命名,“origin” 并无特殊含义,如果你运行 git clone -o booyah,那么你默认的远程分支名字将会是 booyah/master。克隆之后的服务器与本地仓库如图:

本地与远程的工作可以分叉如图:

git fetch 更新你的远程跟踪分支如图:

添加另一个远程仓库如图:

远程跟踪分支 teamone/master如图:

2、推送:

你想要公开分享一个分支时,需要将其推送到有写入权限的远程仓库上。 本地的分支并不会自动与远程仓库同步——你必须显式地推送想要分享的分支。 这样,你就可以把不愿意分享的内容放到私人分支上,而将需要和别人协作的内容推送到公开分支。使用git push <remote> <branch>命令。

$ git push origin serverfix //推送本地的 serverfix 分支来更新远程仓库上的 serverfix 分支。Counting objects: 24, done.Delta compression using up to 8 pressing objects: 100% (15/15), done.Writing objects: 100% (24/24), 1.91 KiB | 0 bytes/s, done.Total 24 (delta 2), reused 0 (delta 0)To /schacon/simplegit* [new branch]serverfix -> serverfix

其他协作者从服务器上抓取数据时,他们会在本地生成一个远程分支 origin/serverfix,指向服务器的 serverfix 分支的引用,当抓取到新的远程跟踪分支时,本地不会自动生成一份可编辑的副本(拷贝)。 这种情况下,不会有一个新的 serverfix 分支——只有一个不可以修改的 origin/serverfix 指针。可以运行 git merge origin/serverfix 将这些工作合并到当前所在的分支。

$ git fetch originremote: Counting objects: 7, done.remote: Compressing objects: 100% (2/2), done.remote: Total 3 (delta 0), reused 3 (delta 0)Unpacking objects: 100% (3/3), done.From /schacon/simplegit* [new branch]serverfix -> origin/serverfix

如果想要在自己的 serverfix 分支上工作,可以将其建立在远程跟踪分支之上:

$ git checkout -b serverfix origin/serverfixBranch serverfix set up to track remote branch serverfix from origin.Switched to a new branch 'serverfix'

3、跟踪分支:

是与远程分支有直接关系的本地分支。 如果在一个跟踪分支上输入 git pull,Git 能自动地识别去哪个服务器上抓取、合并到哪个分支。

git checkout -b <branch> <master>/<branch>:新建跟踪分支

$ git checkout -b sf origin/serverfix //sf本地分支名Branch sf set up to track remote branch serverfix from origin.Switched to a new branch 'sf'

$ git checkout --track <master>/<branch>:指定当前本地分支追踪远程分支

$ git checkout --track origin/serverfixBranch serverfix set up to track remote branch serverfix from origin.Switched to a new branch 'serverfix'

设置已有的本地分支跟踪一个刚刚拉取下来的远程分支,或者想要修改正在跟踪的上游分支, 你可以在任意时间使用 -u 或 --set-upstream-to 选项运行 git branch 来显式地设置。

$ git branch -u origin/serverfixBranch serverfix set up to track remote branch serverfix from origin.

Note:上游快捷方式当设置好跟踪分支后,可以通过简写 @{upstream} 或 @{u} 来引用它的上游分支。 所以在 master 分支时并且它正在跟踪 origin/master 时,如果愿意的话可以使用 git merge @{u} 来取代 git merge origin/master。

查看设置的所有跟踪分支,可以使用 git branch 的 -vv 选项。这会将所有的本地分支列出来并且包含更多的信息,每一个分支正在跟踪哪个远程分支与本地分支是否是领先、落后。

$ git branch -vviss537e424c3 [origin/iss53: ahead 2] forgot the bracketsmaster 1ae2a45 [origin/master] deploying index fix* serverfix f8674d9 [teamone/server-fix-good: ahead 3, behind 1] this should do ittesting 5ea463a trying something new

需要重点注意的一点是这些数字的值来自于你从每个服务器上最后一次抓取的数据。 这个命令并没有连接服务器,它只会告诉你关于本地缓存的服务器数据。 如果想要统计最新的领先与落后数字,需要在运行此命令前抓取所有的远程仓库。 可以像这样做:

$ git fetch --all; git branch -vv

4、拉取:

当 git fetch 命令从服务器上抓取本地没有的数据时,它并不会修改工作目录中的内容。 它只会获取数据然后让你自己合并。 然而,有一个命令叫作 git pull 在大多数情况下它的含义是一个 git fetch 紧接着一个 git merge 命令。

5、删除远程分支:

假设你已经通过远程分支做完所有的工作了——也就是说你和你的协作者已经完成了一个特性, 并且将其合并到了远程仓库的 master 分支(或任何其他稳定代码分支)。 可以运行带有 --delete 选项的 git push 命令来删除一个远程分支。基本上这个命令做的只是从服务器上移除这个指针。 Git 服务器通常会保留数据一段时间直到垃圾回收运行,所以如果不小心删除掉了,通常是很容易恢复的。

$ git push origin --delete serverfixTo /schacon/simplegit- [deleted] serverfix

3.6 变基

变基的基本操作:

合并操作:

分叉的提交历史:

通过合并操作来整合分叉的历史:

变基操作:将 C4 中的修改变基到 C3 上:

$ git checkout experiment$ git rebase masterFirst, rewinding head to replay your work on top of it...Applying: added staged command

回到 master 分支,进行一次master 分支的快进合并。

$ git checkout master$ git merge experiment

Note:

这两种整合方法的最终结果没有任何区别,但是变基使得提交历史更加整洁。无论是通过变基,还是通过三方合并,整合的最终结果所指向的快照始终是一样的,只不过提交历史不同罢了。 变基是将一系列提交按照原有次序依次应用到另一分支上,而合并是把最终结果合在一起。

原则:

只对尚未推送或分享给别人的本地修改执行变基操作清理历史, 从不对已推送至别处的提交执行变基操作,这样,你才能享受到两种方式带来的便利。

变基的详细用法

4. 服务器上的 Git

4.1 协议

Git 可以使用四种不同的协议来传输资料:本地协议(Local),HTTP 协议,SSH(Secure Shell)协议及 Git 协议。 在此,我们将会讨论那些协议及哪些情形应该使用(或避免使用)他们。

1、本地协议:

远程版本库就是同一主机上的另一个目录,优点是简单,并且直接使用了现有的文件权限和网络访问权限。缺点是通常共享文件系统比较难配置,并且比起基本的网络连接访问,这不方便从多个位置访问。最终,这个协议并不保护仓库避免意外的损坏。 每一个用户都有“远程”目录的完整 shell 权限,没有方法可以阻止他们修改或删除 Git 内部文件和损坏仓库。

2、HTTP 协议:

智能HTTP协议:

运行在标准的 HTTP/S 端口上并且可以使用各种 HTTP 验证机制, 这意味着使用起来会比 SSH 协议简单的多,比如可以使用 HTTP 协议的用户名/密码授权,免去设置 SSH 公钥。智能 HTTP 协议或许已经是最流行的使用 Git 的方式了,它即支持像 git:// 协议一样设置匿名服务, 也可以像 SSH 协议一样提供传输时的授权和加密。

哑(Dumb) HTTP 协议:

如果服务器没有提供智能 HTTP 协议的服务,Git 客户端会尝试使用更简单的“哑” HTTP 协议。 哑 HTTP 协议里 web 服务器仅把裸版本库当作普通文件来对待,提供文件服务。 哑 HTTP 协议的优美之处在于设置起来简单。

3、SSH 协议:

架设 Git 服务器时常用 SSH 协议作为传输协议。 因为大多数环境下服务器已经支持通过 SSH 访问 —— 即使没有也很容易架设。 SSH 协议也是一个验证授权的网络协议;并且,因为其普遍性,架设和使用都很容易。

通过 SSH 协议克隆版本库,你可以指定一个 ssh:// 的 URL:

$ git clone ssh://[user@]server/project.git

或者使用一个简短的 scp 式的写法:

$ git clone [user@]server:project.git

在上面两种情况中,如果你不指定可选的用户名,那么 Git 会使用当前登录的用的名字。

4、Git 协议:

优点:

目前,Git 协议是 Git 使用的网络传输协议里最快的。 如果你的项目有很大的访问量,或者你的项目很庞大并且不需要为写进行用户授权,架设 Git 守护进程来提供服务是不错的选择。 它使用与 SSH 相同的数据传输机制,但是省去了加密和授权的开销。

缺点:

Git 协议缺点是缺乏授权机制。 把 Git 协议作为访问项目版本库的唯一手段是不可取的。 一般的做法里,会同时提供 SSH 或者 HTTPS 协议的访问服务,只让少数几个开发者有推送(写)权限,其他人通过 git:// 访问只有读权限。 Git 协议也许也是最难架设的。 它要求有自己的守护进程,这就要配置 xinetd、systemd 或者其他的程序,这些工作并不简单。 它还要求防火墙开放 9418 端口,但是企业防火墙一般不会开放这个非标准端口。 而大型的企业防火墙通常会封锁这个端口。

4.2 在服务器上搭建 Git

4.3 生成 SSH 公钥

4.4 配置服务器

4.5 Git 守护进程

4.6 Smart HTTP

4.7 GitWeb

4.8 GitLab

4.9 第三方托管的选择

5. 分布式 Git

5.1 分布式工作流程

1、集中式工作流:

集中式系统中通常使用的是单点协作模型——集中式工作流。 一个中心集线器,或者说 仓库,可以接受代码,所有人将自己的工作与之同步。 若干个开发者则作为节点,即中心仓库的消费者与中心仓库同步。

2、集成管理者工作流:

Git 允许多个远程仓库存在,使得这样一种工作流成为可能:每个开发者拥有自己仓库的写权限和其他所有人仓库的读权限。 这种情形下通常会有个代表“官方”项目的权威的仓库。 要为这个项目做贡献,你需要从该项目克隆出一个自己的公开仓库,然后将自己的修改推送上去。 接着你可以请求官方仓库的维护者拉取更新合并到主项目。 工作流程如下:

项目维护者推送到主仓库。贡献者克隆此仓库,做出修改。贡献者将数据推送到自己的公开仓库。贡献者给维护者发送邮件,请求拉取自己的更新。维护者在自己本地的仓库中,将贡献者的仓库加为远程仓库并合并修改。维护者将合并后的修改推送到主仓库。

3、主管与副主管工作流:

这其实是多仓库工作流程的变种。 一般拥有数百位协作开发者的超大型项目才会用到这样的工作方式。工作流程如下:

普通开发者在自己的主题分支上工作,并根据 master 分支进行变基。 这里是主管推送的参考仓库的 master 分支。

副主管将普通开发者的主题分支合并到自己的 master 分支中。

主管将所有副主管的 master 分支并入自己的 master 分支中。

最后,主管将集成后的 master 分支推送到参考仓库中,以便所有其他开发者以此为基础进行变基。

5.2 向一个项目贡献

1、提交准则:

首先,你的提交不应该包含任何空白错误。 运行 git diff --check,会找到可能的空白错误并将它们为你列出来。

一个简单的多人 Git 工作流程的通常事件顺序如图:

2、私有管理团队:

小组基于特性进行协作,而团队的贡献将会由其他人整合特性分支到主分支。

管理团队工作流程的基本顺序如图:

3、派生的公开项目:

没有权限直接更新项目的分支,你必须用其他办法将工作给维护者。

克隆主仓库,为计划贡献的补丁或补丁序列创建一个主题分支,然后在那儿做工作。 顺序看起来基本像这样:

$ git clone <url>$ cd project$ git checkout -b featureA... work ...$ git commit... work ...$ git commit

分支工作完成后准备将其贡献回维护者,去原始项目中然后点击“Fork”按钮,创建一份自己的可写的项目派生仓库。 然后需要在本地仓库中将该仓库添加为一个新的远程仓库,在本例中称作 myfork,并推送工作:

$ git remote add myfork <url>$ git push -u myfork featureA

通知原项目的维护者你有想要他们合并的工作:

GitHub 有它自己的 Pull Request 机制也可以运行 git request-pull 命令然后将随后的输出通过电子邮件手动发送给项目维护者。

$ git request-pull origin/master myforkThe following changes since commit 1edee6b1d61823a2de3b09c160d7080b8d1b3a40:Jessica Smith (1):added a new functionare available in the git repository at:git://githost/simplegit.git featureAJessica Smith (2):add limit to log functionchange log output to 30 from 25lib/simplegit.rb | 10 +++++++++-1 files changed, 9 insertions(+), 1 deletions(-)

在一个你不是维护者的项目上,通常有一个总是跟踪 origin/master 的 master 分支会很方便,在主题分支上做工作是因为如果它们被拒绝时你可以轻松地丢弃。如你想要提供第二个特性工作到项目,不要继续在刚刚推送的主题分支上工作——从主仓库的 master 分支重新开始:

$ git checkout -b featureB origin/master... work ...$ git commit$ git push myfork featureB$ git request-pull origin/master myfork... email generated request pull to maintainer ...$ git fetch origin

现在,每一个特性都保存在一个贮藏库中——类似于补丁队列——可以重写、变基与修改而不会让特性互相干涉或互相依赖,像featureB 的初始提交历史这样:

假设项目维护者已经拉取了一串其他补丁,然后尝试拉取你的第一个分支,但是没有干净地合并。 在这种情况下,可以尝试变基那个分支到 origin/master 的顶部,为维护者解决冲突,然后重新提交你的改动,这样会重写你的历史,现在看起来像是 featureA 工作之后的提交历史:

$ git checkout featureA$ git rebase origin/master$ git push -f myfork featureA

可能的情况:维护者看到了你的第二个分支上的工作并且很喜欢其中的概念,但是想要你修改一下实现的细节。 你也可以利用这次机会将工作基于项目现在的 master 分支。 你从现在的 origin/master 分支开始一个新分支,在那儿压缩 featureB 的改动,解决任何冲突,改变实现,然后推送它为一个新分支。

$ git checkout -b featureBv2 origin/master$ git merge --squash featureB... change implementation ...$ git commit$ git push myfork featureBv2

–squash 选项接受被合并的分支上的所有工作,并将其压缩至一个变更集, 使仓库变成一个真正的合并发生的状态,而不会真的生成一个合并提交。 这意味着你的未来的提交将会只有一个父提交,并允许你引入另一个分支的所有改动, 然后在记录一个新提交前做更多的改动。同样 --no-commit 选项在默认合并过程中可以用来延迟生成合并提交。现在你可以给维护者发送一条消息,表示你已经做了要求的修改然后他们可以在你的 featureBv2 分支上找到那些改动。

4、通过邮件的公开项目:

略。

5.3 维护项目

除了如何有效地参与一个项目的贡献之外,你可能也需要了解如何维护项目。 这包含接受并应用别人使用 format-patch 生成并通过电子邮件发送过来的补丁, 或对项目添加的远程版本库分支中的更改进行整合。 但无论是管理版本库,还是帮忙验证、审核收到的补丁,都需要同其他贡献者约定某种长期可持续的工作方式。

1、贡献者在主题分支中工作。

进行工作的特性为分支创建一个简单的名字,比如 ruby_client 或者具有类似描述性的其他名字,这样即使你必须暂时抛弃它,以后回来时也不会忘记。 项目的维护者一般还会为这些分支附带命名空间,比如sc/ruby_client(其中 sc 是贡献该项工作的人名称的简写)。可以使用如下方式基于 development分支建立主题分支:

$ git branch sc/ruby_client development

or同时切换到主题分支

$ git checkout -b sc/ruby_client development

2、应用来自邮件的补丁:

3、使用 am 命令应用补丁:

4、检出远程分支:

如果你的贡献者建立了自己的版本库,并且向其中推送了若干修改, 之后将版本库的 URL 和包含更改的远程分支发送给你,那么你可以将其添加为一个远程分支,并且在本地进行合并。

比如 Jessica 向你发送了一封电子邮件,内容是在她的版本库中的 ruby-client 分支中有一个很不错的新功能, 为了测试该功能,你可以将其添加为一个远程分支,并在本地检出:

$ git remote add jessica git:///jessica/myproject.git$ git fetch jessica$ git checkout -b rubyclient jessica/ruby-client

如果她再次发邮件说另一个分支中包含另一个优秀功能,因为之前已经设置好远程分支了, 你就可以直接进行 fetch 和 checkout 操作。

对于非持续性的合作,如果你依然想要以这种方式拉取数据的话,你可以对远程版本库的 URL 调用 git pull 命令。 这会执行一个一次性的抓取,而不会将该 URL 存为远程引用:

$ git pull /onetimeguy/projectFrom /onetimeguy/project* branch HEAD -> FETCH_HEADMerge made by the 'recursive' strategy.

5、确定引入了哪些东西

你已经有了一个包含其他人贡献的主题分支。 现在你可以决定如何处理它们了。 本节回顾了若干命令,以便于你检查若将其合并入主分支所引入的更改。

一般来说,你应该对该分支中所有 master 分支尚未包含的提交进行检查。 通过在分支名称前加入 --not 选项,你可以排除 master 分支中的提交。 这和我们之前使用的 master…contrib 格式是一样的。 假设贡献者向你发送了两个补丁,为此你创建了一个名叫 contrib 的分支并在其上应用补丁,你可以运行:

$ git log contrib --not mastercommit 5b6235bd297351589efc4d73316f0a68d484f118Author: Scott Chacon <schacon@>Date: Fri Oct 24 09:53:59 -0700seeing if this helps the gemcommit 7482e0d16d04bea79d0dba8988cc78df655f16a0Author: Scott Chacon <schacon@>Date: Mon Oct 22 19:38:36 -0700updated the gemspec to hopefully work better

如果要查看每次提交所引入的具体修改,你应该记得可以给 git log 命令传递 -p 选项,这样它会在每次提交后面附加对应的差异(diff)。

而要查看将该主题分支与另一个分支合并的完整 diff,你可能需要使用一个有些奇怪的技巧来得到正确的结果。 你可能会想到这种方式:

$ git diff master

这个命令会输出一个 diff,但它可能并不是我们想要的。 如果在你创建主题分支之后,master 分支向前移动了,你获得的结果就会显得有些不对。 这是因为 Git 会直接将该主题分支与 master 分支的最新提交快照进行比较。 比如说你在 master 分支中向某个文件添加了一行内容,那么直接比对最新快照的结果看上去就像是你在主题分支中将这一行删除了。

如果 master 分支是你的主题分支的直接祖先,其实是没有任何问题的; 但是一旦两个分支的历史产生了分叉,上述比对产生的 diff 看上去就像是将主题分支中所有的新东西加入, 并且将 master 分支所独有的东西删除。

而你真正想要检查的东西,实际上仅仅是主题分支所添加的更改——也就是该分支与 master 分支合并所要引入的工作。 要达到此目的,你需要让 Git 对主题分支上最新的提交与该分支与 master 分支的首个公共祖先进行比较。

从技术的角度讲,你可以以手工的方式找出公共祖先,并对其显式运行 diff 命令:

$ git merge-base contrib master36c7dba2c95e6bbb78dfa822519ecfec6e1ca649$ git diff 36c7db

或者,更简洁的形式:

$ git diff $(git merge-base contrib master)

然而,这种做法比较麻烦,所以 Git 提供了一种比较便捷的方式:三点语法。 对于 git diff 命令来说,你可以通过把 … 置于另一个分支名后来对该分支的最新提交与两个分支的共同祖先进行比较:

$ git diff master...contrib

该命令仅会显示自当前主题分支与 master 分支的共同祖先起,该分支中的工作。 这个语法很有用,应该牢记。

6、将贡献的工作整合进来

当主题分支中所有的工作都已经准备好整合进入更靠近主线的分支时,接下来的问题就是如何进行整合了。 此外,还有一个问题是,你想使用怎样的总体工作流来维护你的项目? 你的选择有很多,我们会介绍其中的一部分。

6.1、合并工作流

合并主题分支前:

合并主题分支后:

一次发布之后:

6.2、大项目合并工作流

Git 项目包含四个长期分支:master、next,用于新工作的 pu(proposed updates)和用于维护性向后移植工作(maintenance backports)的 maint 分支。

管理复杂的一系列接收贡献的平行主题分支:

如果主题分支需要更多工作,它则会被并入 pu 分支。 当它们完全稳定之后,会被再次并入 master 分支。 这意味着 master 分支始终在进行快进,next 分支偶尔会被变基,而 pu 分支的变基比较频繁:

将贡献的主题分支并入长期整合分支:

6.3、变基与拣选工作流

为了保持线性的提交历史,有些维护者更喜欢在 master 分支上对贡献过来的工作进行变基和拣选,而不是直接将其合并。 当你完成了某个主题分支中的工作,并且决定要将其整合的时候,你可以在该分支中运行变基命令, 在当前 master 分支(或者是 develop 等分支)的基础上重新构造修改。 如果结果理想的话,你可以快进 master 分支,最后得到一个线性的项目提交历史。

另一种将引入的工作转移到其他分支的方法是拣选。 Git 中的拣选类似于对特定的某次提交的变基。 它会提取该提交的补丁,之后尝试将其重新应用到当前分支上。 这种方式在你只想引入主题分支中的某个提交,或者主题分支中只有一个提交,而你不想运行变基时很有用。 举个例子,假设你的项目提交历史类似:

如果你希望将提交 e43a6 拉取到 master 分支,你可以运行:

$ git cherry-pick e43a6Finished one cherry-pick.[master]: created a0a41a9: "More friendly message when locking the index fails."3 files changed, 17 insertions(+), 3 deletions(-)

这样会拉取和 e43a6 相同的更改,但是因为应用的日期不同,你会得到一个新的提交 SHA-1 值。 现在你的历史会变成这样:

现在你可以删除这个主题分支,并丢弃不想拉入的提交。

7、Rerere

如果你在进行大量的合并或变基,或维护一个长期的主题分支,Git 提供的一个叫做“rerere”的功能会有一些帮助。

Rerere 是“重用已记录的冲突解决方案(reuse recorded resolution)”的意思——它是一种简化冲突解决的方法。 当启用 rerere 时,Git 将会维护一些成功合并之前和之后的镜像,当 Git 发现之前已经修复过类似的冲突时, 便会使用之前的修复方案,而不需要你的干预。

这个功能包含两个部分:一个配置选项和一个命令。 其中的配置选项是 rerere.enabled,把它放在全局配置中就可以了:

$ git config --global rerere.enabled true

现在每当你进行一次需要解决冲突的合并时,解决方案都会被记录在缓存中,以备之后使用。

如果你需要和 rerere 的缓存交互,你可以使用 git rerere 命令。 当单独调用它时,Git 会检查解决方案数据库,尝试寻找一个和当前任一冲突相关的匹配项并解决冲突 (尽管当 rerere.enabled 被设置为 true 时会自动进行)。

8、为发布打标签

当你决定进行一次发布时,你可能想要打一个标签,这样在之后的任何一个提交点都可以重新创建该发布。 你在 Git 基础 中已经了解了创建新标签的过程。 作为一个维护者,如果你决定要为标签签名的话,打标签的过程应该是这样子的:

$ git tag -s v1.5 -m 'my signed 1.5 tag'You need a passphrase to unlock the secret key foruser: "Scott Chacon <schacon@>"1024-bit DSA key, ID F721C45A, created -02-09

如果你为标签签名了,你可能会遇到分发用来签名的 PGP 公钥的问题。 Git 项目的维护者已经解决了这一问题,其方法是在版本库中以 blob 对象的形式包含他们的公钥,并添加一个直接指向该内容的标签。 要完成这一任务,首先你可以通过运行 gpg --list-keys 找出你所想要的 key:

$ gpg --list-keys/Users/schacon/.gnupg/pubring.gpg---------------------------------pub 1024D/F721C45A -02-09 [expires: -02-09]uid Scott Chacon <schacon@>sub 2048g/45D02282 -02-09 [expires: -02-09]

之后你可以通过导出 key 并通过管道传递给 git hash-object 来直接将 key 导入到 Git 的数据库中,git hash-object 命令会向 Git 中写入一个包含其内容的新 blob 对象,并向你返回该 blob 对象的 SHA-1 值:

$ gpg -a --export F721C45A | git hash-object -w --stdin659ef797d181633c87ec71ac3f9ba29fe5775b92

既然 Git 中已经包含你的 key 的内容了,你就可以通过指定由 hash-object 命令给出的新 SHA-1 值来创建一个直接指向它的标签:

$ git tag -a maintainer-pgp-pub 659ef797d181633c87ec71ac3f9ba29fe5775b92

如果你运行 git push --tags 命令,那么 maintainer-pgp-pub 标签将会被共享给所有人。 需要校验标签的人可以通过从数据库中直接拉取 blob 对象并导入到 GPG 中来导入 PGP key:

$ git show maintainer-pgp-pub | gpg --import

人们可以使用这个 key 来校验所有由你签名的标签。 另外,如果你在标签信息中包含了一些操作说明,用户可以通过运行 git show 来获取更多关于标签校验的说明。

9、生成一个构建号

Git 中不存在随每次提交递增的“v123”之类的数字序列,如果你想要为提交附上一个可读的名称, 可以对其运行 git describe 命令。作为回应,Git 将会生成一个字符串, 它由最近的标签名、自该标签之后的提交数目和你所描述的提交的部分 SHA-1 值(前缀的 g 表示 Git)构成:

$ git describe masterv1.6.2-rc1-20-g8c5b85c

这样你在导出一个快照或构建时,可以给出一个便于人们理解的命名。 实际上,如果你的 Git 是从 Git 自己的版本库克隆下来并构建的,那么 git --version 命令给出的结果是与此类似的。 如果你所描述的提交自身就有一个标签,那么它将只会输出标签名,没有后面两项信息。

默认情况下, git describe 命令需要有注解的标签(即使用 -a 或 -s 选项创建的标签); 如果你想使用轻量标签(无注解的标签),请在命令后添加 --tags 选项。 你也可以使用这个字符串来调用 git checkout 或 git show 命令, 但是这依赖于其末尾的简短 SHA-1 值,因此不一定一直有效。 比如,最近 Linux 内核为了保证 SHA-1 值对象的唯一性,将其位数由 8 位扩展到了 10 位, 导致以前的 git describe 输出全部失效。

10、准备一次发布

现在你可以发布一个构建了。 其中一件事情就是为那些不使用 Git 的可怜包们创建一个最新的快照归档。 使用 git archive 命令完成此工作:

$ git archive master --prefix='project/' | gzip > `git describe master`.tar.gz$ ls *.tar.gzv1.6.2-rc1-20-g8c5b85c.tar.gz

如果有人将这个压缩包解压,他就可以在一个 project 目录中得到你项目的最新快照。 你也可以以类似的方式创建一个 zip 压缩包,但此时你应该向 git archive 命令传递 --format=zip 选项:

$ git archive master --prefix='project/' --format=zip > `git describe master`.zip

现在你有了本次发布的一个 tar 包和一个 zip 包,可以将其上传到网站或以电子邮件的形式发送给人们。

11、制作提交简报

现在是时候通知邮件列表里那些好奇你的项目发生了什么的人了。 使用 git shortlog 命令可以快速生成一份包含从上次发布之后项目新增内容的修改日志(changelog)类文档。 它会对你给定范围内的所有提交进行总结;比如,你的上一次发布名称是 v1.0.1,那么下面的命令可以给出上次发布以来所有提交的总结:

$ git shortlog --no-merges master --not v1.0.1Chris Wanstrath (6):Add support for annotated tags to Grit::TagAdd packed-refs annotated tag support.Add Grit::Commit#to_patchUpdate version and History.txtRemove stray `puts`Make ls_tree ignore nilsTom Preston-Werner (4):fix dates in historydynamic version methodVersion bump to 1.0.2Regenerated gemspec for version 1.0.2

这份整洁的总结包括了自 v1.0.1 以来的所有提交,并且已经按照作者分好组,你可以通过电子邮件将其直接发送到列表中。

6. GitHub

6.1 账户的创建和配置

1、注册:

官网:

2、SSH 访问:

现在,你完全可以使用 https:// 协议,通过你刚刚创建的用户名和密码访问 Git 版本库。 但是,如果仅仅克隆公有项目,你甚至不需要注册——刚刚我们创建的账户是为了以后 fork 其它项目,以及推送我们自己的修改。

如果你习惯使用 SSH 远程,你需要配置一个公钥。 (如果你没有公钥,参考 生成 SSH 公钥。) 使用窗口右上角的链接打开你的账户设置:

然后在左侧选择“SSH keys”部分。

在这个页面点击“Add an SSH key”按钮,给你的公钥起一个名字,将你的 ~/.ssh/id_rsa.pub (或者自定义的其它名字)公钥文件的内容粘贴到文本区,然后点击“Add key”。

Note:

确保给你的 SSH 密钥起一个能够记得住的名字。 你可以为每一个密钥起名字(例如,“我的笔记本电脑”或者“工作账户”等),以便以后需要吊销密钥时能够方便地区分。

3、两步验证

最后,为了额外的安全性,你绝对应当设置两步验证,简写为 “2FA”。 两步验证是一种用于降低因你的密码被盗而带来的账户风险的验证机制,现在已经变得越来越流行。 开启两步验证,GitHub 会要求你用两种不同的验证方法,这样,即使其中一个被攻破,攻击者也不能访问你的账户。

你可以在 Account settings 页面的 Security 标签页中找到 Two-factor Authentication 设置。Security 标签页中的 2FA:

点击“Set up two-factor authentication”按钮,会跳转到设置页面。该页面允许你选择是要在登录时使用手机 app 生成辅助码(一种“基于时间的一次性密码”),还是要 GitHub 通过 SMS 发送辅助码。

选择合适的方法后,按照提示步骤设置 2FA,你的账户会变得更安全,每次登录 GitHub 时都需要提供除密码以外的辅助码。

6.2 对项目做出贡献

1、派生项目

如果你想要参与某个项目,但是并没有推送权限,这时可以对这个项目进行“派生(Fork)”。 当你“派生”一个项目时,GitHub 会在你的空间中创建一个完全属于你的项目副本,且你对其具有推送权限。

Note:

在以前,“fork”是一个贬义词,指的是某个人使开源项目向不同的方向发展,或者创建一个竞争项目,使得原项目的贡献者分裂。 在 GitHub,“fork”指的是你自己的空间中创建的项目副本,这个副本允许你以一种更开放的方式对其进行修改。

通过这种方式,项目的管理者不再需要忙着把用户添加到贡献者列表并给予他们推送权限。 人们可以派生这个项目,将修改推送到派生出的项目副本中,并通过创建拉取请求(Pull Request,简称 PR)来让他们的改动进入源版本库,下文我们会详细说明。 创建了拉取请求后,就会开启一个可供审查代码的板块,项目的拥有者和贡献者可以在此讨论相关修改,直到项目拥有者对其感到满意,并且认为这些修改可以被合并到版本库。

你可以通过点击项目页面右上角的“Fork”按钮,来派生这个项目。

在这里插入图片描述

稍等片刻,你将被转到新项目页面,该项目包含可写的代码副本。

2、GitHub 流程

GitHub 设计了一个以拉取请求为中心的特殊合作流程。 它基于我们在 主题分支 的 Git 分支 中提到的工作流程。 不管你是在一个紧密的团队中使用单独的版本库,或者使用许多的“Fork”来为一个由陌生人组成的国际企业或网络做出贡献,这种合作流程都能应付。

流程通常如下:

派生一个项目

从 master 分支创建一个新分支

提交一些修改来改进项目

将这个分支推送到 GitHub 上

创建一个拉取请求

讨论,根据实际情况继续修改

项目的拥有者合并或关闭你的拉取请求

将更新后的 master 分支同步到你的派生中

eg:

$ git clone /tonychacon/blink (1)Cloning into 'blink'...$ cd blink$ git checkout -b slow-blink (2)Switched to a new branch 'slow-blink'$ sed -i '' 's/1000/3000/' blink.ino (macOS) (3)# If you're on a Linux system, do this instead:# $ sed -i 's/1000/3000/' blink.ino (3)$ git diff --word-diff (4)diff --git a/blink.ino b/blink.inoindex 15b9911..a6cc5a5 100644--- a/blink.ino+++ b/blink.ino@@ -18,7 +18,7 @@ void setup() {// the loop routine runs over and over again forever:void loop() {digitalWrite(led, HIGH); // turn the LED on (HIGH is the voltage level)[-delay(1000);-]{+delay(3000);+}// wait for a seconddigitalWrite(led, LOW); // turn the LED off by making the voltage LOW[-delay(1000);-]{+delay(3000);+}// wait for a second}$ git commit -a -m 'three seconds is better' (5)[slow-blink 5ca509d] three seconds is better1 file changed, 2 insertions(+), 2 deletions(-)$ git push origin slow-blink (6)Username for '': tonychaconPassword for 'https://tonychacon@':Counting objects: 5, done.Delta compression using up to 8 pressing objects: 100% (3/3), done.Writing objects: 100% (3/3), 340 bytes | 0 bytes/s, done.Total 3 (delta 1), reused 0 (delta 0)To /tonychacon/blink1. [new branch]slow-blink -> slow-blink

将派生出的副本克隆到本地

创建出名称有意义的分支

修改代码

检查改动

将改动提交到分支中

将新分支推送到 GitHub 的副本中

现在到 GitHub 上查看之前的项目副本,可以看到 GitHub 提示我们有新的分支, 并且显示了一个大大的绿色按钮让我们可以检查我们的改动,并给源项目创建拉取请求。

也可以到“Branches”(分支)页面查看分支并创建拉取请求: /<用户名>/<项目名>/branches

如果我们点击那个绿色按钮,就会跳到一个新页面,在这里我们可以为拉取请求填写标题和描述。 花点时间编写一个清晰有用的描述是非常值得的,这能让原项目拥有者明白你做了什么, 为什么这个改动是正确的,以及接受此更改是否能够改进他的项目。

同时我们也能看到比主分支中所“领先”(ahead)的提交(在这个例子中只有一个)以及所有将会被合并的改动与之前代码的对比。

当你单击了“Create pull request”(创建拉取请求)的按钮后,这个项目的拥有者将会收到一条包含关改动和拉取请求页面的链接的提醒。

Note:

虽然拉取请求通常是在贡献者准备好在公开项目中提交改动的时候提交,但是也常被用在仍处于开发阶段的内部项目中。 因为拉取请求在提交后依然可以加入新的改动 ,它也经常被用来建立团队合作的环境,而不只是在最终阶段使用。

3、利用拉取请求

现在,项目的拥有者可以审查修改,只需要单击某一行,就可以对其发表评论。

当维护者发表评论后,提交拉取请求的人,以及所有正在关注(Watching)这个版本库的用户都会收到通知。 我们待会儿将会告诉你如何修改这项设置。现在,如果 Tony 有开启电子邮件提醒,他将会收到这样的一封邮件:

每个人都能在拉取请求中发表评论。在拉取请求讨论页面 里我们可以看到项目拥有者对某行代码发表评论, 并在讨论区留下了一个普通评论。你可以看到被评论的代码也会在互动中显示出来。

现在贡献者可以看到如何做才能让他们的改动被接受。幸运的是,这也是一件轻松的事情。 如果你使用的是电子邮件进行交流,你需要再次对代码进行修改并重新提交至邮件列表, 这些修改会自动更新到拉取请求上。在最终的拉取请求 中,你也可以在更新后的拉取请求中看到已折叠的旧代码评论, 因为它是在修改后的行上添加的评论。

对现有的拉取请求添加提交并不会触发提醒,因此 Tony 在推送了他的修正后, 还需要通过评论告知项目拥有者他完成了修改请求。

如果你点开拉取请求的“Files Changed”(更改的文件)选项卡,你将会看到“整理过的”差异表 —— 也就是这个分支被合并到主分支之后将会产生的所有改动, 其实就是git diff master...<分支名>命令的执行结果。 你可以浏览 确定引入了哪些东西 来了解更多关于差异表的知识。

你还会注意到,GitHub 会检查你的拉取请求是否能直接合并,如果可以,将会提供一个按钮来进行合并操作。 这个按钮只在你对版本库有写入权限并且可以进行简洁合并时才会显示。 你点击后 GitHub 将做出一个“非快进式”(non-fast-forward)合并, 即使这个合并 能够 快进式(fast-forward)合并,GitHub 依然会创建一个合并提交。

如果你需要,你还可以将分支拉取并在本地合并。 如果你将这个分支合并到 master 分支中并推送到 GitHub,这个拉取请求会被自动关闭。

这就是大部分 GitHub 项目使用的工作流程。创建分支,基于分支创建拉取请求,进行讨论, 根据需要继续在分支上进行修改,最终关闭或合并拉取请求。

Note:

有件很重要的事情:你可以在同一个版本库中不同的分支提交拉取请求。 如果你正在和某人实现某个功能,而且你对项目有写权限,你可以推送分支到版本库, 并在 master 分支提交一个拉取请求并在此进行代码审查和讨论的操作。不需要进行“Fork”。

4、拉取请求的进阶用法

目前,我们学到了如何在 GitHub 平台对一个项目进行最基础的贡献。现在我们会教给你一些小技巧,让你可以更加有效率地使用拉取请求。

4.1、拉取请求与补丁

有一件重要的事情:许多项目并不认为拉取请求可以作为补丁, 就和通过邮件列表工作的的项目对补丁贡献的看法一样。 大多数的 GitHub 项目将拉取请求的分支当作对改动的交流方式,并将变更集合起来统一进行合并。

这是个重要的差异,因为一般来说改动会在代码完成前提出,这和基于邮件列表的补丁贡献有着天差地别。 这使得维护者们可以更早的沟通,由社区中的力量能提出更好的方案。 当有人从拉取请求提交了一些代码,并且维护者和社区提出了一些意见,这个补丁系列并不需要从头来过, 只需要将改动重新提交并推送到分支中,这使得讨论的背景和过程可以齐头并进。

举个例子,你可以回去看看 最终的拉取请求,你会注意到贡献者没有变基他的提交再提交一个新的拉取请求, 而是直接增加了新的提交并推送到已有的分支中。 如果你之后再回去查看这个拉取请求,你可以轻松地找到这个修改的原因。 点击网页上的“Merge”(合并)按钮后,会建立一个合并提交并指向这个拉取请求,你就可以很轻松的研究原来的讨论内容。

4.2与上游保持同步

如果你的拉取请求由于过时或其他原因不能干净地合并,你需要进行修复才能让维护者对其进行合并。 GitHub 会对每个提交进行测试,让你知道你的拉取请求能否简洁的合并。不能进行干净合并:

如果你看到了像不能进行干净合并中的画面,你就需要修复你的分支让这个提示变成绿色,这样维护者就不需要再做额外的工作。

你有两种方法来解决这个问题。你可以把你的分支变基到目标分支中去 (通常是你派生出的版本库中的 master 分支),或者你可以合并目标分支到你的分支中去。

GitHub上的大多数的开发者会使用后一种方法,基于我们在上一节提到的理由: 我们最看重的是历史记录和最后的合并,变基除了给你带来看上去简洁的历史记录, 只会让你的工作变得更加困难且更容易犯错。

如果你想要合并目标分支来让你的拉取请求变得可合并,你需要将源版本库添加为一个新的远端,并从远端抓取内容,合并主分支的内容到你的分支中去,修复所有的问题并最终重新推送回你提交拉取请求使用的分支。

在这个例子中,我们再次使用之前的“tonychacon”用户来进行示范,源作者提交了一个改动, 使得拉取请求和它产生了冲突。现在来看我们解决这个问题的步骤。

$ git remote add upstream /schacon/blink (1)$ git fetch upstream (2)remote: Counting objects: 3, done.remote: Compressing objects: 100% (3/3), done.Unpacking objects: 100% (3/3), done.remote: Total 3 (delta 0), reused 0 (delta 0)From /schacon/blink* [new branch]master-> upstream/master$ git merge upstream/master (3)Auto-merging blink.inoCONFLICT (content): Merge conflict in blink.inoAutomatic merge failed; fix conflicts and then commit the result.$ vim blink.ino (4)$ git add blink.ino$ git commit[slow-blink 3c8d735] Merge remote-tracking branch 'upstream/master' \into slower-blink$ git push origin slow-blink (5)Counting objects: 6, done.Delta compression using up to 8 pressing objects: 100% (6/6), done.Writing objects: 100% (6/6), 682 bytes | 0 bytes/s, done.Total 6 (delta 2), reused 0 (delta 0)To /tonychacon/blinkef4725c..3c8d735 slower-blink -> slow-blink

将源版本库添加为一个远端,并命名为“upstream”(上游)

从远端抓取最新的内容

将该仓库的主分支的内容合并到你的分支中

修复产生的冲突

再推送回同一个分支

你完成了上面的步骤后,拉取请求将会自动更新并重新检查是否能干净的合并。

Git 的伟大之处就是你可以一直重复以上操作。如果你有一个运行了十分久的项目, 你可以轻松地合并目标分支且只需要处理最近的一次冲突,这使得管理流程更加容易。

如果你一定想对分支做变基并进行清理,你可以这么做,但是强烈建议你不要强行的提交到已经提交了拉取请求的分支。 如果其他人拉取了这个分支并进行一些修改,你将会遇到 变基的风险 中提到的问题。 相对的,将变基后的分支推送到 GitHub 上的一个新分支中,并且创建一个全新的拉取请求引用旧的拉取请求,然后关闭旧的拉取请求。

4.3参考:

你的下个问题可能是“我该如何引用旧的拉取请求?”。 有许多方法可以让你在 GitHub 上的几乎任何地方引用其他东西。

先从如何对拉取请求或议题(Issue)进行相互引用开始。所有的拉取请求和议题在项目中都会有一个独一无二的编号。 举个例子,你无法同时拥有 3 号拉取请求和 3 号议题。如果你想要引用任何一个拉取请求或议题, 你只需要在提交或描述中输入 #<编号> 即可。 你也可以指定引用其他版本库的议题或拉取请求,如果你想要引用其他人对该版本库的“Fork”中的议题或拉取请求, 输入用户名#<编号> ,如果在不同的版本库中,输入用户名/版本库名#<编号> 。

我们来看一个例子。假设我们对上个例子中的分支进行了变基,并为此创建一个新的拉取请求, 现在我们希望能在新的拉取请求中引用旧的拉取请求。 我们同时希望引用一个派生出的项目中的议题和一个完全不同的项目中的议题, 就可以像在拉取请求中的交叉引用这样填写描述。

拉取请求中的引用:

当我们提交了这个拉取请求,我们将会看到以上内容被渲染成这样:在拉取请求中渲染后的交叉引用

你会注意到完整的 GitHub 地址被简化了,只留下了必要的信息。

如果 Tony 回去关闭了源拉取请求,我们可以看到一个被引用的提示, GitHub 会自动的反向追踪事件并显示在拉取请求的时间轴上。 这意味着任何查看这个拉取请求的人可以轻松地访问新的拉取请求。 这个链接就像 在拉取请求中渲染后的交叉引用中展示的那样。

除了议题编号外,你还可以通过使用提交的 SHA-1 来引用提交。 你必须完整的写出 40 位长的 SHA-1,GitHub 会在评论中自动地产生指向这个提交的链接。 同样的,你可以像引用议题一样对派生的项目中的提交或者其他项目中的提交进行引用。

5、GitHub 风格的 Markdown

对于在 GitHub 中绝大多数文本框中能够做到的事,引用其他议题只是个开始。 在议题和拉取请求的描述,评论和代码评论还有其他地方,都可以使用“GitHub 风格的 Markdown”。 Markdown 可以让你输入纯文本,但是渲染出丰富的内容。

查看 一个 Markdown 的示例和渲染效果 里的示例来了解如何书写评论或文本,并通过 Markdown 进行渲染。

GitHub 风格的 Markdown 增加了一些基础的 Markdown 中做不到的东西。 它在创建拉取请求和议题中的评论和描述时十分有用。

5.1、任务列表

第一个 GitHub 专属的 Markdown 功能,特别是用在拉取请求中,就是任务列表。 一个任务列表可以展示出一系列你想要完成的事情,并带有复选框。 把它们放在议题或拉取请求中时,通常可以展示你想要完成的事情。

你可以这样创建一个任务列表:

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

如果我们将这个列表加入拉取请求或议题的描述中,它将会被渲染 Markdown 评论中渲染后的任务列表 这样。

在拉取请求中,任务列表经常被用来在合并之前展示这个分支将要完成的事情。 最酷的地方就是,你只需要点击复选框,就能更新评论 —— 你不需要直接修改 Markdown。

不仅如此,GitHub 还会将你在议题和拉取请求中的任务列表整理起来集中展示。 举个例子,如果你在一个拉取请求中有任务清单,你将会在所有拉取请求的总览页面上看到它的进度。 这使得人们可以把一个拉取请求分解成不同的小任务,同时便于其他人了解分支的进度。 你可以在 在拉取请求列表中的任务列表总结 看到一个例子。

当你在实现一个任务的早期就提交拉取请求,并使用任务清单追踪你的进度,这个功能会十分的有用。

5.2、代码片段

你也可以在评论中添加代码片段。这在你想要展示尚未提交到分支中的代码时会十分有用。 它也经常被用在展示无法正常工作的代码或这个拉取请求需要的代码。

你需要用“反引号”将需要添加的代码片段包起来。

```java

for(int i=0 ; i < 5 ; i++)

{

System.out.println("i is : " + i);

}

```

如果加入语言的名称,就像我们这里加入的“java”一样,GitHub 会自动尝试对摘录的片段进行语法高亮。 在下面的例子中,它最终会渲染成这个样子: 渲染后的代码片段示例 :

5.3、引用

如果你在回复一个很长的评论之中的一小段,你只需要复制你需要的片段,并在每行前添加 > 符号即可。 事实上,因为这个功能会被经常用到,它也有一个快捷键。 只要你把你要回应的文字选中,并按下 r 键,选中的问题会自动引用并填入评论框。

引用的部分就像这样:

\> Whether 'tis Nobler in the mind to suffer\> The Slings and Arrows of outrageous Fortune,How big are these slings and in particular, these arrows?

经过渲染后,就会变成这样: 渲染后的引用示例:

5.4、表情符号

最后,我们可以在评论中使用表情符号。这经常出现在 GitHub 的议题和拉取请求的评论中。 GitHub 上甚至有表情助手。如果你在输入评论时以 : 开头,自动完成器会帮助你找到你需要的表情。

你也可以在评论的任何地方使用 :<表情名称>: 来添加表情符号。 举个例子,你可以输入以下文字:

I :eyes: that :bug: and I :cold_sweat:.:trophy: for :microscope: it.:+1: and :sparkles: on this :ship:, it's :fire::poop:!:clap::tada::panda_face:

渲染之后,就会变成这样: 使用了大量表情符号的评论

虽然这个功能并不是非常实用,但是它在这种不方便表达感情的媒体里,加入了趣味的元素。

Note:

事实上现在已经有大量的在线服务可以使用表情符号,这里有个列表可以让你快速的找到能表达你的情绪的表情符号:

/tools/emoji-cheat-sheet/

5.5、图片

从技术层面来说,这并不是 GitHub 风格 Markdown 的功能,但是也很有用。 如果不想使用 Markdown 语法来插入图片,GitHub 允许你通过拖拽图片到文本区来插入图片。

如果你回去查看 通过拖拽的方式自动插入图片 ,你会发现文本区上有个“Parsed as Markdown”的提示。 点击它你可以了解所有能在 GitHub 上使用的 Markdown 功能。

派生的 GitHub 公共仓库保持更新

当你派生了一个 GitHub 仓库之后,你的仓库(即你的“派生”)会独立于原仓库而独立。 特别地,当原仓库有新的提交时,GitHub 会通知你:

This branch is 5 commits behind progit:master.(本分支落后 progit:master 5 个提交。)

但你的 GitHub 仓库不会被 GitHub 自动更新,这件事必须由你自己来做。还好,这事儿很简单。

第一种方法无需配置。例如,若你从 /progit/progit2.git 派生了项目, 你可以像这样更新你的 master 分支:

$ git checkout master (1)$ git pull /progit/progit2.git (2)$ git push origin master (3)

如果在另一个分支上,就切换到 master

从 /progit/progit2.git 抓取更改后合并到 master

将 master 分支推送到 origin

这虽然可行,但每次都要输入从哪个 URL 抓取有点麻烦。你可以稍微设置一下来自动完成它:

$ git remote add progit /progit/progit2.git (1)$ git branch --set-upstream-to=progit/master master (2)$ git config --local remote.pushDefault origin (3)

添加源仓库并取一个名字,这里叫它 progit

将 master 分支设置为从 progit 远端抓取

将默认推送仓库设置为 origin

搞定之后,工作流程为更加简单:

$ git checkout master (1)$ git pull (2)$ git push (3)

如果在另一个分支上,就切换到 master

从 progit 抓取更改后合并到 master

将 master 分支推送到 origin

这种方法非常有用,而且没有缺点。Git 非常乐意为你暗中做这些工作,而且它不会在你向 master 提交更改,从 progit 拉取更改,然后向 origin 推送时通知你,所有这些操作在这种配置下都是有效的。 因此你不必对直接提交到 master 有所顾虑,因为该分支从效果上来说属于上游仓库。

6.3 维护项目

1、创建新的版本库

让我们创建一个版本库来分享我们的项目。 通过点击面板右侧的“New repository”按钮,或者顶部工具条你用户名旁边的 + 按钮来开始我们的旅程。

这里除了一个你必须要填的项目名,其他字段都是可选的。 现在只需要点击 “Create Repository” 按钮,Duang!!! – 你就在 GitHub 上拥有了一个以<user>/<project_name>命名的新仓库了。

因为目前暂无代码,GitHub 会显示有关创建新版本库或者关联到一个已有的 Git 版本库的一些说明。 我们不会在这里详细说明此项,如果你需要复习,去看 Git 基础。

现在你的项目就托管在 GitHub 上了,你可以把 URL 给任何你想分享的人。 GitHub 上的项目可通过 HTTP 或 SSH 访问,HTTPS 为/<user>/<project_name>, SSH为git@:<user>/<project_name>。 Git 可以通过以上两种 URL 进行抓取和推送,但是用户的访问权限又因连接时使用的证书不同而异。

Note:

通常对于公开项目可以优先分享基于 HTTPS 的 URL,因为用户克隆项目不需要有一个 GitHub 帐号。 如果你分享 SSH URL,用户必须有一个帐号并且上传 SSH 密钥才能访问你的项目。 HTTPS URL 与你贴到浏览器里查看项目用的地址是一样的。

2、添加合作者

如果你想与他人合作,并想给他们提交的权限,你需要把他们添加为 “Collaborators”。 如果 Ben,Jeff,Louise 都在 GitHub 上注册了,你想给他们推送的权限,你可以将他们添加到你的项目。 这样做会给他们 “推送” 权限,就是说他们对项目和 Git 版本库都有读写的权限。点击边栏底部的 “Settings” 链接。

然后从左侧菜单中选择 “Collaborators” 。 然后,在输入框中填写用户名,点击 “Add collaborator.” 如果你想授权给多个人,你可以多次重复这个步骤。 如果你想收回权限,点击他们同一行右侧的 “X”

3、管理合并请求

现在你有一个包含一些代码的项目,可能还有几个有推送权限的合作者,下面来看当你收到合并请求时该做什么。

合并请求可以来自仓库副本的一个分支,或者同一仓库的另一个分支。 唯一的区别是 fork 过来的通常是和你不能互相推送的人,而内部的推送通常都可以互相访问。

作为例子,假设你是 “tonychacon” ,你创建了一个名为 “fade” 的 Arduino 项目.

3.1、邮件通知

有人来修改了你的代码,给你发了一个合并请求。 你会收一封关于合并请求的提醒邮件,它看起来像 新的合并请求的邮件通知。

关于这个邮件有几个要注意的地方。 它会给你一个小的变动统计结果 — 一个包含合并请求中改变的文件和改变了多少的列表。 它还给你一个 GitHub 上进行合并请求操作的链接。 还有几个可以在命令行使用的 URL。

如果你注意到git pull <url> patch-1这一行,这是一种合并远程分支的简单方式,无需必须添加一个远程分支。 我们很快会在 检出远程分支 讲到它。 如果你愿意,你可以创建并切换到一个主题分支,然后运行这个命令把合并请求合并进来。

还有一些有趣的 URL,像 .diff 和 .patch ,就像你猜的那样,它们提供 diff 和 patch 的标准版本。 你可以技术性地用下面的方法合并“合并请求”:

$ curl /tonychacon/fade/pull/1.patch | git am

3.2、在合并请求上进行合作

就像我们在 GitHub 流程 中说过的,现在你可以跟开启合并请求的人进行会话。 你既可以对某些代码添加注释,也可以对整个提交添加注释或对整个合并请求添加注释, 在任何地方都可以用 GitHub 风格的 Markdown。

每次有人在合并请求上进行注释你都会收到通知邮件,通知你哪里发生改变。 他们都会包含一个到改变位置的链接,你可以直接在邮件中对合并请求进行注释。

一旦代码符合了你的要求,你想把它合并进来,你可以把代码拉取下来在本地进行合并,也可以用我们之前提到过的git pull <url> <branch>语法,或者把 fork 添加为一个 remote,然后进行抓取和合并。

对于很琐碎的合并,你也可以用 GitHub 网站上的 “Merge” 按钮。 它会做一个 “non-fast-forward” 合并,即使可以快进(fast-forward)合并也会产生一个合并提交记录。 就是说无论如何,只要你点击 merge 按钮,就会产生一个合并提交记录。 你可以在 合并按钮和手工合并一个合并请求的指令. 看到,如果你点击提示链接,GitHub 会给你所有的这些信息。

如果你决定不合并它,你可以把合并请求关掉,开启合并请求的人会收到通知。

3.3、合并请求引用

如果你正在处理 许多 合并请求,不想添加一堆 remote 或者每次都要做一次拉取,这里有一个可以在 GitHub 上用的小技巧。 这是有点高级的技巧,但它相当有用,我们会在 引用规范 有更多的细节说明。

实际上 GitHub 在服务器上把合并请求分支视为一种 “假分支”。 默认情况下你克隆时不会得到它们,但它们还是隐式地存在,你可以很容易地访问到它们。

为了展示这个,我们要用到一个叫做 ls-remote 的低级命令(通常被叫做“plumbing”, 我们会在 底层命令与上层命令 读到更多相关内容)。 这个命令在日常 Git 操作中基本不会用到,但在显示服务器上有哪些引用(reference)时很管用。

如果在我们之前用过的 “blink” 版本库上使用这个命令,我们会得到一个版本库里所有的分支,标签和其它引用(reference)的列表。

$ git ls-remote /schacon/blink10d539600d86723087810ec636870a504f4fee4dHEAD10d539600d86723087810ec636870a504f4fee4drefs/heads/master6a83107c62950be9453aac297bb0193fd743cd6erefs/pull/1/headafe83c2d1a70674c9505cc1d8b7d380d5e076ed3refs/pull/1/merge3c8d735ee16296c242be7a9742ebfbc2665adec1refs/pull/2/head15c9f4f80973a2758462ab2066b6ad9fe8dcf03drefs/pull/2/mergea5a7751a33b7e86c5e9bb07b26001bb17d775d1arefs/pull/4/head31a45fc257e8433c8d8804e3e848cf61c9d3166crefs/pull/4/merge

当然,如果你在你自己的版本库或其它你想检查的远程版本库中使用git ls-remote origin,它会显示相似的内容。

如果版本库在 GitHub 上并且有打开的合并请求,你会得到一些以 refs/pull/ 开头的引用。 它们实际上是分支,但因为它们不在 refs/heads/ 中,所以正常情况下你克隆时不会从服务器上得到它们 ——抓取过程正常情况下会忽略它们。

每个合并请求有两个引用——其中以 /head 结尾的引用指向的提交记录与合并请求分支中的最后一个提交记录是同一个。 所以如果有人在我们的版本库中开启了一个合并请求,他们的分支叫做 bug-fix, 指向 a5a775 这个提交记录,那么在 我们的 版本库中我们没有 bug-fix 分支(因为那是在他们的 fork 中), 但我们 可以 有一个 pull/<pr#>/head 指向 a5a775。 这意味着我们可以很容易地拉取每一个合并请求分支而不用添加一堆远程仓库。

现在,你可以像直接抓取引用一样抓取那些分支或提交。

$ git fetch origin refs/pull/958/headFrom /libgit2/libgit2* branch refs/pull/958/head -> FETCH_HEAD

这告诉 Git: “连接到 origin 这个 remote,下载名字为 refs/pull/958/head 的引用。” Git 高高兴兴去执行,下载构建那个引用需要的所有内容,然后把指针指向 .git/FETCH_HEAD 下面你想要的提交记录。 然后你可以用 git merge FETCH_HEAD 把它合并到你想进行测试的分支,但那个合并的提交信息看起来有点怪。 然而,如果你需要审查 一大批 合并请求,这样操作会很麻烦。

还有一种方法可以抓取 所有的 合并请求,并且在你连接到远程仓库的时候保持更新。 用你最喜欢的编辑器打开 .git/config ,查找 origin 远程仓库。 看起来差不多像下面这样:

[remote "origin"]url = /libgit2/libgit2fetch = +refs/heads/*:refs/remotes/origin/*

以 fetch = 开头的行是一个 “refspec.” 它是一种把 remote 的名称映射到你本地 .git 目录的方法。 这一条(就是上面的这一条)告诉 Git,“remote 上 refs/heads 下面的内容在我本地版本库中都放在 refs/remotes/origin 。” 你可以把这一段修改一下,添加另一个 refspec:

[remote "origin"]url = /libgit2/libgit2.gitfetch = +refs/heads/*:refs/remotes/origin/*fetch = +refs/pull/*/head:refs/remotes/origin/pr/*

最后一行告诉 Git: “所有看起来像 refs/pull/123/head 的引用应该在本地版本库像 refs/remotes/origin/pr/123 一样存储” 现在,如果你保存那个文件,执行 git fetch:

$ git fetch# …* [new ref] refs/pull/1/head -> origin/pr/1* [new ref] refs/pull/2/head -> origin/pr/2* [new ref] refs/pull/4/head -> origin/pr/4# …

现在所有的合并请求在本地像分支一样展现,它们是只读的,当你执行抓取时它们也会更新。 这让在本地测试合并请求中的代码变得超级简单:

$ git checkout pr/2Checking out files: 100% (3769/3769), done.Branch pr/2 set up to track remote branch pr/2 from origin.Switched to a new branch 'pr/2'

你的鹰眼系统会发现在 refspec 的 remote 部分的结尾有个 head 。 在 GitHub 那边也有一个refs/pull/#/merge引用,它代表的是如果你在网站上按了 “merge” 按钮对应的提交记录。 这甚至让你可以在按按钮之前就测试这个合并。

3.4、合并请求之上的合并请求

你不仅可以在主分支或者说 master 分支上开启合并请求,实际上你可以在网络上的任何一个分支上开启合并请求。 其实,你甚至可以在另一个合并请求上开启一个合并请求。

如果你看到一个合并请求在向正确的方向发展,然后你想在这个合并请求上做一些修改或者你不太确定这是个好主意,或者你没有目标分支的推送权限,你可以直接在合并请求上开启一个合并请求。

当你开启一个合并请求时,在页面的顶端有一个框框显示你要合并到哪个分支和你从哪个分支合并过来的。 如果你点击那个框框右边的 “Edit” 按钮,你不仅可以改变分支,还可以选择哪个 fork。

这里你可以很简单地指明合并你的分支到哪一个合并请求或 fork。

3.6、提醒和通知

GitHub 内置了一个很好的通知系统,当你需要与别人或别的团队交流时用起来很方便。

在任何评论中你可以先输入一个 @ ,系统会自动补全项目中合作者或贡献者的名字和用户名。

你也可以提醒不在列表中的用户,但是通常自动补全用起更快。

当你发布了一个带用户提醒的评论,那个用户会收到通知。 这意味着把人们拉进会话中要比让他们投票有效率得多。 对于 GitHub 上的合并请求,人们经常把他们团队或公司中的其它人拉来审查问题或合并请求。

如果有人收到了合并请求或问题的提醒,他们会“订阅”它,后面有新的活动发生他们都会持续收到提醒。 如果你是合并请求或者问题的发起方你也会被订阅上,比如你在关注一个版本库或者你评论了什么东西。 如果你不想再收到提醒,在页面上有个 “Unsubscribe” 按钮,点一下就不会再收到更新了。

3.7、通知页面

当我们在这提到特指 GitHub 的 “notifications” ,指的是当 GitHub 上有事件发生时,它通知你的方式,这里有几种不同的方式来配置它们。 如果你打开配置页面的 “Notification center” 标签,你可以看到一些选项。

有两个选项,通过“邮件(Email)”和通过“网页(Web)”,你可以选用一个或者都不选或者都选。

3.8、网页通知

网页通知只在 GitHub 上存在,你也只能在 GitHub 上查看。 如果你打开了这个选项并且有一个你的通知,你会在你屏幕上方的通知图标上看到一个小蓝点。参见通知中心:

如果你点击那个玩意儿,你会看到你被通知到的所有条目,按照项目分好了组。 你可以点击左边栏的项目名字来过滤项目相关的通知。 你可以点击通知旁边的对号图标把通知标为已读,或者点击组上面的图标把项目中 所有的 通知标为已读。 在每个对号图标旁边都有一个静音按钮,你可以点一下,以后就不会收到它相关的通知。

所有这些工具对于处理大量通知非常有用。 很多 GitHub 资深用户都关闭邮件通知,在这个页面上处理他们所有的通知。

3.9、邮件通知

邮件通知是你处理 GitHub 通知的另一种方式。 如果你打开这个选项,每当有通知时,你会收到一封邮件。 我们在 通过电子邮件发送的评论提醒 和 新的合并请求的邮件通知. 看到了一些例子。 邮件也会被合适地按话题组织在一起,如果你使用一个具有会话功能的邮件客户端那会很方便。

GitHub 在发送给你的邮件头中附带了很多元数据,这对于设置过滤器和邮件规则非常有帮助。

举个例子,我们来看一看在 新的合并请求的邮件通知. 中发给 Tony 的一封真实邮件的头部,我们会看到下面这些:

To: tonychacon/fade <fade@>Message-ID: <tonychacon/fade/pull/1@>Subject: [fade] Wait longer to see the dimming effect better (#1)X-GitHub-Recipient: tonychaconList-ID: tonychacon/fade <fade.>List-Archive: /tonychacon/fadeList-Post: <mailto:reply+i-4XXX@>List-Unsubscribe: <mailto:unsub+i-XXX@>,...X-GitHub-Recipient-Address: tchacon@

这里有一些有趣的东西。如果你想高亮或者转发这个项目甚至这个合并请求相关的邮件, Message-ID 中的信息会以<user>/<project>/<type>/<id>的格式展现所有的数据。 例如,如果这是一个问题(issue),那么 字段就会是 “issues” 而不是 “pull” 。

List-Post 和 List-Unsubscribe 字段表示如果你的邮件客户端能够处理这些,那么你可以很容易地在列表中发贴或取消对这个相关帖子的订阅。 那会很有效率,就像在页面中点击静音按钮或在问题/合并请求页面点击 “Unsubscribe” 一样。

值得注意的是,如果你同时打开了邮件和网页通知,那么当你在邮件客户端允许加载图片的情况下阅读邮件通知时,对应的网页通知也将会同时被标记为已读。

3.10、特殊文件

如果你的版本库中有一些特殊文件,GitHub 会提醒你。

4、README

第一个就是 README 文件,可以是几乎任何 GitHub 可以识别的格式。 例如,它可以是 README ,README.md , README.asciidoc 。 如果 GitHub 在你的版本库中找到 README 文件,会把它在项目的首页渲染出来。

很多团队在这个文件里放版本库或项目新人需要了解的所有相关的信息。 它一般包含这些内容:

该项目的作用

如何配置与安装

有关如何使用和运行的例子

项目的许可证

如何向项目贡献力量

因为 GitHub 会渲染这个文件,你可以在文件里植入图片或链接让它更容易理解。

5、贡献 CONTRIBUTING

另一个 GitHub 可以识别的特殊文件是 CONTRIBUTING 。 如果你有一个任意扩展名的 CONTRIBUTING 文件,当有人开启一个合并请求时 GitHub 会显示 开启合并请求时有 CONTRIBUTING 文件存在.。

这个的作用就是你可以在这里指出对于你的项目开启的合并请求你想要的/不想要的各种事情。 这样别人在开启合并请求之前可以读到这些指导方针。

6、项目管理

对于一个单个项目其实没有很多管理事务要做,但也有几点有趣的。

6.1、改变默认分支

如果你想用 “master” 之外的分支作为你的默认分支,其他人将默认会在这个分支上开启合并请求或进行浏览,你可以在你版本库的设置页面的 “options” 标签下修改。

简单地改变默认分支下拉列表中的选项,它就会作为所有主要操作的默认分支,他人进行克隆时该分支也将被默认检出。

6.2、移交项目

如果你想把一个项目移交给 GitHub 中的另一个人或另一个组织,还是设置页面的这个 “options” 标签下有一个 “Transfer ownership” 选项可以用来干这个。

当你正准备放弃一个项目且正好有别人想要接手时,或者你的项目壮大了想把它移到一个组织里时,这就管用了。

这么做不仅会把版本库连带它所有的关注者和星标数都移到另一个地方,它还会将你的 URL 重定向到新的位置。 它也重定向了来自 Git 的克隆和抓取,而不仅仅是网页端请求。

6.4 管理组织

除了个人帐户之外,GitHub 还提供被称为组织(Organizations)的帐户。 组织账户和个人账户一样都有一个用于存放所拥有项目的命名空间,但是许多其他的东西都是不同的。 组织帐户代表了一组共同拥有多个项目的人,同时也提供一些工具用于对成员进行分组管理。 通常,这种账户被用于开源群组(例如:“perl”或者“rails”),或者公司(例如:“google”或者“twitter”)。

1、组织的基本知识

我们可以很简单地创建一个组织,只需要点击任意 GitHub 页面右上角的“+”图标,在菜单中选择“New organization”即可。

首先你必须提供组织的名称和组织的主要联系邮箱。 然后,如果你希望的话,也可以邀请其他用户作为共同拥有人。

完成以上步骤后,你就会拥有一个全新的组织。 类似于个人帐户,如果组织的所有内容都是开源的,那么你就可以免费使用这个组织。

作为一个组织的拥有者,当你在派生一个版本库的时候,你可以选择把它派生到你的组织的命名空间内。 当你新建版本库时,你可以把它存放到你的个人帐户或你拥有的组织内。 同时,你也会自动地“关注”所有这些组织内的新版本库。

就像头像,你可以为你的组织上传头像,使它更个性化。 同时,也和个人帐户类似,组织会有一个着陆页(landing page),用于列出该组织所有的版本库,并且该页面可供所有人浏览。

下面我们来说一些组织和个人帐户不同的地方。

2、团队

组织使用团队(Teams)来管理成员,团队就是组织中的一组个人账户和版本库,以及团队成员对这些版本库的访问权限。

例如,假设你的公司有三个版本库:frontend、backend 和 deployscripts。 你会希望你的 HTML/CSS/Javascript 开发者有 frontend 或者 backend 的访问权限,操作人员有 backend 和 deployscripts 的访问权限。 团队让这个任务变得更简单,而不用为每个版本库管理它的协作者。

组织页面主要由一个面板(dashboard)构成,这个仪表盘包含了这个组织内的所有版本库,用户和团队。

你可以点击组织页面右边的团队侧边栏(Teams)来管理你的团队。 点击之后,你会进入一个新页面,在这里你可以添加新成员和版本库到团队中,或者管理团队的访问权限和其它设置。 每个团队对于版本库可以有只读、读写和管理三种权限。 你可以通过点击在团队页面内的 “Settings” 按钮更改相应权限等级。

当你邀请一个用户加入团队,该用户会收到一封通知他被邀请的邮件。

除此之外,团队也类似于个人帐户,有 @mentions(例如:@acmecorp/frontend)的功能,不同之处就在于被提及的团队内所有成员都会成为这个话题的订阅者。 当你希望得到团队中某个人的关注,又不知道具体应该问谁的时候,这个功能就显得很有帮助。

一个用户可以加入任意数量的团队,所以别把自己局限于拥有访问控制的团队。 对于某一类课题,像 ux, css 或者 refactoring 这样有着特殊关注点的团队就显得很有帮助,而像 legal 和 colorblind 这样的就完全是针对它们各自领域的。

3、审计日志

组织的拥有者还可以访问组织中发生的事情的所有信息。 在 Audit Log 标签页有整个组织的日志,你可以看到谁在世界上哪个地方做了什么事。

你也可以通过选定某一类型的事件、某个地方、某个人对日志进行过滤。

6.5 脚本 GitHub

7. Git 工具

7.1 选择修订版本

7.2 交互式暂存

7.3 贮藏与清理

7.4 签署工作

7.5 搜索

7.6 重写历史

7.7 重置揭密

7.8 高级合并

7.9 Rerere

7.10 使用 Git 调试

7.11 子模块

7.12 打包

7.13 替换

7.14 凭证存储

7.15 总结

8. 自定义 Git

8.1 配置 Git

8.2 Git 属性

8.3 Git 钩子

8.4 使用强制策略的一个例子

8.5 总结

9. Git 与其他系统

9.1 作为客户端的 Git

9.2 迁移到 Git

9.3 总结

10. Git 内部原理

10.1 底层命令与上层命令

10.2 Git 对象

10.3 Git 引用

10.4 包文件

10.5 引用规范

10.6 传输协议

10.7 维护与数据恢复

10.8 环境变量

10.9 总结

11. 一个成功的Git分支模板

参考"A successful Git branching model"文章

/posts/a-successful-git-branching-model/

master、develop分支为黑色标识,作为远程仓库存在永远维持保存,而hotfixes、release、feature分支作为辅助分支通常存在于开发者本地开发仓库,用完删除。

主分支:

masterdevelop

develop分支:我们认为 origin/master是一支其 HEAD源代码总是代表了生产环境准备就绪的状态的主分支。

master分支:我们认为 origin/develop是一支其 HEAD源代码总是代表了最后一次交付的可以赶上下一次发布的状态的主分支。

每当 develop分支到达一个稳定的阶段,可以对外发布时,所有的改变都会被合并到 master分支,并打一个发布版本的 tag。

因此,每次改动被合并到 master的时候,这就是一个真正的新的发布产品。建议对此进行严格的控制,因此理论上我们可以为每次 master分支的提交都挂一个钩子脚本,向生产环境自动化构建并发布我们的软件

支持型分支:

feature 分支release 分支hotfix 分支

feature分支:

这类分支是很频繁的从develop分支上branch出来,完成开发和本地测试后,又频繁的合并到develop。这类分支不论存在时间长短,一般只存在开发人员的本地,不推送到远端。在某些情况下,当需求变更,某些特性不需要了时,分支直接就放弃删除了。

可能派发自:develop必须合并回:develop分支命名规范:除了 master、develop、release-或 hotfix-的任何名字

eg:

$ git checkout -b myfeature developSwitched to a new branch "myfeature"$ git checkout developSwitched to a new branch "develop"$ git merge --no-ff myfeature Updating ea1b82a..05e9557(Summary of changes)$ git branch -d myfeatureDeleted branch myfeature (was 05e9557)$ git push origin develop

--no-ff参数:非快进式合并,优点:develop可以正确回滚到develop上次提交,可以查看feature分支实现提交历史

release分支

当开发到一定时间后,准备发布新版本,从develop分支branch一个新的分支,取名release,或者是1.0这样的版本号,接下来这个分支只进行bug修复,不再从别的分支合并新特性,并且每修复一个bug,就要合并到develop分支。等到bug修复得差不多了,可以发布新版了,就把release分支合并到master和develop,并在master分支上打上tag,release分支生命周期结束,删除。这个分支一般会存在一段时间,也需要团队一起修复多个bug,所以推送到remote。

可能派发自:develop

必须合并回:develop和 master

分支命名规范:release-*

eg:

我们当前的生产环境发布的版本是 1.1.5,马上有一个 release 要发布了。develop分支已经为“下一次”release 做好了准备,并且我们已经决定把新的版本号定为 1.2 (而不是 1.1.6 或 2.0)。所以我们派发一个 release 分支并以新的版本号为其命名:

$ git checkout -b release-1.2 developSwitched to a new branch "release-1.2"$ ./bump-version.sh 1.2Files modified successfully, version bumped to 1.2.$ git commit -a -m "Bumped version number to 1.2"[release-1.2 74d9424] Bumped version number to 1.21 files changed, 1 insertions(+), 1 deletions(-)

创建好并切换到新的分支之后,我们完成对版本号的晋升。这里的 bump-version.sh是一个虚构的用来改变代码库中某些文件以反映新版本的 shell 脚本。(当然你也可以手动完成这些改变——重点是有些文件发生了改变)然后,晋升了的版本号会被提交。

这个新的分支会存在一段时间,直到它确实发布出去了为止。期间可能会有 bug 修复(这比在 develop做更合理)。但我们严格禁止在此开发庞大的新Feature,它们应该合并到 develop分支,并放入下次发布。

当 release 分支真正发布成功之后,还有些事情需要收尾。首先,release 分支会被合并到 master(别忘了,master上的每一次提交都代表一个真正的新的发布);然后,为 master上的这次提交打一个 tag,以便作为版本历史的重要参考;最后,还要把 release 分支产生的改动合并回 develop,以便后续的发布同样包含对这些 bug 的修复。

前两部在 git 下是这样操作的:

$ git checkout masterSwitched to branch 'master'$ git merge --no-ff release-1.2Merge made by recursive(Summary of changes)$ git tag -a 1.2

现在发布工作已经完成了,同时 tag 也打好了,用在未来做参考。

补充:你也可以通过 -s或-u <key>标记打 tag。

为了保留 release 分支里的改动记录,我们需要把这些改动合并回 develop。git 操作如下:

$ git checkout developSwitched to branch 'develop'$ git merge --no-ff release-1.2Merge made by recursive.(Summary of changes)

这一步有可能导致冲突的发生(只是有理论上的可能性,因为我们已经改变了版本号),一旦发现,解决冲突然后提交就好了。

现在我们真正完成了一个 release 分支,该把它删掉了,因为它的使命已经完成了:

$ git branch -d release-1.2Deleted branch release-1.2 (was ff452fe).

hotfixes分支

这个分支用于已经正式发布的生产版本(也就是master上打了tag)出现bug的时候,从master分支的某个tag(一般是最新的一个,除非同时维护多个发布版本)里,branch一个分支,一般起名hotfixNN(NN代表两位数字),用于修复bug。当bug修复后,就合并到master和dev分支,并且在master分支上打上新的tag,hotfixes生命周期结束,就可以删除了。这个分支存在时间较短,一般情况下不推送到remote。

可能派发自:master

必须合并回:develop 和 master

分支命名规范:hotfix-*

eg:

假设 1.2 版本是目前的生产环境且出现了一个严重的 bug,但是目前的 develop并不足够稳定。那么我们可以派发出一个 hotfix 分支来开始我们的修复工作:

$ git checkout -b hotfix-1.2.1 masterSwitched to a new branch "hotfix-1.2.1"$ ./bump-version.sh 1.2.1Files modified successfully, version bumped to 1.2.1.$ git commit -a -m "Bumped version number to 1.2.1"[hotfix-1.2.1 41e61bb] Bumped version number to 1.2.11 files changed, 1 insertions(+), 1 deletions(-)

别忘了在派发出分支之后晋升版本号!

然后,修复 bug,提交改动。通过一个或多个提交都可以。

$ git commit -m "Fixed severe production problem"[hotfix-1.2.1 abbe5d6] Fixed severe production problem5 files changed, 32 insertions(+), 17 deletions(-)

当我们完成之后,对 bug 的修复需要合并回 master,同时也需要合并回 develop,以保证接下来的发布也都已经解决了这个 bug。这和 release 分支的完成方式是完全一样的。

首先,更新 master并为本次发布打一个 tag:

$ git checkout masterSwitched to branch 'master'$ git merge --no-ff hotfix-1.2.1Merge made by recursive(Summary of changes)$ git tag -a 1.2.1

补充:你也可以通过 -s或-u <key>标记打 tag。

然后,把已修复的 bug 合并到 develop:

$ git checkout developSwitched to branch 'develop'$ git merge --no-ff hotfix-1.2.1Merge made by recursive(Summary of changes)

这个规矩的一个额外之处是:如果此时已经存在了一个 release 分支,那么 hotfix 的改变需要合并到这个 release 分支,而不是 develop 分支。因为把对 bug 的修复合并回 release 分支之后,release 分支最终还是会合并回 develop分支的。(如果在 develop分支中立刻需要对这个 bug 的修复,且等不及 release 分支合并回来,则你还是可以直接合并回 develop分支的,这是绝对没问题的)

最后,删掉这个临时的分支:

$ git branch -d hotfix-1.2.1Deleted branch hotfix-1.2.1 (was abbe5d6).

本内容不代表本网观点和政治立场,如有侵犯你的权益请联系我们处理。
网友评论
网友评论仅供其表达个人看法,并不表明网站立场。