3,版本控制git-多人协做

若是你想得到一份已经存在了的 Git 仓库的拷贝,好比说,你想为某个开源项目贡献本身的一份力,这时就要用到 `git clone` 命令。 若是你对其它的 VCS 系统(好比说Subversion)很熟悉,请留心一下你所使用的命令是"clone"而不是"checkout"。 这是 Git 区别于其它版本控制系统的一个重要特性,Git 克隆的是该 Git 仓库服务器上的几乎全部数据,而不是仅仅复制完成你的工做所须要文件。当你执行 `git clone` 命令的时候,默认配置下远程 Git 仓库中的每个文件的每个版本都将被拉取下来。 事实上,若是你的服务器的磁盘坏掉了,你一般可使用任何一个克隆下来的用户端来重建服务器上的仓库(虽然可能会丢失某些服务器端的挂钩设置,可是全部版本的数据仍在)。git

克隆仓库的命令格式是 git clone [url] 。 好比,要克隆 Git 的可连接库 libgit2,能够用下面的命令:

$ git clone https://github.com/libgit2/libgit2

这会在当前目录下建立一个名为 “libgit2” 的目录,并在这个目录下初始化一个 .git 文件夹,从远程仓库拉取下全部数据放入 .git 文件夹,而后从中读取最新版本的文件的拷贝。 若是你进入到这个新建的 libgit2 文件夹,你会发现全部的项目文件已经在里面了,准备就绪等待后续的开发和使用。 若是你想在克隆远程仓库的时候,自定义本地仓库的名字,你可使用以下命令:github

$ git clone https://github.com/libgit2/libgit2 mylibgit

这将执行与上一个命令相同的操做,不过在本地建立的仓库名字变为 mylibgitweb

Git 支持多种数据传输协议。 上面的例子使用的是 https:// 协议,不过你也可使用 git:// 协议或者使用 SSH 传输协议,好比 user@server:path/to/repo.git .缓存

在上一节咱们看到了,多人在同一个分支上协做时,很容易出现冲突。即便没有冲突,后push的童鞋不得不先pull,在本地合并,而后才能push成功。bash

每次合并再push后,分支变成了这样:服务器

$ git log --graph --pretty=oneline --abbrev-commit
* d1be385 (HEAD -> master, origin/master) init hello
*   e5e69f1 Merge branch 'dev'
|\  
| *   57c53ab (origin/dev, dev) fix env conflict
| |\  
| | * 7a5e5dd add env
| * | 7bd91f1 add new env
| |/  
* |   12a631b merged bug fix 101
|\ \  
| * | 4c805e2 fix bug 101
|/ /  
* |   e1e9c68 merge with no-ff
|\ \  
| |/  
| * f52c633 add merge
|/  
*   cf810e4 conflict fixed

总之看上去很乱,有强迫症的童鞋会问:为何Git的提交历史不能是一条干净的直线?网络

实际上是能够作到的!app

Git有一种称为rebase的操做,有人把它翻译成“变基”。学习

先不要随意展开想象。咱们仍是从实际问题出发,看看怎么把分叉的提交变成直线。fetch

在和远程分支同步后,咱们对hello.py这个文件作了两次提交。用git log命令看看:

$ git log --graph --pretty=oneline --abbrev-commit
* 582d922 (HEAD -> master) add author
* 8875536 add comment
* d1be385 (origin/master) init hello
*   e5e69f1 Merge branch 'dev'
|\  
| *   57c53ab (origin/dev, dev) fix env conflict
| |\  
| | * 7a5e5dd add env
| * | 7bd91f1 add new env
...

注意到Git用(HEAD -> master)(origin/master)标识出当前分支的HEAD和远程origin的位置分别是582d922 add authord1be385 init hello,本地分支比远程分支快两个提交。

如今咱们尝试推送本地分支:

$ git push origin master
To github.com:michaelliao/learngit.git
 ! [rejected]        master -> master (fetch first)
