一、GIT发展史
同生活中的许多伟大事件一样,Git 诞生于一个极富纷争大举创新的年代。Linux 内核开源项目有着为数众广的参与者。绝大多数的 Linux 内核维护工作都花在了提交补丁和保存归档的繁琐事务上(1991-2002年间)。到 2002 年,整个项目组开始启用分布式版本控制系统 BitKeeper 来管理和维护代码。
到 2005 年的时候,开发 BitKeeper 的商业公司同 Linux 内核开源社区的合作关系结束,他们收回了免费使用 BitKeeper 的权力。这就迫使 Linux 开源社区(特别是 Linux 的缔造者 Linus Torvalds )不得不吸取教训,只有开发一套属于自己的版本控制系统才不至于重蹈覆辙。他们对新的系统订了若干目标:
- 速度
- 简单的设计
- 对非线性开发模式的强力支持(允许上千个并行开发的分支)
- 完全分布式
- 有能力高效管理类似 Linux 内核一样的超大规模项目(速度和数据量)
自诞生于 2005 年以来,Git 日臻成熟完善,在高度易用的同时,仍然保留着初期设定的目标。它的速度飞快,极其适合管理大项目,它还有着令人难以置信的非线性分支管理系统,可以应付各种复杂的项目开发需求。
二、如何安装GIT
1、Ubuntu上安装
$ sudo apt-get install git-core
2、Windows上安装
有个叫做 msysGit 的项目提供了安装包,参考:
http://code.google.com/p/msysgit
3、从源码安装
Git的工作需要调用curl,zlib,openssl,expat,libiconv等库的代码,所有要先安装这些依赖工具。
$ sudo apt-get install curl-devel expat-devel gettext-devel openssl-devel zlib-devel
再从下面的 Git 官方站点下载最新版本源代码 :
http://git-scm.com/download
然后编译并安装,例如:
$ tar -zxf git-1.6.0.5.tar.gz
$ cd git-1.6.0.5
$ make prefix=/usr/local all
$ sudo make prefix=/usr/local install
现在已经可以用 git 命令了,例如把 Git 项目仓库克隆到本地,以便日后更新:
$ git clone git://git.kernel.org/pub/scm/git/git.git
三、运行GIT前的配置
一般在新的系统上,我们都需要先配置下自己的 Git 工作环境。配置工作只需一次,以后升级时还会沿用现在的配置。当然,如果需要,你随时可以用相同的命令修改已有的配置。
Git 提供了一个叫做 git config 的工具,专门用来配置或读取相应的工作环境变量。而正是由这些环境变量,决定了 Git 在各个环节的具体工作方式和行为。这些变量可以存放在以下三个不同的地方:
/etc/gitconfig文件:
系统中对所有用户都普遍适用的配置。若使用 git config 时用 --system 选项,读写的就是这个文件。~/.gitconfig文件:用户目录下的配置文件只适用于该用户。若使用 git config 时用 --global 选项,读写的就是这个文件。
当前项目的 git 目录中的配置文件(也就是工作目录中的 .git/config 文件):这里的配置仅仅针对当前项目有效。每一个级别的配置都会覆盖上层的相同配置,所以 .git/config 里的配置会覆盖/etc/gitconfig 中的同名变量。
第一个要配置的是你个人的用户名称和电子邮件地址。这两条配置很重要,每次 Git 提交时都会引用这两条信息,说明是谁提交了更新,所以会随更新内容一起被永久纳入历史记录:
$ git config --global user.name "John Doe"
$ git config --global user.email johndoe@example.com
如果用了 --global 选项,那么更改的配置文件就是位于你用户主目录下的那个,以后你所有的项目都会默认使用这里配置的用户信息。如果要在某个特定的项目中使用其他名字或者电邮,只要去掉 --global 选项重新配置即可,新的设定保存在当前项目的 .git/config 文件里。
接下来要设置的是默认使用的文本编辑器。Git 需要你输入一些额外消息的时候,会自动调用一个外部文本编辑器给你用。默认会使用操作系统指定的默认编辑器,一般可能会是 Vi 或者 Vim。如果你有其他偏好,比如 Emacs 的话,可以重新设置:$ git config --global core.editor emacs
还有一个比较常用的是,在解决合并冲突时使用哪种差异分析工具。比如要改用 vimdiff 的话:$ git config --global merge.tool vimdiff
Git可以理解 kdiff3,tkdiff,meld,xxdiff,emerge,vimdiff,gvimdiff,ecmerge,和 opendiff 等合并工具的输出信息。当然,你也可以指定使用自己开发的工具。
查看配置信息
要检查已有的配置信息,可以使用 git config --list 命令:
$ git config --list
user.name=Scott Chacon
user.email=schacon@gmail.com
也可以直接查阅某个环境变量的设定,只要把特定的名字跟在后面即可,例如:
git config user.name
Scott Chacon
四、获取GIT帮助
想了解 Git 的各种命令该怎么用,可以阅读它们的使用帮助,方法有三:
$ git help <verb>
$ git <verb> --help
$ man git-<verb>
比如,要学习 config 命令可以怎么用,运行:$ git help config
我们随时都可以浏览这些帮助信息而无需连网。不过,要是你觉得还不够,可以到 Frenode IRC 服务器(irc.freenode.net)上的 #git 或 #github 频道寻求他人帮助。这两个频道上总有着上百号人,大多都有着丰富的 git 知识,并且乐于助人。
五、Git中文件的状态和基本工作流程
对于任何一个文件,在 Git 内都只有三种状态:已提交(committed),已修改(modified)和已暂存(staged)。已提交表示该文件已经被安全地保存在本地数据库中了;已修改表示修改了某个文件,但还没有提交保存;已暂存表示把已修改的文件放在下次提交时要保存的清单中。
由此我们看到 Git 管理项目时,文件流转的三个工作区域:Git 的本地数据目录,工作目录以及暂存区域。
每个项目都有一个 git 目录,它是 Git 用来保存元数据和对象数据库的地方。该目录非常重要,每次克隆镜像仓库的时候,实际拷贝的就是这个目录里面的数据。
从项目中取出某个版本的所有文件和目录,用以开始后续工作的叫做工作目录。这些文件实际上都是从 git 目录中的压缩对象数据库中提取出来的,接下来就可以在工作目录中对这些文件进行编辑。
所谓的暂存区域只不过是个简单的文件,一般都放在 git 目录中。有时候人们会把这个文件叫做索引文件,不过标准说法还是叫暂存区域。
基本的 Git 工作流程如下所示:
- 在工作目录中修改某些文件。
- 对这些修改了的文件作快照,并保存到暂存区域。
- 提交更新,将保存在暂存区域的文件快照转储到 git 目录中。
所以,我们可以从文件所处的位置来判断状态:如果是 git 目录中保存着的特定版本文件,就属于已提交状态;如果作了修改并已放入暂存区域,就属于已暂存状态;如果自上次取出后,作了修改但还没有放到暂存区域,就是已修改状态。
六、Git常用命令
git init
git clone
git add <file>
git commit
git diff
git diff --cached
git rm <file>
git rm <file> --cached
git mv <file> <newfile>
git status
git log
git commit --amend
git reset HEAD <file>
git reset --soft HEAD~n
git reset --hard HEAD~n
git checkout -- <file>
git checkout <file>
git remote
git remote add [shortname] [url]
git fetch [remotename]
git push [remotename] [localbranch]:[remotebranch]
git remote show [remotename]
git remote rename [remotename] [new-remotename]
git remote rm [remotename]
git tag
git tag -a [tagname] –m [comments]
git push [remotename] [tagname]
git push [remotename] –tags
git branch [branchname]
git checkout [branchname]
git branch -d [branchname]
git merge [branchname]
git branch
git checkout -b [branchname] [remotename]/[branchname]
git push [remotename] :[remotebranch]
git rebase [branchname]
git pull
七、Git命令使用举例
从当前目录初始化
要对现有的某个项目开始用 Git 管理,只需到此项目所在的目录,执行:git init
初始化后,在当前目录下会出现一个名为 .git 的目录,所有 Git 需要的数据和资源都存放在这个目录中。从现有仓库克隆,如克隆git的代码库
git clone git://git.kernel.org/pub/scm/git/git.git
跟踪新文件和暂存已修改文件
git add <file>
提交更新
git commit
这种方式会启动文本编辑器以便输入本次提交的说明。也可以使用 -m 参数后跟提交说明的方式,在一行命令中提交更新:git commit -m “Initial commit of test repo”
查看已暂存和未暂存的修改
git diff
此命令比较的是工作目录中当前文件和暂存区域快照之间的差异,也就是修改之后还没有暂存起来的变化内容。若要看已经暂存起来的文件和上次提交时的快照之间的差异,可以用 git diff --cached 命令移除文件
要从 Git 中移除某个文件,就必须要从已跟踪文件清单中移除(确切地说,是从暂存区域移除),然后提交。git rm <file>
另外一种情况是,我们想把文件从 Git 仓库中删除(亦即从暂存区域移除),但仍然希望保留在当前工作目录中。 可以用--cached选项git rm --cached <file>
移动文件
git mv <file> <newfile>
检查当前文件状态
git status
查看提交历史
git log
修改最后一次提交
有时候我们提交完了才发现漏掉了几个文件没有加,或者提交信息写错了。想要撤消刚才的提交操作,可以使用 --amend 选项重新提交:git commit --amend
如果刚才提交完没有作任何改动,直接运行此命令的话,相当于有机会重新编辑提交说明,而所提交的文件快照和之前的一样。如果刚才提交时忘了暂存某些修改,可以先补上暂存操作,然后再运行 --amend 提交。取消已经暂存的文件
git reset HEAD <file>
取消对文件的修改
git checkout -- <file>
git checkout <file>
一般与上面的命令效果相同,但如果有一个分支名与文件名相同,就不一样了。加--来消除歧义。查看当前的远程库
git remote
也可以加上 -v 选项,显示对应的克隆地址git remote -v
添加远程仓库
git remote add [shortname] [url]
从远程仓库抓取数据
git fetch [remotename]
此命令会到远程仓库中拉取所有你本地仓库中还没有的数据。运行完成后,你就可以在本地访问该远程仓库中的所有分支,将其中某个分支合并到本地,或者只是取出某个分支,一探究竟。推送数据到远程仓库
git push [remotename] [localbranch]:[remotebranch]
查看远程仓库信息
git remote show [remotename]
远程仓库的删除和重命名
git remote rename [remotename] [new-remotename]
git remote rm [remotename]
显示已有的标签
git tag
新建标签
git tag -a [tagname] –m [comments]
如果想为以前的某次提交打标签,只要在打标签的时候跟上对应提交对象的校验和(或前几位字符)即可 。用某个标签新建分支
git checkout –b [branchname] [tagname]
分享标签
默认情况下,git push 并不会把标签传送到远端服务器上,只有通过显式命令才能分享标签到远端仓库。git push [remotename] [tagname]
如果要一次推送所有(本地新增的)标签上去,可以使用 --tags 选项:git push [remotename] –tags
创建分支
git branch [branchname]
切换分支
git checkout [branchname]
新建并切换到该分支
git checkout -b [branchname]
删除分支
git branch -d [branchname]
合并分支
git merge [branchname]
以上命令将[branchname]分支合并到当前分支查看分支
git branch
远程分支和创建跟踪分支
远程分支(remote branch)是对远程仓库状态的索引。它们是一些无法移动的本地分支;只有在进行 Git 的网络活动时才会更新。远程分支就像是书签,提醒着你上次连接远程仓库时上面各分支的位置。我们用 (远程仓库名)/(分支名) 这样的形式表示远程分支。从远程分支检出的本地分支成为跟踪分支。git checkout -b [branchname] [remotename]/[branchname]
或者git checkout --track [remotename]/[branchname]
删除远程分支
git branch -r -d origin/[branchname]
衍合
git rebase [branchname]
从远程仓库抓取数据并merge
git pull
八、Git分支
在 Git 中提交时,会保存一个提交(commit)对象,它包含一个指向暂存内容快照的指针,作者和相关附属信息,以及一定数量(也可能没有)指向该提交对象直接祖先的指针:第一次提交是没有直接祖先的,普通提交有一个祖先,由两个或多个分支合并产生的提交则有多个祖先。
为直观起见,我们假设在工作目录中有三个文件,准备将它们暂存后提交。暂存操作会对每一个文件计算校验和(即SHA-1 哈希字串),然后把当前版本的文件快照保存到 Git 仓库中(Git 使用 blob 类型的对象存储这些快照),并将校验和加入暂存区域。现在,Git 仓库中有五个对象:三个表示文件快照内容的 blob 对象;一个记录着目录树内容及其中各个文件对应 blob 对象索引的 tree 对象;以及一个包含指向 tree 对象(根目录)的索引和其他提交信息元数据的 commit 对象 。
概念上来说,仓库中的各个对象保存的数据和相互关系看起来 如下所示:
多次提交后,仓库历史如下:
Git 中的分支,其实本质上仅仅是个指向 commit 对象的可变指针。
Git 通过个git branch命令创建分支,比如新建一个testing分支:git branch testing
这会在当前commit对象上新建一个分支指针, 同时Git保存着一个名为HEAD的特别指针,来让Git知道当前在哪个分支上工作。如下:
要切换到其他分支,可以执行命令git checkout,如切换到新建的testing分支:git checkout testing
这样HEAD就指向testing分支,如下:
此时在testing分支上工作,如果有新的提交,提交后的结果如下:
此时切换回master分支再提交后的结果如下:
九、Git分支合并与衍合
如将experiment分支合并回master分支执行以下命令:
git checkout master
git merge experiment```
合并前后如下所示:
![Paste_Image.png](http://upload-images.jianshu.io/upload_images/1049928-78e3e7bdccdc4965.png?imageMogr2/auto-orient/strip%7CimageView2/2/w/1240)
有时候合并操作并不会如此顺利。如果你修改了两个待合并分支里同一个文件的同一部分,Git 就无法干净地把两者合到一起,这种问题只能由人来解决。如果你想用一个有图形界面的工具来解决这些问题,不妨运行 git mergetool,它会调用一个可视化的合并工具并引导你解决所有冲突。确认所有冲突都解决后,可以用 git commit 来完成这次合并提交。
其实,合并两个分支还有另外一个选择:你可以把在 C3 里产生的变化补丁重新在 C4 的基础上打一遍。在 Git 里,这种操作叫做衍合(rebase)。有了 rebase 命令,就可以把在一个分支里提交的改变在另一个分支里重放一遍。
git checkout experiment
git rebase master
衍合前后如下所示:
![Paste_Image.png](http://upload-images.jianshu.io/upload_images/1049928-1d40b0417c9988aa.png?imageMogr2/auto-orient/strip%7CimageView2/2/w/1240)
再进行一次快进:
git checkout master
git merge experiment
结果如下所示:
![Paste_Image.png](http://upload-images.jianshu.io/upload_images/1049928-f6d5ba058b7464a5.png?imageMogr2/auto-orient/strip%7CimageView2/2/w/500)
现在,合并后的 C3(即现在的 C3’)所指的快照,同三方合并例子中的 C5 所指的快照内容一模一样了。最后整合得到的结果没有任何区别,但衍合能产生一个更为整洁的提交历史。如果视察一个衍合过的分支的历史记录,看起来更清楚:仿佛所有修改都是先后进行的,尽管实际上它们原来是同时发生的。
但衍合也并不是完美无缺的,一句话可以总结这点:
永远不要衍合那些已经推送到公共仓库的更新。