Pro Git笔记

来源:互联网 发布:软件项目绩效考核指标 编辑:程序博客网 时间:2024/06/07 16:32

一、版本控制

      记录若干文件内容变化,以便将来查阅特定版本修订情况的系统。

      本地版本控制系统(RCS) ----> 集中化的版本控制系统(CVCS)

      ------>分布式版本控制系统(DVCS):

  客户端并不只提取最新版的快照,而是把原始的代码库镜像下来。



二、git 基础要点

    1、直接快照,而非比较差异

          git和其他版本控制系统的主要区别在于:git只关心文件数据的整体是否发生变化,

          而大多数其他系统而只关心文件系统的差异。

          

                           Git 更像是小型的文件系统,但它同时还提供了许多以此为基础的超强工具。

       2、近乎所有操作都可本地执行

       3、时刻保存数据完整性

        在保存到Git 之前,所有数据都要进行内容的校验和(checksum)计算,

        并将此结果作为数据的唯一标识和索引

        4、多数操作仅添加数据

  常用的Git 操作大多仅仅是把数据添加到数据库。

       5、三种状态

            在Git内部只有三种状态:已提交(committed)、已修改(modified)和已暂存(staged)。

            已提交表示该文件已经被安全地保存在本地数据库中了;

            已修改表示修改了某个文件,但还没有提交保存;

            已暂存表示把已修改的文件放在下次提交时要保存的清单中。

           

           每个项目都有一个git 目录,它是Git 用来保存元数据和对象数据库的地方。该目录非常重要,

  每次克隆镜像仓库的时候,实际拷贝的就是这个目录里面的数据。
          从项目中取出某个版本的所有文件和目录,用以开始后续工作的叫做工作目录。

这些文件实际上都是从git 目录中的压缩对象数据库中提取出来的,接下来就可以在工            

作目录中对这些文件进行编辑。
        所谓的暂存区域只不过是个简单的文件,一般都放在git 目录中。

有时候人们会把这个文件叫做索引文件,不过标准说法还是叫暂存区域。

               基本的Git 工作流程如下所示:
   1. 在工作目录中修改某些文件。
    2. 对这些修改了的文件作快照,并保存到暂存区域。
    3. 提交更新,将保存在暂存区域的文件快照转储到git 目录中。


二、Git 基础

   1、记录每次更新到仓库

      1.1、检查当前文件状态   git   status

            $ git status
            # On branch master
            nothing to commit (working directory clean)

        1. 2、跟踪文件    git   add     文件名

            文件未跟踪

             # Untracked files:
            # (use "git add <file>..." to include in what will be committed)

         1.3、暂存已修改文件  git  add  文件名

           “Changes to be committed” 这行下面的,就说明是已暂存状态。

            出现在“Changed but not updated” 这行下面,说明已跟踪文件的内容发生了变化,但还没有放到暂存区。

        1.4、查看已暂存和未暂存的更新       git  diff

            实际上git status 的显示比较简单,仅仅是列出了修改过的文件,如果要查看具体修改了什么地方,可以用git diff 命令。

           git diff 会使用文件补丁的格式显示具体添加和删除的行。

           此命令比较的是工作目录中当前文件暂存区域快照之间的差异,也就是修改之后还没有暂存起来的变化内容。

          若要看已经暂存起来的文件上次提交时的快照之间的差异,可以用    git  diff  --cached/--staged

       1.5、提交更新    git   commit   -m  "提交说明"

          每次准备提交前,先用git status 看下,是不是都已暂存起来了,然后再运行提交命令git commit:

          记住,提交时记录的是放在暂存区域的快照,任何还未暂存的仍然保持已修改状态,

可以在下次提交时纳入版本管理。每一次运行提交操作,都是对你项目作一次快照,    

  以后可以回到这个状态,或者进行比较。

     1. 6、跳过暂存区域    git  commit  -a  -m  “提交说明”

         Git 提供了一个跳过使用暂存区域的方式,只要在提交的时候,给git commit 加上-a 选项,

Git就会自动把所有已经跟踪过的文件暂存起来一并提交,从而跳过git add 步 骤。

      1.7、移除文件   git  rm  (-f)

          要从Git 中移除某个文件,就必须要从已跟踪文件清单中移除(确切地说,是从暂存区域移除),