error: failed to push some refs to 'git@github.com:michaelliao/learngit.git'
hint: Updates were rejected because the remote contains work that you do
hint: not have locally. This is usually caused by another repository pushing
hint: to the same ref. You may want to first integrate the remote changes
hint: (e.g., 'git pull ...') before pushing again.
hint: See the 'Note about fast-forwards' in 'git push --help' for details.

很不幸,失败了,这说明有人先于咱们推送了远程分支。按照经验,先pull一下:

$ git pull
remote: Counting objects: 3, done.
remote: Compressing objects: 100% (1/1), done.
remote: Total 3 (delta 1), reused 3 (delta 1), pack-reused 0
Unpacking objects: 100% (3/3), done.
From github.com:michaelliao/learngit
   d1be385..f005ed4  master     -> origin/master
 * [new tag]         v1.0       -> v1.0
Auto-merging hello.py
Merge made by the 'recursive' strategy.
 hello.py | 1 +
 1 file changed, 1 insertion(+)

再用git status看看状态:

$ git status
On branch master
Your branch is ahead of 'origin/master' by 3 commits.
  (use "git push" to publish your local commits)

nothing to commit, working tree clean

加上刚才合并的提交,如今咱们本地分支比远程分支超前3个提交。

git log看看:

$ git log --graph --pretty=oneline --abbrev-commit
*   e0ea545 (HEAD -> master) Merge branch 'master' of github.com:michaelliao/learngit
|\  
| * f005ed4 (origin/master) set exit=1
* | 582d922 add author
* | 8875536 add comment
|/  
* d1be385 init hello
...

对强迫症童鞋来讲,如今事情有点不对头,提交历史分叉了。若是如今把本地分支push到远程,有没有问题?

有!

什么问题?

很差看!

有没有解决方法?

有!

这个时候,rebase就派上了用场。咱们输入命令git rebase试试:

$ git rebase
First, rewinding head to replay your work on top of it...
Applying: add comment
Using index info to reconstruct a base tree...
M    hello.py
Falling back to patching base and 3-way merge...
Auto-merging hello.py
Applying: add author
Using index info to reconstruct a base tree...
M    hello.py
Falling back to patching base and 3-way merge...
Auto-merging hello.py

输出了一大堆操做,究竟是啥效果?再用git log看看:

$ git log --graph --pretty=oneline --abbrev-commit
* 7e61ed4 (HEAD -> master) add author
* 3611cfe add comment
* f005ed4 (origin/master) set exit=1
* d1be385 init hello
...

本来分叉的提交如今变成一条直线了!这种神奇的操做是怎么实现的?其实原理很是简单。咱们注意观察,发现Git把咱们本地的提交“挪动”了位置,放到了f005ed4 (origin/master) set exit=1以后,这样,整个提交历史就成了一条直线。rebase操做先后,最终的提交内容是一致的,可是,咱们本地的commit修改内容已经变化了,它们的修改再也不基于d1be385 init hello,而是基于f005ed4 (origin/master) set exit=1,但最后的提交7e61ed4内容是一致的。

这就是rebase操做的特色:把分叉的提交历史“整理”成一条直线,看上去更直观。缺点是本地的分叉提交已经被修改过了。

最后,经过push操做把本地分支推送到远程:

Mac:~/learngit michael$ git push origin master
Counting objects: 6, done.
Delta compression using up to 4 threads.
Compressing objects: 100% (5/5), done.
Writing objects: 100% (6/6), 576 bytes | 576.00 KiB/s, done.
Total 6 (delta 2), reused 0 (delta 0)
remote: Resolving deltas: 100% (2/2), completed with 1 local object.
To github.com:michaelliao/learngit.git
   f005ed4..7e61ed4  master -> master

再用git log看看效果:

