-- 故国神游,多情应笑我,早生华发。html
Git是一个版本控制工具,代码管理工具,团队协做工具。它跟SVN等传统工具实现一样的目的;但从某种程度来讲,它更快,更灵活。我想绝大多数读者都已经在接触这个工具了,而且用于平常的项目中去了。个人这篇文章,不是做为一个Git入门教程,也不是做为一本大块头的教科书。(说到教科书,我推荐下面的这本。这本书确实好,很全面。个人这篇文章,其实就是这本书的读书笔记而已。)java
Pro Git -- http://git.oschina.net/progit/git
接着说。个人这篇文章,主旨在于启发你们超越平时的使用局限,从另外一种视角去看待Git和使用Git,让人有一种“啊哈,Git也能这么用“的感受。这有点王婆卖瓜的味道了,这我必须认可。程序员
不少项目组,对于刚入职的程序员,会像下面这样对他们普及Git的知识:redis
#使用git clone克隆项目的代码 git clone https://your-project-address #使用git add添加修改的文件 git add your-filename #或者使用git add --all添加全部修改的文件 git add --all #使用git commit提交代码到本地的代码库 git commit -m some-description-message #使用git push提交代码到远程的代码库 git push
#使用git pull拉下远程代码库中更新的代码
git pull
仅使用这五个命令,确实就足够进行团队协做了。这是使用Git的一种模式,并且无可厚非。不过,这样作有种仅仅把Git当成了纯粹的代码共享库的感受,多多少少显得有点不大规范。其实,Git有更加丰富的能力,它能让你的项目开发有更加规范的流程,整个项目历史有更加清晰的追溯,分工协做上也更加井井有理。并发
Git的特点有哪些呢?app
Git的最大特点可能就是做为一款分布式代码管理工具了。Git的绝大部分操做都是在本地进行的,这使得Git更快,并且更灵活。更灵活是由于在你的改动被提交到远程代码库以前,你能够在本地进行任意的操做。另外一方面,这也意味着改动提交到远程被共享以后,就不方便再改动旧的提交历史了。因此,请慎重上传改动到远程。编辑器
代码的提交历史是一个项目开发历程的记录。这其中不只仅是情怀的记录。更重要的是,提交历史给了咱们项目回溯的能力,从而让咱们可以找出问题代码的来源,并修正它。Git的提交历史很强大,咱们能够去翻看历史,还能附加上丰富的筛选条件;甚至能够修改有问题的历史。为了更好地利用Git的历史功能,咱们要时刻保持提交历史的细致与干净。这意味着,咱们要恰到好处地提交咱们的代码,以及给它一个恰到好处的描述。分布式
Git的另外一特点就是分支了。简单地说,分支就是不一样的代码故事。它们相互区别又互有联系,渐行渐远又能在某一处汇合。使用分支,咱们可以控制代码的不一样走向,从而很好地安排咱们的开发工做。灵活使用分支能够简化咱们的不少工做。ide
说Git简单,听起来有点怪怪的。若是你要了解Git 内部原理,那确实够复杂的。不过,Git的特点在于,即便你不了解这些原理,你仍然能够灵活运用它。只用去学Git的一些基本的概念就足够了,例如仓库,暂存,提交,历史,分支等。说实话,我到如今是说不出这些概念的具体定义的,并且也只是浅要地了解了下这些概念的原理。可是,这并不影响我去使用它。Git的特点在于,学些基本的概念和原理;剩下的,去用就够了。这让我想起了不少的Unix工具,如Vim,LaTex等,以及Unix哲学。
所谓Git哲学,是指使用Git的思惟方式。下面是我以为的Git哲学中最要紧的几点:
若是说提交历史是一个版本控制工具最核心的模块,我以为一点也不为过。代码的提交历史要涵盖到具体的每一次改动,而且要有清晰一致的描述。这要求咱们:
每次的提交得是一个具体的总体。所谓具体的,是指你的提交不能是一次笼统的或归纳的改动;所谓总体,是指你的提交是一个已经完成的改动,而不能是悬而未决的。其实,这两点能够从你的提交说明中检验出来:
其实作到这一点也很容易 --
尽量得多提交,尽量得早提交,当完成一个小的功能点时就提交。
描述应该简洁明了。简洁是指可以用一句话描述你作的事情,而且不要覆盖其余没必要要的信息;明了是指绝对不能含糊,不只你能看得懂,也要让局外人也能看得懂。我列了几个要点:
因为每一个提交都是一个细致的改动单元,当项目进行到必定阶段,整个提交历史必定会显得很长。这时候你可能会以为提交不少很杂,会淹没一些重要的版本发布提交,例如上线的版本v1.0,v1.1。我想这是没必要要的担忧,认真地对待提交历史,会让整个提交历史显得多而不杂。并且,彻底能够为重要的提交打标签。请记住 git tag 命令,它能够为重要的提交打标签。
Git的提交历史是至关灵活的。你能够查看历史;更厉害的,你还能够去修改它。因此,你能够在任意时刻去修正你的历史。这里有个例外,就是你没法修改远程仓库的提交历史。一方面,有的远程代码仓库不支持提交历史的修改;另外一方面,即便支持修改历史,这样的修改也会对其余开发者形成混乱。关于远程提交的准则是:
-- 一个分支就是一个故事,一个剧情。使用分支是为了避免让第三者破坏完美的剧情。
要有意识地去使用多分支,而不是去忽略它。能够在下面的状况下考虑使用分支:
这两个使用分支的策略都是本地临时分支使用策略。不管是添加特性分支仍是BUG修复分支,它们都有存在周期短的特色,而且都只是为解决某个特定问题而存在的。当这个问题被解决之后,它们就要被并入到主分支,这样就完成了本身的历史使命。使用分支的一种状况是:当你要同时进行多个任务的时候。
不管是主分支,仍是上面的特性添加分支或者BUG修复分支,都是你本身一我的的任务。你要同时完成两个或者三个任务。这时候才是分支发挥它最大做用的时候。不要在一个分支里同时作这几件事情,这样会违反第一哲学。记住:
每次只作一个任务;若是不得已要临时切换到其余任务,请使用分支。
Git既存在本地分支也存在远程分支;擅用分支,实际指的是灵活运用本地分支,而不是滥用远程分支。你能够随意地建立本地分支,频繁地在多个分支的下来回工做。但不要把这一切牵扯进远程分支。通常来讲,远程分支是长期分支,是做为项目不一样的发展阶段或者发展路线而存在的。例如稳定版、开发版、激进版等。这通常是项目决策者操心的事情。
再啰嗦一句:若是分支再也不须要了,就删除它。
私有化意味着封装。相似于面向对象上的封装,团队协做上也有封装的概念。你能够在本地灵活地使用Git的各类操做,而不会对他人形成任何影响。不过这种灵活性应局限于本地,而不要将冗余公开给远程的共享库。这方面有些准则,其中有些是对上面两个哲学的总结,以下:
体现Git哲学的最好方式就是实践它,用Git的方式去工做,学会使用Git思惟。接下来我会涉及到具体的Git操做。
【这一部分纯当是复习。】
初始化一个新仓库:
git init
从现有仓库克隆:
git clone <your-project-address>
时刻检查目录状态:
git status
.gitignore 文件能够声明脱离版本控制的文件:
#haha
暂存修改:
git add <new-file-or-modified-file> git add --all
若是不当心 git add 了某个文件,使用 git rm 从暂存区移除(附带--cached参数移除跟踪但不删除文件,以便稍后在 .gitignore
文件中补上):
git rm --cached <your-added-file>
想知道代码作了哪些改动(会精确到行),使用:
git diff <some-file> git diff #这会显示全部文件的改动
上面的命令是显示工做目录中当前文件和暂存区域快照之间的差别;若是想知道暂存区域与最近一次提交之间的差别,附加 --cached 参数:
git diff --cached <some-file> git diff --cached
提交更新:
git commit #这会为你打开一个文本编辑器
git commit -m <your-commit-message>
为了更好地说明用法,我使用了JFinal项目的Git源码做为例子。
使用 git log 能够查看提交历史。你应该看到相似于下面的结果:
$ git log
commit 121e247032be9e4d6a3c7eb8035914f59857c43d Author: James <jfinal@126.com> Date: Sun Jul 26 13:40:44 2015 +0800 修改变量名,actoin 改成 action commit d330532f9db493034578d5dac1ece43c65136569 Author: James <jfinal@126.com> Date: Sun Jul 26 11:00:55 2015 +0800 变量名修改
能够看到SHA-1 校验和,做者的信息,时间以及提交说明。Git使用校验和来指代每一次提交,例如输出第一行的 121e247032be9e4d6a3c7eb8035914f59857c43d 。另外,若是不产生歧义的话,能够用校验和的前几个字符来指代提交,例如 121e247032be9e4d6a3c 和 121e247032 。
若是想看某一次具体的提交,指明它的校验和便可:
$ git log d330532f9 commit d330532f9db493034578d5dac1ece43c65136569 Author: James <jfinal@126.com> Date: Sun Jul 26 11:00:55 2015 +0800 变量名修改 commit cdec1c3ceaceecfa4297c72efd8d8095640757f8 Author: James <jfinal@126.com> Date: Thu Jul 16 10:10:49 2015 +0800 修改 Cache.expireAt(...) 方法上的注释
这会显示从指定提交开始往前回溯的提交历史。若是只是想显示指定的这一个提交,用 -1 参数( -2 则显示两次提交,依次类推):
$ git log d330532f9 -1 commit d330532f9db493034578d5dac1ece43c65136569 Author: James <jfinal@126.com> Date: Sun Jul 26 11:00:55 2015 +0800 变量名修改
使用 git log --pretty=oneline 能够显示更紧凑的提交信息,每次提交用一行来显示:
$ git log --pretty=oneline 121e247032be9e4d6a3c7eb8035914f59857c43d 修改变量名,actoin 改成 action d330532f9db493034578d5dac1ece43c65136569 变量名修改 cdec1c3ceaceecfa4297c72efd8d8095640757f8 修改 Cache.expireAt(...) 方法上的注释 749fbe0d8e9da0e33fe2b30085f7467e86881e17 JFinal 2.0 release ^_^ 52633ce2da704cf35a48aa9b8a1afe99d6c2ed1d JFinal 2.0 release ^_^ 1e00c07348bd7b1ed16bb5c224e0a0de67b9b13b JFinal 2.0 release ^_^ 0330d7ed5f3d742eaca201456927d9cecb40e215 JFinal 2.0 release ^_^ aa4a95af60a1dc12dfd649bd208de473dcfb369f jfinal 1.9 release ^_^ af6469eb6f49c23cba8215f2b0e9c8d51cd5f8c9 jfinal 1.9 release ^_^ 4ab71ce41cca8b7d16bef89655b51e6de6548d30 jfinal 1.9 release ^_^ 03a3c5a2bdb848ad2f9a30e29eba4f468176497f jfinal 1.9 release ^_^
使用 -p 选项展开每次提交所作的修改。例如咱们使用下面的命令展开哈希前缀为 d330532f9 的那次提交的修改:
$ git log d330532f9 -p -1 commit d330532f9db493034578d5dac1ece43c65136569 Author: James <jfinal@126.com> Date: Sun Jul 26 11:00:55 2015 +0800 变量名修改 diff --git a/.gitignore b/.gitignore index 416db66..b1e58ba 100644 --- a/.gitignore +++ b/.gitignore @@ -14,6 +14,7 @@ integration-repo /build/ # IDEA metadata and output dirs +/.idea/^M *.iml *.ipr *.iws diff --git a/src/com/jfinal/config/Routes.java b/src/com/jfinal/config/Routes.ja index fab2604..8ad1b03 100644 --- a/src/com/jfinal/config/Routes.java +++ b/src/com/jfinal/config/Routes.java @@ -86,11 +86,11 @@ public abstract class Routes {
使用 --stat 选项仅显示代码修改的统计信息(仅显示简要的增删行数统计,特别适合做代码审查):
$ git log --stat commit 121e247032be9e4d6a3c7eb8035914f59857c43d Author: James <jfinal@126.com> Date: Sun Jul 26 13:40:44 2015 +0800 修改变量名,actoin 改成 action src/com/jfinal/core/ActionMapping.java | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) commit d330532f9db493034578d5dac1ece43c65136569 Author: James <jfinal@126.com> Date: Sun Jul 26 11:00:55 2015 +0800 变量名修改 .gitignore | 1 + src/com/jfinal/config/Routes.java | 6 +++--- src/com/jfinal/plugin/activerecord/Sqls.java | 4 ++-- src/com/jfinal/plugin/activerecord/tx/TxByMethods.java | 14 +++++++------- src/com/jfinal/plugin/redis/RedisInterceptor.java | 11 ++++++++++- 5 files changed, 23 insertions(+), 13 deletions(-)
我从《Pro Git》中摘出了一些选项及说明:
选项 说明 -p 按补丁格式显示每一个更新之间的差别。 --stat 显示每次更新的文件修改统计信息。 --shortstat 只显示 --stat 中最后的行数修改添加移除统计。 --name-only 仅在提交信息后显示已修改的文件清单。 --name-status 显示新增、修改、删除的文件清单。 --abbrev-commit 仅显示 SHA-1 的前几个字符,而非全部的 40 个字符。 --relative-date 使用较短的相对时间显示(好比,“2 weeks ago”)。 --graph 显示 ASCII 图形表示的分支合并历史。 --pretty 使用其余格式显示历史提交信息。可用的选项包括 oneline,short,full,fuller 和 format(后跟指定格式)。
Git提交历史的一大特点在于能够按照条件筛选历史。例如筛选出最近两周到最近一周的提交历史:
$ git log --since=2.weeks --until=1.weeks commit cdec1c3ceaceecfa4297c72efd8d8095640757f8 Author: James <jfinal@126.com> Date: Thu Jul 16 10:10:49 2015 +0800 修改 Cache.expireAt(...) 方法上的注释
我也列出了一些可以设置筛选条件的一些选项,一样来源于《Pro Git》:
选项 说明 -(n) 仅显示最近的 n 条提交 --since, --after 仅显示指定时间以后的提交。 --until, --before 仅显示指定时间以前的提交。 --author 仅显示指定做者相关的提交。 --committer 仅显示指定提交者相关的提交。 -- 命令的最后一个选项,后跟路径名表示只关心某一路径下的提交历史
Git提交历史的另外一大特点在于你能够修改它。不过格外注意的是,不要修改那些已经推送到远程版本库的提交历史。
首先介绍如何撤销。这多少与修改历史无关,由于此时的历史可能还未造成。
若是只是但愿将某个文件的改动从暂存区拿出来,而保持文件原有内容不变,使用命令:
git reset HEAD <your-file>
其中HEAD指代最近一次提交(*)。你可使用HEAD变量来避免使用哈希值。
下述命令会令整个项目的状态回退(保留修改):
git reset HEAD
若是想将文件恢复到最近一次提交的版本,使用命令 git checkout 。这会使得其从暂存区拿出来(若是已经存入暂存区),而且文件内容恢复到最近一次提交的状态:
git checkout <your-file>
若是想直接撤销全部改动(包括修改),使用命令:
git reset --hard HEAD #恢复全部改动到最近一次提交
git reset --hard HEAD~1 #恢复全部改动到最近一次提交的上一次提交
git reset --hard HEAD~2 #恢复全部改动到最近一次提交的前两次提交
git reset --hard d330532 #恢复到指定提交
带有 --hard 选项的版本回退是一个不可恢复的操做,请谨慎用之。
当咱们完成提交后,发现漏掉了某些改动(例如忘记添加相应的API注释)。这时咱们固然能够将改动做为一次新的提交提交一发。不过,还有其余的方案,即将漏掉的改动从新补回到对应的历史提交中,毕竟漏掉的改动确实是那次提交的一部分。Git给了咱们修改提交的能力。
若是只是要修改最近的一次提交,使用 git add 命令暂存相应的改动,而后输入命令 git commit --amend 。这会把你代入修改提交说明的编辑器中。保存后退出,咱们就完成了咱们的提交修改。
若是只是想修改最近一次提交的提交说明,直接输入 git commit --amend ,而后进入编辑器修改提交说明并保存退出便可。
要修改历史中更早的提交,就要用到 git rebase -i 交互式的提交历史修改工具。我在命令行中输入:
git rebase -i HEAD~4
它会带我进入一个文本编辑器中(在个人系统里意外地是VIM)。关注前四行,会看到咱们能够修改最近的四次提交历史( HEAD~4 指定)。其中提交由早到晚地排列,与 git log 显示顺序相反。
1 pick 749fbe0 JFinal 2.0 release ^_^ 2 pick cdec1c3 修改 Cache.expireAt(...) 方法上的注释 3 pick d330532 变量名修改 4 pick 121e247 修改变量名,actoin 改成 action
这时咱们能够修改某次提交,例如修改第2行的提交,只需将 pick 换成 edit 便可。而后保存并退出编辑器。
pick 749fbe0 JFinal 2.0 release ^_^ edit cdec1c3 修改 Cache.expireAt(...) 方法上的注释 pick d330532 变量名修改 pick 121e247 修改变量名,actoin 改成 action
像以前同样,使用 git commit --amend 修改提交;最后使用 git rebase --continue 完成修改任务。
咱们也能够删除某个提交,只需将相应的提交行删掉便可。例如删除第2行的提交:
pick 749fbe0 JFinal 2.0 release ^_^ pick d330532 变量名修改 pick 121e247 修改变量名,actoin 改成 action
从新排列提交也是OK的。只须要从新排列便可。例以下面的编辑把最近一次提交移到最前,最先的一次提交移到最近:
pick 121e247 修改变量名,actoin 改成 action pick cdec1c3 修改 Cache.expireAt(...) 方法上的注释 pick d330532 变量名修改 pick 749fbe0 JFinal 2.0 release ^_^
一个有用的功能是压制提交,即把多个提交合并成一次提交。例以下面的编辑将多个描述都为”JFinal 2.0 release ^_^“的提交合并为一个提交:
pick 0330d7e JFinal 2.0 release ^_^ squash 1e00c07 JFinal 2.0 release ^_^ squash 52633ce JFinal 2.0 release ^_^ squash 749fbe0 JFinal 2.0 release ^_^ pick cdec1c3 修改 Cache.expireAt(...) 方法上的注释
相应地,能够拆分一个提交。它的技巧在于 edit 某次提交,而后调用 git reset HEAD^ 回到父提交,而后再屡次 git commit 便可。
git reset HEAD^ git add file1 git commit -m "add file1" git add file2 git commit -m "add file2" git rebase --continue
当准备进入交互式rebase工具时,若是当前的工做区存在修改而没有被提交,则会被禁止进入。此时咱们能够先将修改提交;有时又不但愿这么作,可能咱们的代码改动还不能构成一个完整的提交。咱们只但愿先保存本身的工做,而后在须要的时候释放出来。这时就用到 git stash 命令了。
咱们能够将改动保存在一个栈中,称之为储藏(Stashing):
git stash
而后查看栈状态:
$ git stash list stash@{0}: WIP on master: 121e247 修改变量名,actoin 改成 action
由于是栈,能够屡次调用 git stash ,而后查看栈状态:
$ git stash list stash@{0}: WIP on master: 121e247 修改变量名,actoin 改成 action stash@{1}: WIP on master: 121e247 修改变量名,actoin 改成 action stash@{2}: WIP on master: 121e247 修改变量名,actoin 改成 action
而后在适当的时候,释放一个储藏。既能够释放指定的储藏,例如 stash@{0} 、 stash@{1} 、 stash@{2},也能够默认释放最近的储藏(stash@{0}):
git stash apply git stash apply stash@{2}
当储藏再也不须要时,删除它:
git stash drop stash@{2}
或者当即释放最近的储藏并删除它:
git stash pop
储藏的典型应用场景是当工做区有未提交的修改时,要切回到旧提交去修改( git rebase -i )或者切换到其余分支( git checkout )。
Git的分支是从某个分支引出的一个分叉。在Git中建立分支时,必定是以某个分支为基准,这个分支就是你当前工做的分支。
要查看当前在哪一个分支下工做,输入命令:
git branch
此时会列出全部本地分支,行首标有*号的是当前工做分支。
建立分支:
git branch <new-branch-name>
切换到新分支:
git checkout <new-branch-name>
同时建立及切换分支:
git checkout -b <new-branch-name>
合并分支:
git checkout master
git merge <new-branch-name>
这能保证将新分支的改动合并到主分支。
删除分支:
git branch -d <new-branch-name>
只有已被合并的分支才能顺利删除,不然会提示错误。
若是要强制删除分支,使用命令:
git branch -D <new-branch-name>
以上只是分支的基本操做命令。要想活用这些命令,就要知道分支的使用思惟。分支的使用思惟,我以为就是一句话,重复以前的一句话:
当你要放下手中的任务,临时切换到其余任务时,使用分支。
我这里想举一个关于分支使用的简单的不能再简单的例子。
首先你在进行master上进行主线开发,实现功能点一。忽然你临时接到任务,完成功能点二,并立刻上线。此时你不得不放下手中的工做,投入到实现功能点二中去。
这时,你首先要作的是保存你正在进行的工做以便未来能够恢复。可使用储藏 git stash 或者临时提交你的代码以在未来经过 git commit --amend 修改你的历史。这里假设你使用的是储藏。
而后新建分支并切换到新分支工做:
git checkout -b feature2
当你完成功能点二的开发时,提交你的代码:
git add --all git commit -m "finish feature 2"
以后切换到主分支,合并feature2分支:
git checkout master
git merge feature2
改动能够提交到远程上线:
git push
而后你可使用 git stash pop 恢复你的工做。
队伍中的长期分支控制了不一样的开发进度。通常来讲,应该有一个稳定分支和开发分支:你能够将master做为稳定分支,并配有一个develop分支;或者反过来,将master做为开发分支,并配有一个stable分支。这里假设master是稳定分支,而develop是开发分支。develop分支通常比master要超前,而且当测试稳定后才会并入到master分支发布。因此通常的工做流程就是:
在develop分支开发,测试稳定后并入到master分支发布
另外,若是已发布的版本遭遇到一个紧急BUG亟待修复,这时你应该保存develop分支的工做,而后切换到master分支去修复。由于要紧急发布,你应当切换到稳定的master分支完成BUG修复,而不是基于不稳定develop分支。当修复完毕并发布后,再回到develop分支恢复工做。
利用分支,咱们能够很好地分离了咱们的不一样工做,不让它们相互干扰,从而减小bug的来源;利用历史,咱们能够追踪代码的变化,为咱们找出问题代码提供了途径。其实Git也为咱们提供了其余的好用的工具,利于咱们调试。
git blame
:谁动了个人代码你是否会惊奇你的代码为何忽然变成这副模样?不要紧,使用 git blame 命令能够显示你的代码是谁最后修改的。命令格式:
git blame -L 12,22 simple.rb
能够显示文件simple.rb的第12至22行这块代码最后是谁、何时修改并提交的。
还有一个有趣的命令是 git bisect 。它能够以二分查找的策略逐步逼近你在乎的坏代码的来源。
首先输入 git bisect start 来启动二分查找。
输入 git bisect bad 来告知当前的提交已是问题提交了。
而后输入 git bisect good <good-commit> 来告知你知道的最晚的正常提交。
接着就能够肯定坏代码的来源在<good-commit>和当前提交之间,二分查找策略就能够开启了,这时Git监测处于中间的一个提交。假设从<good-commit>到当前提交一共有12个提交,记编号为0、一、…、11. 这时咱们检出的提交应该是6.
你能够输入 git bisect good 来告知这个提交是正常的,这样它会舍弃编号为0、一、…、6的提交;也能够输入 git bisect bad 来告知问题依然存在,这样它会舍弃编号为七、八、…、11的提交。而后继续二分策略…一直到咱们只剩下一个问题提交的时候。这个提交就是咱们的问题提交的最初来源。
当你完成的时候,应该运行 git bisect reset 从新回到最初的地方。
Git是伟大的,但咱们依然要超越Git;毕竟Git只是一个工具,而咱们是使用工具的人。Git的功能是代码管理和版本控制,但Git的本质在于其重视团队,重视协做的开发精神。它重视协做,但又不束缚我的的创造。它使得每一个人均可以自由地编码,同时又保持整个项目开发的协调一致。