然后提交。可以用git rm 命令完成此项工作,并连带从工作目录中删除指定的文件,这样以后就不会出现在未跟踪文件清单中了。

         如果只是简单地从工作目录中手工删除文件,运行git status 时就会在“Changed butnot updated” 部分(也就是_未暂存_清单)

        如果删除之前修改过并且已经放到暂存区域的话,则必须要用强制删除选项-f(译注:即force 的首字母),以防误删除文件后丢失修改的内容

       另外一种情况是,我们想把文件从Git 仓库中删除(亦即从暂存区域移除),但仍然希望保留在当前工作目录中。换句话说,仅是从跟踪清单中删除。

     git   rm --cached   文件/目录名

      1.8、移动文件    git   mv   file_from  file_to

           Changes to be committed:
             # (use "git reset HEAD <file>..." to unstage)
             # renamed: README.txt -> README

           当于运行了下面三条命令:

               mv   file_from  file_to

              git   rm file_from

              git   add   file_to

       2、查看提交历史   git  log

           --p 选项展开显示每次提交的内容差异,用-2 则仅显示最近的两次更新:  git  log  -p  -2

           --stat,仅显示简要的增改行数统计:

      3、撤销操作

               3.1  修改最后一次提交   git  commit  --amend

        有时候我们提交完了才发现漏掉了几个文件没有加,或者提交信息写错了。想要撤消刚才的提交操作,可以使用--amend 选项重新提交:

           此命令将使用当前的暂存区域快照提交。如果刚才提交完没有作任何改动,直接运行此命令的话,

相当于有机会重新编辑提交说明,而所提交的文件快照和之 前的一样。启动文本编辑器后,

会看到上次提交时的说明,编辑它确认没问题后保存退出,就会使用新的提交说明覆盖刚才失误的提交。

如果刚才提交时忘了暂存 某些修改,可以先补上暂存操作,然后再运行--amend 提交:

git  commit  -m  'tijiao'

git   add   file

git   commit   --amend 

       3.2    取消已经暂存的文件   git  reset  HEAD file

3.3   取消对文件的修改git   checkout  -- file

       4、远程仓库的使用

4.1  查看当前的远程库   git  remote

   -v   显示对应的克隆地址(列出的地址只有origin 用的是SSH URL 链接)

     4.2   添加远程仓库   git  remote   name  url

                        git  remote   liveman   git://github.com/paulboone/ticgit.git

    4.3   从远程仓库抓取数据   git  fetch  [remote-name]

 git fetch 命令只是将远端的数据拉到本地仓库,并不自动合并到当前工作分支,只有当你确实准备好了,才能手工合并

 git pull 命令自动抓取数据下来,然后将远端分支自动合并到本地仓库中当前分支

4.4  推送数据到远程仓库   git   push  [remote-name]  [branch-name]

  如果要把本地的master 分支推送到origin 服务器上(再次说明下,克隆操作会自动使用默认的master 和origin 名字),可以运行下面的命令:

git push origin master

只有在所克隆的服务器上有写权限,或者同一时刻没有其他人在推数据,这条命令才会如期完成任务。如果在你推数据前,

已经有其他人推送了若干更新,那 你的推送操作就会被驳回。你必须先把他们的更新抓取到本地,并到自己的项目中,然后才可以再次推送

 4.5  查看远程仓库信息  git remote show [remote-name]

                     $ git remote show origin
* remote origin
  Fetch URL: git@git.letv.cn:zhoujie/gene_liveman.git
  Push  URL: git@git.letv.cn:zhoujie/gene_liveman.git
  HEAD branch: master
  Remote branches:
    Branch_v2.0.12.201609021900 tracked
    Branch_v2.1.1.201609241234  tracked
    develop                     tracked
    master                      tracked
  Local branches configured for 'git pull':
    develop merges with remote develop
    master  merges with remote master
  Local refs configured for 'git push':
    develop pushes to develop (local out of date)
    master  pushes to master  (local out of date)

   4.6  远程仓库的删除和重命名  git  remote  rm  remote_name    /    git  remote rename   oldn_ame  new_name    

