几乎所有的版本控制系统都以某种形式支持分支。使用分支意味着可以将工作从开发主线上分离开来,以免影响开发主线。Git的分支模型是一个强大而又独特的“必杀技特性”,使得Git从众多版本控制系统中脱颖而出。
Git的一个显著优点:分支的创建、切换、删除和合并非常快捷易用。Git只是改变指向当前版本的指针,成本几乎为零。但是,太方便了也会产生副作用。如果不加注意,很可能会留下一个枝节蔓生、四处开放的版本库,到处都是分支,完全看不出主干发展的脉络。
时间线(timeline)是一种形象称谓,按照提交时间依次将多个版本(称为节点,第一个至最新版本)人为串成一条线,图谱化显示了各个版本的演变历程(族谱关系)。时间线上的节点是一个已提交的版本。
Git 的分支(branch),本质上是指向提交对象(commit object)的可变指针,会在每次的提交操作中自动向前移动(始终指向最后提交的版本)。
Git 的默认分支名字是master。习惯上,使用分支名代表一条时间线。
Git的“master”分支并不是一个特殊分支,它就跟其它分支完全没有区别。之所以几乎每一个仓库都有master分支,是因为
git init
命令默认创建它,并且大多数人都懒得去改动它。
Git存储的是一系列不同时刻的文件快照(blob对象),不是文件的变化或差异内容。为了真正理解Git处理分支的方式,需要回顾一下Git是如何保存数据的。
Git的一次提交过程生成3个对象,存入Git的数据库(.git/objects目录):
尚未暂存(git add
)时,已修改内容的工作文件尚未被Git跟踪
暂存(git add
)后,一个文件快照创建一个blob对象
blob对象存储已修改内容的工作文件(简称文件快照)。
提交(git commit
)后,先后创建tree对象和commit对象
tree对象存储文件快照的目录位置(目录树结构)和blob对象索引。
commit对象存储tree对象索引和所有提交信息(parent、author、commiter、提交备注)。
.git/objects目录存储Git的对象(commit对象、tree对象、blob对象),哈希值的首2个字符构成在objects目录内的子目录名,剩余部分构成文件名。
使用文本编辑器查看对象内容,只会显示乱码。使用以下命令查看Git的对象内容:
命令 | 功能 |
---|---|
cat 文件名 |
查看工作文件存储的文本内容 |
ls -al |
查看当前目录下的所有文件(包括隐藏文件) |
git cat-file -t 对象哈希值 |
查看对象的类型 |
git cat-file -p 对象哈希值 |
查看对象的存储内容 |
git cat-file -s 对象哈希值 |
查看对象的大小(size) |
假设一个提交对象的分解示意图如下所示:
commit对象
存储某一次提交的信息,包括所在的tree(目录结构,包含blob对象)、parent(父对象,祖先关系)、author(作者)的姓名和邮箱、commiter(提交者)的姓名和邮箱、提交时输入的备注信息。
首次提交产生的提交对象没有父对象,普通提交操作产生的提交对象有一个父对象,而由多个分支合并产生的提交对象有多个父对象。
查看commit对象的存储信息:
$ git cat-file -p 456a329
tree 0cf1fd7821d40d3a85b9997e5489f3b192f9bdb8 # 所属tree(一个引用)
author 作者名 <邮箱> 1577673591 +0800 # 作者
committer 修改者名 <邮箱> 1577673591 +0800 # 修改者
master分支新增new.txt文件 # 提交时的备注信息
tree对象
tree对象相当于目录结构(所处位置),包含blob对象或其它tree(若有)的索引。
查看tree对象的存储信息:
$ git cat-file -p 0cf1fd78
100644 blob f015593a2713371b83af3e09200083ac2b6dc38d new.txt
100644:文件类型
100644表示是普通文件、100755表示是可执行文件、120000表示是符号链接。
blob:对象类型是blob
f015593…:blob对象的哈希值(一个引用)
new.txt:工作文件名
blob对象
blob对象即文件快照,存储已跟踪(git add
)工作文件(累计至提交时的所有修改内容)。
查看blobt对象的存储信息:
$ git cat-file -p f015593a2
master分支增加111 # 工作文件的所有修改内容
若将所有commi都串连到master分支上(即只在一个分支上开发),意味着分支线会非常非常长。假如某一次提交后出现了冲突,而这个冲突很难解决,那么开发就停滞,无法向后再开发;又或者master上的分支出现了很大的问题,同样也无法接着开发。
.git/refs目录记录了分支的引用(reference)内容:
分支的类型有:
本地分支:在本地仓库内创建的分支
.git/refs/heads目录存储本地分支的引用(reference,指向最后提交的版本)。
远程分支:在远程仓库内创建的分支
在远程仓库的code(代码)界面可查看远程分支的名称和内容。远程分支的提交历史存放在远程仓库内。Git托管平台只能显示出远程分支最后版本的内容,不能显示远程仓库。
远程跟踪分支(分支跟踪关系,track)
在本地分支与远程分支之间人为设置某种对应的跟踪关系,称为远程跟踪分支。
.git/refs/remotes/远程仓库名目录存放某个远程仓库的所有远程跟踪分支
远程跟踪分支的作用
本地分支和远程分支建立关联关系
如果本地分支和远程分支建立了关联关系,当对本地分支进行push或pull时,Git会知道推送(push)到哪个远程分支,或者从哪个远程分支拉取(pull)到本地分支。
在本地仓库内记录最后一次跟踪时的版本
无论本地分支或远程分支的版本如何更新变化,在未执行命令(pull、push)同步时,远程跟踪分支依旧保持最后一次跟踪时的版本。
在执行命令(pull、push)时,Git根据远程跟踪分支记录的版本、本地分支版本和远程分支版本三者的变化情况,同步本地分支或远程分支。最后,远程跟踪分支更新跟踪版本。
Git切换分支(git checkout 分支名
)时:
首先在refs/heads目录内查看本地分支
若有,则切换到本地分支。
然后在refs/remotes目录内查找远程跟踪分支
若有,则将远程跟踪分支检出(新建)为本地分支。
例外情况:git clone
时,在refs/remotes/远程仓库别名目录内只有一个HEAD文件,没有远程跟踪分支,此时,可在packed-refs文件中查找。
使用git branch -a
可查看本地仓库内的所有分支(本地和远程跟踪分支):
$ git branch -a
* master # 本地分支(前有*号,表示当前分支)
temp # 本地分支
remotes/origin/master # 远程跟踪分支
使用git branch -r
可查看本地仓库内的远程跟踪分支:
$ git branch -r
origin/master
使用git remote -v
可查看本地仓库关联的远程仓库:
$ git remote -v
gitee git@gitee.com:用户名/仓库名.git (fetch)
gitee git@gitee.com:用户名/仓库名.git (push)
origin git@github.com:用户名/仓库名.git (fetch)
origin git@github.com:用户名/仓库名.git (push)
分支始终指向最新版本:
git add
阶段
工作文件内容以快照(blob对象,以哈希值命名)的方式存入Git。文件快照(blob对象)存储的是一个完整的工作文件(累计至提交时的所有修改内容)。
git commit
阶段
Git生成一个提交对象(commit object,简称版本,以哈希值命名)。提交对象存储有指向文件快照(blob对象)的索引等信息。
创建分支阶段
分支本质是一个可以移动的指针(又称索引、引用)。
Git创建分支时,默认是在最新提交对象上创建了一个可以移动的新指针,(若没有切换分支时)在每次提交操作中自动向前移动(始终指向最后提交的版本)。
在当前分支(已有)上新建分支(版本之间有祖先关系)。
在指定提交对象上新建分支但不切换分支
git branch 新分支名 start_point
start_point:分叉节点
xxx/111 (master)
$ git branch new
新建且切换到新分支(HEAD指向新分支)
git checkout -b 新分支名 start_point
start_point:分叉节点
$ git checkout -b 123 bdef38c1
Switched to a new branch '123'
orphan分支(孤儿分支),目的是创建独立分支(与当前分支的版本之间没有祖先关系)。
git checkout --orphan 新分支名
首先,将当前分支的最新提交对象放入暂存区
若需要,则提交(git commit
)暂存区,或修改工作文件后添加到暂存区再提交
新分支与旧分支之间没有祖先关系,但文件内容使用了旧分支的文件内容。
若希望得到一个完全独立的分支(文件内容和祖先关系都与旧分支无关)
git rm --cached <file>
git clean -df
git checkout
命令,使HEAD指针指向了新分支示例:
xxx/111 (master)
$ git checkout --orphan new
Switched to a new branch 'new'
删除本地的单个分支
# 删除本地已合并分支(fully merged,指定分支合并到当前分支后没有新提交)
git branch -d 本地分支名 # d表示在遇到错误时自动终止删除
# 删除本地未合并分支(慎用)
git branch -D 本地分支名 # D表示强制删除
删除远程的单个分支
git push origin :远程分支名
# 或
git push origin -d 远程分支名
# 或
直接在远程仓库界面上删除远程分支
批量删除本地分支(不能删除当前分支)
git branch -a | grep 'n' | xargs git branch -D
git branch -a
:表示列出本地所有分支
grep 'lyn_'
:Linux的grep命令用于查找文件里符合条件的字符串
使用正则匹配(git branch -a
的结果)分支名是否含有'n’
xargs
:Linux的xargs命令是给命令传递参数的一个过滤器,捕获一个命令的输出,然后传递给另外一个命令
xargs git branch -D
:
将以上匹配结果作为参数传给git branch -D
,执行删除本地分支操作
|
:是一个管道符,表示将上一段的结果传给下一段
批量删除远程分支
git branch -r| grep 'n' | sed 's/origin\///g' | xargs -I {} git push origin :{}
sed
:Linux的sed命令是利用脚本来处理文本
使用正则将接受到的分支都过滤掉origin/
,得到实际的远程分支名
切换分支时,即修改HEAD指针的指向(HEAD始终指向当前分支)。Git会用该分支的最后提交的快照替换工作目录内容。
git checkout branch_name
branch_name:已有的分支名(本地分支、远程跟踪分支)
若不是已有的分支名,将触发错误:
$ git checkout abdee
error: pathspec 'abdee' did not match any file(s) known to git
运行命令后,GIt切换到xxx分支,即HEAD指向了xxx分支
将工作目录恢复为xxx分支所指向的快照内容
若当前分支有修改内容尚未提交或贮存,将会触发错误并终止切换:
xxx/111 (master)
$ git checkout new
error: Your local changes to the following files would be overwritten by checkout:
new.txt
Please commit your changes or stash them before you switch branches.
Aborting
若切换分支时使用-f
参数,强制切换,这将丢弃本地修改
合并,使用git merge
(常用)和git rebase
(不建议使用)命令,将指定分支的内容(从分叉节点累计到分支末端的所有commits)重现(replay)到当前分支中。
git merge 指定的分支名
:将两个开发历史(分支)连接在一起,即将指定提交(显式指定某个分支)中的修改合并到当前分支中。
两个开发历史(分支)有共同祖先
例如:一个分支是从另一个分支的某个节点(版本)分叉发展而来。
两个开发历史(分支)完全独立,没有共同祖先
例如:其中一个分支是由orphan分支(孤儿分支)发展而来。
两个开发历史(分支)有共同祖先时的示例(假设所有节点都修改同一文件):
创建分支时
从节点(版本)角度来看,当前分支和新分支都指向分叉节点2
从克隆角度来看,新分支克隆了当前分支(从开始至分叉节点的开发历史)
切换到新分支new后,运行git log
命令,显示的提交历史包含1和2。
各自分支不断开发(从时间线角度来看)
分叉出分支的节点2称为分叉节点,分叉前的节点1和2称为共同祖先
从分叉节点2分叉(diverge)出不同的时间线
master分支的开发历史(时间线)为:1、2、5。
new分支的开发历史(时间线)为:1、2、3、4。
分支各自开发生成新的节点(在有需要时合并),分支始终指向开发历史(时间线)上的最新版本,HEAD指针始终指向当前分支。
合并时
当前分支假设为master,使用git checkout master
命令切换到master,然后运行git merge new
命令:
xxx/111 (master) # 当前分支是master,显示在状态栏
$ git merge new # merge命令是将指定分支(new)合并到当前分支
to分支(当前分支master)受合并操作影响,from分支(指定分支new)不受影响:
合并命令在当前分支master上新建一个节点6,节点6的父节点为4和5
合并后的节点6简称为合并节点。
master向前移动,指向自己时间线上的最新节点6
由于当前分支依然是master,所以,HEAD依然指向master
合并后,new分支保持原状,依旧指向自己时间线上的最新节点4
git merge [-n] [--stat] [--no-commit] [--squash] [--[no-]edit]
[--no-verify] [-s <strategy>] [-X <strategy-option>] [-S[<keyid>]]
[--[no-]allow-unrelated-histories]
[--[no-]rerere-autoupdate] [-m <msg>] [-F <file>] [<commit>…]
git merge (--continue | --abort | --quit)
合并方法:
--ff
:默认值。将合并解析为快进(fast-forward,仅移动指针,不创建合并节点)
--no-ff
:即使可以使用fast-forward,也要创建一个新的合并节点
保留某个功能分支的开发记录信息,保证了版本演进的清晰。它将创建更多的提交对象,但收益远大于成本,这对于以后代码进行分析特别有用。强烈建议使用。
附:将--no-ff
设置为默认值的方法
git config branch.master.mergeoptions "--no-ff"
或将以下内容添加到$(REPO)/.git/config
文件(选择配置文件的级别)中:
[branch "master"]
mergeoptions = --no-ff
--ff-only
:除非当前HEAD节点已经up-to-date(最新节点),或能使用fast-forward模式进行合并,否则的话将拒绝合并,并返回一个失败状态
缺省参数时,Git首先尝试使用--ff
参数(fast-farward merge);若失败,则尝试使用--no-ff
参数(no-fast-farward merge)
合并策略:
-s strategy
:指定合并时策略。
若缺省-s
选项,则使用内置的策略列表:
合并操作
--continue
:继续合并
解决合并冲突(保存协商内容,且使用git add
命令添加到暂存区)后,使用git merge --continue
(等同于git commit
)命令继续完成合并,此命令在调用git commit
前检查是否存在正在进行中(暂时中断)的合并。
--abort
:终止合并
合并冲突后决定不合并,终止合并过程并尝试重建合并前状态
--quit
:放弃合并
忘记当前正在进行的合并,保持索引和工作树不变。
警告:若当前分支有修改内容尚未提交(commit)或贮存(stash)时运行git merge
,将会在冲突中难以回退。所以,Git将会触发错误并终止合并:
xxx/111 (master)
$ git merge new
error: Your local changes to the following files would be overwritten by merge:
new.txt
Please commit your changes or stash them before you merge.
Aborting
当指定分支和当前分支指向的版本内容不一致时,将会产生合并冲突:
xxx/111 (master)
$ git merge new
Auto-merging new.txt
CONFLICT (content): Merge conflict in new.txt
Automatic merge failed; fix conflicts and then commit the result.
xxx/111 (master|MERGING)
Git合并时触发fast-farward merge(快进式合并)的前置条件:
当前分支(HEAD指向的分支)指向分叉节点,即分叉后当前分支没有新增节点
分叉节点是所有分叉分支的根节点(root commit)。
分叉后,被合并分支有新增节点
符合上述条件,Git自动触发fast-farward merge(不创建合并节点,仅移动指针);否则,自动触发no-fast-farward merge(创建合并节点,且移动指针)。
xxx/111 (master)
$ git merge new
Updating 53edba0..6c779a9 # 更新此区间内的版本
Fast-forward # 表示当前合并使用Fast-forward。若没有此行信息,表示当前合并使用no-Fast-forward
new.txt | 2 ++
1 file changed, 2 insertions(+)
合并过程详解:
在当前分支(假设为master)的节点2分叉出new分支
将new合并到master(git merge new
)
符合触发fast-farward的前置条件,使用fast-forward策略合并:
删除被合并分支(new)后,使用git log --graph --all
命令(或Sourcetree第三方工具)查看当前分支的图示化提交历史,没有显示new分支的被合并历史。
显式使用--no-ff
参数或不符合使用--ff
参数的条件时,Git将自动使用no-fast-farward merge(非快进式合并)。
xxx/111 (master)
$ git merge new --no-ff
Merge made by the 'recursive' strategy.
new.txt | 2 ++
1 file changed, 2 insertions(+)
合并过程详解:
在当前分支(假设为master)的节点2分叉出new分支
将new合并到master
from分支(new)不受合并操作影响,to分支(master)受影响:
删除被合并分支(new)后,使用git log --graph --all
命令(或第三方工具Sourcetree)查看当前分支的图示化提交历史,依旧保留new分支的被合并历史(只是没有分支名)。
在合并(merge)或撤销(主要是revert)过程中,可能会产生冲突(conflict)。
revert冲突参阅:revert冲突。
示例:直接运行git merge new
命令。
xxx/111 (master) # 当前分支是master,显示在状态栏
$ git merge new # 将new分支合并到master分支
Auto-merging new1.txt # Git尝试自动合并
CONFLICT (content): Merge conflict in new1.txt # 触发冲突:new1.txt文件产生合并冲突
Automatic merge failed; fix conflicts and then commit the result. # 自动合并失败,Git建议手动合并(修改冲突,然后提交修改结果)
xxx/111 (master|MERGING) # 进入合并状态,暂停合并命令,等待修改冲突结果
合并时,Git尝试自动合并(Auto-merging),两个分叉分支(new和master)和分叉节点进行一次简单的三方自动合并。
合并前
git checkout master
命令切换到master合并时(假设将new合并到master)
首先,Git用两个分支的末端(4和5)和分叉节点(2)进行一次简单的三方自动合并。
假设两个版本(5和4)都修改了同一文本内容:
运行合并命令时,触发合并冲突
合并时,Git首先进行冲突判断:
判断涉及的节点
对当前分支master、被合并分支new和分叉节点进行三方判断。
分叉节点是所有分叉分支的根节点(root commit),是判断的基准数据。
判断内容
三方节点的同一文本内容,是指同一文件的同一位置(单位:行)上的文本内容。
判断依据
对同一文本内容是否有多次修改。基于分叉节点,两个分支是否都有不同的修改。
若两个分支各自修改了不同文件,合并时不会产生冲突
示例:new新建文件,master修改了分叉节点上的文件(或新建文件)
将new合并到master时,master增加了new的新建文件。
示例:分叉节点有两个文件A和B,new修改了文件A,master修改了文件B
将new合并到master时,master增加了文件A的修改内容。
若两个分支都修改了分叉节点上的相同文件
若没有修改相同位置(同一行)上的文本内容,合并时不会产生冲突
示例:new修改了第1行,master修改了第4行
new相对于分叉节点修改了第1行,没有修改第4行;master相对于分叉节点修改了第4行,没有修改第1行;将new合并到master时,分叉节点的第1行只有一个修改,所以,master的第1行被new的第1行修改了;new没有修改第4行,所以第4行依然为master的第4行。
若修改了相同位置(同一行)上的文本内容,合并时会产生冲突
解决方法:协商保留冲突内容,将协商结果添加到暂存区且提交为新版本。
产生冲突(CONFLICT)的原因:两个分叉分支都修改了同一文本内容,即基于分叉节点,同一文件的同一位置上有两个不同的修改内容,Git无法判断应该如何选择哪个分支的修改。
自动合并失败后,需要手动解决冲突。
方法:在冲突结果文件中协商保留哪些内容(删除不需要内容),然后将协商内容添加到暂存区且提交为新版本。
简单方法:使用文本编辑器修改产生冲突的工作文件
冲突后,工作文件被修改为(示例):
111 # 共同祖先(分叉节点)的第1行内容
222 # 共同祖先(分叉节点)的第2行内容
<<<<<<< HEAD # 上面是共同祖先(分叉节点)的内容
333 # 当前分支(HEAD指向的分支)的第3行内容
444 # 当前分支(HEAD指向的分支)的第4行内容
======= # =======和<<<<<<< HEAD之间是当前分支内容(不含共同祖先)
aaa # 被合并分支(new)的第3行内容
bbb # 被合并分支(new)的第4行内容
>>>>>>> new # =======和>>>>>>> new之间是被合并分支内容(不含共同祖先)
删除所有的分隔符:<<<<<<< HEAD
、=======
和>>>>>>> new
协商保留内容
<<<<<<< HEAD
和>>>>>>> new
之间是冲突内容。
原则上,同一位置(同一行)只保留一个修改内容(删除另一分支同行内容)。
实际上,保留内容是协商后的结果,即协商后可随意增删改内容。
保存协商结果
工作文件进入未暂存状态,等待添加到暂存区。
将协商结果添加到暂存区
xxx/111 (master|MERGING)
$ git add new.txt
将协商结果提交为新版本
使用git merge --continue
或git commit -m "提交备注"
命令继续合并(将协商结果提交为新版本):
xxx/111 (master|MERGING)
$ git merge --continue
[master c66f1b0] Merge branch 'new'
xxx/111 (master) # 状态栏恢复为master
提交后,在当前分支创建一个commit(6),master指向版本6,master|MERGING状态恢复为master。
直观对比方法:使用第三方工具(例如:Beyond Compare 4)修改冲突内容
下载Beyond Compare中文版后安装
破解
Beyond Compare 4的30天试用期过后的破解方法:
方法一:重命名BCUnRAR.dll
在安装目录下找到文件BCUnRAR.dll,重命名(任意)或删除。
方法二:修改注册表
在CMD运行regedit
,打开注册表。
删除项目:计算机\HKEY_CURRENT_USER\Software\Scooter Software\Beyond Compare 4\CacheID
。
重新启动,就可以正常使用(继续30天试用期)。
配置Beyond Compare 4
打开C:/Users/用户名/.gitconfig文件,拷贝以下内容:
[difftool "bc4"]
cmd = 'D:/Git/Beyond Compare 4/BComp.exe' \"$LOCAL\" \"$REMOTE\"
[mergetool "bc4"]
cmd = 'D:/Git/Beyond Compare 4/BComp.exe' \"$LOCAL\" \"$REMOTE\" \"$BASE\" -o \"$MERGED\"
trustExitCode = true
[diff]
tool = bc4
[difftool]
prompt = true
[merge]
tool = bc4
[mergetool]
prompt = true
使用此配置在Git Bash命令行内不能打开Beyond Compare工具,最后采用了Beyond Compare + Sourcetree。
删除分支:
只是删除了一个指针
不会删除提交对象,.git\objects目录依然存放已删除分支的提交对象
恢复分支,本质上就是在指定版本上创建分支,不是指恢复已删除分支的整条时间线:
查看当前分支的提交历史,获取恢复版本(已删除分支的某个版本)
git log -g # -g:遍历reflog条目,从最新到开始(from the most recent one to older ones)
创建新分支(在指定版本分叉出新分支)
git branch 新分支名 start_point
切换分支(自动恢复指定版本的工作文件内容)
git checkout 新分支名
参考:A successful Git branching model。
Git的核心功能之一是分支。Git非常提倡使用分支,创建分支、合并分支等简单快捷。分支不在多而在恰到好处,如果分支创建多了,管理起来就麻烦了。
一个简单的分支管理模型(分支策略branching scheme):
开发模型的远程仓库(中央存储库)应该包含两个无限生命的主要分支:
主分支,例如命名为master
源代码的稳定分支,反映稳定版本(Stable Release)的开发状态。
开发分支,例如命名为develop
源代码的开发分支(或称整合分支),反映测试版本(Nightly Builds)的开发状态。
在主要分支(master、develop)的旁边,开发模型使用各种支持分支帮助团队成员之间并行开发,简化对特性的跟踪,为生产版本做准备,并帮助快速修复实际的生产问题。与主要分支不同,这些分支的生命周期总是有限的,因为它们最终会被移除。
这些分支都有特定用途,并受严格的规则约束,即哪些分支可能是其原始分支,哪些分支必须是其合并目标。
支持分支有:
功能分支(Feature branches)
或称主题分支,用于为即将发布或遥远的将来版本开发新功能。
当开始开发功能时,可能不知道将合并该功能的目标版本。功能分支的本质是只要功能正在开发中就存在,但最终会合并到develop(确保将新功能添加到即将发布的版本中)或丢弃(以防实验失败)。
功能分支通常仅存在于开发人员的本地仓库中,而不存在于远程仓库中。分支命名随意(master、develop、release-xxx、hotfix-xxx除外)。
预发布分支(Release branches)
用于准备新版本的发布,允许修复小量bug和为发布准备元数据(版本号、构建日期等)。通过在发布分支上完成所有这些工作,开发分支就可以接收下一个大版本的特性。
从develop分叉出发布分支的关键是:develop达到了新版本的期望值。
分支命名约定为:release-xxx。
修复分支(Hotfix branches)
修复分支类似于发布分支,都是计划外,但旨在为新版本做准备。它们源于对不期望的实时稳定版本状态立即采取行动的必要性。当必须立即解决稳定版本中的严重错误时,可以从master分支上的相应标记中分支出一个修补程序分支。
一个人正在修复master分支,团队成员可以继续在开发分支上的工作。
分支命名约定为:hotfix-xxx。
查看版本历史
查看项目的版本库的发展变化以及比较差异的命令:
git show-branch
:查看版本库中每个分支的世系(父系,指版本的族谱历史,例如master^是指分支master的上一版本)发展状态,可以看到每次提交的内容是否已进入每个分支git diff 分支A 分支B
:查看两个版本的差异git whatchanged
:查看当前分支的发展最实用的还是Git的第三方图形工具:Sourcetree + Beyond Compare 4。
逆转(Undo)和恢复(Redo)版本(某一阶段的工作文件)
git reset
命令:重置版本
将当前分支的HEAD指针定位到任何已提交版本后,有三个重置的算法选项:
--mixed
:默认值,只重置暂存区
--soft
:不重置任何文件,仅更新ORIG_HEAD指针(.git/ORIG_HEAD文件)
--hard
:重置暂存区和工作目录
合并分支
合并自己(本地仓库)的分支
运行git merge
命令。
合并其他人(远程仓库)的分支
远程合并就是:“抓取(git fetch
)一个远程仓库中的工作到一个临时标签中”,然后再运行git-merge
命令。
提取数据
运行git checkout -f commit_id
命令,在“detached HEAD”状态查看指定版本内容:
xxx/111 (master)
$ git checkout -f e363454
Note: switching to 'e363454'.
You are in 'detached HEAD' state.
…………
交换工作
假设情景:远程仓库单向下载后,在本地修改了版本,但自己无权限上传到远程仓库,其他人也无权限访问本地机器抓取工作。这种情景应该如何交换工作?
复制远程仓库(clone或fetch)
本地工作(在本地仓库新增了版本)
抓取远程仓库最新版本
git fetch origin # 假设远程仓库的别名为origin
目的:防止origin不是最新的公共版本,产生错误的补丁文件。
将本地新增工作迁移到远程跟踪分支(远程分支)
git rebase origin
生成补丁文件
git format-patch origin
在当前目录下生成一个大概名为0001-xxx.txt补丁文件, 建议使用文本编辑器查看这个文件的具体形式,然后将这个文件以附件的形式发送到项目维护者的邮箱。
项目的维护者将补丁文件合并到项目
当项目维护者收到邮件后,只需运行git-am
命令,就可以将补丁合并到项目中。
git checkout -b xxx # 新建分支用于合并补丁
git am /path/to/0001-xxx.txt # 将补丁合并到项目
协同工作
所有人都对远程仓库有双向权限(下载和上传)。
命令有:clone、fetch、pull、push等。
打包
.git/objects目录保存了创建的所有Git对象,这对于自动和安全地创建对象很有效,但是对于网络传输则不方便。Git对象一旦创建了,就不能被改变,但可以“打包”优化存储。
xxx/111 (master)
$ git repack
Enumerating objects: 21, done.
Counting objects: 100% (21/21), done.
Delta compression using up to 4 threads
Compressing objects: 100% (7/7), done.
Writing objects: 100% (21/21), done.
Total 21 (delta 1), reused 0 (delta 0)
.git/objects/pack目录存放了“打包”结果,有两个文件:
若已经打包了对象,则可以删除已被打包的原始对象:
git prune-packed
发布工作
从本地(私有)仓库发布更新(commit)到远程(公共)仓库中,需要远程机器的写权限。
首先,在远程机器上创建一个空的版本库存放公共版本库。
这个版本库只需要在开始时创建一次,以后将通过push保持更新。
项目领导人(project lead)的工作:
1. 在自己的本地机器上准备好主版本库。你的所有工作都在这里完成
2. 准备一个能让大家访问的公共版本库
如果其他人是通过默认协议方式(http)导入版本库,那么你有必要保持这个协议的友好性。git-init-db
后,复制自标准模板库的$GIT_DIR/hooks/post-update
,将包含一个对git-update-server-info
的调用,但是post-update默认是不能唤起它自身的。通过chmod +x post-update命令使能它。这样让git-update-server-info保证那些必要的文件是最新的。
3. 将你的主版本库推入公共版本库
4. git-repack公共版本库。这将建立一个包含初始化提交对象集的打包作为项目的起始线,可能的话,执行一下 git-prune,要是你的公共库是通过 pull 操作来从你打包过的版本库中导入
5. 在你的主版本库中开展工作,这些工作可能是你自己的最项目的编辑,可能是你由email收到的一个补丁,也可能是你从这个项目的“子系统负责人” 的公共库中导入的工作等等。你可以在任何你喜欢的时候重新打包你的这个私人的版本库。
6. 将项目的进度推入公共库中,并给大家公布一下。
7. 经过一段时间以后,“git-repack” 公共库。并回到第5步继续工作。
项目子系统负责人(subsystem maintainer)也有自己的公共库,工作流程大致如下:
1. 准备一个你自己的工作目录,它通过git-clone克隆自项目领导人的公共库。原始的克隆地址(URL)将被保存在.git/remotes/origin中。
2. 准备一个可以给大家访问的公共库,就像项目领导人所做的那样。
3. 复制项目领导人的公共库中的打包文件到你的公共库中,除非你的公共库和项目领导人的公共库是在同一部主机上。以后你就可以通过objects/info/alternates文件的指向来浏览它所指向的版本库了。
4. 将你的主版本库推入你的公共版本库,并运行git-repack,如果你的公共库是通过的公共库是通过pull来导入的数据的话,再执行一下git-prune 。
5. 在你的主版本库中开展工作。这些工作可能包括你自己的编辑,来自email的补丁,从项目领导人,“下一级子项目负责人”的公共库哪里导入的工作等等。你可以在任何时候重新打包你的私人版本库。
6. 将你的变更推入公共库中,并且请“项目领导人”和“下级子系统负责人”导入这些变更。
7. 每隔一段时间之后,git-repack公共库。回到第 5 步继续工作。
一般开发人员不需要自己的公共库,大致的工作方式是:
1. 准备你的工作库,它应该用git-clone克隆自“项目领导人”的公共库(如果你只是开发子项目,那么就克隆“子项目负责人”的)。克隆的源地址(URL)会被保存到.git/remotes/origin中。
2. 在你的个人版本库中的master分支中开展工作。
3. 每隔一段时间,向上游的版本库运行一下git-fetch origin 。这样只会做git-pull一半的操作,即只克隆不合并。公共版本库的新的头就会被保存到.git/refs/heads/origins 。
4. 用git-cherry origin命令,看一下你有什么补丁被接纳了。并用git-rebase origin命令将你以往的变更迁移到最新的上游版本库的状态中。
5. 用git-format-patch origin生成email形式的补丁并发给上游的维护者。回到第二步接着工作。
感谢您的赞赏支持: