作者:zccst
SVN用得很熟了,但是git一直用得不习惯,看来有必要学习一下原理。
Git做为一个资源管理和跟踪系统,如果想要把自己的文件托管在Git上,那么首先你得让Git知道你需要管理的文件在哪。比如说现在我有一个项目,它在test文件夹里,我想让Git管理这个项目,这个时候你需进入到这个目录,然后运行“git init”命令。这个时候Git就会在该目录下生成一个.git的隐藏目录,Git用来进行版本控制和内容跟踪的所有文件都在该文件夹下。
处于git跟踪下的文件只具有三种状态:
1.Modified(working directory):被修改过的文件
2.Staged(staging area):通过git add添加到暂存区域的文件
3.Committed(git directory):通过git commit提交到仓库的文件
所以,一般的git工作流程可能是这样:修改过某些文件,然后把这些文件添加都暂缓区,再提交到仓库中形成一个版本或快照,最后提交到git服务器上。而在中间,可能伴随着分支管理,分支切换,撤消与合并。
可能有些人会觉得很奇怪,为什么git会有暂存区域这个概念,直接提交到仓库中不就ok了。其实这是git为了做版本控制用的,试想如果没有暂存区域,每修改一个文件,就会形成一个版本,太过频繁,不易于管理。暂存区域其实就是下一个版本的文件清单,你可以自由控制该往仓库中提交什么文件,这也可以避免在一个版本中包含一些中间文件,比如编译后的文件。
[img]http://dl2.iteye.com/upload/attachment/0106/5951/57e24f0d-419c-3ed5-b6f8-c8178b0be289.png[/img]
[img]http://dl2.iteye.com/upload/attachment/0106/5953/a24045c6-d7ff-3447-9295-91843c24a15b.png[/img]
1.Git是分布式的版本控制系统,它没有中心服务器的概念(虽然实际开发中可以建一个中心服务器),每一台开发机器上都保存完整的历史记录;但是它有本地代码仓库和远程代码仓库的概念(不然怎么多人协作?),而且可以追踪多个远程仓库;
2.Git能够非常快地建立分支和合并分支,并具有强大的跟踪分支和切换分支的能力。
Git的关键词:working directory; repository; stage; commit; remote; branch; merge
每一个项目都应该有一个工作目录(Working directory),我们可以自己建一个目录,然后把这个目录里面的代码用Git管理起来(使用git init命令和git add命令),也可以通过git clone命令从别的地方克隆一个项目过来自动生成一个工作目录。在工作目录中的文件就是当前编辑和修改的文件,如果是新建立的目录或新clone来的目录,工作目录中的文件就是该项目最新的状态。Git是在本地保存有所有的历史记录和分支记录的,这些内容都在工作目录的.git目录中,称之为本地仓库(local repository)。当切换分支或查看以前的历史版本时,工作目录中的文件自动改变(这才是重点,工作无需切换目录,目录中的文件会自动切换)。工作目录中的文件有三种状态:已修改、已暂存(stage)、已提交。修改后的文件可以先加入暂存区域,一次工作结束后一起提交。
Git是分布式的,没有中心服务器的概念,但实际工作中仍然可以把代码仓库放到一台大家都可以访问的服务器上,做实际的中心服务器使用(仅在小团队时使用此工作流程,原因后面详述)。在本地机器上工作完后,使用git push命令把仓库推送到服务器上,换一个地方换一台机器后,只需要git clone一下,又可以获得所有的代码(包含所有的历史记录及分支)继续工作。服务器故障也没问题,因为每一个工作的机器上都保存有完整的代码仓库,所以从不用担心代码丢失。没有网络也没有关系,在本地机器上照样可以提交(git commit),因为整个仓库就在自己的机器上,当有网络时,push一下就可以了。
Git有远程仓库(remote repository)的概念,而且可以管理很多个远程仓库,远程仓库可以是服务器,也可以是别人的个人计算机(但一般没有人这么用),每一个远程仓库都有一个简短的名字和一个地址,最开始clone代码的那个远程仓库别名往往默认为origin,自己添加的远程仓库可以随意指定别名,当然所有的远程仓库都可以随意修改别名。可以从远程仓库获取代码(git fetch 命令或 git pull命令),也可以把自己的代码推送到远程仓库(git push命令,需要写权限)。
既然Git即可以随便从远程仓库获取代码,又可以把自己的代码推送到远程仓库,那么当多人协作时,岂不会乱套吗?解决这个问题的,就是Git的必杀之技——创建分支及分支合并。下面要用图表来说明问题了。下面一系列图片来自Git官方网站上的电子书《Pro Git》。
首先,随着一次次的提交,在本地代码库中形成一个主分支,如下图:
[img]http://dl2.iteye.com/upload/attachment/0106/5956/97928e0f-7d07-3b3b-af7d-a2aab9a2abc9.png[/img]
有时为了开发新特性,随时可以开一个新分支,如下图:
[img]http://dl2.iteye.com/upload/attachment/0106/5958/5e7fecc2-96f2-3ae9-aa63-b52462f1e75e.png[/img]
新分支和主分支之间可以随意切换,随着分支的发展,形式如下图:
[img]http://dl2.iteye.com/upload/attachment/0106/5960/c98fa04d-aec4-306a-82a1-55dee587a178.png[/img]
主分支也可以向前发展,如下:
[img]http://dl2.iteye.com/upload/attachment/0106/5962/9a966bb0-e954-3d6b-8200-73ccfc94c201.png[/img]
最终,当新分支代码很稳定以后,可以将其合并到主分支,如下图:
[img]http://dl2.iteye.com/upload/attachment/0106/5964/e52691d2-01dc-3ba2-8022-f52111bf71b9.png[/img]
而能够防止多人协作时出现混乱的关键就在于,当从远程仓库clone代码库到本地或fetch代码库到本地时,远程分支的标记并不等于本地分支的标记。从远程clone一个代码库到本地后,其master分支有两个标记,一个标记为origin/master表示远程库中的master分支,一个标记为master,表示本地的master分支。如下图:
[img]http://dl2.iteye.com/upload/attachment/0106/5968/71c16837-dfc1-3576-8a04-17c6be19378e.png[/img]
可以想象,由于别人的工作,远程仓库中的master分支肯定会向前继续移动,但是在下次联网之前,该origin/master标记不会移动。而本地的master标记继续向前移动。
[img]http://dl2.iteye.com/upload/attachment/0106/5970/ae33c4fa-dabc-39b2-9cc0-abc273e4cd04.png[/img]
直到下次联网,使用git fetch命令将远程仓库的内容取回本地,origin/master标记才会改变位置,这时,看起来就像是两个分支,如下图:
[img]http://dl2.iteye.com/upload/attachment/0106/5972/b4dde936-de51-36c2-bc32-254b70f118af.png[/img]
最后,将origin/master分支合并到master分支中(使用get 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支持以SSH、HTTP等协议传输数据,如果需要对服务器有写权限,就开通SSH服务吧,设一个账户供所有人访问Git仓库即可。如果只需要只读权限,使用任何一个HTTP服务器均可。关于Git服务器的建设,请自行参考官方文档。如果是个人的、开源的项目,可以使用Github网站提供的服务,直接存储在互联网上。(Github私人仓库是要收钱的。)
看来要把Git讲清楚并不容易,用了这么多篇幅。下面把Git常用的命令回顾一下:
git config 配置Git,一般使用不需要特别配置,但至少要设置开发者的名字和邮箱
git init和git add 创建一个新仓库,并跟踪工作目录下的文件
git clone 从远程克隆一个项目,包括工作目录和仓库
git add 将修改后的文件放入缓存区域(staging area),或这表示冲突已经解决
git status 显示文件状态,是已修改还是已缓存还是已提交
git commit 提交项目
git remote 管理远程仓库
git fetch和git pull 从远程仓库抓取数据
git push 向远程仓库推送数据
git branch和git merge 创建分支及合并分支
git checkout 切换分支
[size=large][b]第一次使用[/b][/size]
在第一次使用Git时,你需要告诉你的协同开发者,你是谁以及你的邮箱,在你提交的时候,Git需要这两个信息。具体通过以下命令设置:
git config --global user.name “Test OSS”
git config --global user.email
[email protected]
当你把Git设置好之后,如果你要和从Git服务器上获得仓库,或者向Git服务器提交你的代码(比如github),你可能需要生成你自己的ssh密钥对。Git支持4种与服务器端通信的协议:git、http、ssh和https。其中git只是一个只读协议,也就是说你只可以从服务器端获取仓库,但是你不能提交你自己的代码。而http和https用的很少,大部分都只支持ssh协议和Git协议。
当你通过ssh协议与远端服务器进行通信的时候,你可以通过以下命令生成ssh密钥对:
ssh-keygen -t rsa
Windows下
#ssh-keygen.exe
#cat ~/.ssh/id_rsa.pub
如果你没有指定密钥名称和存放路径的话,它默认把两个不对称密钥放在你的家目录下的.ssh目录下,密钥文件默认名称为id_rsa和id_rsa.pub,前者是私钥,后者是公钥。中间可能会要你设定访问密钥密码,这个可以设,可以不设,但为了安全考虑,还是建议你设一个访问密码。否则,意味着任何持有你密钥的人都可以使用该密钥。
然后把你的公钥发给Git仓库管理员,然后你就可以通过ssh协议来访问服务器端,期间程序会自动进行密钥对匹配,如果你设了访问密码,你可能需要输入密码。
进入某个存放代码的目录
git clone git://git2.kernel.org/pub/scm/git/git.git
[size=large][b]GIT基本流程[/b][/size]
1.初始化仓库
初始化仓库有两种情况,一种是直接在一个空目录里建立一个项目,这时候你可以这样干:
git init
另一种是从其他机器复制一个仓库,比如这样:
git clone git://git2.kernel.org/pub/scm/git/git.git (远程仓库)
git clone https://github.com/jquery/jquery.git (远程仓库)
git clone
[email protected]:wengpingbo/MicroBlog.git (远程仓库)
git clone /home/oss/test.git (本地仓库)
第一次从服务器上复制一个仓库,可能比较慢,因为git要把所有的历史记录和版本全部复制下来,这也算git的一个弊端吧!
复制完后,就会在当前目录下生成一个工作目录,名字以仓库名字命名。如果你不想指定目录,那就在上面的命令后加一个目录就ok了。比如我想把test仓库放到oss仓库中:git clone /home/oss/test.git oss
之后,你就可以开始你的工作啦!
2.添加文件
在编辑了几个文档之后,你可能突然想起来,好像文件还没有让git跟踪。Git并不会实时的跟踪你的文件,只在你明确让它记录你的文件时,它才会把指定的文件的当前状态记录到仓库中去,然后又撒手不管了。我想这就是说git笨的原因吧。这个时候,你需要手动添加你的文件当暂存区域:
git add filename1 filename2
如果你懒得一个一个加,你可以试试这个:
git add -A
它会把当前目录下所有的文件都添加到暂存区域。
3.添加一个版本
在添加完文件后,你可能觉得应该创建一个commit了。
git commit
怎么样?是不是有点不对劲,好像这个命令并没有按你想象的那样跳出一个提交成功的提示,而是直接跑到了你在配置中指定的编辑器中了。仔细看一下,原来是让你给这个版本做一些备注,随便写点什么,然后保存退出就ok了。如果你不想这么麻烦,可以这么干:
git commit -m ‘initial version’
可能你觉得之前讲的太罗嗦了,提交一个commit还这么麻烦,其实有一个捷径可以使你跳过添加文件这个过程:
git commit -a -m ‘initial version’
大功告成,这个命令会把之前所有的已经添加的文件都加入到这个版本中。
可能你又有疑问了,之前添加的文件不是自动会加入到下一个版本中吗,问什么还加这个-a参数?
其实git add命令只是把指定文件的当前状态添加到暂存区域,并不代表一个文件一旦添加,就会一直存在每个版本中。如果你添加一个文件后对这个又进行了修改,在你commit时候,只会commit这个文件添加时的状态,不会把之后的修改也commit进去,除非你再次添加。
4.推送变更
在你commit完之后,你可能想把自己的代码提交到github或者其他git服务器上,与他人交流共享,这时候就需要和远程服务器打交道了。
如果你是在本地建立起的仓库,默认情况下是没有任何服务器地址的,如果你是从其他服务器复制过来的仓库,这个服务器地址会自动添加到你的仓库中,你可以这样查看:
git remote -v
如果只输入”git remote”,就只会列出服务器端的别名,不会列出地址来。
一个仓库可以有多个服务器地址,这就意味着,你可以从不同的人手中复制同一个仓库,但这并不会打乱你自己的分支,哪怕双方的分支名字都一样。假如你现在在和另外两个人做同一个项目中的同一个分支,你发现A的一个模块正是你想要的,你想把他的代码合并到你现在的版本中,这时候你可以这样做:
git remote add code_a git://url/test.git //添加对方的地址,code_a是别名
git fetch code_a //复制对方的仓库到本地,但不合并,git pull会自动合并
git merge code_a/master //把对方master分支合并到自己当前版本下
合并完之后,你可能想提交你的代码到其他的服务器上,这时候你可以先把要提交的服务器地址添加进来,然后这样做:
git push origin master
上面的命令就是把自己master的分支提交到名字为origin的服务器上
5.创建并管理分支
在做项目的时候,你可能会想写一些扩展性的功能,或者做一些小实验,但是你又不想影响你现在的项目。这时候,你可以创建一个分支,然后在这个分支里写东西,当觉得不好的时候,你可以把这个分支删除掉,对你之前的主分支没有任何影响。或者你觉得这个新特性超出了自己的预想,可以合并到主分支里,这时候你只要把工作转回主分支,然后合并分支,最后删除分支,然后就跟那个分支没创建一样。具体操作如下:
git branch test //创建一个test分支
git checkout test //转到test分支
edit something...commit something...
git checkout master //转到master分支
git merge test //合并test分支
git checkout -b test2 //创建test2分支,并转到test2分支
git branch -d test //删除test分支
git branch //列出分支列表
git branch -v //列出分支列表和当前commit
Git merge的实质是把两个版本合在一起,然后在当前分支创建一个新的commit,如果你在两个分支的同一个文件的同一个地方都做了修改,这时候merge就会失败,git就不会自动创建一个新的commit,而是直接停住。你需要手动修改这些冲突的文件,选择这两个分支中的一个版本,或者自己重写这个部分,然后手动添加这些文件到暂存区域,再commit一下就ok了。要查看哪些文件冲突了,可以用”git status”查看。
6.撤消改动
是人就会犯错。当你执行某个命令之后,突然发现,自己写错了,或者漏了一个文件,这时候怎么办?
如果你提交得太早,忘了添加某些文件,你可以这样做:
git commit -m ‘add something ’
git add file1
git commit --amend
最后一个命令会把你当前暂存区域最为上一次的commit。如果你commit以后,马上amend,这时候git会直接跳到编辑commit备注里面,这样你可以修改你上次commit的备注。
如果你添加了不该添加的文件,你可以这样挽回:
git add . //把所有的文件都添加进去
git reset HEAD readme //把readme文件从暂存区域去除
如果你发现你编辑错了一个文件,你想把它恢复到上一个版本的状态,这时候你可以这样:
git checkout -- filename1 //只撤消这一个文件
如果你觉得这个版本糟糕透了,想完全回滚到上一个版本,你可以干如下事情:
git reset --hard HEAD^
HEAD是指向当前版本,^指当前版本的父版本,这个操作无法撤消。你可以把--hard换成--soft,这只会回退commit信息。还有一个--mixed默认选项,大家可以参考官方文档,查看这3个选项的具体区别。
参考资料:
[url]http://www.uml.org.cn/pzgl/201501261.asp[/url]
[url]http://www.blogjava.net/youxia/archive/2013/12/29/408182.html[/url]
[url][/url]
如果您觉得本文的内容对您的学习有所帮助,您可以微信:
[img]http://dl2.iteye.com/upload/attachment/0109/0668/fb266dfa-95ca-3d09-b41e-5f04a19ba9a1.png[/img]