$ git log --graph --pretty=oneline --abbrev-commit
* 7e61ed4 (HEAD -> master, origin/master) add author
* 3611cfe add comment
* f005ed4 set exit=1
* d1be385 init hello
...

远程分支的提交历史也是一条直线。

远程分支

远程引用是对远程仓库的引用(指针),包括分支、标签等等。 你能够经过 git ls-remote (remote) 来显式地得到远程引用的完整列表,或者经过 git remote show (remote) 得到远程分支的更多信息。 然而,一个更常见的作法是利用远程跟踪分支。

远程跟踪分支是远程分支状态的引用。 它们是你不能移动的本地引用,当你作任何网络通讯操做时,它们会自动移动。 远程跟踪分支像是你上次链接到远程仓库时,那些分支所处状态的书签。

它们以 (remote)/(branch) 形式命名。 例如,若是你想要看你最后一次与远程仓库 origin 通讯时 master分支的状态,你能够查看 origin/master 分支。 你与同事合做解决一个问题而且他们推送了一个 iss53 分支,你可能有本身的本地 iss53 分支;可是在服务器上的分支会指向 origin/iss53 的提交。

这可能有一点儿难以理解,让咱们来看一个例子。 假设你的网络里有一个在 git.ourcompany.com 的 Git 服务器。 若是你从这里克隆,Git 的 clone 命令会为你自动将其命名为 origin,拉取它的全部数据,建立一个指向它的 master 分支的指针,而且在本地将其命名为 origin/master。 Git 也会给你一个与 origin 的 master 分支在指向同一个地方的本地 master 分支,这样你就有工做的基础。

Note “origin” 并没有特殊含义远程仓库名字 “origin” 与分支名字 “master” 同样,在 Git 中并无任何特别的含义同样。 同时 “master” 是当你运行 git init 时默认的起始分支名字,缘由仅仅是它的普遍使用,“origin” 是当你运行 git clone 时默认的远程仓库名字。 若是你运行 git clone -o booyah,那么你默认的远程分支名字将会是 booyah/master

 

推送

当你想要公开分享一个分支时,须要将其推送到有写入权限的远程仓库上。 本地的分支并不会自动与远程仓库同步 - 你必须显式地推送想要分享的分支。 这样,你就能够把不肯意分享的内容放到私人分支上,而将须要和别人协做的内容推送到公开分支。

若是但愿和别人一块儿在名为 serverfix 的分支上工做,你能够像推送第一个分支那样推送它。 运行 git push (remote) (branch):

$ git push origin serverfix
Counting objects: 24, done.
Delta compression using up to 8 threads.
Compressing 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 https://github.com/schacon/simplegit
 * [new branch]      serverfix -> serverfix

这里有些工做被简化了。 Git 自动将 serverfix 分支名字展开为 refs/heads/serverfix:refs/heads/serverfix,那意味着,“推送本地的 serverfix 分支来更新远程仓库上的 serverfix 分支。” 咱们将会详细学习 refs/heads/ 部分,可是如今能够先把它放在儿。 你也能够运行 git push origin serverfix:serverfix,它会作一样的事 - 至关于它说,“推送本地的 serverfix 分支,将其做为远程仓库的 serverfix 分支” 能够经过这种格式来推送本地分支到一个命名不相同的远程分支。 若是并不想让远程仓库上的分支叫作 serverfix,能够运行 git push origin serverfix:awesomebranch 来将本地的 serverfix 分支推送到远程仓库上的 awesomebranch 分支。

下一次其余协做者从服务器上抓取数据时,他们会在本地生成一个远程分支 origin/serverfix,指向服务器的 serverfix 分支的引用:

$ git fetch origin
remote: 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 https://github.com/schacon/simplegit
 * [new branch]      serverfix    -> origin/serverfix

要特别注意的一点是当抓取到新的远程跟踪分支时,本地不会自动生成一份可编辑的副本(拷贝)。 换一句话说,这种状况下,不会有一个新的 serverfix 分支 - 只有一个不能够修改的 origin/serverfix指针。