注意,对远程仓库的重命名,也会使对应的分支名称发生变化,原来的pb/master 分支现在成了paul/master。
碰到远端仓库服务器迁移,或者原来的克隆镜像不再使用,又或者某个参与者不再贡献代码,那么需要移除对应的远端仓库,


5、打标签

同大多数VCS 一样,Git 也可以对某一时间点上的版本打上标签。人们在发布某个软件
版本(比如v1.0 等等)的时候,经常这么做。

5.1 列表已有标签 git tag

我们可以用特定的搜索模式列出符合条件的标签。在Git 自身项目仓库中,有着超过
240 个标签,如果你只对1.4.2 系列的版本感兴趣,可以运行下面的命令:

$ git tag -l 'v1.4.2.*'
v1.4.2.1
v1.4.2.2
v1.4.2.3
v1.4.2.4

5.2 新建标签

Git 使用的标签有两种类型:轻量级的(lightweight)和含附注的(annotated)。轻量
级标签就像是个不会变化的分支,实际上它就是个指向特定提交对象的引用。而含附注标
签,实际上是存储在仓库中的一个独立对象,它有自身的校验和信息,包含着标签的名字,
电子邮件地址和日期,以及标签说明,标签本身也允许使用GNU Privacy Guard (GPG) 来
签署或验证。一般我们都建议使用含附注型的标签,以便保留相关信息;当然,如果只是临
时性加注标签,或者不需要旁注额外信息,用轻量级标签也没问题。

5.2.1 含附注的标签 git tag -a 标签名 -m '附注'

创建一个含附注类型的标签非常简单,用-a (译注:取annotated 的首字母)指定标
签名字即可:
$ git tag -a v1.4 -m 'my version 1.4'
$ git tag
v0.1
v1.3
v1.4

而-m 选项则指定了对应的标签说明,Git 会将此说明一同保存在标签对象中。如果在此
选项后没有给出具体的说明内容,Git 会启动文本编辑软件供你输入。
可以使用git show 命令查看相应标签的版本信息,并连同显示打标签时的提交对象。

5.2.2 签署标签 git tag -s标签名 -m '附注'

如果你有自己的私钥,还可以用GPG 来签署标签,只需要把之前的-a 改为-s (译注:
取Signed 的首字母)即可:
$ git tag -s v1.5 -m 'my signed 1.5 tag'
You need a passphrase to unlock the secret key for
user: "Scott Chacon <schacon@gee-mail.com>"
1024-bit DSA key, ID F721C45A, created 2009-02-09

5.2.3 轻量级标签 git tag 标签名

$ git tag v1.4-lw
$ git tag
v0.1
v1.3
v1.4
v1.4-lw
v1.5

5.2.4 验证标签 git tag -v标签名

可以使用git tag -v [tag-name] (译注:取verify 的首字母)的方式验证已经签署的
标签。此命令会调用GPG 来验证签名,所以你需要有签署者的公钥,存放在keyring 中,
才能验证:
$ git tag -v v1.4.2.1

若是没有签署者的公钥,会报告类似下面这样的错误:
gpg: Signature made Wed Sep 13 02:08:25 2006 PDT using DSA key ID F3119B9A
gpg: Can't check signature: public key not found
error: could not verify the tag 'v1.4.2.1'

5.2.5  后期加注标签  

你甚至可以在后期对早先的某次提交加注标签。比如在下面展示的提交历史中:
$ git log --pretty=oneline

166ae0c4d3f420721acbb115cc33848dfcc2121a started write support
9fceb02d0ae598e95dc970b74767f19372d61af8 updated rakefile
964f16d36dfccde844893cac5b347e7b3d44abbc commit the todo
8a5cbc430f1a9c3d00faaeffd07798508422908a updated readme
我们忘了在提交“updated rakefile” 后为此项目打上版本号v1.2,没关系,现在也
能做。只要在打标签的时候跟上对应提交对象的校验和(或前几位字符)即可:
$ git tag -a v1.2 9fceb02
可以看到我们已经补上了标签:
$ git tag
v0.1
v1.2
v1.3
v1.4
v1.4-lw
v1.5
$ git show v1.2
tag v1.2
Tagger: Scott Chacon <schacon@gee-mail.com>
Date: Mon Feb 9 15:32:16 2009 -0800
version 1.2
commit 9fceb02d0ae598e95dc970b74767f19372d61af8
Author: Magnus Chacon <mchacon@gee-mail.com>
Date: Sun Apr 27 20:43:35 2008 -0700
updated rakefile
...

