Git讲义

讲义内容参考:http://www.cnblogs.com/wilber2013/p/4189920.html

Git简介

Git是一个分布式版本控制工具

集中式版本控制

一个系统中只有一个机器是服务端,其他机器全是客户端。

缺点:

  • 网络依赖性强,工作环境保持网络连接,如果网络断掉了,所有的客户端就无法工作了。

  • 安全性较弱,所有的代码以及版本信息保存在服务器中,一旦服务器挂掉了,代码和版本控制信息就丢失了。

分布式版本控制

没有服务端/客户端的概念,每台机器都是一个服务器。但是一般选一个机器作为中心服务器,方便大家交换更新。

优点:

  • 每台机器都是一台服务器,无需依赖网络就可以帮自己的更新提交到本地服务器,支持离线工作。当有网络环境的时候,就可以把更新推送给其他服务器。

  • 安全性高,每台机器都有代码以及版本信息的维护,所有即使某些机器挂掉了,代码依然是安全的。

Git下载

Git刚开始只能支持Linux和Unix环境,后来才慢慢的支持Windows系统。

主程序:Git

Windows下可视化界面:TortoiseGit

Git安装后的配置

安装完成后,一般都会对本机的Git进行一些基本的配置。下面的命令就是给Git环境配置全局的用户名和邮箱地址,这样每一个从这台机器上提交的更新都会标上这些用户信息。

  • 当第一次安装完Git时

设置全局名字:设置成自己在GitHub上的账号显示名

# 设置名字
$ git config --global user.name "你的名字"
# 设置邮箱
$ git config --global user.email "你的邮箱"
  • 当每一次创建项目时

设置项目中自己的名字:这里设置的名字是针对本项目的,本地设置的user.name要和服务器上的账号显示名一致

# 设置名字
$ git config user.name "你的名字"
# 设置邮箱(邮箱与全局相同可以不设置)
$ git config user.email "你的邮箱"

在git设置中注意关闭忽略大小写,这一项貌似设成全局的不管用,所以每个项目创建时都要设置一下

#关闭忽略大小写
git config core.ignorecase false

尽量使用UTF-8编码

Git基本概念

在Git中,每个版本库都叫做一个仓库(repository,缩写repo),每个仓库可以简单理解成一个目录,这个目录里面的所有文件都通过Git来实现版本管理,Git都能跟踪并记录在该目录中发生的所有更新。

我们现在建立一个仓库(repo),那么在建立仓库的这个目录中会有一个”.git”的文件夹。这个文件夹非常重要,所有的版本信息、更新记录,以及Git进行仓库管理的相关信息全都保存在这个文件夹里面。所以,不要修改/删除其中的文件,以免造成数据的丢失。

创建仓库

通过”Git Bash”命令行窗口进入到想要建立版本仓库的目录,通过git init就可以轻松的建立一个仓库。

添加

先编写一个文件reader.txt

通过git status可以查看WorkSpace的状态,看到输出显示reader.txt没有被Git跟踪,并且提示我们可以使用”git add …”把该文件添加到待提交区(暂存区)。

git add reader.txt
git add .

通过git status可以查看WorkSpace的状态,这时的更新已经从WorkSpace保存到了Stage中。

git commit -m "xxx"

xxx是对此操作的描述,这时的更新已经又从Stage保存到了Local Repo中。

更新

修改文件reader.txt

修改文件后,查看WorkSpace的状态,会发现提示文件有更新,但是更新只是在WorkSpace中,没有存到暂存区中。

同样,通过add、commit的操作,我们可以把文件的更新先存放到暂存区,然后从暂存区提交到repo中。

git diff

通过”git diff”来查看WorkSpace和Stage的diff情况,当我们把更新add到Stage中,diff就不会有任何输出了。

当然,我们也可以把WorkSpace中的状态跟repo中的状态进行diff,命令如下,关于HEAD,将在后面解释。

$ git diff HEAD~n

撤销更新

根据前面对基本概念的了解,更新可能存在三个地方,WorkSpace中、Stage中和repo中。下面就分别介绍一下怎么撤销这些更新。

  • 撤销WorkSpace中的更新

即:暂存区有内容,即有未commit的内容,此操作是将暂存区覆盖到WorkSpace,即将最后没add的内容取消

$ git checkout -- filename

注意,在使用这种方法撤销更新的时候一定要慎重,因为通过这种方式撤销后,更新将没有办法再被找回。

  • 撤销Stage中的更新

即:add完成,但是还没commit

$ git reset HEAD filename

此时,就把暂存区的更新移出到WorkSpace中。如果想继续撤销WorkSpace中的更新,请参考上面一步。

  • 撤销repo中的更新

即:commit操作完成

查看历史记录:通过下面的命令我们可以查看commit的历史记录

$ git log

撤销commit有两种方式:使用HEAD指针和使用commit id

HEAD指针指向当前分支中最新的提交:

$ git reset --hard HEAD^
$ git reset --hard 一长串数字

注:当前版本,我们使用”HEAD^”,那么再前一个版本可以使用”HEAD^^”,如果想回退到更早的提交,可以使用”HEAD~n”。(也就是,HEAD^=HEAD~1,HEAD^^=HEAD~2)

  • 恢复撤销repo中的更新
$ git reflog

git reflog 查看记录在这个仓库中的所有分支所有更新记录,包括已经撤销的更新

注:在”.git/logs”文件夹内有一个HEAD文件和refs目录,这些就是记录reflog的地方

git log 查看当前分支中的commit记录

有了reflog,我们就可以查找到刚才撤销的提交,然后可以通过下面命令来恢复撤销操作。

$ git reset --hard HEAD@{1}
$ git reset --hard 一长串数字

–hard和–soft

–hard:撤销并删除相应的更新
–soft:撤销相应的更新,把这些更新的内容放的Stage中

删除文件

$ git rm

Git对象模型

Git对象

在Git系统中有四种类型的对象,所有的Git操作都是基于这四种类型的对象。

  • “blob”:这种对象用来保存文件的内容
  • “tree”:可以理解成一个对象关系树,它管理一些”tree”和 “blob”对象
  • “commit”:只指向一个”tree”,它用来标记项目某一个特定时间点的状态。它包括一些关于时间点的元数据,如时间戳、最近一次提交的作者、指向上次提交(初始commit没有这一项)
  • “tag”:给某个提交(commit) 增添一个标记

SHA1哈希值

在Git系统中,每个Git对象都有一个特殊的ID来代表这个对象,这个特殊的ID就是我们所说的SHA1哈希值。

SHA1哈希值是通过SHA1算法(SHA算法家族的一种)计算出来的哈希值,对于内容不同的对象,会有不同的SHA1哈希值。前面讲的根据commit id撤销更新的,这里的commit id就是一个SHA1哈希值。

Git对象模型实例

    1. 新建一个仓库,添加一个文件

通过git log --pretty=raw可以得到每个commit的SHA1哈希值,也可以得到这个commit对应的tree的哈希值。

所以,一个commit对象一般包含以下信息:代表commit的哈希值、指向tree 对象的哈希值、作者、提交者、注释

在Git对象模型的研究中,有一个很有用的命令git cat-file,可以通过这个命令查询特定对象的信息:

#通过一个对象的哈希值查看对象的类型(blob、tree、commit或tag)
$ git cat-file -t commit_id
#通过对象的哈希值可以查看这个对象的内容
$ git cat-file -p commit_id
  • 2.更新文件,添加新东西
$ git log --pretty=raw
$ git cat-file

.git目录

