京山游侠

专注技术 拒绝扯淡
  博客园  :: 首页  :: 新随笔  :: 联系 :: 订阅 订阅  :: 管理

特别说明:要在我的随笔后写评论的小伙伴们请注意了,我的博客开启了 MathJax 数学公式支持,MathJax 使用$标记数学公式的开始和结束。如果某条评论中出现了两个$,MathJax 会将两个$之间的内容按照数学公式进行排版,从而导致评论区格式混乱。如果大家的评论中用到了$,但是又不是为了使用数学公式,就请使用\$转义一下,谢谢。

想从头阅读该系列吗?下面是传送门:

参考资料##

我是通过阅读《Pro Git》这本书学习 Git 的,我读的时候还是第一版的英文版,现在已经出第二版了,而且英文版和中文版都有。英文第二版的地址是 https://git-scm.com/book/en/v2,中文第二版的地址是https://git-scm.com/book/zh/v2。想看第一版的话把地址中的v2改成v1就可以了。如果哪天突然链接打不开了,也别着急,记住《Pro Git》这个招牌,使用搜索引擎很快就能找到它。

Git 是什么##

Git 是一个版本控制系统,它可以保存工作文件的所有修订版本。有了它你就可以将某个文件回溯到之前的状态,甚至将整个项目都回退到过去某个时间点的状态,你可以比较文件的变化细节,查出最后是谁修改了哪个地方,从而找出导致怪异问题出现的原因,又是谁在何时报告了某个功能缺陷等等。使用版本控制系统通常还意味着,就算你乱来一气把整个项目中的文件改的改删的删,你也照样可以轻松恢复到原先的样子。但额外增加的工作量却微乎其微。

Git 的特点:

  1. Git是分布式的版本控制系统,它没有中心服务器的概念(虽然实际开发中可以建一个中心服务器),每一台开发机器上都保存完整的历史记录;但是它有本地代码仓库和远程代码仓库的概念(不然怎么多人协作?),而且可以追踪多个远程仓库。
  2. Git 在没有网的情况下也可以工作。Git在本地磁盘上保存有项目的完整历史,Git 中的绝大多数操作都只需要访问本地文件和资源,一般不需要来自网络上其它计算机的信息。所以 Git 能够非常快地建立分支和合并分支,并具有强大的跟踪分支和切换分支的能力。大部分操作看起来瞬间完成。有网的时候,再向远程仓库 push 一下就行了。
  3. Git 一般只添加数据。你执行的 Git 操作,几乎只往 Git 数据库中增加数据。 很难让 Git 执行任何不可逆操作,或者让它以任何方式清除数据。一旦你提交数据到 Git 中,就难以再丢失数据,特别是如果你定期的推送数据库到其它仓库的话。

下面是我简化了的 Git 使用小结,图片都来源于《Pro Git》第一版。

每一个项目都应该有一个工作目录(Working directory),我们可以自己建一个目录,然后把这个目录里面的代码用 Git 管理起来(使用git init命令和git add命令),也可以通过git clone命令从别的地方克隆一个项目过来自动生成一个工作目录。在工作目录中的文件就是当前编辑和修改的文件,如果是新建立的目录或新克隆来的目录,工作目录中的文件就是该项目最新的状态。Git 是在本地保存有所有的历史记录和分支记录的,这些内容都在工作目录的.git目录中,称之为本地仓库(local repository)。当切换分支或查看以前的历史版本时,工作目录中的文件自动改变(这才是重点,工作无需切换目录,目录中的文件会自动切换)。工作目录中的文件有三种状态:已修改(modified)、已暂存(staged)、已提交(committed)。修改后的文件可以先加入暂存区域,一次工作结束后一起提交。

Git是分布式的,没有中心服务器的概念,但实际工作中仍然可以把代码仓库放到一台大家都可以访问的服务器上,做实际的中心服务器使用(仅在小团队时使用此工作流程,原因后面详述)。在本地机器上工作完后,使用git push命令把仓库推送到服务器上,换一个地方换一台机器后,只需要git clone一下,又可以获得所有的代码(包含所有的历史记录及分支)继续工作。服务器故障也没问题,因为每一个工作的机器上都保存有完整的代码仓库,所以从不用担心代码丢失。没有网络也没有关系,在本地机器上照样可以提交(git commit),因为整个仓库就在自己的机器上,当有网络时,git push一下就可以了。

Git有远程仓库(remote repository)的概念,而且可以管理很多个远程仓库,远程仓库可以是服务器,也可以是别人的个人计算机(但一般没有人这么用),每一个远程仓库都有一个简短的名字和一个地址,最开始用git clone克隆代码的那个远程仓库别名往往默认为 origin,自己添加的远程仓库可以随意指定别名,当然所有的远程仓库都可以随意修改别名。可以从远程仓库获取代码(git fetch命令或git pull命令),也可以把自己的代码推送到远程仓库(git push命令,需要写权限)。

既然 Git 即可以随便从远程仓库获取代码,又可以把自己的代码推送到远程仓库,那么当多人协作时,岂不会乱套吗?解决这个问题的,就是 Git 的必杀之技——创建分支及分支合并。

首先,随着一次次的提交,在本地代码库中形成一个主分支,如下图:

有时为了开发新特性,随时可以开一个新分支,如下图:

新分支和主分支之间可以随意切换,随着分支的发展,形式如下图:

主分支也可以向前发展,如下:

最终,当新分支代码很稳定以后,可以将其合并到主分支,如下图:

而能够防止多人协作时出现混乱的关键就在于,当从远程仓库 clone 代码库到本地或 fetch 代码库到本地时,远程分支的标记并不等于本地分支的标记。从远程 clone 一个代码库到本地后,其 master 分支有两个标记,一个标记为 origin/master 表示远程库中的 master 分支,一个标记为 master,表示本地的 master 分支。如下图:

可以想象,由于别人的工作,远程仓库中的 master 分支肯定会向前继续移动,但是在下次联网之前,该 origin/master 标记不会移动。而本地的 master 标记继续向前移动。

直到下次联网,使用git fetch命令将远程仓库的内容取回本地,origin/master 标记才会改变位置,这时,看起来就像是两个分支,如下图:

最后,将 origin/master 分支合并到 master 分支中(使用git merge命令),本地代码库又一次变成了一个单一的 master 分支,继续向前开发,并可以将它 push 到远程仓库,供别人使用。

Git冲突的处理完全靠人工完成。(从逻辑上讲,机器也不可能完美处理冲突。)比如一个小型团队一起工作,他们可以设置一个服务器用于保存远程 Git 仓库,然后每个人工作之前先从该远程仓库 fetch 代码,接着工作,工作完成后,先在本地提交,最后 push 到远程仓库。但是当一个人 push 的时候,已经有人在他之前 push 了,如果他们工作在同一个分支,就会出现冲突。解决冲突的办法就是先把别人 push 的内容再次 fetch 下来,合并分支,然后再 push。

通过前面对git原理的了解,可以分析得出使用Git时有以下几种工作流程:

  1. 一个人单干,不需要考虑冲突,随时可以开分支、合并分支和切换分支,随时可以本地提交。如果为了防止代码丢失,可以开一个服务器,每次工作完成就 push 到服务器上;
  2. 小型团队合作,如前所述,开一个服务器保存代码仓库,然后所有的人把该服务器当成远程仓库,工作之前先 fetch,工作之后再 push。如果有冲突,则先 fetch,合并分支解决冲突后再 push。如果团队人数太多,每个人都向该服务器 push,那冲突该是有多少?有可能一个开发者第一次向服务器 push 的时候,有人在他之前已经 push 过了,他只好先 fetch,手工合并解决冲突,可等他再次 push 的时候,发现又有人再他之前已经 push 了,于是他只好再做一次解决冲突的流程,可是如果在他工作的时候,又有人 push 了呢?这也是之前讲的该工作流程只适合小型开发团队的原因。
    以上流程经过适当修改也可以供大型团队使用,那就是将团队分组,每个组的成员共用一个服务器当远程仓库,组长合并了该组的工作成果后,再push到另一个服务器当总的远程仓库,这样就可以大大减少冲突的数量,减少工作量。
  3. 开源项目的合作,在这种情况下,每个人都把自己的仓库暴露在互联网上。开源项目的组织者或负责人将所有人的仓库设为远程仓库,并把有意义的工作合并到主分支,然后发布官方的 Git 仓库。每个开发者从官方仓库 fetch 代码后,完成自己的工作,然后再把它 push 到互联网上自己的仓库,等着项目负责人将自己的工作整合到官方仓库中。如果项目负责人不干了,改人了,只要还有人继续开发,该项目就可以继续下去。碰到团队比较大的情况,也可以进行分组。