5.2.6 分享标签 git tag origin 标签名

默认情况下,git push 并不会把标签传送到远端服务器上,只有通过显式命令才能分享
标签到远端仓库。其命令格式如同推送分支,运行git push origin [tagname] 即可:
$ git push origin v1.5
Counting objects: 50, done.
Compressing objects: 100% (38/38), done.
Writing objects: 100% (44/44), 4.56 KiB, done.
Total 44 (delta 18), reused 8 (delta 1)
To git@github.com:schacon/simplegit.git
* [new tag] v1.5 -> v1.5
如果要一次推送所有(本地新增的)标签上去,可以使用--tags 选项:

$ git push origin --tags
Counting objects: 50, done.
Compressing objects: 100% (38/38), done.
Writing objects: 100% (44/44), 4.56 KiB, done.
Total 44 (delta 18), reused 8 (delta 1)
To git@github.com:schacon/simplegit.git
* [new tag] v0.1 -> v0.1
* [new tag] v1.2 -> v1.2
* [new tag] v1.4 -> v1.4
* [new tag] v1.4-lw -> v1.4-lw
* [new tag] v1.5 -> v1.5
现在,其他人克隆共享仓库或拉取数据同步后,也会看到这些标签。


三、分支

3.1 何谓分支

在Git 中提交时,会保存一个提交(commit)对象,它包含一个指向暂存内容快照的指针,作者和相关附属信息,

以及一定数量(也可能没有)指向该提交对象直接祖先的指针:第一次提交是没有直接祖先的,普通提交有一个祖先,

由两个或多个分支合并产生的提交则有多个祖先。

暂存操作会对每一个文件计算校验和(即第一章中提到的SHA-1 哈希字串),然后把当前版本的文件快照保存到Git 仓库中

(Git 使用blob 类型的对象存储这些快照),并将校验和加入暂存区域:

当使用git commit 新建一个提交对象前,Git 会先计算每一个子目录(本例中就是项目根目录)的校验和,

然后在Git 仓库中将这些目录保存为树(tree)对象。之后Git 创建的提交对象,除了包含相关提交信息以外,

还包含着指向这个树对象(项目根目录)的指针,如此它就可以在将来需要的时候,重现此次快照的内容了。

现在,Git 仓库中有五个对象:三个表示文件快照内容的blob 对象;一个记录着目录树内容及其中各个文件对应blob 对象索引的tree 对象;

以及一个包含指向tree 对象(根目录)的索引和其他提交信息元数据的commit 对象。

Git 中的分支,其实本质上仅仅是个指向commit 对象的可变指针。Git会使用master 作为分支的默认名字。

在若干次提交后,你其实已经有了一个指向最后一次提交对象的master 分支,它在每次提交的时候都会自动向前移动。

3.1.1 新建分支 git branch 分支名


那么,Git 是如何知道你当前在哪个分支上工作的呢?其实答案也很简单,它保存着一个名为HEAD 的特别指针。

在Git 中,它是一个指向你正在工作中的本地分支的指针。运行git branch 命令,仅仅是建立了一个新的分支,

但不会自动切换到这个分支中去,所以在这个例子中,我们依然还在master 分支里工作(参考图3.5)。


3.1.2 切换分支 git checkout 分支名

再提交一次,现在testing 分支向前移动了一格,而master 分支仍然指向原先git checkout 时所在的commit 对象。现在我们回到master 分支看看:




git checkout master

这条命令做了两件事。它把HEAD 指针移回到master 分支,并把工作目录中的文件换成了master 分支所指向的快照内容。

也就是说,现在开始所做的改动,将始于本项目中一个较老的版本。它的主要作用是将testing 分支里作出的修改暂时取消,

这样你就可以向另一个方向进行开发。