进入.git目录,通过ls -l查看.git目录中的文件和子目录

  • (D) hooks:这个目录存放一些shell脚本,可以设置特定的git命令后触发相应的脚本;在搭建gitweb系统或其他git托管系统会经常用到hook script
  • (D) info:包含仓库的一些信息
  • (D) logs:保存所有更新的引用记录
  • (D) objects:所有的Git对象都会存放在这个目录中,对象的SHA1哈希值的前两位是文件夹名称,后38位作为对象文件名
  • (D) refs:这个目录一般包括三个子文件夹:heads、remotes和tags,heads中的文件标识了项目中的各个分支指向的当前commit
  • (F) COMMIT_EDITMSG:保存最新的commit message,Git系统不会用到这个文件,只是给用户一个参考
  • (F) config:这个是Git仓库的配置文件
  • (F) description:仓库的描述信息,主要给gitweb等git托管系统使用
  • (F) index:这个文件就是我们前面文章提到的暂存区(stage),是一个二进制文件
  • (F) HEAD:这个文件包含了一个当前分支(branch)的引用,通过这个文件Git可以得到下一次commit的parent
  • (F) ORIG_HEAD:HEAD指针的前一个状态

Git引用

Git系统中的分支名、远程分支名、tag等都是指向某个commit的引用。比如master分支,origin/master远程分支,命名为V1.0.0.0的tag等都是引用,它们通过保存某个commit的SHA1哈希值指向某个commit。

  • HEAD

HEAD也是一个引用,一般情况下间接指向你当前所在的分支的最新的commit上。

HEAD跟Git中一般的引用不同,它并不包含某个commit的SHA1哈希值,而是包含当前所在的分支,所以HEAD直接指向当前所在的分支,然后间接指向当前所在分支的最新提交。

  • Git索引(index)

index(索引)显示一个存放了已排序的路径的二进制文件,并且每个路径都对应一个SHA1哈希值。

#显示index文件的内容
$ git ls-files --stage

从命令的输出可以看到,所有的记录都对应仓库中的文件(包含全路径)。通过git cat-file命令查看reader.txt对应的哈希值,可以看到这个哈希值就是代表reader.txt的blob对象。

现在我们更新之前的文件,加上新内容并通过”git add”添加到暂存区,这时发现index中之前对象的哈希值已经变化了。

通过这个例子,我们也可以理解diff操作应该会有怎样的输出了:

git diff:比较WorkSpace和stage,add之前有diff输出;add之后没有diff输出
git diff HEAD:比较WorkSpace和repo,add之前之后都有diff输出
git diff –cached:比较stage和repo,add之前没有diff输出;add之后有diff输出

对象的存储

前面提到所有的Git对象都会存放在”.git/objects”目录中,对象SHA1哈希值的前两位是文件夹名称,后38位作为对象文件名。

所以,我们前面提到的master上最新的commit对象的哈希值是”4ea6c317a67e73b0befcb83c36b915c1481f2efe”,那么这个对象会被存储在”.git/objects/4e/a6c317a67e73b0befcb83c36b915c1481f2efe”。进入objects目录后,我们确实找到了这个文件。
在Git系统中有两种对象存储的方式,松散对象存储和打包对象存储。

  • 松散对象(loose object)

松散对象存储就是前面提到的,每一个对象都被写入一个单独文件中,对象SHA1哈希值的前两位是文件夹名称,后38位作为对象文件名。

  • 打包对象(packed object)

对于松散存储,把每个文件的每个版本都作为一个单独的对象,它的效率比较低,而且浪费空间。所以就有了通过打包文件(packfile)的存储方式。

Git使用打包文件(packfile)去节省空间.。在这个格式中,,Git只会保存第二个文件中改变了的部分,然后用一个指针指向相似的那个文件。

一般Git系统会自动完成打包的工作,在已经发生过打包的Git仓库中,”.git/objects/pack”目录下会成对出现很多”pack-.idx”和”pack-.pack”文件。

Git 分支

在代码版本控制工具中,都会有branch的概念。刚开始建立版本仓库的时候,我们只有一个主分支(master branch),我们不可能把日常的新功能开发、代码优化以及bug修复等概念工作全都放在主分支上,这样会使主分支很难维护。这就是为什么会有branch。

分支的创建

git checkout -b dev //创建并切换到分支dev
#相当于:
$ git branch dev
$ git checkout dev

查看当前分支

$ git branch

注:checkout — 表示还原,checkout 表示切换。

可以git log查看一下

分支的删除

$ git branch -d dev

分支的合并

