git分支
# git分支
Git 的分支,其实本质上仅仅是指向提交对象的可变指针。 Git 的默认分支名字是 master
。 在多次提交操作之后,你其实已经有一个指向最后那个提交对象的 master
分支。 master
分支会在每次提交时自动向前移动。
Git 的
master
分支并不是一个特殊分支。 它就跟其它分支完全没有区别。 之所以几乎每一个仓库都有master
分支,是因为git init
命令默认创建它,并且大多数人都懒得去改动它。
# 创建分支
创建分支本质上是创建了一个可以移动的新的指针。
git branch testing # 创建名为 testing 的分支。
Git 又是怎么知道当前在哪一个分支上呢? 也很简单,它有一个名为 HEAD
的特殊指针。 请注意它和许多其它版本控制系统(如 Subversion 或 CVS)里的 HEAD
概念完全不同。 在 Git 中,它是一个指针,指向当前所在的本地分支(译注:将 HEAD
想象为当前分支的别名)。 在本例中,你仍然在 master
分支上。 因为 git branch
命令仅仅 创建 一个新分支,并不会自动切换到新分支中去。
# 切换分支
切换到一个已存在的分支,你需要使用 git checkout
命令。
git checkout testing
这样 HEAD
就指向 testing
分支了。
如果这个时候再提交一次:
git commit -a -m 'testing 提交'
可以看到,指针向前移动了。重新指向了 testing
分支。但是 master
分支并没有移动。如果我们再切回 master
分支:
git checkout master
HEAD
的指针又指向了 master
分支。
切换分支会改变你工作目录中的文件。
如果我们在 master
分支上提交一次记录,那么分支就会产生 分叉 ,使用以下命令查看提交历史、各个分支的指向以及项目的分支分叉情况。
git log --oneline --decorate --graph --all
# 创建新分支的同时切换过去
git checkout -b <newbranchname>
# 合并分支(merge)
为了更好的了解分支以及分支合并,我们先新建一个目录,初始化本地仓库并新建三个分支:master
,dev
, test
新建 master
分支并初始化仓库:
git init
echo "我是master分支" > README.md
git add README.md
git commit -m "master"
2
3
4
增加 dev
分支:
git checkout -b dev
echo "我是dev分支" > README.md
git add README.md
git commit -m "dev"
2
3
4
切换回 master
分支,并新建 test
分支:
git checkout master
git checkout -b test
echo "我是test分支" > README.md
git add README.md
git commit -m "test"
2
3
4
5
这时,我们执行 git log --oneline --decorate --graph --all
,输出如下:
* 3454cb3 (HEAD -> test) test
| * 8c5e9b0 (dev) dev
|/
* 881e305 (master) master
2
3
4
我们发现,分支形成了 分叉 。
假设 master
分支是我们的 正式环境 的分支,dev
是我们 开发环境 的分支,那么如果我们要进行发版,怎么样能把 开发环境 的分支合并到 正式环境 中呢?
git checkout master # 切换到主分支
git merge dev # 合并分支
2
此刻 HEAD
指针指向了 master
,dev
分支,并且 master
分支指针向前移动了。
# 遇到冲突时的分支合并
如果你在两个不同的分支中,对同一个文件的同一个部分进行了不同的修改,Git 就没法干净的合并它们。
接下来我们给 master
分支和 dev
分支分别进行一些修改:
git checkout master # 切换 master 分支
echo "我是master分支
删除xxx功能" > README.md
git add README.md
git commit -m "删除xxx功能"
2
3
4
5
git checkout dev # 切换 dev 分支
echo "我是dev分支
新增加xxx功能" > README.md
git add README.md
git commit -m "新增加xxx功能"
2
3
4
5
这时,我们再次执行 git log --oneline --decorate --graph --all
,输出如下:
* a1b9cb4 (master) “删除xxx功能”
| * 754bb2d (HEAD -> dev) 新增加xxx功能
|/
* 8c5e9b0 dev
| * 3454cb3 (test) test
|/
* 881e305 master
2
3
4
5
6
7
在这时进行 git merge dev
时,会报错:
Auto-merging README.md
CONFLICT (content): Merge conflict in README.md
Automatic merge failed; fix conflicts and then commit the result.
2
3
此时 Git 做了合并,但是没有自动地创建一个新的合并提交。直到你解决了冲突之后才会进行。此时执行 git status
:
On branch master
You have unmerged paths.
(fix conflicts and run "git commit")
(use "git merge --abort" to abort the merge)
Unmerged paths:
(use "git add <file>..." to mark resolution)
both modified: README.md
2
3
4
5
6
7
8
此时我们查看冲突的文件:
<<<<<<< HEAD
我是master分支
删除xxx功能
=======
我是dev分支
新增加xxx功能
>>>>>>> dev
2
3
4
5
6
7
======= :分割线,上半部分为当前 HEAD
指针所指向的分支,为 master
分支;下半部分为 dev
分支。
文件中只保留你要保存的文件, 例如,我要保存dev分支的内容,文件内容编辑如下:
我是dev分支
新增加xxx功能
2
确认文件保存无误后,执行 git add
来标记为冲突已解决。随后执行 git commit
来进行分支合并。
此时,Git 将此次合并的结果做了一个新的快照并且自动创建一个新的提交指向它。 这个被称作一次合并提交,它的特别之处在于他有不止一个父提交。
# 变基(rebase)
在合并分支的时候,我们做了一个新的快照进行合并提交。那么有没有什么方法能够不产生这个分叉进行合并呢?
使用 git rebase
进行 变基(rebase) ,将提交到某一分支上的所有修改都移至另一分支上,我们不执行合并分支,直接执行:
git checkout dev
git rebase master
2
这样会把 dev
分支变基到 master
分支上。当然我们在这里执行的时候,发现是无法正常进行的:
Auto-merging README.md
CONFLICT (content): Merge conflict in README.md
error: could not apply 4b12cf0... 新增加xxx功能
Resolve all conflicts manually, mark them as resolved with
"git add/rm <conflicted_files>", then run "git rebase --continue".
You can instead skip this commit: run "git rebase --skip".
To abort and get back to the state before "git rebase", run "git rebase --abort".
Could not apply 4b12cf0... 新增加xxx功能
2
3
4
5
6
7
8
这就又回归到遇到冲突时的分支合并了,操作步骤大致跟合并分支做的是一致的,区别只有执行 git add
来标记为冲突已解决之后随后执行 git rebase --continue
来进行分支变基。
切换回 master
分支后,再进行一次合并:
git checkout master
git merge dev
2
完成后我们可以看到,变基会使得提交历史更加整洁。
# 删除分支
git branch -d
git branch -D # 强制删除分支
2
# 分支管理
查看当前所有分支的一个列表:
git branch
查看每一个分支的最后一次提交:
git branch -v
--merged
与 --no-merged
这两个有用的选项可以过滤这个列表中已经合并或尚未合并到当前分支的分支。
# 远程仓库
远程仓库是指托管在网络上的你的项目的版本库。
git remote -v #查看远程仓库,显示需要读写远程仓库使用的 Git 保存的简写与其对应的 URL。
# 添加远程仓库
运行 git remote add <shortname> <url>
添加一个新的远程 Git 仓库,同时指定一个方便使用的简写:
git remote add origin https://github.com/username/repo.git
我们添加了 简写为origin
的远程仓库到当前的项目中。
# 拉取远程仓库
假如如果你想拉取 origin
的仓库中有但你没有的信息,可以运行 git fetch origin
进行拉取。
工作目录中需要有本地分支才可见远程跟踪分支。
# 推送到远程仓库
推送到上游。 这个命令很简单:git push <remote name> <branch name>
。 当你想要将 master
分支推送到 origin
服务器时那么运行这个命令就可以将你所做的推送到远程仓库。例如:
git push origin master
# 查看某个远程仓库
执行以下命令会列出远程仓库的 URL 与跟踪分支的信息:
git remote show <remote name>
# 远程分支
远程引用是对远程仓库的引用(指针),包括分支、标签等等。
通过 git ls-remote <remote name>
来显式地获得远程引用的完整列表。
远程跟踪分支是远程分支状态的引用。它们以 <remote name>/<branch name>
的形式命名。 例如,如果你想要看你最后一次与远程仓库 origin
通信时 master
分支的状态,你可以查看 origin/master
分支。
“origin” 并无特殊含义。
远程仓库名字 “origin” 与分支名字 “master” 一样,在 Git 中并没有任何特别的含义一样。 同时 “master” 是当你运行
git init
时默认的起始分支名字,原因仅仅是它的广泛使用, “origin” 是当你运行git clone
时默认的远程仓库名字。 如果你运行git clone -o booyah
,那么你默认的远程分支名字将会是booyah/master
。
origin
就是一个名字,它是在你clone
一个代码库时,git为你默认创建的指向这个远程代码库的标签。
假设你的网络里有一个在 git.ourcompany.com
的 Git 服务器。 如果你从这里克隆,Git 的 clone
命令会为你自动将其命名为 origin
,拉取它的所有数据, 创建一个指向它的 master
分支的指针,并且在本地将其命名为 origin/master
。 Git 也会给你一个与 origin 的 master
分支在指向同一个地方的本地 master
分支,这样你就有工作的基础。
如果要与给定的远程仓库同步数据,运行 git fetch <remote>
命令(在本例中为 git fetch origin
)。 这个命令查找 “origin” 是哪一个服务器(在本例中,它是 git.ourcompany.com
), 从中抓取本地没有的数据,并且更新本地数据库,移动 origin/master
指针到更新之后的位置。
我们假定你有另一个内部 Git 服务器,仅服务于你的某个敏捷开发团队。 这个服务器位于 git.team1.ourcompany.com
。 你可以运行 git remote add
命令添加一个新的远程仓库引用到当前的项目 将这个远程仓库命名为 teamone
,将其作为完整 URL 的缩写。
现在,可以运行 git fetch teamone
来抓取远程仓库 teamone
有而本地没有的数据。 因为那台服务器上现有的数据是 origin
服务器上的一个子集, 所以 Git 并不会抓取数据而是会设置远程跟踪分支 teamone/master
指向 teamone
的 master
分支。