Git 教程(一) 基本的 git 版本控制
教程分两部分,依据 git man page 页整理。
(一) 基本的 git 版本控制
(二) git 的基本构成及原理
本篇介绍第一部分,主要包括如何如何使用 Git 导入项目,修改项目,并与其他开发者分享修改的内容。
(一) 基本的 git 版本控制
本教程介绍如何使用 Git 导入项目,修改项目,并与其他开发者分享修改的内容。
在进行操作之前,最好向Git提供自己的身份信息。可以通过以下命令:
$ git config --global user.name "名称"
$ git config --global user.email "电子邮件@地址"
1、导入项目
假设已经有一个项目的压缩文件 project.tar.gz
(1)可以使用以下命令将其置于 git 的管理之下:
$ tar xzf project.tar.gz
$ cd project
$ git init
这时git将回应
$ Initialized empty git repository in .git/
此时便已经初始化了工作目录,可以注意到创建了一个名为 .git 的目录。
(2)下一步,使用git add命令告诉 git 创建当前目录下所有文件的快照。
$ git add .
此时,git 便将快照存到一个称为 index 的临时 staging 中。
(3)此时,可以通过 git commit 命令将 index 的内容永久存储到 repository 中。
$ git commit
git 将反馈一条提交信息,此时你便已经将 project 的第一版存入了 git。
2、做些修改
(1)对一些文件进行修改后,把更新内容加入 index
$ git add file1 file2 file3
此时,可以准备提交了。提交之前,你可以通过 git diff 带参数 --cached 来查看做了哪些修改。
$ git diff --cached
如果不使用 --cached 参数,git diff 将显示哪些尚未使用 git add 命令加入 index 的修改。还可以使用 git status 获得当前项目的简报。
如果需要继续修改,则重复以上动作,把修改后的内容 git add 进 index。最后,使用git commit 提交变更。此时将再次提示你输入变更的描述信息,并记录下项目的新版本。
(2)另一种方法。可以不用先运行 git add ,而直接使用命令
$ git commit -a
这个命令将会自动一次性将所有已经修改的文件加入 index 并提交。但不会把新文件加进去(只加入修改的原有文件)。
关于提交信息的格式说明:虽然不是严格要求的,但还是建议按以下格式编写提交变更说明信息。首先以一段简明扼要的文字说明变更的内容(少于50个字符);空一行,然后对变更做更加具体的描述。此时,第一行简述信息将作为所提交变更的标题,并在git 中经常用到。例如,git-commit-patch 命令的功能是把提变更的内容变成 email格式,它将把第一行作为标题,而空行后面的内容作为邮件的主件内容。
3、Git 跟踪内容变化而不是文件的变化
多数版本控制系统提供的 add 命令是通过告诉系统以新增文件的方式跟踪变更情况的。但是 Git 的 git add 命令更加简洁而强大,它同时用于新增的文件和新修改的文件,在两种情况下,它都将取得相关文件快照,并把相关内容组织进索引中,以备下次 commit 时包含进跟踪轨迹中。
4、查看项目历史
(1)查看项目的修改历史,使用
$ git log
(2)如果要查看每次修改的具体内容,使用
$ git log -p
(3)经常性查看变化概况,有利于掌握每一步变化情况:
$ git log --stat --summary
5、管理分支 branches
(1)一个 git 库可以包括多个开发分支。通过以下命令为项目增加一个experimental 分支:
$ git branch experimental
然后可以通过以下命令察看目前项目有哪些分支:
$ git brach
(2)在分支间切换
如果切换到 experimental 分支,使用:
$ git brach experimental
这是如果对文件进行修改,然后,提交,切换到 master 分支
$ git commit -a
$ git brach master
你会发现之前在 experimental 分支下所做的修改不见了。
(3)合并不同分支修改的内容
在 master 分支下作另一些修改,然后
$ git commit -a
此时,两个分支都有了各自的修改内容。如果要把 experimental 中的变更合并到 master 分支中,使用如下命令:
$ git merge experimental
如果两者不发生冲突(两边都进行了修改),就完成了合并操作。
否则,将提示存在冲突。并将问题记录在 ploblematic文件中。可以使用以下命令查看冲突情况:
$ git diff
此时可以对发生冲突的文件进行编辑后提交变更:
$ git commit -a
(4)删除分支,使用
$ git branch -d experimental
这个命令将会检查合并情况,如果没有合并到 master 分支,将会给出提示并放弃删除分支的操作。
还可以使用
$ git branch -D experimental
强制删除一个分支,而忽略其中所做的修改。
6、使用git 进行合作开发
假设 Alice 用 Git 在代码库 /home/alice/project 中开启了一个新项目,而 Bob 在同一台机器上拥有自己的目录,希望参与该项目开发,于是他可以执行以下命令:
Bob$ git clone /home/alice/project myrepo
这将创建一个新的名为 myrepo 的目录,其中包含了 Alice 的代码库的克隆。该克隆与原始代码库建立了关联,同事具有原始项目历史记录的独立拷贝。
然后 Bob 进行了一些修改并提交:
Bob$ git commit -a
(1)拖入变更
完成上述操作后,Bob 让 Alice 将他的工作成果 pull 进原始库,Alice 输入以下命令:
Alice $ cd /home/alice/project
Alice $ git pull /home/bob/myrepo master
这将把 Bob 的 master 分支中的变更情况『拖』进 Alice 的当前分支。如果 Alice 在此期间也对代码做了一些修改,那么她需要手工对相应的冲突进行验证和确认。
pull 命令执行了两个过程:从远程分支中取回变更,然后把这些变更合并到当前选定的分支中。
注意:一般情况下,Alice 在发起 pull 之前会提交她的本地变更情况。如果因为他们同时对项目进行了修改而导致合并冲突, Alice 将用他的索引和文件解决这些冲突,现存的本地变更将会接入到冲突处置过程( Git 仍将执行取回操作,但是会拒绝合并变更——当这种情况发生时, Alice 将被迫用某种方式取消她的本地变更然后重新 pull)。
(2)检视变更情况
Alice可以在合并变更之前先检视一下 Bob 作了哪些变动。通过使用 fetch 命令及特定的 FETCH_HEAD 记号来确定值得合并。
alice $ git fetch /home/bob/myrepo master
alice $ git log -p HEAD..FETCH_HEAD
即使 Alice 没有提交本地的变更,该命令也是安全的。范围记号 『HEAD..FETCH_HEAD』意味着显示可以从 FETCH_HEAD 获取的所有修改内容,但不包括可以从 HEAD 获取的修改内容。Alice 已经掌握了项目达到目前的 HEAD 的状态的所有变更情况(HEAD),并用这个命令来查看那些她尚未获知的变更情况(从其他用户取得的变更情况 FETCH_HEAD)。
如果 Alice 想以图形方式呈现她与 Bob 在上次合并后所做的变更,可以使用以下命令:
$ gitk HEAD..FETCH_HEAD
该命令使用与前面的 git log 命令中一样的两点记号。
如果 Alice 想同时查看他们两个人所做的变更,可以使用三点记号的形式:
$ gitk HEAD...FETCH_HEAD
这意味着查看两者分别所做的变更,但不包括两者相同的内容。同样请注意这些范围记号既可用于 git log 也可以用于 gitk。
在检视完 Bob 所做的工作后,如果 Alice 认为并不需要急于将 Bob 的工作成果合并进来,则继续自己的工作即可。
如果 Bob 的工作成果正是 Alice 急于用到的,她便可以选择先把自己所做的变更『隐藏』起来,完成对 Bob 工作成果的 pull 操作后,再将自己的工作成果恢复到结果记录集的最末端。
(3)使用 remote 命令
当你与一个小型团队紧密协作开发时,对同一个代码库进行频繁交互是难以避免的事。通过使用 remote 命令创建远程代码库的快捷方式,可以减轻不少工作量。
alice $ git remote add bobo /home/bob/myrepo
这种情况下,Alice可以将合并变更情况分开操作。首先,可以利用这个快捷方式单独执行 git fetch 命令(不进行合并操作)
alice $ git fetch bob
与使用长路径不同之处在于,使用 remote 设置的远程代码库快捷方式时,取回的资料均存储在一个叫做远程跟踪的分支中,在本例中为 bob/master。因此用
alice $ git log -p master..bob/master
可以显示 bob 在上次合并之后所做的修改情况。
检视这些变更后,Alice 可以将这些变更合并进自己的 master 分支:
alice $ git merge bob/master
这个合并操作也可以利用她自己的远程跟踪分支来完成:
alice $ git pull . remotes/bob/master
请注意 pull 命令将忽略其他参数,直接将变更合并到当前选中的分支中。
接着,Bob 也可以将 Alice 的最近变更合并到自己的代码库:
bob $ git pull
注意:Bob 无需指定 Alice 的代码库路径,当他克隆 Alice 的代码库时, Git 已经将她的代码库地址保存到了配置文件中,并在 pull 命令中直接使用。
bob $ git config --get remote.origin.url
/home/alice/project
git clone 所创建的完整配置可用 git config -l 命令查看,git config 的 man page 解释了其中每一项配置的具体含义。
Git 还将为 Bob 在目录 origin/master 中保留一个不会改动的 Alice 主分支的拷贝。
bob $ git branch -r
origin/master
如果 Bob 接着想到另一台机器上工作,他可以通过 ssh 协议来执行 clone 和 pull 操作:
bob $ git clone alice.org:/home/alice/project myrepo
此外,Git 还有一个内置协议,或者通过 rsync,或者通过 http 来完成相关操作。具体操作可查阅 git-pull 的 man page。
Git 还可以以 CVS-风格的方式来管理代码库,即使用一个中心库,多个用户将变更 push 到其中。可通过查阅 git-push 和 gitcvs-migration 的 man page 来获取详细信息。
7、浏览历史变更记录
(1)查看 commit 历史
Git 的历史变更记录表现为一系列的提交变更操作(commit)。我们前面已经见识过git log 命令可以列出这些提交动作。注意每个 git log 入口的第一行给出了该 commit 的名称:
$ git log
commit c82a22c39cbc32576f64f5c6b3f24b99ea8149c7
Author: Junio C Hamano <junkio@cox.net>
Date: Tue May 16 17:18:22 2006 -0700
merge-base: Clarify the comments on post processing.
可以将该名称作为参数传递给 git show 命令来查看该变更提交的细节。
$ git show c82a22c39cbc32576f64f5c6b3f24b99ea8149c7
还可以使用其他办法来指代该 commit 对象,比如可以使用 commit 名称的前若干个字符(只要能够唯一标识该对象)来指代它。
$ git show c82a22c39c #一般情况下前面几个字符就可以唯一标识该变更提交了
$ git show HEAD #显示当前分支的末端(最后变更)
$ git show experimental #显示 experimental 分支的末端
每一个提交变更都有其父版本,指向前一次变更状态:
$ git show HEAD^ #查看 HEAD 的前一版本
$ git show HEAD^^ #查看 HEAD 前一版本的前一版本
$ git show HEAD~4 #查看 HEAD 往前数第4个版本
注意,合并的变更可能有多个父版本。
$ git show HEAD^1 #查看第一个父版本,结果跟 HEAD^ 一样
$ git show HE AD^2 #查看第二个父版本
(2)为提交变更 commit 命名
可以为 commit 命名,如果运行以下命令:
$ git tag v2.5 1b2e1d63ff
此时就为 commit 对象 1b2e1d63ff 取了一个别名 v2.5。如果需要与其他人共用这个名称(比如,用于指明一个发布版本),那么应该创建一个 tag 对象,视情还需 sign 它(?)。可以查看 git-tag 的 man page 页面获得更多详细信息。
所有的需要使用 commit 对象名称的命令均可使用以上这些名称的任何一种来指代具体的 commit 对象。如:
$ git diff v2.5 HEAD # 比较当前 HEAD 与 v2.5
$ git branch stable v2.5 # 基于 v2.5 建立名为 stable 的新分支
$ git reset --hard HEAD^ # 将当前分支和工作目录重置到 HEAD^ 的状态
必须慎用最后一个命令,因为除了该命令会丢弃当前目录中所有工作之外,还将从该分支中移除所有后续的变更提交记录。如果是在包含这些变更记录的唯一分支中运行 git reset 命令,那么这些记录将永久消失,再也无法恢复。
同样重要的是,不要在一个其他用户需要从中 pull 的公共分支中使用 git reset。因为它为了清楚变更记录,将强制其他开发者执行不必要的合并操作。如果需要在其中撤销自己所推送的内容,请使用 git revert 命令。
(3)搜索项目
git grep 命令可以在项目任何版本中搜索字符串:
$ git grep "hello" v2.5
以上命令将在 v2.5 中搜索所有 hello 字符串出现的位置。
如果不指定提交变更的名称, git grep 将在当前目录中搜索 git 所管理的所有版本的文件。
$ git grep "hello"
是在 git 所管理的文件中快速进行搜索的有效方法。
(4)使用多个版本
很多 git 命令可以使用多种形式的 commit 版本范围,下面有些 git log 命令的例子:
$ git log v2.5..v2.6 # v2.5 和 v2.6 之间的变更情况
$ git log v2.5.. # v2.5 之后的变更情况
$ git log --since="2 weeks ago" #上两周的变更情况
$ git log v2.5.. Makefile # v2.5 以后修改 Makefile 的情况
同样,还可以查看两个没有前后继承关系的版本之间的变更情况。例如,如果 stable 分支的末端和 master 分支的末端某段时间前源自同一个提交版本,那么
$ git log stable..master
将会先显示 master 所做的修改,但不显示 stable 所做的修改。而
$ git log master..stable
则与前面的相反。
git log 有个不足之处在于,它只能以列表方式呈现变更情况,如果变更历史记录中存在这种情况:两组开发人员在某时刻分开工作然后又进行了合并(merge),那么 git log 呈现的变更顺序就没有什么实际含义了。
许多拥有多个贡献团队的项目(例如 Linux Kernel 或 Git 本身)会发生很多次合并,这种情况下使用 gitk 能够更好地呈现这些历史变更,例如:
$ gitk --since="2 weeks age" drivers/
让你能够浏览 drivers/ 目录下两周内的任何变更提交记录(注意:可以使用 control 键加 - 或 + 键调整 gitk 的字体)。
最后,许多 git 命令允许你在文件名前加一个 commit 名称前缀来指定该文件的特定版本。
$ git diff v2.5:Makefile HEAD:Makefile.in
你也可以使用 git show 来察看这种文件:
$ git show v2.5:Makefile
8、后续学习
本部分教程对于使用 git 执行基本的项目版本控制应该是足矣。但如果想更加深入地了解 Git 的强大,你首先得理解它的两个基础部分:
- 对象数据库,一个设计得简洁优雅系统,用来存储项目历史中的所有对象——文件、目录,以及变更提交。
- 索引文件,项目中目录树的状态缓存文件,用于创建变更提交,切换工作目录,以及在执行合并操作时容纳所涉及的大量树形结构。
本教程的第二部分详细介绍了对象数据库、索引文件以及你在使用 Git 时的其他一些零星事项。可通过查阅 gittutorial-2 man page 找到该教程。
如果暂时不想深入研究那个问题,这里还有些相关的题外话可供研究:
- git-format-patch(1),git-am(1) 这两个命令可将一系列 git 变更提交转换为 emailed patches(邮件补丁),对于像 Linux Kernel 这种重点依靠邮件补丁的项目相当有用
- git-bisect(1),进行项目回归测试时,有一个追踪错误的办法是通过全面搜索修改历史记录以定位发生错误的版本。Git bisect 能够帮助你执行该版本的二进制搜索。该命令已经过优化,可以对复杂的乃至有多个合并版本的非线性历史记录情况执行智能化的二进制查找。
- gitworkflows(7),提供一个建议的使用 git 的工作流程
- giteveryday(7),20个日常使用的 git 命令
- gitcvs-migration,CVS用户的 git 方法。