branch的创建是为了方便开发、修复bug以及保持master的稳定。但是最终branch上的内容还是要合并到master的,接下来就看看分支的合并。

#合并到master上
$ git merge dev

注意:一定要先切换回master分支git checkout master后再合并。

合并冲突

在branch的合并中,很多时候我们会遇到冲突,那么我们就需要手动解决冲突,然后再提交了。

为了演示冲突合并,我们回退master到上一次提交

$ git reset --hard HEAD~1

然后在master分支上修改reader.txt,之后add、commit

这时我们在合并master和dev上的内容,就会出现冲突。

在Git中,会用”<<<<<<<“,”=======”,”>>>>>>>”标记出冲突区域,我们需要根据这些符号找到所有的冲突并解决,之后再次add、commit即可。

查看日志

$ git log --graph --pretty=oneline --abbrev-commit
  • –graph:显示commit的历史,包括不同分支的提交
  • –pretty=oneline:只显示一行提交的信息; 多行:–pretty=raw
  • –abbrev-commit:以精简的方式显示commit的哈希值

stash

在branch的使用过程中,我们还会经常使用到stash和diff操作,下面分别进行介绍。

在Git中,stash是个很有用的命令,可以保存我们做到一半的工作,可以理解成一个未完成工作的保存区。

假如我们在dev branch做了一些更新,但是想做的事情还没有全部完成,不能提交,这时我们又要切换到master branch,此时Git会禁止branch切换。

这个时候我们就需要使用stash来保存未完成的工作了。

注:在要保存的分支上进行stash操作。

# 保存未提交的内容
$ git stash
# 查看所有stash内容
$ git stash list

此时,我们就可以在其他分支上继续做别的工作了。

当其他工作完成,想回来继续刚才的操作的时候,我们可以通过git stash apply还原被保存的工作状态。

# 查看dev分支的状态
$ git status
# 还原被保存的工作
$ git stash apply

stash空间就像是一个栈空间,每次通过stash保存等内容都会被压入stash栈。命令不仅仅是支持简单的list、apply操作,接下来我们看看更多的stash命令。

  • git stash save可以通过自定义的信息来描述一个stash
$ git stash save "Reupdate reader.txt"
#再次查看当前stash的内容
$ git stash list
  • git stash apply stash@{n}通过这个命令,我们可以选择stash栈中的stash,从而恢复到特定的状态;”git stash apply”使用栈顶来还原WorkSpace。
  • git stash pop就像”git stash apply”使用栈顶来还原WorkSpace,但是同时会从stash栈中删除当前的栈顶stash。

branch之间的diff

在前面的文章中我们通过diff比较过同一个分支上的内容在WrokSpace、stage和repo中的差别。

同样diff可以支持分支之间的比较。

git diff branchName把当前branch跟branchName进行比较,也可以使用git diff branchNameA branchNameB

git diff branchName -- fileName比较两个branch的fileName文件差异


Git 远程仓库

前面文章中出现的所有Git操作都是基于本地仓库的,但是日常工作中需要多人合作,不可能一直都在自己的代码仓库工作。所以,这里我们就开始介绍Git远程仓库。

在Git系统中,用户可以通过push/pull命令来推送/获取别的开发人员的更新,当时对于一个工作组来说,这种方式会效率比较低。所以,在一个Git系统中,都会有一个中心服务器,大家都通过中心服务器来推送/获取更新。

为了方便本篇例子的进行,我就使用多个目录来模拟多个用户以及中心服务器,这样就不用搭建Git服务器了。

中心服务器:C:\VM\CentralRepo

用户A:C:\VM\AWorkSpace

用户B:C:\VM\BWorkSpace

建立中心服务器(服务器文件)

前面通过git init来建立一个Git仓库,这里,我们使用git init --bare来建立中心仓库。

git init --bare方法创建的是一个裸仓库,之所以叫裸仓库是因为这个仓库只保存Git历史提交的版本信息,而不允许用户在上面进行各种git操作。