Git 服务器的建设也相当简单,因为 Git 支持以 SSH、HTTP 等协议传输数据,如果需要对服务器有写权限,就开通 SSH 服务吧,设一个账户供所有人访问 Git 仓库即可。如果只需要读权限,使用任何一个 HTTP 服务器均可。关于 Git 服务器的建设,请自行参考官方文档。如果是个人的、开源的项目,可以使用 Github 网站提供的服务,直接存储在互联网上。(Github 私人仓库是要收钱的。)

在 Eclipse 中使用 Git##

得益于 Eclipse 中的 EGit 插件,在 Eclipse 中使用 Git 非常简单。任何一个项目,都可以使用快捷菜单中的 "Team" -> "Share Project" 将文件交给版本控制软件管理,如下图:

现在最流行的版本控制软件当然是非 Git 莫属了。Eclipse 会提示我们创建一个 Git 仓库(Repository)。Eclipse 中的项目(Project)是一个比较小的概念,它不能等同于 Git 中的工作目录(Working Directory),Git 中也没有项目的概念,但是在 Git 的工作目录中管理多个文件夹是没有什么问题的,而 Eclipse 中的项目就是一个文件夹,所以,在 Git 的一个工作目录中管理多个 Project 是没有问题的。因此,只有 Eclipse 的工作区(Workspace)才等同于 Git 中的工作目录(Working Directory)。而 Git 的仓库(Repository)一般是放在 Git 工作目录中的一个.git目录,考虑到 Eclipse 的工作区(Workspace)中本来就已经包含了很多元数据,再在里面创建一个.git目录,并且在里面存放 Git 的数据容易引起混乱,因此最好是把 Git 的工作目录和仓库创建在 Eclipse 的 Workspace 之外。幸好,Eclipse 支持这样的功能。如下图,点击 "Create" 按钮,创建一个 Git 仓库,这里输入的路径/home/youxia/git/samples其实是一个工作目录:

从下图可以看出,工作目录为/home/youxia/git/samples,Git 的仓库为/home/youxia/git/samples/.git,原本在 Workspace 中的项目 JavaIODemo 被自动移到了/home/youxia/git/samples目录中:

可以使用 Navigator 视图查看项目中所有的文件,没有被 Git 跟踪的文件其图标中显示一个小问号。可以使用 "Ignore" 菜单项让 Git 忽略对某些文件的跟踪,比如源代码编译后产生的类文件,如下图:

使用 "Add to Index" 菜单项让 Git 对那些需要进行版本控制的文件进行跟踪,如下图:

进行提交,如下图:

提交时需输入 Commit Message,并指定 Author 和 Committer,可以查看此次提交涉及哪些文件。如下图:

已提交的文件其图标又会发生变化,如下图:

再来看一下在一个仓库中管理多个项目。再创建两个项目,JaasDemo 和 SecurityDemo,点击菜单项 "Share Project",在弹出的对话框中,不再选择创建仓库,而是选择现有的仓库,如下图:

操作完成后,这三个项目都被同一个 Git 仓库所管理。再有更多的项目都可以添加到这个仓库中。我在 Github 中注册了一个账号,youxia 这个用户名已经被占用了,所以我只能用 youxia-cn,cn 代表中国,其实这个账号挺好记的。然后,我又创建了一个 samples 仓库,用来存放我博客中写的源代码。以后大家需要参看我的源代码的时候,只需要访问 https://github.com/youxia-cn/samples 就可以了。

最后,把我本地的仓库推送(Push)到 Github 中,如下三图:


Clone、Pull 和 Fetch##

从远程仓库中获取数据的方法有多种。如果是第一次获取远程仓库,可以使用git clone命令。在 Eclipse 中,需要使用 "File" -> "Import" 功能。下面换一台电脑,这台电脑安装的是 Ubuntu 系统,启动 Eclipse,点击菜单 "File" -> "Import",获取刚才存放在 Github 中的项目,在弹出的对话框中输入正确的 URL 后,一路 Next,如下动图:

可以使用 "Show in History" 和 "Show in Repositories View" 查看仓库的详细信息,如下两图:

从远程仓库获取数据还可以使用git pull命令,其对应的 GUI 操作界面如下:

可以看到,它会让你选择是将远程仓库中的分支 Merge 到本地的当前分支还是将本地的当前分支 Rebase 到远程仓库中的分支。使用 Pull 功能时,分支的 Merge 和 Rebase 是自动进行的,所以如果没有准备好的话,不要轻易使用。

安全的做法是使用git fetch命令从远程仓库获取数据。Fetch 过来的分支不会自动合并,你可以切换到这些分支进行查看,然后手工进行合并。其操作界面如下:

Checkout、Reset、Merge 和 Rebase##

前面介绍过,随着 Git 一次次的提交,会形成一个主分支,除此之外,还可以创建新的分支。通过git branch命令创建新分支,通过git checkout命令切换分支。在 Git 中,有一个HEAD指针总是指向当前正在工作的分支,如下图:

如果切换分支,HEAD指针也会随着移动,如下图:

同时,还可以使用git reset命令改变HEAD指针的指向,以达到撤销操作的目的。随意改变HEAD指针的指向是危险的,特别是使用git reset命令的--hard选项的话,它会使我们的部分工作丢失。在《Pro Git》的第二版中专门有一节“重置揭秘”对该命令及其涉及的原理进行了论述。在 GUI 中使用 Reset 也是非常简单的,如下图:

而分支的合并又有两种方式,Merge 和 Rebase。Merge 比较简单,如下图:

在图中有两个分支 origin 和 mywork,origin 做了提交 C3 和 C4,mywork 做了提交 C5 和 C6。如果将这两个分支合并的话,就会产生提交 C7,如下图:

但是如果使用 Rebase 就不一样了,如果把 mywork 分支 Rebase 到 origin 上,就相当于把 C5 和 C6 中针对 C2 所做的修改在 C4 上重演一次,产生了 C5' 和 C6' 两次提交,而原有的 C5 和 C6 会被丢弃,如下图:

所以,Reset 和 Rebase 都是比较危险的命令,有引起数据丢失的风险。但是在 Git 中,只要是曾经 Commit 过的数据,都是可以找回来的,可以参考《Pro Git》中“维护与数据恢复”这一节(第一版位于第 9 章,第二版位于第 10 章)。另外,《Pro Git》提到,对于已经公开发布到远程仓库中的代码,不要使用 Rebase,否则会引起版本库的混乱。

总结##

GUI 界面的使用没有什么困难的,难的是理解 Git 中的概念,比如 Push、Pull、Clone、Fetch、Checkout、Reset、Merge、Rebase 什么的。学习 Git 最好的办法,当然是认真阅读《Pro Git》这本宝典。除了使用 GUI,最好也能多敲一下命令行,这样对 Git 会有更加深入的了解。

版权申明##

该随笔由京山游侠在2018年11月15日发布于博客园,引用请注明出处,转载或出版请联系博主。QQ邮箱:1841079@qq.com