再次提交,现在我们的项目提交历史产生了分叉(如图3.9 所示),因为刚才我们创建了一个分支,

转换到其中进行了一些工作,然后又回到原来的主分支进行了另外一些工作。这些改变分别孤立在不同的分支里:

我们可以在不同分支里反复切换,并在时机成熟时把它们合并到一起。而所有这些工作,仅仅需要branch 和checkout 这两条命令就可以完成。



3.1.3 新建并切换分支 git branch -b 分支名

相当于下面这两条命令: git branch 分支名 git checkout 分支名

切换分支前,留心你的暂存区或者工作目录里,那些还没有提交的修改,它会和你即将检出的分支产生冲突从而阻止Git 为你转换分支。

转换分支的时候最好保持一个清洁的工
作区域。

3.1.4 合并分支 git merge 分支名

切换分支 git checkout master

合并分支 git merge 分支名


请注意,合并时出现了“Fast forward”(快进)提示。由于当前master 分支所在的commit 是要并入的hotfix 分支的直接上游,

Git 只需把指针直接右移。换句话说,如果顺着一个分支走下去可以到达另一个分支,那么Git 在合并两者时,

只会简单地把指针前移,因为没有什么分歧需要解决,所以这个过程叫做快进(Fast forward)。

现在的目录变为当前master 分支指向的commit 所对应的快照,可以发布了


3.1.5 删除分支 git branch -d 分支名

在那个超级重要的修补发布以后,你想要回到被打扰之前的工作。因为现在hotfix 分支和master 指向相同的提交,

现在没什么用了,可以先删掉它。使用git branch 的-d 选
项表示删除:
$ git branch -d hotfix
Deleted branch hotfix (3a0874c).

请注意,这次合并的实现,并不同于之前hotfix 的并入方式。这一次,你的开发历史是从更早的地方开始分叉的。

由于当前master 分支所指向的commit (C4)并非想要并入分支
(iss53)的直接祖先,Git 不得不进行一些处理。就此例而言,

Git 会用两个分支的末端(C4 和C5)和它们的共同祖先(C2)

进行一次简单的三方合并计算。图3.16 标出了Git在用于合并的三个更新快照:


Git 没有简单地把分支指针右移,而是对三方合并的结果作一新的快照,并自动创建一个指向它的commit(C6)(见图3.17)。

我们把这个特殊的commit 称作合并提交(mergecommit),因为它的祖先不止一个。

值得一提的是Git 可以自己裁决哪个共同祖先才是最佳合并基础;这和CVS 或Subversion(1.5 以后的版本)不同,

它们需要开发者手工指定合并基础。所以此特性让Git 的
合并操作比其他系统都要简单不少。


3.2 冲突的合并

如果你修改了两个待合并分支里同一个文件的同一部分,Git 就无法干净地把两者合到一起(译注:逻辑上说,这种问题只能由人来解决)

要看看哪些文件在合并时发生冲突,可以用git status查阅:
[master*]$ git status
index.html: needs merge
# On branch master
# Changed but not updated:
# (use "git add <file>..." to update what will be committed)
# (use "git checkout -- <file>..." to discard changes in working directory)
#
# unmerged: index.html
#
任何包含未解决冲突的文件都会以未合并(unmerged)状态列出。Git 会在有冲突的文件里加入标准的冲突解决标记,可以通过它们来手工定位并解决这些冲突。

<<<<<<< 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

可以看到======= 隔开的上半部分,是HEAD(即master 分支,在运行merge 命令时检出的分支)中的内容,

下半部分是在iss53 分支中的内容。解决冲突的办法无非是二者
选其一或者由你亲自整合到一起。

这个解决方案各采纳了两个分支中的一部分内容,而且我还删除了<<<<<<<,=======,和>>>>>>> 这些行。

在解决了所有文件里的所有冲突后,运行git add将把它们标记为已解决(resolved)。因为一旦暂存,就表示冲突已经解决。

3.3 分支管理

3.3.1 显示当前所有分支 git branch

注意看master 分支前的* 字符:它表示当前所在的分支。也就是说,如果现在提交更新,master 分支将随着开发进度前移