之所以有裸仓库,是因为用git init初始化的版本库,用户也可以在该目录下执行所有git方面的操作。但别的用户在将更新push上来的时候容易出现冲突。在这种情况下,最好就是使用”–bare”选项建立一个裸仓库。

Clone一个仓库 (A仓库的位置)

在Git中,我们有两种方式建立Git仓库:一个是通过git init建立一个新的仓库,另一个是通过git clone命令clone一个已有的Git仓库。

既然中心服务器,用户A就可以通过clone命令来复制一个Git仓库。

$ git clone /c/vm/CentralRepo/ #被clone的仓库的地址

这时,用户A就从中心服务器clone了一个空的仓库,接下来A就可以在这个本地仓库工作了。

更新的push和pull

现在A在本地仓库中添加了一个”calc.py”的文件,并且提交到了本地仓库。

$ git add calc.py
$ git commit -m "add calc file"

为了使其他用户可以得到这个更新,A需要把这个更新push到中心服务器上。

$ git push origin master

现在用户B可以通过clone方式获得心服务器上的仓库副本,通过”git log”显示A的更新已经自动被clone了下来。

$ git clone /c/vm/CentralRepo/
$ cd CentralRepo
$ git log

现在,B提交了一个新的更新,那么A就可以通过git pull从中心服务器得到这个更新

上游仓库(upstream repository)

在Git系统中,通常用”origin” 来表示上游仓库。我们可以通过 git branch -r命令查看上游仓库里所有的分支,再用 origin/name-of-upstream-branch 的名字来抓取(fetch)远程追踪分支里的内容。

中心服务器上建立两个新的”dev1″和”dev2″分支,通过git pull ,用户B就看到了这些上游分支。

–set-upstream-to

当我们在本地仓库建立一个branch的时候,我们的pull操作会遇到以下问题,提示我们这个branch没有任何的跟踪信息。仔细想想也是,我们应该把本地仓库中的分支与上游分支关联起来。这时,我们就可以根据Git的提示,使用”–set-upstream-to”命令进行关联了。

# 先在B上创建同名分支
$ git checkout -b dev1
# 拉取
$ git pull # 出错!
# 本地仓库中的分支与上游分支关联起来
$ git branch --set-upstream-to=origin/dev1 dev1
# 此时拉取就能成功了
$ git pull

还可以通过下面的命令创建关联分支

#一般,"localBranchName"名称跟"remoteBranchName"名称设置成一样。
$ git checkout -b localBranchName origin/remoteBranchName

patch

在Git中patch绝对是一个很有用的东西。假设在一个没有网络的环境中,A和B还要继续工作,这时B有一个更新,A需要基于这个更新进行下一步的工作。如果是集中式的代码版本工具,这种情况就没有办法工作了,但是在Git中,我们就可以通过patch的方式,把B的更新拷贝给A。

在Git中有两种patch的方式

  • 通过git diff生成一个标准的patch
  • 通过git format-patch生成一个Git专用的patch。

  • 基于git diff的patch

假设现在B更新”calc.py”文件并且通过git diff生成了一个patch。

# B更新后,通过`git diff `查看B的更新
$ git diff
# 把diff的结果保存到一个文件中,生成一个patch
$ git diff &gt; BPatch

下面,我们把 BPatch这个文件拷贝到A的工作目录中,然后通过git apply应用这个patch,从而得到B的更新。

# A通过`git apply`命令应用BPatch得到了B的更新
$ git apply BPatch
# 通过`git diff`查看B的更新
$ git diff
  • 基于git format-patch的patch

B更新提交之后,使用”git format-patch”来生成patch。

$ git format-patch origin/master

注意git format-patch命令的参数”origin/master”,这个参数的含义是,找出当前master跟origin/master之间的差别,然后生成一个patch,比如:0001-update-calc.py.patch。

把patch文件拷贝到A的工作目录,则此我们通过”git am”命令来应用这个patch。

# 应用patch
$ git am 0001-update-calc.py.patch
# 再查看日志
$ git log
  • 两种patch方式的比较

