用git这个版本控制器也有一段时间了,但奈何一直停留在初浅的使用阶段,对诸如:分支,远程库,log,reflog,pull,clone等命令一直无法清晰的认识,仅仅是简单的add,commit,push,出了问题再查找。
始终无法串接成自有的知识体系,git 作为当下最流行的版本控制器,不得不说我对她真是既爱又恨,喜欢她的小巧高效功能强大;恨上手困难,虽然也有了一些简单易懂的教程,但在在实际使用时,始终不能形成自己的知识体系,原因可能还是项目中实际用的过少,这篇文章,会花一段时间,详细的介绍git的大概工作过程,对一些常用命令精简的介绍,然后从我个人的角度来认识git,总结自己的经验。*

这篇文章的过程意在记录自己使用git的学习经历,并不断的总结推荐一些良好的配置以及相关软件。

GIT介绍:

Git 是一款免费、开源的分布式版本控制系统,用于敏捷高效地处理任何或小或大的项目。Git 的读音为/gɪt/。Git 是一个开源的分布式版本控制系统,可以有效、高速的处理从很小到非常大的项目版本管理。Git 是 Linus Torvalds 为了帮助管理 Linux 内核开发而开发的一个开放源码的版本控制软件。

版本控制器介绍:

版本控制透过文档控制(documentation control)记录程序各个模组的改动,并为每次改动编上序号。这种方法是工程图(engineering drawings)维护(maintenance)的标准做法, 它伴随着工程图从图的诞生一直到图的定型。 一种简单的版本控制形式,例如,赋给图的初版一个版本等级“A”。当做了第一次改变后,版本等级改为“B”,以此类推等等。

GIT特点:

GIT 采用分支的结构形式来记录所有文本的修改,可以看出分支和指针是 git 的核心,git 因为指针的高速切换而高效。所有的分支名都是指针,且该指针无条件的指向这条分支的最末端,例如:master指针永远指向 master 这条分支的最后一个节点,而HEAD指针则指向的是当前位置,即用户目前所在的工作位置。在 git 的工作过程中最主要使用的是:add,commit,log,checkout 这几个命令,而诸如 reflog,reset 等其实用的并不算多,如果有远程库的话还需要注意 pull,push,remote。

常用GIT指令:

  • git add filename:将相关文件加入到暂存区stage中,注意有新文件的话,add步骤不能少。如果是一个人做的项目,一般如果整个项目加入可以使用git add . 或者git add * 。

  • git commit -m “commment”:git 的版本提交,执行完这步骤,你就完成了版本的创建提交了,也是最常用的步骤。建议:使用git commit -m 即可,去配置文件中修改 editor,这样可以调用如 vim 之类的编辑器,否则备注写起来麻烦,且字数有限制。

  • git branch:查看分支结构,git 的颇有c语言和数据结构的风格,整个 git 的版本控制,类似于一颗数,可以不断创建新的分支,然后分支名称类似于一个 tag 指针,始终指向分支的末尾处,而head这个指针指向的地方就是当前你所能看到的处在的版本位置,严格的来说并不是指向当前,而是上一个提交了的版本的位置,当前如果做了一些修改,而没去提交,则使用reset会回到上一个提交的版本,修改的内容会丢失

  • git checkout(该命令的理解贯穿git的始终,如果理解不精准,往往会遇到很多莫名其妙的错误。)用于控制head指针所指向的位置,也就是说控制当前用户的工作位置。该指令尤为重要,一般都是通过checkout找到过去的某个版本,然后在该位置创建分支结构,再进行相应修改。(注意是切换HEAD的位置,此时仅仅用于查看该版本,而如果你没有在该位置建立新的分支,则无法对改版本进行修改,git 的所有修改都是基于分支进行的,每条枝干的前后各个版本都是存在强关联性的,所有枝干的后一个节点都是基于前一个节点建立的,如果 checkout 到前一个节点对它进行了破坏,则后面也必然会受影响,这种情况一般是不允许发生的,因此 checkout 的每次修改都是要求该节点必须要重新开枝散叶,这样新的修改会在新的枝干上面进行,不会影响原有的内容。) checkout 的好处在于不会破坏原有的分支结构,可以任意在整个的目录树上面对 head 进行移动,方便用户查看各个版本。(再次注意:checkout只是用来控制 head 的,如果 head 通过 checkout 发生了移动,不再指向原有分支的末尾如:master,则必须建立新分支,才可以对文件进行提交修改。另外即便 checkout 指向原有分支的末尾,但如果是版本号的话也是没法进行修改的,必须要是指向 master 这个分支名才可以进行修改,类似于HEAD必须指向指针才可以,而不是指向某一个节点,这一点有指针操作经验的话,较容易理解。),checkout – filename:回退某一个文件,注意需要加–,这一步并不是更改 HEAD 指向,而仅仅是回退某一个错误修改了的文件,这也是撤销修改文件最常用的命令。

  • git reset “branchname/editionnum”:事实上这个指令很少用的,或者说谨慎使用,一旦使用了就会破坏原有的分支结构,reset 是一个回退功能,会将原有的分支剪掉,虽然仍然可以通过 reflog 查找出版本信息,强制指向某个回退之前的版本,但这并不是该有的操作,且很可能因为误操作造成更大麻烦。建议:只有在确定很清楚自己要进行的操作和上次提交错误的情况下使用该指令,其他时候都不使用。版本回退使用 reset 后面一般加上 –hard 彻底回退,否则有些修改了的未建立版本的文件仍然会保留。

  • git merge “branchname”:将两个分支进行合并,也是比较常用的操作,如果没有文件冲突,一般 git 会快速自动合并,如果有冲突,会需要你进行比较手动修改冲突,合并的过程是当前HEAD指向的分支名,会跳转到 branchname 的位置。

  • git branch -d dev:合并分支后,可以将被合并了的分支号删除。

  • git tag :这个也是个比较有用的方法,毕竟版本号是一大串随机数字,即便只要输入前 4 个,你也很难记得,对某个特别的版本加入 tag 标签名,之后可以直接使用 git checkout tagname 来指向该版本。

  • git diff : 将当前所有状态同指令后面对应的版本状态进行比较。