3.3.2 查看各个分支最后一次commit 信息 git branch -v

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

3.3.3 筛选出你已经(或尚未)与当前分支合并的分支 git branch --merged / --no--merged

比如git branch -merge 查看哪些分支已被并入当前分支:
$ git branch --merged
iss53
* master
之前我们已经合并了iss53,所以在这里会看到它。一般来说,列表中没有* 的分支通常都可以用git branch -d 来删掉。

原因很简单,既然已经把它们所包含的工作整合到了其他分支,删掉也不会损失什么。

另外可以用git branch --no-merged 查看尚未合并的工作:
$ git branch --no-merged
testing
我们会看到其余还未合并的分支。因为其中还包含未合并的工作,用git branch -d 删除该分支会导致失败:
$ git branch -d testing
error: The branch 'testing' is not an ancestor of your current HEAD.
不过,如果你坚信你要删除它,可以用大写的删除选项-D 强制执行,例如git branch -D testing。

3.4 远程分支

远程分支(remote branch)是对远程仓库状态的索引。远程分支就像是书签,提醒着你上次连接远程仓库时上面各分支的位置。

我们用(远程仓库名)/(分支名) 这样的形式表示远程分支。

假设你们团队有个地址为git.ourcompany.com 的Git服务器。如果你从这里克隆,Git 会自动为你将此远程仓库命名为origin,

并下载其中所有的数据,建立一个指向它的master 分支的指针,在本地命名为origin/master,

但你无法在本地更改其数据。接着,Git 建立一个属于你自己的本地master 分支,始于origin上master 分支相同的位置,你可以就此开始工作。


要是你在本地master 分支做了会儿事情,与此同时,其他人向git.ourcompany.com 推送了内容,

更新了上面的master 分支,那么你的提交历史会开始朝不同的方向发展。不过只要你不和服务器通讯,你的origin/master 指针不会移动(见图3.23)。


可以运行git fetch origin 来进行同步。该命令首先找到origin 是哪个服务器(本例为git.ourcompany.com),

从上面获取你尚未拥有的数据,更新你本地的数据库,然后把
origin/master 的指针移到它最新的位置(见图3.24)。



3.5 推送

要想和其他人分享某个分支,你需要把它推送到一个你拥有写权限的远程仓库。

你的本地分支不会被自动同步到你引入的远程分支中,

除非你明确执行推送操作。换句话说,对于无意分享的,

你尽可以保留为私人分支,而只推送那些协同工作的特性分支。

3.5.1 推送远程分支 git push 远程仓库名 分支名

$ git push origin serverfix
Counting objects: 20, done.
Compressing objects: 100% (14/14), done.
Writing objects: 100% (15/15), 1.74 KiB, done.
Total 15 (delta 5), reused 0 (delta 0)
To git@github.com:schacon/simplegit.git
* [new branch] serverfix -> serverfix

这其实有点像条捷径。Git 自动把serverfix 分支名扩展为refs/ heads/ serverfix:refs/heads/serverfix,

意为“取出我的serverfix 本地分支,推送它来更新远程
\仓库的serverfix 分支”。我们将在第九章进一步介绍refs/heads/ 部分的细节,不过一般使用的时候都可以省略它。

也可以运行git push origin serverfix:serferfix 来实现 相同的效果,它的意思是“提取我的serverfix 并更新到远程仓库的serverfix”。

通过此语法,你可以把本地分支推送到某个命名不同的远程分支:若想把远程分支叫作
awesomebranch,可以用git push origin serverfix:awesomebranch 来推送数据。

接下来,当你的协作者再次从服务器上获取数据时,他们将得到一个新的远程分支
origin/serverfix:
$ git fetch origin
remote: Counting objects: 20, done.
remote: Compressing objects: 100% (14/14), done.
remote: Total 15 (delta 5), reused 0 (delta 0)
Unpacking objects: 100% (15/15), done.
From git@github.com:schacon/simplegit
* [new branch] serverfix -> origin/serverfix
值得注意的是,在fetch 操作抓来新的远程分支之后,你仍然无法在本地编辑该远程仓库。换句话说,在本例中,

你不会有一个新的serverfix 分支,有的只是一个 你无法移动的origin/serverfix 指针。