patch兼容性:git diff生成的patch兼容性强。也就是说,如果别人的代码版本库不是Git,那么必须使用git diff生成的patch才能被别的版本控制系统接受。
patch合并提示:对于git diff生成的patch,你可以用git apply –check 查看patch是否能够干净顺利地应用到当前分支中;如果git format-patch生成的patch不能打到当前分支,git am会给出提示,帮你解决冲突,两者都有较好的提示功能。
patch信息管理:通过git format-patch生成的patch中有很多信息,比如开发者的名字,因此在应用patch时,这个名字会被记录进版本库,这样做是比较合理的。

.gitignore

项目中可能会经常生成一些Git系统不需要追踪(track)的文件,在编译生成过程中 产生的文件或是编程器生成的临时备份文件。我们可以在使用”git add”是避免添加这些文件到暂存区中,但是每次都这么做会比较麻烦。

所以,为了满足上面的需求,Git系统中有一个忽略特定文件的功能。我们可以在工作目录中添加一个叫”.gitignore”的文件,j将要忽略的内容写进文件,来告诉Git系统要忽略哪些文件。

注意:

  • 在windows环境中不支持文件名为”.gitignore”,所以可以把文件命名为”.gitignore.”
  • “.gitignore”文件只会对当前目录以及所有当前目录的子目录生效;也就是说如果我们把”.gitignore”文件移到”advance”文件夹中,那么过滤规则就是会对”advance”及其子目录生效了
  • 建议把”.gitignore”文件提交到仓库里,这样其他的开发人员也可以共享这套过滤规则

