教程目录
一、Git高层命令 - 分支
几乎所有的版本控制系统都以某种形式支持分支。使用分支意味着你可以把你的工作从开发主线上分离开来,以免影响开发主线。在很多版本控制系统中,这是一个略微低效的过程——常常需要完全创建一个源代码目录的副本。对于大项目来说,这样的过程会耗费很多时间。
而 Git的分支模型极其的高效轻量的。是 Git的必杀技特性,也正因为这一特性,使得 Git从众多版本控制系统中脱颖而出。
创建分支
# 命令:
git branch 分支名
作用:为你创建了一个可以移动的新的指针。比如,创建一个 testing分支:git branch testing。这会在当前所在的提交对象上创建一个指针。
注意:git branch 分支名 创建一个新分支,并不会自动切换到新分支中去
示例:
git branch testing
新建一个分支并且使分支指向对应的提交对象
命令:git branch name commitHash
$ git log --oneline --decorate --graph --all
* d900f1f (testing) 在分支进行修改
* 448553e (HEAD -> master) 测试
* 0ca93bf rename newDemo.txt to new.docx
* 92fa400 delete HelloWorld.txt
* 9be80ec first commit
* d86d577 修改了文件名
* 8a3d0fb 提交new.txt
* aa97d21 删除README
* d80c4c1 跳过使用暂存区域
* eb8e6ce 提交修改的数据
# 创建分支并指向d80c4c1版本
$ git branch goBefore d80c4c1
# 切换分支
$ git checkout goBefore
Switched to branch 'goBefore'
# 查看
$ ll
total 1
-rw-r--r-- 1 Administrator 197121 80 八月 23 22:14 README
显示分支列表
git branch不只是可以创建与删除分支。如果不加任何参数运行它,会得到当前所有分支的一个列表
$ git branch
* master
testing
查看当前分支所指对象
# 命令:(提供这一功能的参数是 --decorate)
$ git log --oneline --decorate
448553e (HEAD -> master, testing) 测试
0ca93bf rename newDemo.txt to new.docx
92fa400 delete HelloWorld.txt
9be80ec first commit
d86d577 修改了文件名
8a3d0fb 提交new.txt
aa97d21 删除README
d80c4c1 跳过使用暂存区域
eb8e6ce 提交修改的数据
切换分支
命令&图示
$ git checkout testing
Switched to branch 'testing'
做出修改,再提交
Administrator@BD2AJMYA5AVVP0R MINGW64 ~/Desktop/workspace (testing)
$ vim new.docx
Administrator@BD2AJMYA5AVVP0R MINGW64 ~/Desktop/workspace (testing)
$ git commit -a -m '在分支进行修改'
warning: LF will be replaced by CRLF in new.docx.
The file will have its original line endings in your working directory
[testing d900f1f] 在分支进行修改 1 file changed, 1 insertion(+)
Administrator@BD2AJMYA5AVVP0R MINGW64 ~/Desktop/workspace (testing)
$ git log --oneline
d900f1f (HEAD -> testing) 在分支进行修改
448553e (master) 测试
0ca93bf rename newDemo.txt to new.docx
92fa400 delete HelloWorld.txt
9be80ec first commit
d86d577 修改了文件名
8a3d0fb 提交new.txt
aa97d21 删除README
d80c4c1 跳过使用暂存区域
eb8e6ce 提交修改的数据
切回 master
# 命令:
$ git checkout master
Switched to branch 'master'
注意:分支切换会改变你工作目录中的文件,在切换分支时,一定要注意你工作目录里的文件会被改变。如果是切换到一个较旧的分支,你的工作目录会恢复到该分支最后一次提交时的样子。如果 Git不能干净利落地完成这个任务,它将禁止切换分支。
最佳体验:每次在切换分支前提交一下当前分支
切换分支会改变三个地方:
HEAD:指示目前被检出的分支(在哪个分支上面)
暂存区:暂存区恢复最后一次提交时的样子
工作目录:恢复到该分支最后一次提交时的样子
在切换分支的时候,如果当前分支上有未暂存的文件(首次)或未提交的暂存(首次),分支可以正常切换,但是这种操作可能会污染其他分支;
如果当前分支是已经提交过的,只是对其进行了修改,那么禁止分支切换;
查看项目分叉历史
git log --oneline --decorate --graph --all
删除分支
# 命令
git branch -d testing
# 错误:分支“testing”没有完全合并。
# 如果确定要删除它,请运行“git branch -D testing”。
error: The branch 'testing' is not fully merged.
If you are sure you want to delete it, run 'git branch -D testing'.
# 强制删除
git branch -D testing
Deleted branch testing (was d900f1f).
查看每一个分支的最后一次提交
# 命令
git branch -v
合并分支
# 命令:
git merge 分支名
实际案例
一、工作流:
1、开发某个网站。
2、为实现某个新的需求,创建一个分支。
3、在这个分支上开展工作。
正在此时,你突然接到一个电话说有个很严重的问题需要紧急修补。你将按照如下方式来处理:
1、切换到你的线上分支(production branch)。
2、为这个紧急任务新建一个分支,并在其中修复它。
3、在测试通过之后,切换回线上分支,然后合并这个修补分支,最后将改动推送到线上分支。
4、切换回你最初工作的分支上,继续工作。
二、git 流
首先,我们假设你正在你的项目上工作,并且已经有一些提交。
现在,你已经决定要解决你的公司使用的问题追踪系统中的 #53问题。想要新建一个分支并同时切换到那个分支上,你可以运行一个带有 -b参数的 git checkout 命令
# 创建分布并切换分支
git checkout -b iss53
# 相当于
git branch iss53
git checkout iss53
你继续在 #53问题上工作,并且做了一些提交。在此过程中,iss53分支在不断的向前推进,因为你已经检出到该分支。
现在你接到那个电话,有个紧急问题等待你来解决:
有了 Git的帮助,你不必把这个紧急问题和 iss53的修改混在一起,你也不需要花大力气来还原关于 53#问题的修改,然后再添加关于这个紧急问题的修改,最后将这个修改提交到线上分支。你所要做的仅仅是切换回master分支。
但是,在你这么做之前,要留意你的工作目录和暂存区里那些还没有被提交的修改,它可能会和你即将检出的分支产生冲突从而阻止 Git切换到该分支。最好的方法是,在你切换分支之前,保持好一个干净的状态。(提交你的所有修改)
git checkout master
这个时候,你的工作目录和你在开始 #53问题之前一模一样,现在你可以专心修复紧急问题了。请牢记:当你切换分支的时候,Git会重置你的工作目录,使其看起来像回到了你在那个分支上最后一次提交的样子。 Git会自动添加、删除、修改文件以确保此时你的工作目录和这个分支最后一次提交时的样子一模一样。
接下来,你要修复这个紧急问题。
让我们建立一个针对该紧急问题的分支(hotfix branch),在该分支上工作直到问题解决:
git checkout -b hotfix
# 做出修改
git commit -a -m 'fixed the broken email address'
你可以运行你的测试,确保你的修改是正确的,然后将其合并回你的master分支来部署到线上。
三、快进合并
你可以使用 git merge命令来达到上述目的:
# 切换到主分支
git checkout master
# 合并分支
git merge hotfix
在合并的时候,有时候会出现 "快进(fast-forward)"这个词。由于当前master分支所指向的提交是你当前提交的直接上游,所以 Git只是简单的将指针向前移动。换句话说,当你试图合并两个分支时,如果顺着一个分支走下去能够到达另一个分支,那么 Git在合并两者的时候,只会简单的将指针向前推进(指针右移),因为这种情况下的合并操作没有需要解决的分歧——这就叫做 “快进(fast-forward)
关于这个紧急问题的解决方案发布之后,你准备回到被打断之前时的工作中。然而,你应该先删除hotfix分支,因为你已经不再需要它了——master分支已经指向了同一个位置。你可以使用带-d选项的git branch 命令来删除分支。现在你可以切换回你正在工作的分支继续你的工作,也就是针对 #53问题的那个分支
# 删除hotfix分支
git branch -d hotfix
# 切换到iss53分支
git checkout iss53
你在hotfix分支上所做的工作并没有包含到 iss53分支中。如果你需要拉取 hotfix所做的修改,你可以使用 git merge master 命令将master分支合并入 iss53分支,或者你也可以等到 iss53分支完成其使命,再将其合并回master分支。
git checkout master
git merge iss53
四、典型合并
当前的合并和你之前合并hotfix分支的时候看起来有一点不一样。在这种情况下,你的开发历史从一个更早的地方开始分叉开来(diverged )。因为,master分支所在提交并不是iss53分支所在提交的直接祖先,Git不得不做一些额外的工作。出现这种情况的时候,Git会使用两个分支的末端所指的快照(C4和C5)以及这两个分支的工作祖先(C2),做一个简单的三方合并。
和之前将分支指针向前推进所不同的是,Git将此次三方合并的结果做了一个新的快照并且自动创建一个新的提交指向它。这个被称作一次合并提交,它的特别之处在于他有不止一个父提交。
需要指出的是,Git会自行决定选取哪一个提交作为最优的共同祖先,并以此作为合并的基础;这和更加古老的 CVS系统或者 Subversion(1.5版本之前)不同,在这些古老的版本管理系统中,用户需要自己选择最佳的合并基础。 Git的这个优势使其在合并操作上比其他系统要简单很多最终删除 iss53号分支
git branch -d iss53
五、冲突
有时候合并操作不会如此顺利。如果你在两个不同的分支中,对同一个文件的同一个部分进行了不同的修改,Git就没法干净的合并它们。如果你对 #53问题的修改和有关 hotfix的修改都涉及到同一个文件的同一处,在合并它们的时候就会产生合并冲突。
此时 Git做了合并,但是没有自动地创建一个新的合并提交。 Git会暂停下来,等待你去解决合并产生的冲突。你可以在合并冲突后的任意时刻使用 git status命令来查看那些因包含合并冲突而处于未合并(unmerged)状态的文件。
任何因包含合并冲突而有待解决的文件,都会以未合并状态标识出来。
<<<<<<< HEAD:index.html
<div id="footer">contact : email.support@github.com</div>
=======
<div id="footer">
please contact us at support@github.com
</div>
>>>>>>> iss53:index.html
在你解决了所有文件里的冲突之后,对每个文件使用 git add命令来将其标记为冲突已解决。一旦暂存这些原本有冲突的文件,Git就会将它们标记为冲突已解决。
步骤:
1. 编辑冲突文件,删除特殊符号
2. 把文件修改到满意的程度,保存退出
3. git add [文件名]
4. git commit -m "日志信息"
注意:此时 commit 一定不能带具体文件名
分支模式
长期分支
许多使用 Git的开发者都喜欢使用这种方式来工作,比如只在master分支上保留完全稳定的代码——有可能仅仅是已经发布或即将发布的代码。他们还有一些名为develop或者next的平行分支,被用来做后续开发或者测试稳定性——这些分支不必保持绝对稳定,但是一旦达到稳定状态,它们就可以被合并入master分支了,等待下一次的发布。
随着你的提交而不断右移的指针。稳定分支的指针总是在提交历史中落后一大截,而前沿分支的指针往往比较靠前。
特性分支
特性分支对任何规模的项目都适用。特性分支是一种短期分支,它被用来实现单一特性或其相关工作。也许你从来没有在其他的版本控制系统(VCS)上这么做过,因为在那些版本控制系统中创建和合并分支通常很费劲。然而,在 Git中一天之内多次创建、使用、合并、删除分支都很常见。
实例:考虑这样一个例子,你在master分支上工作到C1,这时为了解决一个问题而新建 iss91分支,在iss91分支上工作到 C2,这时思路断了,你暂时放弃修复iss91,切回主分支又工作到了c3(画了几个页面)。
这时你突然对iss91问题有了新的想法,你切回iss91继续工作到了c6。在完成了对 iss91的 bug修复之后。你发现你C4之后的修改都没有使用ES6语法。于是你再新建一个iss91v2分支重新使用 ES6语法开发到 C8,写了一会写累了。接着你回到master分支又画了一会页面到 C10,画完页面后你一咬牙切回iss91v2完成 es6版本的修改到 C11.你又冒出了一个不太确定的想法,切回 master后新建一个dumbidea分支,并在上面做些实验。
你的提交历史看起来像下面这个样子:
现在,我们假设两件事情:你决定使用第二个方案来解决那个问题,即使用在iss91v2分支中方案;另外,你将dumbidea分支拿给你的同事看过之后,结果发现这是个惊人之举。这时你可以抛弃 iss91分支(即丢弃C5和C6提交),然后把另外两个分支合并入主干分支。最终你的提交历史看起来像下面这个样子:
在 master分支是先合并dumbidea分支
切回iss91v2分支合并掉iss91
删除iss91
切回 master分支再合并iss91v2分支
分支本质
Git的分支,其实本质上仅仅是指向提交对象的可变指针。
Git的默认分支名字是master。在多次提交操作之后,你其实已经有一个指向最后那个提交对象的master分支。它会在每次的提交操作中自动向前移动。
注意:Git的 “master”分支并不是一个特殊分支。它就跟其它分支完全没有区别。之所以几乎每一个仓库都有 master分支,是因为 git init命令默认创建它,并且大多数人都懒得去改动它。
图示
分支原理
.git/refs/heads目录
这个目录中保存了分支及其对应的提交对象
HEAD引用
当运行类似于 git branch (branchname)这样的命令时, Git会取得当前所在分支最新提交对应的 SHA-1值,并将其加入你想要创建的任何新分支中。
当你执行 git branch (branchname)时,Git如何知道最新提交的 SHA-1值呢?答案是 HEAD文件。
HEAD文件是一个符号引用(symbolic reference),指向目前所在的分支。所谓符号引用,意味着它并不像普通引用那样包含一个 SHA-1值。它是一个指向其他引用的指针
其他命令
git branch --merged
查看哪些分支已经合并到当前分支 (当前分支只要包含了分支的所有内容,就相当于已经合并)
在这个列表中分支名字前没有 *号的分支通常可以使用
git branch --no-merged
查看所有包含未合并工作的分支
尝试使用 git branch -d命令删除在这个列表中的分支时会失败。
如果真的想要删除分支并丢掉那些工作,可以使用 -D选项强制删除它。
总结
Git的分支,其实本质上仅仅是指向提交对象的可变指针。
HEAD:
是一个指针 它默认指向master分支 切换分支时其实就是让HEAD指向不同的分支
每次有新的提交时 HEAD都会带着当前指向的分支 一起往前移动
git log --oneline --decorate --graph --all : 查看整个项目的分支图
git branch : 查看分支列表
git branch -v: 查看分支指向的最新的提交
git branch name : 在当前提交对象上创建新的分支
git branch name commithash: 在指定的提交对象上创建新的分支
git checkout name : 切换分支
git branch -d name : 删除空的分支 删除已经被合并的分支
git branch -D name : 强制删除分支
二、Git存储
有时,当你在项目的一部分上已经工作一段时间后,所有东西都进入了混乱的状态,而这时你想要切换到另一个分支做一点别的事情。问题是,你不想仅仅因为过会儿回到这一点而为做了一半的工作创建一次提交。针对这个问题的答案是:
# 命令
git stash
git stash命令会将未完成的修改保存到一个栈上,而你可以在任何时候重新应用这些改动(git stash apply)
# 查看存储
git stash list
# 如果不指定一个储藏,Git认为指定的是最近的储藏
git stash apply [stash@{0}]
# 加上将要移除的储藏的名字来移除它
git stash drop
# 来应用储藏然后立即从栈上扔掉它
git stash pop
三、配别名
Git并不会在你输入部分命令时自动推断出你想要的命令。如果不想每次都输入完整的 Git命令,可以通过 git config文件来轻松地为每一个命令设置一个别名。
$ git config --global alias.co checkout
$ git config --global alias.br branch
$ git config --global alias.ci commit
$ git config --global alias.st status
$ git config --global alias.lol 'log --oneline --decorate --graph --all'
当要输入 git commit时,只需要输入 git ci