如果要把该内容合并到当前分支,可以运行git merge origin/serverfix。如果想要一份自己的serverfix 来开发,

可以在远程分支的基 础上分化出一个新的分支来:
$ git checkout -b serverfix origin/serverfix
Branch serverfix set up to track remote branch refs/remotes/origin/serverfix.
Switched to a new branch "serverfix"
这会切换到新建的serverfix 本地分支,其内容同远程分支origin/serverfix 一致,你可以在里面继续开发了。

3.5.2 跟踪分支

创建本地分支并跟踪远程分支 git checkout -b [分支名] [远程名]/[分支名]

简化: git checkout --track[远程名]/[分支名]

从远程分支检出的本地分支,称为跟踪分支(tracking branch)。跟踪分支是一种和远程
分支有直接联系的本地分支。在跟踪分支里输入git push,Git 会自行推断应该向哪个服
务器的哪个分支推送数据。反过来,在这些分支里运行git pull 会获取所有远程索引,并
把它们的数据都合并到本地分支中来。

在克隆仓库时,Git 通常会自动创建一个master 分支来跟踪origin/master。这正是
git push 和git pull 一开始就能正常工作的原因。当然,你可以随心所欲地设定为其它
跟踪分支,比如origin 上除了master 之外的其它分支。刚才我们已经看到了这样的一个
例子:git checkout -b [分支名] [远程名]/[分支名]。如果你有1.6.2 以上版本的Git,
还可以用--track 选项简化:
$ git checkout --track origin/serverfix
Branch serverfix set up to track remote branch refs/remotes/origin/serverfix.
Switched to a new branch "serverfix"
要为本地分支设定不同于远程分支的名字,只需在前个版本的命令里换个名字:

$ git checkout -b sf origin/serverfix
Branch sf set up to track remote branch refs/remotes/origin/serverfix.
Switched to a new branch "sf"
现在你的本地分支sf 会自动向origin/serverfix 推送和抓取数据了。

3.5.3 删除远程分支 git push [远程名]:[分支名]

有种方便记忆这条命令的方法:记住我们不久前见过的git push
[远程名] [本地分支]:[远程分支] 语法,如果省略[本地分支],那就等于是在说“在这里
提取空白然后把它变成[远程分支]”。

3.6 衍合

把一个分支整合到另一个分支的办法有两种:merge(合并) 和rebase(衍合)。

最容易的整合分支的方法是merge 命令,它会把两个分支最新的快照(C3
和C4)以及二者最新的共同祖先(C2)进行三方合并。如图3.28 所示:

其实,还有另外一个选择:你可以把在C3 里产生的变化补丁重新在C4 的基础上打一
遍。在Git 里,这种操作叫做衍合(rebase)。有了rebase 命令,就可以把在一个分支
里提交的改变在另一个分支里重放一遍。
在这个例子里,可以运行下面的命令:
$ git checkout experiment
$ git rebase master
First, rewinding head to replay your work on top of it...
Applying: added staged command

它的原理是回到两个分支(你所在的分支和你想要衍合进去的分支)的共同祖先,提取你
所在分支每次提交时产生的差异(diff),把这些差异分别保存到临时文件里,然后从当前
分支转换到你需要衍合入的分支,依序施用每一个差异补丁文件。图3.29 演示了这一过
程:



衍合能产生一个更为整洁的提交历史。如果视察一个衍合过的分支的历史记录,看起来更清楚:仿佛所有修改都是先后进行
的,尽管实际上它们原来是同时发生的。

你可以经常使用衍合,确保在远程分支里的提交历史更清晰。比方说,某些项目自己不是
维护者,但想帮点忙,就应该尽可能使用衍合:先在一个分支里进行开发,当准备向主项目
提交补丁的时候,再把它衍合到origin/master 里面。这样,维护者就不需要做任何整合
工作,只需根据你提供的仓库地址作一次快进,或者采纳你提交的补丁。

请注意,合并结果中最后一次提交所指向的快照,无论是通过一次衍合还是一次三方合
并,都是同样的快照内容,只是提交的历史不同罢了。衍合按照每行改变发生的次序重演发
生的改变,而合并是把最终结果合在一起。