过滤语法

  • 斜杠”/”开头表示目录
  • 星号”*”通配多个字符
  • 问号”?”通配单个字符
  • 方括号”[]”包含单个字符的匹配列表
  • 叹号”!”表示不忽略匹配到的文件或目录
    下面举一些简单的例子:
  • foo/*:忽略目录 foo下的全部内容
  • *.[oa]:忽略所有.o和.a文件
  • !calc.o:不能忽略calc.o文件

远程仓库命令

git branch

  • git branch -r 列出远程分支
  • git branch -a 列出所有分支

git remote

  • git remote 列出所有远程主机
  • git remote -v列出所有远程主机,并显示远程主机地址

git push

push命令用来将本地分支的更新推送的远程仓库,该命令的格式如下:

git push &lt;远程主机名&gt; &lt;本地分支名&gt;:&lt;远程分支名&gt;

通过”git push”更新、创建、删除远程分支

  • git push origin dev1:dev1 将本地dev1分支推送到远程origin的dev1分支上
  • git push origin dev1 省略远程分支名时,推送到远程origin下的同名(dev1)分支上
  • git push origin dev0 如果远程分支不存在,则创建一个同名远程分支与本地分支相关联
  • git push origin :dev1 省略本地分支名时,表示删除指定的远程分支,因为这等同于推送一个空的本地分支到远程分支。等价于git push origin --delete dev1,只是删除了远程分支,本地还有~

git pull

pull命令的作用是取回远程某个分支的更新,然后合并到指定的本地分支,pull命令格式如下:

git pull &lt;远程主机名&gt; &lt;远程分支名&gt;:&lt;本地分支名&gt;

git pull origin dev1:dev1:取回origin主机dev1分支的更新,与本地的dev1分支合并。

一般来说,pull命令都是在关联的本地分支和远程分支之间进行;当然,你可以使用不关联的本地分支和远程分支进行pull操作,但是不建议这么做。

省略本地分支名:git pull origin dev1,表示取回origin/dev1远程分支的更新,然后合并到当前分支

如果当前分支存在上游(关联)分支,可以直接使用git pull origin

git pull操作实际上等价于,先执行git fetch获得远程更新,然后git merge与本地分支进行合并。

pull命令也支持使用rebase模式进行合并:

git pull --rebase &lt;远程主机名&gt; &lt;远程分支名&gt;:&lt;本地分支名&gt;

在这种情况下,git pull就等价于git fetch加上git rebase

建议使用”git fetch”加上”git rebase”的方式来取代”git pull”获取远程更新。

git fetch

fetch命令比较简单,作用就是将远程的更新取回到本地。

git fetch origin表示将远程origin主机的所有分支上的更新取回本地

git fetch origin master只取回远程origin主机上master分支上的更新

Git 的merge和 rebase

merge

merge命令会有三种情况发生:

  1. merge命令不生效

当前分支已经是最新的了,在这种情况下merge命令没有任何效果。

$ git merge dev

提示”Already up-to-date.”,表示merge不生效,因为master比dev还要新,就不用merge了。

  1. Fast-forward合并模式

git merge --no-ff master 合并master时可以禁止Fast-forward, 以保留版本演进的历史信息,否则commit仍然是线性的

  1. 三方合并:不使用!

cherry-pick

在实际应用中,我们可能会经常碰到这种情况,在分支A上提交了一个更新,但是后来发现我们同样需要在分支B上应用这个更新。那么这时cherry-pick就可以帮助你解决问题。

我们要做的就是通过git reflog找到A上那个更新的SHA1哈希值,然后切换到B分支上使用git cherry-pick

#假设A上的更新是00abc3f
$ git cherry-pick 00abc3f

如果出错,按提示修改,先进行手动合并冲突,之后git add,然后继续cherry-pick,git cherry-pick --continue即可。

如果想要终止cherry-pick:git cherry-pick --abort

rebase

在merge的过程中,比较好的就是我们可以看到分支的历史信息,但是,如果分支很多的时候,这个分支历史可能就会变得很复杂了。如果我们使用rebase,那么提交的历史会保持线性。

rebase的原理:先将当前分支的HEAD引用指向目标分支和当前分支的共同祖先commit节点,然后将当前分支上的commit一个个apply到目标分支上,apply完以后再将HEAD引用指向当前分支。是不是有点绕,下面我们看个实例。
下面就开始rebase的介绍,我们会基于master新建一个release-1.0的分支,并在该分支上提交一个更新。

这时,我们在release-1.0分支上执行”git rebase master”,就会得到下面的对象关系图。

根据rebase的工作原理进行分析:
把当前分支的HEAD引用指向”00abc3f”
然后将当前分支上的commit一个个apply到目标分支,这里就是把”ed53897″更新apply到master上;注意,如果没有冲突,这里将直接生成一个新的更新
最后更新当前分支的HEAD引用执行最新的提交”8791e3b”
这个就是rebase操作,可以看到通过rebase操作的话,我们的commit历史会一直保持线性。在这种情况下,当我们切换到master分支,然后进行”git merge release-1.0″分支合并时,master将会直接是Fast-forward合并模式,commit历史也会是线性的。
当然rebase操作也会产生冲突,当一个冲突发生的时候,我们可以skip过当前的patch(一定要当心,不要随便使用,以免丢失更新);也可以手动的解决冲突,然后继续rebase操作

rebase交互模式
其实,rebase还有别的很强大功能,比如rebase交互模式,通过交互模式我们可以改变commit的信息,删除commit,合并commit以及更改commit的顺序等等。
假如我们现在想要更新一个commit的信息,我们就可以使用rebase交互模式,找到commit的哈希值,然后进入交互模式。

根据rebase的操作提示,我们选择edit操作,然后保存退出。

这时,Git将会提示我们,是进行更改,还是可以继续操作。这里我们通过”git commit –amend”进入编辑模式。

在编辑模式中对commit进行更新,然后保存退出,继续rebase操作。

关于rebase交互模式的其他命令,这里就不做介绍了,有兴趣的同学可以google一下。
总结
这篇文章主要对merge和rebase进行了介绍。对于最终的结果而言,rebase和merge是没有区别的,不会发生rebase和merge导致最终更新不一致的情况。
rebase和merge的差别主要是:
rebase更清晰,因为commit历史是线性的,但commit不一定按日期先后排,而是当前分支的commit总在后面(参考rebase原理)
merge之后commit历史变得比较复杂,并且一定程度上反映了各个分支的信息,而且 commit按日期排序的。
大家可以根据自己的项目需求进行选择使用哪种方式拉取远程的更新。