能够运行 git merge origin/serverfix 将这些工做合并到当前所在的分支。 若是想要在本身的 serverfix分支上工做,能够将其创建在远程跟踪分支之上:

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

这会给你一个用于工做的本地分支,而且起点位于 origin/serverfix

跟踪分支

从一个远程跟踪分支检出一个本地分支会自动建立一个叫作 “跟踪分支”(有时候也叫作 “上游分支”)。 跟踪分支是与远程分支有直接关系的本地分支。 若是在一个跟踪分支上输入 git pull,Git 能自动地识别去哪一个服务器上抓取、合并到哪一个分支。

当克隆一个仓库时,它一般会自动地建立一个跟踪 origin/master 的 master 分支。 然而,若是你愿意的话能够设置其余的跟踪分支 - 其余远程仓库上的跟踪分支,或者不跟踪 master 分支。 最简单的就是以前看到的例子,运行 git checkout -b [branch] [remotename]/[branch]。 这是一个十分经常使用的操做因此 Git 提供了 --track 快捷方式:

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

若是想要将本地分支与远程分支设置为不一样名字,你能够轻松地增长一个不一样名字的本地分支的上一个命令:

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

如今,本地分支 sf 会自动从 origin/serverfix 拉取。

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

$ git branch -u origin/serverfix
Branch 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 -vv
  iss53     7e424c3 [origin/iss53: ahead 2] forgot the brackets
  master    1ae2a45 [origin/master] deploying index fix
* serverfix f8674d9 [teamone/server-fix-good: ahead 3, behind 1] this should do it
  testing   5ea463a trying something new

这里能够看到 iss53 分支正在跟踪 origin/iss53 而且 “ahead” 是 2,意味着本地有两个提交尚未推送到服务器上。 也能看到 master 分支正在跟踪 origin/master 分支而且是最新的。 接下来能够看到 serverfix 分支正在跟踪 teamone 服务器上的 server-fix-good 分支而且领先 3 落后 1,意味着服务器上有一次提交尚未合并入同时本地有三次提交尚未推送。 最后看到 testing 分支并无跟踪任何远程分支。

须要重点注意的一点是这些数字的值来自于你从每一个服务器上最后一次抓取的数据。 这个命令并无链接服务器,它只会告诉你关于本地缓存的服务器数据。 若是想要统计最新的领先与落后数字,须要在运行此命令前抓取全部的远程仓库。 能够像这样作:$ git fetch --all; git branch -vv

拉取

当 git fetch 命令从服务器上抓取本地没有的数据时,它并不会修改工做目录中的内容。 它只会获取数据而后让你本身合并。 然而,有一个命令叫做 git pull 在大多数状况下它的含义是一个 git fetch紧接着一个 git merge 命令。 若是有一个像以前章节中演示的设置好的跟踪分支,无论它是显式地设置仍是经过 clone或 checkout 命令为你建立的,git pull 都会查找当前分支所跟踪的服务器与分支,从服务器上抓取数据而后尝试合并入那个远程分支。

因为 git pull 的魔法常常使人困惑因此一般单独显式地使用 fetch 与 merge 命令会更好一些。

删除远程分支

假设你已经经过远程分支作完全部的工做了 - 也就是说你和你的协做者已经完成了一个特性而且将其合并到了远程仓库的 master 分支(或任何其余稳定代码分支)。 能够运行带有 --delete 选项的 git push 命令来删除一个远程分支。 若是想要从服务器上删除 serverfix 分支,运行下面的命令:

$ git push origin --delete serverfix
To https://github.com/schacon/simplegit
 - [deleted]         serverfix

基本上这个命令作的只是从服务器上移除这个指针。 Git 服务器一般会保留数据一段时间直到垃圾回收运行,因此若是不当心删除掉了,一般是很容易恢复的。

相关文章
相关标签/搜索