3.6.1  更多有趣的衍合

  在master上衍合server分支  git rebase [主分支] [特性分支]

  git rebase master server


你还可以在衍合分支以外的地方衍合。以图3.31 的历史为例。你创建了一个特性分支
server 来给服务器端代码添加一些功能,然后提交C3 和C4。然后从C3 的地方再增加一
个client 分支来对客户端代码进行一些修改,提交C8 和C9。最后,又回到server 分
支提交了C10。


假设在接下来的一次软件发布中,你决定把客户端的修改先合并到主线中,而暂缓并入服
务端软件的修改(因为还需要进一步测试)。你可以仅提取对客户端的改变(C8 和C9),
然后通过使用git rebase 的--onto 选项来把它们在master 分支上重演:


$ git rebase --onto master server client


这基本上等于在说“检出client 分支,找出client 分支和server 分支的共同祖先之
后的变化,然后把它们在master 上重演一遍”。是不是有点复杂?不过它的结果,如图
3.32 所示,非常酷:
现在可以快进master 分支了(见图3.33):


$ git checkout master
$ git merge client


现在你决定把server 分支的变化也包含进来。可以直接把server 分支衍合到master
而不用手工转到server 分支再衍合。git rebase [主分支] [特性分支] 命令会先检出特
性分支server,然后在主分支master 上重演:
$ git rebase master server
于是server 的进度应用到master 的基础上,如图3.34:


    $ git checkout master 

    $ git merge server

\

3.6.2  衍合的风险

永远不要衍合那些已经推送到公共仓库的更新。

在衍合的时候,实际上抛弃了一些现存的commit 而创造了一些类似但不同的新commit。
如果你把commit 推送到某处然后其他人下载并在其基础上工作,然后你用git rebase 重
写了这些commit 再推送一次,你的合作者就不得不重新合并他们的工作,这样当你再次从
他们那里获取内容的时候事情就会变得一团糟。

假设你从一个中央服务器
克隆然后在它的基础上搞了一些开发,提交历史类似图3.36:



现在,其他人进行了一些包含一次合并的工作(得到结果C6),然后把它推送到了中央
服务器。你获取了这些数据并把它们合并到你本地的开发进程里,让你的历史变成类似图
3.37 这样:


接下来,那个推送C6 上来的人决定用衍合取代那次合并;他们用git push --force 覆
盖了服务器上的历史,得到C4’。然后你再从服务器上获取更新:


这时候,你需要再次合并这些内容,尽管之前已经做过一次了。衍合会改变这些commit
的SHA-1 校验值,这样Git 会把它们当作新的commit,然而这时候在你的提交历史早就
有了C4 的内容(见图3.39):


你迟早都是要并入其他协作者提交的内容的,这样才能保持同步。当你做完这些,你的提
交历史里会同时包含C4 和C4’,两者有着不同的SHA-1 校验值,但却拥有一样的作者日
期与提交说明,令人费解!更糟糕的是,当你把这样的历史推送到服务器,会再次把这些衍
合的提交引入到中央服务器,进一步迷惑其他人。
如果把衍合当成一种在推送之前清理提交历史的手段,而且仅仅衍合那些永远不会公开的
commit,那就不会有任何问题。如果衍合那些已经公开的commit,而与此同时其他人已经
用这些commit 进行了后续的开发工作,那你有得麻烦了。


四、git内部原理

从根本上来讲Git 是一套内
容寻址(content-addressable) 文件系统,在此之上提供了一个VCS 用户界面

config 文件包含了项目特有的配置选项,info
目录保存了一份不希望在.gitignore 文件中管理的忽略模式(ignored patterns) 的全局
可执行文件。hooks 目录包住了第六章详细介绍了的客户端或服务端钩子脚本。
另外还有四个重要的文件或目录:HEAD 及index 文件,objects 及refs 目录。这些是
Git 的核心部分。objects 目录存储所有数据内容,refs 目录存储指向数据(分支) 的提
交对象的指针,HEAD 文件指向当前分支,index 文件保存了暂存区域信息。马上你将详细
了解Git 是如何操纵这些内容的。


0 0
原创粉丝点击