概念分析

基本概念

  • HEAD : 这是当前分支版本顶端的别名,也就是在当前分支你最近的一个提交,即 HEAD 的本质是一次已经进行过的提交(commit)。
  • Index : 也被称为 staging area,是指一整套即将被下一个提交的文件集合,他也是将成为HEAD的父亲的那个commit,index 实际是通过 add 等生成的即将被提交的区域。
  • Working Copy : 代表你正在工作的那个文件集,Working Copy 是当前你工作目录的状态。

具体执行区别:

  • 当你第一次 checkout 一个分支,HEAD 就指向当前分支的最近一个 commit。在 HEAD 中的文件集(实际上他们从技术上不是文件,他们是 blobs,但是为了讨论的方便我们就简化认为他们就是一些文件)和在 index 中的文件集是相同的,在 working copy 的文件集和 HEAD,index 中的文件集是完全相同的。所有三者(HEAD,INDEX(STAGING),WORKING COPY)都是相同的状态。
  • 当你对一个文件执行一次修改,你的 working copy 不再和index,HEAD 相同。
  • 当你执行一个git add,它就stages the file in the index,你的working copy和index区是相同的,但是他们和HEAD区是不同的。
  • 当你执行一个git commit,GIT就创建一个新的commit,随后HEAD就指向这个新的commit,而index,working copy的状态和HEAD就又完全匹配相同了。

reset

如果你仔细研究 reset 命令本身就知道,它本身做的事情就是重置 HEAD (当前分支的版本顶端)到另外一个 commit。假设我们有一个分支(名称本身无所谓,所以我们就简单称为”super-duper-feature”分支吧),图形化表示如下:

如果我们执行:git reset HEAD 任何事情都不会发生,这是因为我们告诉GIT重置这个分支到HEAD,而这个正是它现在所在的位置。git reset HEAD~1 当我们再执行上面的命令时(HEAD~1是 “the commit right before HEAD” 的别名,或者说:put differently “HEAD’s parent”),我们的分支将会如下所示

如果我们执行git reset HEAD~2,则意味着将HEAD从顶端的commit往下移动两个更早的commit。

  • soft : –soft 参数告诉 Git 重置 HEAD 到另外一个 commit,但也到此为止。如果你指定 –soft 参数,Git 将停止在那里而什么也不会根本变化。这意味着 index,working copy 都不会做任何变化,所有的在 original HEAD 和你重置到的那个 commit 之间的所有变更集都放在 stage(index) 区域中。

  • hard : –hard 参数将会 blow out everything.它将重置HEAD返回到另外一个 commit (取决于~12的参数),重置 index 以便反映 HEAD 的变化,并且重置 working copy 也使得其完全匹配起来。这是一个比较危险的动作,具有破坏性,数据因此可能会丢失!如果真是发生了数据丢失又希望找回来,那么只有使用:git reflog 命令了。makes everything match the commit you have reset to.你的所有本地修改将丢失。如果我们希望彻底丢掉本地修改但是又不希望更改branch所指向的commit,则执行 git reset --hard = git reset --hard HEAD; i.e. don’t change the branch but get rid of all local changes. 另外一个场景是简单地移动 branch 从一个到另一个 commit 而保持 index/work 区域同步。这将确实令你丢失你的工作,因为它将修改你的 work tree!

  • mixed(default):–mixed 是 reset 的默认参数,也就是当你不指定任何参数时的参数。它将重置HEAD 到另外一个 commit,并且重置 index 以便和 HEAD 相匹配,但是也到此为止。working copy 不会被更改。所有该 branch 上从 original HEAD(commit)到你重置到的那个 commit 之间的所有变更将作为 local modifications 保存在 working area 中,(被标示为 local modification or untracked via git status),但是并未 staged 的状态,你可以重新检视然后再做修改和 commit。

简述分支策略:

在实际开发中,我们应该按照几个基本原则进行分支管理:

  • master 分支应该是非常稳定的,也就是仅用来发布新版本,平时不能在上面干活;
  • 干活都在 dev 分支上,也就是说,dev分支是不稳定的,到某个时候,比如 1.0 版本发布时,再把 dev 分支合并到 master 上,在 master 分支发布 1.0 版本;
  • 每个人都在 dev 分支上干活,每个人都有自己的分支,时不时地往 dev 分支上合并就可以了。

团队合作示意图:

参考链接:
http://www.liaoxuefeng.com/wiki/0013739516305929606dd18361248578c67b8067c8c017b000
http://baike.baidu.com/link?url=weNC33XuaXOCTBp5s3WeZVu-Z9LP41KY_M6_WdGxAJZpJs_sq7jcgZ-3B01u2EiqfK6gb0L6s3-IDiWp1cVgF_
http://www.cnblogs.com/kidsitcn/p/4513297.html
http://maiyang.github.io/git/2016/04/21/git-reset-checkout-revert
https://github.com/geeeeeeeeek/git-recipes/wiki/5.2-%E4%BB%A3%E7%A0%81%E5%9B%9E%E6%BB%9A%EF%BC%9AReset%E3%80%81Checkout%E3%80%81Revert%E7%9A%84%E9%80%89%E6%8B%A9