建立Git项目的方法主要有两种。
第一种是把现有的项目或目录导入到Git中
;
第二种是从服务器上克隆现有的Git仓库
。
要想在Git中对现有项目进行跟踪管理,只需要进入项目目录并执行下面的命令
git init
这会创建一个.git
的子目录。这个子目录包含了构成Git仓库骨架的所有必需文件。但此刻Git尚未跟踪项目中的任何文件。(有关于.git目录中具体的信息,会在后面进行分析)。
如果你打算着手对现有文件(非空目录)进行版本控制,那么就应该开始跟踪这些文件并进行初次提交。
对需要提交的文件执行几次git add
命令,然后输入 git commit
命令即可:
git add *.c
git add LICENSE
git commit -m "initial project version"
关于这几条命令。下面会进行分析。
实际操作
进入到需要Git进行版本控制的目录cd Test-1
使Git对现有目录进行跟踪管理 git init
。查看是否创建.git
子目录
添加需要跟踪的文件git add xxx
提交 git commit -m "Version0.0"
注意:这里还只是在本地的操作,上传到服务器下面的内容会详细教学
如果需要获取现有仓库的一份副本(比如这是你想参与的一个项目),可以使用git clone
命令。如果你熟悉其他版本控制系统(比如Subversion),就会注意到这个命令“clone”不是“checkout”。这是一个很重要的差异,因为Git会对服务器仓库的几乎所有数据进行完整复制,而不只是复制当前工作目录。git clone
默认会从服务器上把整个项目历史中每个文件的所有历史版本都拉去下来。实际上,如果你的服务器磁盘损坏,你通常可以用任何客户端计算机上的Git仓库副本恢复服务器【如果这样的话,服务器端的钩子设置(server-side hook)也许会丢失,但全部的版本数据都会都会恢复如初,后面会详细分析】
克隆仓库需要使用git clone [url]
命令。例如,要克隆Git的链接库Libgit2,可以像下面这样做:
git clone https://github.com/libgit2/libgit2
当然你也可以按自己的命名进行克隆,只需要执行如下命令
git clone https://github.com/libgit2/libgit2 mylibgit
注意:如果遇到自己无法下载,查看以下是否设置了代理,如果没有执行以下命令进行设置
(详细原因会在后面进行分析)
设置代理
git config --global https.proxy http://127.0.0.1:1080
取消代理
git config --global --unset http.proxy
现在拥有了一个真正的Git仓库并检出了项目文件的可用副本。下一步就是做出一些更改,当项目到达某个需要记录的状态时向仓库提交这些变更的快照。
请记住,工作目录下的每一个文件都处于两种状态之一:已跟踪(tracked)
或未跟踪(untracked)
。已跟踪的文件是指上一次快照中包含的文件。这些文件又可以分为未修改、已修改或已暂存三种状态。而未跟踪的文件则是工作目录中除去已跟踪文件之外的所有文件,也就是既不在上一次快照中,也不在暂存区中的文件
。当你刚刚完成仓库克隆时,所有文件的状态都是已跟踪且未修改的,因为你刚刚把他们检出,而没有做出任何改动。
如果修改了文件,它们在Git中的状态就会变成已修改,这意味着自从上一次提交以来文件已经发生了变化。你接下来要把这些已修改的文件添加到暂存区,提交所有已暂存的变更,随后重复这个过程。
检查文件所处状态的主要工具是git status
命令。如果在克隆仓库后立即执行这个命令,就会看到类似的输出:(以前面下载的libgit为例
)
意思是你的项目工作目录是干净的。工作目录下没有任何已跟踪的文件被修改过。Git也没有找到任何未跟踪的文件,否则这些文件会被列出。最后,该命令还会显示当前所处的分支,告诉你现在所处的本地分支与服务器上的对应的分支没有出现偏离。就目前而言,我们一直处于默认的main分支(以前是叫master
分支,因为含种族歧视现在改为main
)。会在下一篇博客详细解析分支问题。
现在,让我们把一个简单的README文件添加到项目中。如果之前项目中不存在这个文件,那么这次执行git status
就会看到这个未跟踪的文件:(还是以前面下载的目录为例个
)
未跟踪的文件就是Git在上一次快照(提交)中没有发现的文件。Git并不会主动把这些文件包含到下一次提交的文件范围中,除非你明确的告诉Git你需要跟踪这些文件。这样做是为了让你避免不小心把编译生成的二进制文件或者其他你不想跟踪的文件包含进来。需要让Git跟踪该文件,才能将README加入。
可以使用git add
命令让Git开始跟踪新的文件。执行以下命令来跟踪README文件:
git add README
此时再次执行git status
命令,就可以看到README文件已经处于跟踪状态,并被添加到暂存区等待提交:
在“Changes to be commited”(等待提交的变更)标题下列出的就是已暂存的文件。如果现在提交,那么之前执行git add
时的文件版本就会被添加到历史快照中。回想一下,在开始那个Test-1
目录,在最早执行git init
时,你执行的下一个命令就是git add (files)
,这条命令就是让Git开始跟踪工作目录下的文件。git add
命令接受一个文件或目录的路径名作为参数。如果提供的参数是目录,该命令会递归地添加该目录下的所有文件。
这里我切回Test-1,这个目录。后面以Test-1这个目录为参考
这次让我们来更改一个已跟踪的文件。假如你更改了之前已经被Git跟踪的CONTIRBUTING.md文件,再次执行git status
会有如下输出:
CONTRIBUTING.md文件会出现在名为“Changes not staged for commit”(已修改但未添加到暂存区)的区域中,这表示处于跟踪状态的文件在工作目录下已被修改,但尚未添加到暂存区。要想在暂存这些文件,需要执行git add
命令。git add
是一个多功能的命令,既可以用来跟踪新文件,也可以用来暂存文件。它还可以做一些其他的事情,比如把存在合并冲突的文件标记为已解决
。所以,把git add命令看成“添加内容到下一次提交中”而不是"把这个文件加入到项目中",更有助于理解该命令。现在使用git add
将CONRIBUTING.md添加到暂存区,再次执行git status
输出如下:
上面列出的这两个文件都已暂存,并将进入下一个提交中。加上你在这时突然想起在提交之前还要对CONTRBUTING.md做一个小小的修改。于是你打开文件,做出改动,然后准备提交。然后再次执行git status
。输出如下:
发现,CONTRIBUTING.md文件竟然同时出现在了已暂存和未暂存的列表中。这是为什么呢?其实,在暂存一个文件时,Git保存的是你执行git add
时文件的样子。如果你现在执行git commit
命令进行提交,包含在这次提交中的CONTRIBUTING.md是你上次执行git add
命令时的文件版本,而不是现在工作目录中该文件的当前文件版本。所以,如果执行了git add
之后又对已添加到暂存区的文件做了修改,就需要再次执行git add
将文件添加到暂存区。
虽然git status
命令的输出信息很全面,但是着实冗长。对此,Git也提供了一个显示简短状态的命令行选项,使你可以以一种更为紧凑的形式查看变更。执行git status -s
或git status --short
就可查看到类似下面的信息
未被跟踪的新文件旁边会有一个??
标记,已暂存的新文件会有A
标记,而已修改的文件则会有一个M
标记,等等。实际上,文件列表旁边的标记是分成两列的,左列标明了文件是否已暂存,而右列表明了文件是否已修改。
很多时候,你并不希望某一类文件被Git自动添加,甚至不想这些文件被显示在未跟踪的文件列表下面。这些文件一般是自动生成的文件(比如日志文件)或是由构建系统创建的文件。在这种情况下,可以创建名为.gitignore
的文件,在其中列出待匹配文件的模式。下面是一个.gitignore
文件的例子:
其中第一行告诉Git忽略所有以.o或.a
结尾的文件,这些都是构建代码的过程中所生成的对象归类文档文件。第二行则是告诉Git忽略所有以波浪号~
结尾的文件,Emacs等许多文本编辑器都会将其标记为临时文件。你可以让Git忽略log目录、tmp目录、pid目录以及自动生成的文档等
。最好在开始工作前配置好.gitignore
文件,这样你就不会意外的把不想纳入Git仓库的文件提交进来了。
可以写入.gitignore文件中的匹配模式规则如下:
glob
星号(*)
匹配0个或多个字符,[abc]
匹配方括号内的任意单个字符(在这个例子里是a或b或c),而问号(?)
则匹配任意单个字符。在方括号中使用短划线分隔两个字符(例如[0-9]
)的模式能够匹配在这两个字符范围内的任何单个字符(这个例子中就是0-9之间的任意数字)。你还可以用两个星号匹配嵌套目录。比如a/**/z能够匹配a/z、a/b/z和a/b/c/z等。*.a #忽略.a类型的文件
!lib.a #仍然跟踪lib.a,即使上一行指令要忽略.a类型的文件
/TODO #只忽略当前目录的TODO文件,而不忽略子目录下的TODO
build/ #忽略build/目录下的所有文件
doc/*.txt #忽略doc/目录下的所有以.txt结尾的文件(不包含子目录)
doc/**/*.pdf #忽略doc目录下的所有所有.pdf文件(包含子目录)
GitHub维护了一份相当全面的.gitignore参考示例,大家可以自行去参考
以上面的制定的规则进行演示
如果git status
命令的输出信息对你来说太过泛泛,你想知道修改的具体内容,而不仅仅是你更改了那些文件,这时可以使用git diff
命令。将在稍后讲解git diff的细节。现在只需要知道它基本上可以用来解决两个问题:
1.那些变更还没暂存?
2.那些已暂存的变更正待提交?
尽管git status
可以通过列举文件名的方式大致回答上述问题,但git diff
则会显示出你具体添加和删除那些行。换句话说,git diff的输出是补丁(patch)
。
假设你又编辑并暂存了README
文件,之后更改了CONTRIBUTING.md
但没有暂存它。如果你现在执行git status
命令,那么又会看到类似下面的输出:
要查看尚未添加到暂存区的变更,直接输入不加参数的git diff
命令:
这条命令会将当前目录下的内容与暂存区的内容进行对比。对比的结果就显示了有那些还没暂存的新变更。
如果你想看看有那些已暂存的内容会进入下一次提交,可以使用git diff --staged
命令。这条命令会将暂存的变更与上一次提交的内容相比较:
注意,执行git diff
本身并不会显示出自从上一次提交以来所有的变更,而只会显示出还没有进入暂存区的那些变更。如果你已经把所有变更添加到了暂存区,git diff
不会有任何输出,这会让人摸不着头脑。
再看一个例子,如果暂存了CONTRIBUTING.md之后,对他做出修改,可以用git diff
命令来观察已暂存和未暂存的变更:
现在使用git diff
命令来查看未暂存的更改:
执行git diff --cached
查看当前已暂存的更改(–staged 和 --cached是同义词),如下所示。
注意区分对比的参考,一个是已暂存区和未暂存的对比,一个是进入下一次提交和当前已暂存的对比
现在你的暂存区已经准备妥当了,可以提交了。请记得所有未暂存的变更都不会进入到提交的内容中,这包括任何在编辑之后没有执行git add
命令添加到暂存区的新建的或修改过的文件。这些文件在提交后状态并不会发生改变,仍然是已修改的状态。举个例子,假设你上次执行git status
命令时看到所有的变更都已暂存并等待提交。这时最简单的提交方式就是执行git commit
命令。
执行git commit
命令后就会打开你所选择的文本编辑器。(默认会采用shell环境变量$EDITOR
所指定的文本编辑器,通常是Vim或 Emacs)。
执行git commit
如下
注意:我这里没有进行配置,使用的是默认的编辑器。
Ctrl + x
推出
可以看出默认的提交信息会包括被注释掉的git status
命令的最新输出结果,在最上边还有一行是空行。你既可以删除掉这些注释并输入自己的提交信息,也可以保留这些注释,以帮助你记住提交的具体的内容。(若需要记下更详细的更改记录,可以给git commit
加上-v
参数。这样会把这次提交的差异比对显示在文本编辑器中,让你可以看到要提交的具体变更。)当你退出编辑器时,Git会移除注释内容和差异对比,把剩下的提交信息记录到所创建的提交中。
完成上述提交还有一种方式,那就是直接在命令行上键入提交信息。这需要给git commit
命令加上-m
选项。
至此,你就完成了自己的首次提交!可以看到命令输出中包含了和该提交本身相关的一些信息:提交到那个分支(master)、提交的SHA-1校验和是多少(8ee9f0a)、改动了多少个文件以及源文件新增和删除了多少行的统计信息。
请记住,提交时记录的都是暂存区中的快照。任何未暂存的内容仍然会保持自己的已修改状态。你可以再次提交这些内容,将其纳入到版本历史记录中。每次提交时,都记录了项目的快照,日后可以用于对比和恢复。
在按照你的要求精确地生成提交内容时,暂存区非常有用,但就工作而言,它有时显得有点过于繁琐了。如果你想跳过暂存区直接提交,Git为你提供了更快捷的途径。给git commit 命令传入 -a 选项
,就能让Git自动把已跟踪的所有文件添加到暂存区,然后再提交,这样就不用再执行git add
命令了:
因此就不需要再执行git add来添加CONTIBUTING.md文件了
要从Git中移除某个文件,你需要把它从已跟踪文件列表中移除(确切的说,是从暂存区中移除),然后再提交。git rm
会帮你完成这些操作,另外该命令还会把文件从目录中移除,这样下一次你就不会在未跟踪文件列表中看到这些文件了。
如果你只是简单地把文件从你的工作目录移除,而没有使用git rm
,那么在执行git status
时会看到文件出现在"Changes not staged for commit"区域(也就是未暂存区域):
如果这时,你执行git rm
,Git才会把文件的移除状态记录到暂存区:
下次提交的时候,这个文件就不存在了,也不会再被Git跟踪管理。如果你更改了某个文件,并已经把它加入到了索引当中(已暂存),要想让Git移除它就必须使用-f
选项强制移除。这是为了防止没有被记录到快照中的数据被意外移除而设定的安全特性,因为这样的数据意外被移除后无法由Git恢复。
另一件你可能想要做的有用的事情是把文件保留在工作目录,但从暂存区中移除该文件。换句话说,你也许想将文件保留在硬盘上,但不想让Git对其进行跟踪管理。如果你忘了向.gitignore
文件中天骄相应的规则,不小心把一个很大的日志文件或者一些编译生成的.a文件添加进来,上述做法尤其有用。只需使用--cached
选项即可。
git rm --cached README
你可以将文件,目录和文件的glob模式传递给git rm
命令。这意味着你可以像下面这样:
git rm log/\*.log
请注意在*前面的反斜杠()是必需的,这是因为shell和Git先后都要处理文件名扩展。上述命令会移除log目录中所有扩展名为.log的文件。或者,你也可以像下面这样:
git rm \*~
这条命令会移除所有以~结尾的文件。
Git与很多其他版本控制系统不同,它并不会显式跟踪文件的移动。如果你在Git中重命名了文件,仓库的元数据并不会记录这次重命名操作。不过Git非常聪明,他能推断出究竟发生了什么。至于Git究竟如何检测到文件的移动操作,我们后面再谈。
因此,当你看到Git有一个mv
命令时就会有点搞不明白了。在Git中可以执行下面的命令重命名文件
其实相当于执行了下面三条命令:
mv README README.md
git rm README
git add README.md
不管你是用Git的mv
命令,还是直接给文件改名,Git都能推断出这是重命名操作。唯一的区别是git mv
只需要键入一条命令而不是三条命令,所以会比较方便。更重要的是,你可以用任何你喜欢的工具或方法来重命名文件,然后在提交之前再执行Git的add
和rm
命令。
在完成了几次提交,或者克隆了一个已有提交历史的仓库后,你可能想要看看历史记录。可是使用git log
命令来实现,这是最基础却又强大的一条命令。
下面的这些例子要用到一个非常简单的示例项目simplegit
。要获取这个项目,进行如下操作:
当你在此项目中执行git log
时,会有如下输出:
默认不加参数的情况下,git log
会按照时间顺序列出仓库中的所有提交,其中最新的提交显示在最前面。如你所见,和每个提交一同列出的还有其他的SHA-1校验和、作者的姓名和邮箱、提交日期和以及提交信息。
git log
有很多的不同的选项,可以直观的展示出所需内容。现在我们来看一些最常用的选项。
最有用的一个选项是-p
,他会显示出每次提交所引入的差异。你还可以加上-2
参数,只输出最近2次的提交。
加上这个选项后,仍会显示原来的信息,不同之处是每一条提交记录后都带有diff
信息。在代码审查或者快速浏览某个项目参与者的一系列提交时,这个选项会非常有用。git log
还提供了一系列的摘要选项。例如,可以用--stat
选项来查看每个提交的简要统计信息:
如你所见,--stat
选项会在每个提交下面列出如下内容:改动的文件列表、共有多少个文件被改动以及文件里有多少新增行和删除行。另外还会在最后输出总计信息。
另外一个颇为有用的选项是--pretty
,它可以更改日志输出的默认格式。Git有一些预置的格式供你选择。例如,在浏览大量提交时,oneline
格式选项很有用,它可以在每一行中显示一个提交。除此之外,short、full和fuller
格式选项会分别比默认输出减少或增加一些信息:
最值得注意的是选项format
,它允许你指定自己的输出格式。这样的输出特别有助于机器解析,因为你可以明确指定输出格式,其结果不会随着Git软件版本更新而改变:
下面列举了一些有用的格式选项。
格式选项 | 输出的格式描述 |
---|---|
%H | 提交对象的散列值 |
%h | 提交对象的简短散列值 |
%T | 树对象的散列值 |
%t | 树对象的简短散列值 |
%P | 父对象的散列值 |
%p | 父对象的简短散列值 |
%an | 作者名字 |
%ae | 作者的电子邮箱地址 |
%ad | 创作日期(可使用–date=选项指定日期格式) |
%ar | 相对于当前日期的创作日期 |
%cn | 提交者的名字 |
%ce | 提交者的电子邮箱地址 |
%cd | 提交日期 |
%cr | 相对于当前日期的提交日期 |
%s | 提交信息的主题 |
你可能不太清楚作者(author)和提交者(commiter)的区别是什么。作者是最初开展工作的人,而提交者则是最后将工作成果提交的人。所以,如果你为某个项目提交了补丁,随后项目的一位核心成员应用了补丁,那么你和这位核心人员都会得到认可:你称为作者,核心成员作为提交者。后面将详细阐述其中的差别。
oneline和format
这两个选项如果与log
命令的另一个选项--graph
一起使用,就能发挥更大的作用。具体来说,--graph
选项会用ASCII字符形式的简单图来显示Git分支和合并的历史,如下所示:
下一章会详细介绍,Git分支和合并机制之后,再看上图就能理解了。
上面我们只讲解了git log
的一些简单的输出格式选项,实际上还有很多其他选项可供使用。下表列举的选项包括了我们已经讲解的和其他一些常用的格式选项机及其效果。
选项 | 描述 |
---|---|
–p | 按补丁格式显示每个提交引入的更改 |
–stat | 显示每个提交中被更改的文件的统计信息 |
–shortstat | 只显示上述–stat输出中包含"已更改/新增/删除"行的统计信息 |
–name-only | 在每个提交信息后显示被更改的文件列表 |
–name-status | 在上一个选项输出的基础上还显示出“已更改/新增/删除”统计信息 |
–abbrev-commit | 只显示完整的SHA-1 40位校验和字符串中的前几个字符 |
–relative-date | 显示相对日期(例如“两周以前”),而不是完整日期 |
–graph | 在提交历史旁边显示ASCII图表,用于展示分支和合并的历史信息 |
–pretty | 用一种可选格式显示提交。选项有oneline、short、full、fuller和format(用于指定自定义格式) |
git log
除了有输出格式的选项之外还有一些有用的限制选项,这些选项可以只显示出部分提交。你已经见过了其中一个这样的选项,也就是-2
选项,它可以只显示最近的两次提交。一般来说,你可以用-
显示最新的n次提交,其中n是任意整数。不过这个选项在Git实际应用中并不常用,因为默认情况下Git所有输出会通过管道机制输入给分页程序(pager),使得一次只显示出一页内容。
与上述选项不同,就像--since
和--until
这样按照时间限制输出的选项是十分有用的。例如,下面的命令会列举出最近两周内的所有提交:
你还可以按照某种搜索条件过滤提交列表。--author
选项只允许你只查找某位作者的提交,--grep
选项则能让你搜索提交信息中的关键字(如果需要同时指定author和grep选项,则需添加--all-match
参数,否则Git会列出所有符合任何单一条件的提交)。
另外一个非常有用的过滤条件是-S
选项,它后面需要接上一个字符串参数。这个选项只输出那些添加或删除指定字符串的提交。举个例子,如果你想查找添加或删除特定函数引用的最近一次提交,可以执行一下命令:
git log -Sfunction_name
最后一个很有用的git log
过滤条件是文件路径。如果你指定了一个目录名或者文件名,就可以只输出更改了指定文件的那些提交。这个选项总是作为最后一个命令选项出现,通常需要在之前加两个短横线(--
)来将路径和其他选项分隔开。
下表列出了一些其他常用选项以供参考
选项 | 描述 |
---|---|
-(n) | 只显示最新的n次提交 |
–since, --after | 只输出指定日期之后的提交 |
–until,–before | 只输出指定日期之前的提交 |
–author | 只输出作者与指定字符串匹配的提交 |
–committer | 只输出提交者与指定字符串匹配的提交 |
–grep | 只输出提交信息包含指定字符串的提交 |
–S | 只输出包含“添加或删除指定字符串”的更改的提交 |
举个例子,在Git项目历史中查看Junio Humano在2008年10月发起的那些提交更改了源代码中的测试文件并且没有合并,可以执行以下命令:
git log --pretty="%h - %s" --author=gister --since="2008-10-01" --before="2008-11-01" --no-merges -- t
在任何时刻,你都有可能想撤销之前的操作。现在让我们来看看撤销更改所用到的几个基本工具。
有一种撤销操作的常见使用场景是提交之才发现自己忘了添加某些文件,或者写错了提交信息。如果这时你想重新尝试提交,可以使用--amend
选项:
git commit --amend
上述命令会提交暂存区的内容。如果你在上次提交之后并没做出任何改动(比如你在上次提交后立即执行上面的命令),那么你的提交快照就不会有变化,但你可以改动提交信息。
举一个例子,如果你提交后才意识到忘记了添加某个之前更改过的文件,可以执行类似下面的操作:
最终只产生一个提交,因为第二个提交命令修正了第一个提交的结果
假设你修改了两个文件,想分两次提交,却不小心键入了git add *
,把者两个文件都添加到了暂存区。这时你该如何把它们从暂存区移除呢?其实git status
命令会提示你该如何做:
使用git reset HEAD
命令把文件移除暂存区。所以,我们就用提示的办法把CONTRIBUTING.md文件移除暂存区:
如果你突然发现,自己不需要对CONTRIBUITNG.md文件所作的修改,这是该怎么办?如何轻松地撤销修改并把文件恢复到上次提交时的状态(或者刚克隆仓库后的状态,或是一开始它在工作目录时的状态)?幸运的是,git status
这次也会告诉你该怎么做。在上一个例子的输出内容中,为暂存的工作区如下所示:
因此,进行如下操作:
注意,这是一条危险的命令
如果你仍想保留之前对文件做出的修改,却又需要把更改暂时隐藏一会儿,使他们不影响手头的工作,这种情况下使用储藏(stash)和分支机制更好(后面分析)
请记住,在Git中提交的任何变更几乎总是可以进行恢复。哪怕是在已删除的分支上的提交或是被--amend
覆盖的提交,都可以进行恢复(后面分析)。但是,任何未提交过的变更一旦丢失,就很可能再也找不回来了。
要参与任何一个Git项目的协作,你需要了解如何管理远程仓库。远程仓库是指在互联网或其他网络上托管的项目版本仓库。你可以拥有多个远程仓库,而对于其中每个仓库,你可能会拥有只读权限或者读写权限。要同别人协作,就要管理这些仓库,在需要分享工作成果时,向其推送数据、从中拉取数据。管理远程仓库需要知道如何添加远程仓库、移除无效的远程仓库、管理各种远程分支和设置是否跟踪这些分支,等等。
要查看已经设置了那些远程仓库,请使用git remote
命令。该命令会列出每个远程仓库的简短名称。在克隆某个仓库后,你至少可以看到名为origin
的远程仓库,这是Git给克隆源服务器取得默认名称。
你也可以使用-v
参数,这样会显示出Git存储的每个远程仓库对应的URL
如果你不止一个远程仓库,上面的命令会把它们都列举出来。例如,为了便于多人协作,一个仓库会拥有多个远程仓库地址。
我们已经在之前部分提到过如何添加远程仓库,并给出了一些演示。现在讲解如何显示的添加仓库。要添加一个远程仓库,并给它起一个简短的名称以便引用,可以执行以下命令
git remote add [shortname] [url]命令
:
现在你可以在命令行中使用pb
字符串替代完整的URL。比如,要获取Paul拥有而你还没有的全部数据,可以执行git fetch
命令:
现在,你可以在本地用pb/master的名称访问到Paul的master分支,还可以把它和你的一个分支合并,或是检出一个本地分支便于检查其中的更改。
正如上面所见,要获取远程仓库的护具,可以执行:
git fetch [remote-name]
这条命令会从远程仓库中获取所有本地仓库没有的数据。在执行上述命令后,你就可以在本地引用远程仓库包含的所有分支,并且可以在任何时候合并或检查这些分支。
注意,git fetch
命令只会把数据拉取到本地仓库,然而它并不会自动将这些数据合并到本地的工作成果中,也不会修改当前工作目录下的任何数据。在准备好之后,需要手动将这些数据合并到本地内容中。
如果你有一个跟踪着某个远程分支的本地分支,可以使用git pull
命令来自动获取远程数据,并将远程分支合并到当前本地分支。这种简单易用的工作流可能会更适合你。而且默认情况下,git clone
命令会自动设置你的本地master分支,使其跟踪被克隆的服务器端的master分支(或是其他名称的默认远程分支)。这个时候,执行git pull
就会从被克隆的服务器上获取更多的数据,然后自动尝试将其合并当前工作目录下的本地数据。
当你项目进行到某个阶段,需要与他人分享你的工作成果时,就要把变更推送到远程仓库去。用到的命令很简单:
git push [remote-name] [branch-name]
如果想把本地地master分支推送到远程仓库地origin服务器上(再次说明,Git克隆操作会自动使用上面两个名称作为默认设置),那么可以执行以下命令,把任意提交推送到服务器端:
git push origin master
上述命令能够正常工作的前提是必须拥有克隆下来的远程仓库的写权限,并且克隆后没有任何其他人向远程仓库推送过数据。如果别人和你克隆了这个仓库,而他先推送,你后推送,那么你的这次推送就会被拒绝。你必须先拉取别人的变更,将其整合到你的工作成果中,然后才能推送。
要查看关于某个远程仓库的更多信息,可使用git remote show [remote-name]
命令。如果给该命令提供一个仓库的短名称,比如jacky,就会看到如下输出:
上述命令列出远程仓库的URL地址以及每个分支的跟踪信息。这条命令会输出一些很有帮助的信息,比如告诉你在master分支上执行git pull
会获取到所有的远程引用,然后自动合并入master分支。它还显示了拉取下来的所有远程引用的信息。
上述示例给出的一种简单情况。当你大量使用Git时,git remote show
可能会给出非常多的信息。另外,他还会显示服务器上有哪些本地还没有的远程分支,那些本地分支对应的远程分支已被删除,执行git pull
时那些分支会自动合并到最新的变更。
使用git remote reaname
来重命名远程仓库jacky为paul
值得一提的是,上述操作也会更改远程分支的名称。先前的jacky/master分支现在变成了paul/master。
使用git remote rm
命令来删除某个远程分支
就像大多数版本控制系统一样,Git可以把特定的历史版本标记为重要版本。其典型应用场景是标出发布版本(v1.0等)。下面你可以学到很多如何列举所有可用的标签,如何创建新的标签以及不同标签之间的差异。
这条命令会按字母顺序列出所有的标签。列举的顺序先后和标签的重要性无关。
你还可以按照特定的匹配模式搜索标签。举例来说,Git的源代码仓库包括500个标签。如果你只想查看1.8.5系列的标记版本,可以执行以下命令
git tag -l "v1.8.5"
Git使用标记主要有两种类型:轻量(lightweight)标签和注释(annotated)标签
轻量标签很像是一个不变的分支——它只是一个指向某个提交的指针。
注释标签则会作为完整的对象存储在Git数据库中。Git会计算其检验和、除此之外还包含其他信息,比如标记者(tagger)的名字,邮箱地址和标签的创建时间,还有标记消息(tagging message),另外还可以利用CNU Privacy Guard(CPG)对它们进行签名和验证。一般推荐创建注释标签,这样可以可以包含上述所有信息。但如果你需要的只是一个临时标签,或者偶遇与某些原因不需要包含那些额外信息,也可以用轻量标签。
创建注释标签很简单,只需要执行带有-a
选项的tag
命令即可:
-m
选项指定了标记信息,它会伴随标签一起被存储。如果你没有为标签指定标记信息,Git会打开文本编辑器以便让你进行输入。
执行git show
命令可以看到标签数据以及对应的提交:
另一种用来标记提交的方法是使用轻量级标签。这种标签基本上就是把提交的校验和保存到文件中,除此之外,不包含其他任何信息。创建一个轻量标签时不需要使用-a、-s或-m
选项:
如果你现在在这个标签上执行git show
,除了提交信息之外,不会看到别的标签信息。
你还可以随后再给之前的提交添加标签。假设你的提交历史看起来如下
现在,假如你忘记了给项目添加v0.6.0版本的标签,而该版本对应的应该是“fix tests”这次提交。你仍然可以在这时标记这次提交。只需要在命令最后指定提交的校验和(或者部分校验和)即可。如下:
默认情况下,git push
命令不会把标签传输到远程服务器上。在创建了标签之后,你必须明确地将标签推送到共享服务器上。这个过程有点像推送分支,对应的命令是
git push origin [tagname]
如果你有很多标签需要一次推送,可以使用git push
命令的--tags
选项。这会把所有服务器上还没有的标记都推送上去。
git push origin --tags
执行完上述命令后,如果其他人对此仓库执行克隆或拉取操作,它们也能够得到所有的标签。
你是无法在Git中真正检出一个标签的,这是因为标签无法移动。如果想将某个版本的仓库放入像是标签的工作目录中,可以使用git checkout -b [branchname] [tagname]
在特定标签上创建一个新的分支:
git checkout -b version2.0 v2.0.0
如果你执行了上面的操作并且完成了提交,那么version2.0分支会和你的v2.0.0标签会有不同,因为他携带了新的变更,所以要小心操作。
如果你键入的Git命令不完整,Git不会自动推断并补全命令。虽然如此,但如果你不想每次都费力地键入完整的Git命令,也可以轻松地通过git config
设置每个Git命令的别名。下面是一些你可能想设置的别名:
git config --global alias.co checkout
git config --global alias.br branch
git config --global alias.ci commit
git config --global alias.st status
执行这些命令后,你就可以用git ci
来替代git commit
了。随着对Git使用的逐渐深入,你也可能经常会用到其他的一些Git命令。这时候别忘了创建新的命令别名来简化工作。
这种技巧还可以用来创建那些你认为Git本就该提供,但实际却没有的命令。比如,把文件从暂存区移出这个命令不太好用,因此你可以给这条Git命令取个别名:
git config --global alias.unstage 'reset HEAD --'
执行完上面的命令后,以下两个命令就完全等价了:
git unstage fileA
git reset HEAD --fileA
这样看起来会更清晰一点。还有一种常见的做法是添加一个能够显示最后一次提交信息的命令别名,就像下面这样:
git config --global alias.last 'log -1 HEAD'
这样一来,就可以很容易地看到最后一次提交的信息了:
如你所见,Git只是简单地把新创建的别名替换成原有的命令。但有时候你想执行的是外部命令,而不是Git系统命令。这种情况下要给外部命令前面加上!
字符。如果你自己编了Git仓库的辅助工作,这样的别名就会派上用场了。举例说,我们可以通过指定git visual
别名,让他执行gitk
如下:
git config --global alias.visual '!gitk'
现在你就可以完成所有基本的Git本地操作了:创建或克隆仓库、做出更改、暂存并提交更改,以及查看仓库的变更历史。
下一篇会分析Git的杀手锏特征:分支模型