赞
踩
本文涵盖了你在使用Git的绝大多数时间里会用到的所有基础命令。学完之后,你应该能够配置并初始化Git仓库、开始或停止跟踪文件、暂存或者提交更改。我们也会讲授如何让Git忽略某些文件和文件模式,如何简单快速地撤销错误操作,如何浏览项目版本历史并查看版本之间的差异,以及如何向远程仓库推送或从中拉取数据。
在本文中,我们将在命令行中使用Git。一方面是因为命令行是唯一可以执行所有Git命令的地方,大多数GUI出于简化的目的,只实现了Git的部分功能,如果你知道如何使用命令行,那大概也能猜出如何使用GUI;不过,反过来可就不一定了。另一方面,尽管图形化客户端的选择属于个人喜好问题,但命令行工具是所有的Git用户都拥有的。
建立Git项目的方法主要有两种:
要想在Git中对现有项目进行跟踪管理,只需进入项目目录并输入:
$ git init
这会创建一个名为.git的子目录。这个子目录包含了构成Git仓库骨架的所有必需文件。但此刻Git尚未跟踪项目中的任何文件。
如果你打算着手对现有文件(非空目录)进行版本控制,那么就应该开始跟踪这些文件并进行初次提交。对需要跟踪的文件执行几次git add命令, 然后输入git commit命令即可:
- $ git add *.c
- $ git add LICENSE
- $ git commit -m 'initial project version'
稍后我们会逐一解释这些命令的含义。现在,你的Git仓库已经包含了这些被跟踪的文件并进行了初次提交。
如果需要获取现有仓库的一份副本(比如这是你想参与的一个项目),可以使用git clone命令。如果你熟悉其他版本控制系统(比如Subversion),就会注意到这个命令是“clone" 而不是“checkout”。这是一个很重要的差异,因为Git会对服务器仓库的几乎所有数据进行完整复制,而不只是复制当前工作目录。git clone默认会从服务器上把整个项目历史中每个文件的所有历史版本都拉取下来。实际上,如果你的服务器磁盘损坏,你通常可以用任何客户端计算机上的Git仓库副本恢复服务器[如果这样的话,服务器端的钩子设置( server-side hook )也许会丢失,但全部的版本数据都会恢复如初]。
克隆仓库需要使用git clone [url]命令。 例如,要克隆Git的链接库Libgit2,可以像下面这样做:
$ git clone https://github.com/libgit2/libgit2
这会创建一个名为libgit2的新目录,并在其中初始化.git目录,然后将远程仓库中的所有数据拉取到本地并检出最新版本的可用副本。进入新的libgit2目录中,会看到所有项目文件已经准备就绪。
如果想将项目克隆到其他名字的目录中,可以把目录名作为命令行选项传入:
$ git clone https ://github.com/libgit2/libgit2 mylibgit
这一条命令与上一条命令功能相同,只是目标目录的名称变成了mylibgit。
Git可以用几种不同的协议传输数据。上一个例子使用的是http://协议,除此之外也可以使用git://协议或者是SSH传输协议(如user@server:path/to/repo.git)。
你现在拥有了一个真正的Git仓库并检出了项目文件的可用副本。下一 步就是做出一些更改,当项目到达某个需要记录的状态时向仓库提交这些变更的快照。
请记住,工作目录下的每一个文件都处于两种状态之一:已跟踪( tracked )或未跟踪( untracked )。已跟踪的文件是指上一次快照中包含的文件。这些文件又可以分为未修改、已修改或已暂存三种状态。而未跟踪的文件则是工作目录中除去已跟踪文件之外的所有文件,也就是既不在上一次快照中,也不在暂存区中的文件。当你刚刚完成仓库克隆时,所有文件的状态都是已跟踪且未修改的,因为你刚刚把它们检出,而没有做出过任何改动。
如果修改了文件,它们在Git中的状态就会变成已修改,这意味着自从上次提交以来文件已经发生了变化。你接下来要把这些已修改的文件添加到暂存区,提交所有已暂存的变更,随后重复这个过程。
检查文件所处状态的主要工具是git status命令。如果在克隆仓库后立即执行这个命令,就会看到类似下面的输出:
- $ git status
- On branch master
- nothing to commit, working directory clean
上述输出说明你的项目工作目录是干净的。也就是说,工作目录下没有任何已跟踪的文件被修改过。Git也没有找到任何未跟踪的文件,否则这些文件会被列出。最后,该命令还会显示当前所处的分支,告诉你现在所处的本地分支与服务器上的对应的分支没有出现偏离。就目前而言,我们一直处在默认的master分支。
现在,让我们把一个简单的README文件添加到项目中。如果之前项目中不存在这个文件,那么这次执行git status就会 看到这个未跟踪的文件:
- $ echo 'My Project' > README
- $ git status
- On branch master
- Untracked files:
- (use "git add <file>..." to include in what will be committed)
- README
- nothing added to commit but untracked files present (use "git add" to track)
可以看到,新的README文件处于未跟踪状态,因为git status输 出时把这个文件显示在“Untracked files" (未跟踪的文件)条目下。未跟踪的文件就是Git在上一次快照(提交)中没有发现的文件。Git并不会主动把这些文件包含到下一次提交的文件范围中,除非你明确告诉Git你需要跟踪这些文件。这样做是为了避免你不小心把编译生成的二进制文件或者其他你不想跟踪的文件包含进来。需要让Git跟踪该文件,才能将README加入。
可以使用git add命令让Git开始跟踪新的文件。执行以下命令来跟踪README文件:
$ git add README
此时如果重新执行查看项目状态的命令,就可以看到README文件已处于跟踪状态,并被添加到暂存区等待提交:
- $ git status
- On branch master
- Changes to be conmitted:
- (use "git reset HEAD <file>..." to unstage)
- new file: README
在“Changes to be commited" (等待提交的更改)标题下列出的就是已暂存的文件。如果现在提交,那么之前执行git add时的文件版本就会被添加到历史快照中。回想一下,在早先执行git init时,你执行的下一个命令就是git add (files), 这条命令就是让Git开始跟踪工作目录下的文件。git add命令接受一个文件或目录的路径名作为参数。如果提供的参数是目录,该命令会递归地添加该目录下的所有文件。
这次让我们来更改一个已跟踪的文件。假如你更改了之前已经被Git跟踪的CONTRIBUTING.md文件,此时再执行git status命令, 会看到类似下面的输出:
- $ git status
- On branch master
- Changes to be comnitted:
- (use "git reset HEAD <file>..." to unstage)
- new file:README
- Changes not staged for connit:
- (use "git add <file>..." to update what will be committed)
- (use "git checkout .. <file>..." to discard changes in working directory)
- nodified: CONTRIBUTING.md
CONTRIBUTING.md文件会出现在名为“Changes not staged for commit" (已更改但未添加到暂存区)的区域中,这表示处于跟踪状态的文件在工作目录下已被修改,但尚未被添加到暂存区。要想暂存这些文件,需要执行git add命令。git add是一个多功能命令,既可以用来跟踪新文件,也可以用来暂存文件,它还可以做其他的一些事,比如把存在合并冲突的文件标记为已解决。所以,把git add命令看成“添加内容到下一次提交中”而不是“把这个文件加入到项目中”,更有助于理解该命令。现在让我们执行git add命令,将CONTRIBUTING.md添加到暂存区,然后重新执行git status:
- $ git add CONTRIBUTING.md
- $ git status
- On branch master
- Changes to be comnitted:
- (use "git reset HEAD <file>..." to unstage )
- new file: README
- modified: CONTRIBUTING.md
上面列出的这两个文件都已暂存,并将进入下一个提交中。假设你在这时想起来在提交之前还要再对CONTRIBUTINGmd做一个小小的修改。 于是你打开文件,做出改动,然后准备提交。
不过让我们先来再执行一次git status:
- $ vim CONTRIBUTING.md
- $ git status
- On branch master
- Changes to be committed:
- (use "git reset HEAD <file>..." to unstage)
- new file: README
- nodified: CONTRIBUTING.md
- Changes not staged for commit:
- (use "git add <file>..." to update what wlll be committed)
- (use "git checkout -- <file>..." to discard changes in working directory)
- modified: CONTRIBUTING.md
这是怎么回事?现在CONTRIBUTING.md文件竟然同时出现在了已暂存和未暂存的列表中。这怎么可能呢?其实,在暂存一个 文件时,Git保存的是你执行git add时文件的样子。如果你现在执行git commit命令进行提交,包含在这次提交中的CONTRIBUTING.md是你上次执行git add命令时的文件版本,而不是现在工作目录中该文件的当前版本。所以,如果在执行了git add之后又对已添加到暂存区的文件做了修改,就需要再一次执行git add将文件的最新版本添加到暂存区:
- $ git add CONTRIBUTING.md
- $ git status
- On branch master
- Changes to be committed:
- (use "git reset HEAD <file>..." to unstage)
- new file: README
- nodified: CONTRIBUTING.md
虽然git status命令的输出信息很全面,但也着实冗长。对此,Git也提供了一个显示简短状态的命令行选项,使你可以以一种更为紧凑的形式查看变更。执行git status -s或者git status --short就可以看到类似下面的效果。
- $ git status -s
- M README
- MM Rakefile
- A lib/git.rb
- M lib/simplegit.rb
- ?? LICENSE.txt
未被跟踪的新文件旁边会有一个?? 标记,已暂存的新文件会有A标记,而已修改的文件则会有一个M标记,等等。实际上,文件列表旁边的标记是分成两列的,左列标明了文件是否已暂存,而右列表明了文件是否已修改。以上面的命令行输出为例,工作目录下的README文件已被修改,但还没有被暂存。另一个lib/simplegt.rb 文件是已修改而且已暂存的状态。而Rakefile文件则是已修改并被添加到暂存区,之后又被修改过,因此暂存区和工作区都包含了该文件的变更。
很多时候,你并不希望某一类文件被Git自动添加,甚至不想这些文件被显示在未跟踪的文件列表下面。这些文件一般是自动生成的文件(比如日志文件)或是由构建系统创建的文件。在这种情况下,可以创建名为.gitignore的文件,在其中列出待匹配文件的模式。下面是一个.gitignore文件的例子:
- $ cat .gitignore
- *.[oa]
- *~
其中第一行告诉Git忽略所有以 .o 或 .a 结尾的文件,这些都是构建代码的过程中所生成的对象和归档文件。第二行则是告诉Git忽略所有以波浪号(~)结尾的文件, Emacs等许多文本编辑器都会将其标记为临时文件。你也可以让Git忽略log目录、tmp目录、pid目录以及自动生成的文档等。最好在开始工作前配置好gitignore文件,这样你就不会意外地把不想纳入Git仓库的文件提交进来了。
可以写入.gitignore文件中的匹配模式的规则如下:
glob模式类似于shell所使用的简化版正则表达式。具体来讲,星号(* )匹配零个或更多字符,[abc]匹配方括号内的任意单个字符(在这个例子里是a、b或c), 而问号(?)则匹配任意单个字符。在方括号中使用短划线分隔两个字符( 例如[0-9] )的模式能够匹配在这两个字符范围内的任何单个字符(在这个例子里是0到9之间的任何数字)。你还可以用两个星号匹配嵌套目录,比如a/**/z能够匹配a/z、a/b/z 和a/b/c/z等。
下面是另一个.gitignore文件的例子:
- *.a #忽略.a类型的文件
- !lib.a #仍然跟踪lib.a,即使上一行指令要忽略.a类型的文件
- /TODO #只忽略当前目录的TODO文件, 而不忽略子目录下的TODO
- build/ #忽略build/目录下的所有文件
- doc/*.txt #忽略doc/notes.txt,而不忽略doc/server/arch.txt
- doc/**/*.pdf # 忽略doc/目录下的所有.pdf文件
如果git status命令的输出信息对你来说太过泛泛,你想知道修改的具体内容,而不仅仅是你更改了哪些文件,这时可以使用git diff命令。我们将稍后讲解git diff的细节,现在只需知道它基本上可以用来解决两个问题:哪些变更还没有被暂存?哪些已暂存的变更正待提交?尽管git status也可以通过列举文件名的方式大致回答上述问题,但git diff则会显示出你具体添加和删除了哪些行。换句话说,git diff的输出是补丁( patch )。
假设你又编辑并暂存了README文件,之后更改了CONTRIBUTING.md但没有暂存它。如果你现在执行git status命令,那么又会看到类似下面的输出:
- $ git status
- On branch master
- Changes to be conmitted:
- (use "git reset HEAD <file>..." to unstage)
- new file: README
- Changes not staged for commit:
- (use "git add <file>..." to update what will be committed)
- (use "git checkout .. <file..." to discard changes in working directory)
- modified: CONTRIBUTING.md
要查看尚未添加到暂存区的变更,直接输入不加参数的git diff命令:
- $ git diff
- diff --git a/CONTRIBUTING.nd b/CONTRIBUTING.nd
- index 8ebb991. .643e24f 100644
- --- a/CONTRIBUTING.md
- +++ b/CONTRIBUTING.nd
- 00 -65,7 +65,8 0@ branch directly, things can get nessy.
- Please include a nice description of your changes when you submit your PR;
- if we have to read the whole diff to figure out why you're contributing
- in the first place, you're less likely to get feedback and have your change
- -merged in.
- +merged in. Also, split your changes into conprehensive chunks if your patch is
- +longer than a dozen lines.
- If you are starting to work on a particular area, feel free to subnit a PR
- that highlights your work in progress (and note in the PR title that it's
这条命令会将当前工作目录下的内容与暂存区的内容作对比。对比的结果就显示了有哪些还没有暂存的新变更。
如果你想看看有哪些已暂存的内容会进入下一次提交,可以使用git diff --staged命令”。这条命令会将暂存的变更与上一次提交的内容相比较:
- $ git diff --staged
- diff --git a/README b/README
- new file mode 100644
- index 000000. .03902a1
- --- /dev/null
- +++ b/README
- @@ -0,0 +1 @@
- +My Project
请注意,执行git diff本身并不会显示出自从上一次提交以来所有的变更,而只会显示出还没有进入暂存区的那些变更。如果你已经把所有变更添加到了暂存区,git diff不会有任何输出,这会让人摸不着头脑。
再看另一个例子,如果暂存了CONTRIBUTING.md之后又对它做出了修改,可以用git diff命令来观察已暂存和未暂存的变更:
- $ git add CONTRIBUTING.md
- $ echo '# test line' >> CONTRIBUTING.nd
- $ git status
- On branch master
- Changes to be conmitted:
- (use "git reset HEAD <file>..." to unstage)
- modifled: CONTRIBUTING.md
- Changes not staged for commit:
- (use "git add <file>..." to update what will be conmitted)
- (use "git checkout -- <file..." to discard changes in working directory)
- modified: CONTRIBUTING.md
现在你的暂存区已经准备妥当,可以提交了。请记得所有未暂存的变更都不会进入到提交的内容中,这包括任何在编辑之后没有执行git add命令添加到暂存区的新建的或修改过的文件。
这些文件在提交后状态并不会发生变化,仍然是已修改的状态。举个例子,假设你上次执行git status命令时看到所有变更都已暂存并等待提交。这时最简单的提交方式就是执行git commit命令:
$ git commit
执行这条命令后就会打开你所选择的文本编辑器。(默认会采用shel的环境变量SEDITOR所指定的文本编辑器,通常是Vim或者Emacs。
文本编辑器会显示以下文本(以Vim为例):
- # Please enter the cormit nessage for your changes. Lines starting
- # with '#' will be ignored, and an enpty nessage aborts the connit.
- # On branch master
- # Changes to be comnitted:
- # new file: README
- # nodified: CONTRIBUTING.md
- #
- ~
- ~
- ~
- ".git/COMMIT_EDITMSG" 9L, 283C
可以看出,默认的提交信息会包括被注释掉的git status命令的最新输出结果,在最上边还有一行是空行。你既可以删掉这些注释并输入自己的提交信息,也可以保留这些注释,以帮助你记住提交的具体内容。(若需要记下更详细的更改记录,可以给git commit加上-v参数。这样会把这次提交的差异比对显示在文本编辑器中,让你可以看到要提交的具体变更。)当你退出编辑器时,Git会移除注释内容和差异比对,把剩下的提交信息记录到所创建的提交中。
完成上述提交还有另一种方式,那就是直接在命令行上键人提交信息。这需要给git commit命令加上-m选项:
- $ git commit -m "Story 182: Fix benchnarks for speed"
- [naster 463dc4f] Story 182: Fix benchmarks for speed
- 2 files changed, 2 insertions(+)
- create node 100644 README
你终于完成了自己的首次提交!可以看到命令输出中包含了和该提交本身相关的一些信息:提交到哪个分支( master ).提交的SHA-1校验和是多少( 463dc4f )。改动了多少个文件以及源文件新增和删除了多少行的统计信息。
请记住,提交时记录的是暂存区中的快照。任何未暂存的内容仍然保持着已修改状态。你可以再次提交这些内容,将其纳入到版本历史记录中。每次提交时,都记录了项目的快照,日后可以用于比对或恢复。
在按照你的要求精确地生成提交内容时,暂存区非常有用,但就工作流而言,它有时显得有点过于繁琐了。如果你想要跳过暂存区直接提交,Git为你提供了更快捷的途径。给git commit命令传入-a选项,就能让Git自动把已跟踪的所有文件添加到暂存区,然后再提交,这样你就不用再执行git add了:
- $ git status
- On branch master
- Changes not staged for commit:
- (use "git add <file>..." to update what will be comnitted)
- (use "git checkout .. <file>..." to discard changes in working directory)
- modified: CONTRIBUTING.md
- no changes added to conmit (use "git add" and/or "git commit -a")
- $ git commit -a -m ' added new benchnarks '
- [master 83e38c7] added new benchnarks
- 1 file changed, 5 insertions(+), 0 deletions(-)
注意在上面的例子中,提交前不再需要执行git add来添加CONTRIBUTING.md文件了。
要从Git中移除某个文件,你需要把它先从已跟踪文件列表中移除(确切地说,是从暂存区中移除),然后再提交。git rm会帮你完成这些操作,另外该命令还会把文件从工作目录中移除,这样下一次你就不会在未跟踪文件列表中看到这些文件了。
如果你只是简单地把文件从你的工作目录移除,而没有使用git rm, 那么在执行git status时会看到文件出现在"Changes not staged for commit”区域(也就是未暂存区域):
- $ rm PROJECTS.md
- $ git status
- On branch master
- Your branch is up-to-date with ' origin/master'.
- Changes not staged for conmit:
- (use "git add/rm <file..." to update what will be committed)
- (use "git checkout -- <file>..." to discard changes in working directory)
- deleted: PROJECTS.nd
- no changes added to commit (use "git add" and/or "git commit -a")
如果你这时执行git rm, Git才 会把文件的移除状态记录到暂存区:
- $ git rm PROJECTS.md
- rm 'PROJECTS.md'
- $ git status
- On branch raster
- Changes to be committed:
- (use "git reset HEAD <file>..." to unstage)
- deleted: PROJECTS.md
下一次提交的时候,这个文件就不存在了,也不会再被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中可以执行下面的命令重命名文件:
$ git mv file_from file_to
结果没有问题。实际上,执行了这条命令后再去查看状态的话,就会发现Git识别出了重命名后的文件:
- $ git mv README.md README
- $ git status
- On branch master
- Changes to be committed:
- (use "git reset HEAD <file>..." to unstage)
- renaned:
- README.md -> README
其实这相当于执行了下面的三条命令:
- $ mv README.md README
- $ git rm README.md
- $ git add README
不管你是用Git的mv命令,还是直接给文件改名,Git都能推断出这是重命名操作。唯一的区别是git mv只需键入一条命令而不是三条命令,所以会比较方便。更重要的是,你可以用任何你喜欢的工具或方法来重命名文件,然后在提交之前再执行Git的add和rm命令。
此小节不细讲命令,了解即可,因为我们平时开发的时候使用相关的GUI工具会比较更方便。
在完成了几次提交,或者克隆了一个已有提交历史的仓库之后,你可能想要看看历史记录。可以使用git log命令来实现,这是最基础却又最强大的一条命令。
默认不加参数的情况下,git log会按照时间顺序列出仓库中的所有提交,其中最新的提交显示在最前面。如你所见,和每个提交一同列出的还有它的SHA-1校验和、作者的姓名和邮箱、提交日期以及提交信息。
git log有很多不同的选项, 可以直观地展示出所需内容。现在我们来看一些最常用的选项。最有用的一个选项是-p, 它会显示出每次提交所引人的差异。你还可以加上-2参数,只输出最近的两次提交。
在任何时刻,你都有可能想撤销之前的操作。现在让我们来看看撤销更改所用到的几个基本工具。有些撤销操作是不可逆的,所以请务必当心。在Git中,误操作导致彻底丢失工作成果的情景并不多见,而这就是其中之一。
有一种撤销操作的常见使用场景是提交之后才发现自己忘了添加某些文件,或者写错了提交信息。如果这时你想重新尝试提交,可以使用--amend选项:
$ git commit --amend
上述命令会提交暂存区的内容。如果你在上次提交之后并没做出任何改动(比如你在上次提交后立即执行上面的命令),那么你的提交快照就不会有变化,但你可以改动提交信息。和之前提交时一样,这次也会启动提交信息编辑器,有所不同的是打开的编辑器中会显示上次提交的信息。你可以像之前-样编辑,保存后就会覆盖之前的提交信息。
举一个例子,如果你提交后才意识到忘记了添加某个之前更改过的文件,可以执行类似下面的操作:
- $ git commit -m 'initial commit'
- $ git add forgotten_file
- $ git commit --amend
最终只是产生了一个提交,因为第二个提交命令修正了第一个提交的结果。
接下来的两节内容会演示如何管理暂存区和工作目录的变更。好消息是,我们用来显示上述两个区域状态的命令同样也会告诉我们如何撤销这两个区域的变更。举例来说,假设你更改了两个文件,想要分两次提交,却不小心键入了git add *,把这两个文件都添加到了暂存区。这时你该如何把它们从暂存区移出呢?其实git status命令会提示你该如何做:
- $ git add *
- $ git status
- On branch master
- Changes to be committed:
- (use "git reset HEAD <file>..." to unstage)
- renamed: README.md > README
- modified: CONTRIBUTING.md
在"Changes to be committed”正下方显示的提示是“使用git reset HEAD <file>... 命令把文件移出暂存区”。所以,我们就用提示的办法把CONTRIBUTING.md文件移出暂存区:
- $ git reset HEAD CONTRIBUTING.md
- Unstaged changes after reset:
- M CONTRIBUTING.md
- $ git status
- On branch master
- Changes to be committed:
- (use "git reset HEAD <file..." to unstage)
- renamed: README.md -> README
- Changes not staged for conit:
- (use "git add <file>..." to update what will be comitted)
- (use "git checkout .. <file>..." to discard changes in working directory)
- modified: CONTRIBUTING.md
这条命令看起来有点奇怪,但是它确实管用。CONTRIBUTING.md又恢复到了已修改但未暂存的状态。
注意:尽管git reset加 上--hard参数时很危险,但是如果你使用上述例子中的命令来操作,工
作目录中的文件就不会被改动。也就是说,执行不加选项的git reset是安全的, 它只更改暂存区。
如果你突然发现,自己不再需要对CONTRIBUTING.md文件所做的更改,这时该怎么办?如何轻松地撤销修改并把文件恢复到上次提交时的状态(或是刚克隆仓库后的状态,或是一开始它在工作目录时的状态) ?幸运的是,git status这次也会告诉你该怎么做。在上一个例子的输出内容中,未暂存的工作区如下所示:
- Changes not staged for commit:
- (use "git add <file>..." to update what will be cormitted)
- (use "git checkout .. <file>..." to discard changes in working directory)
- modified: CONTRIBUTING.md
上述输出很明确地告诉了你如何舍弃对文件的更改。让我们照它说的做:
- $ git checkout .. CONTRIBUTING.md
- $ git status
- On branch master
- Changes to be conmitted:
- (use "git reset HEAD <file..." to unstage)
- renamed: README.md > README
可以看出,之前所做的修改已经恢复了。
注意:重要的是要了解git checkout -- [file]是一条危险的命令。执行该命令后,任何对[file]文件做出的修改都会丢失,因为上述命令用之前版本的文件做了覆盖。除非你确信不再需要这些文件,否则不要用这个命令。
请记住,在Git中提交的任何变更几乎总是可以进行恢复。哪怕是在已删除的分支上的提交或是被--amend覆盖的提交,都可以进行恢复。但是,任何未提交过的变更一旦丢失,就很可能再也找不回来了。
要参与任何一个Git项目的协作,你需要了解如何管理远程仓库。远程仓库是指在互联网或其他网络上托管的项目版本仓库。你可以拥有多个远程仓库,而对于其中每个仓库,你可能会拥有只读权限或者读写权限。要同别人协作,就要管理这些远程仓库,在需要分享工作成果时,向其推送数据,从中拉取数据。管理远程仓库需要知道如何添加远程仓库、移除无效的远程仓库。管理各种远程分支和设置是否跟踪这些分支,等等。在本节中,我们会讲解上述远程仓库管理技巧中的一部分。
要查看已经设置了哪些远程仓库,请使用git remote命令。该命令会列出每个远程仓库的简短名称。在克隆某个仓库之后,你至少可以看到名为origin的远程仓库,这是Git给克隆源服务器取的默认名称。
- $ git clone https://github.com/schacon/ticgit
- Cloning into 'ticgit' ...
- remote: Reusing existing pack: 1857, done.
- remote: Total 1857 (delta 0), reused 0 (delta 0)
- Receiving objects: 100% (1857/1857), 374.35 KiB| 268.00 KiB/s, done.
- Resolving deltas: 100% (772/772), done.
- Checking connectivity... done.
- $ cd ticgit
- $ git remote
- origin
你也可以使用-v参数,这样会显示出Git存储的每个远程仓库对应的URL:
- $ git remote -v
- Origin https://github.com/schacon/ticgit (fetch)
- Origin https://github.com/schacon/ticgit (push)
如果你有不止一个远程仓库,上面的命令会把它们都列出来。
如何显式地添加仓库。要添加一个远程仓库, 并给它起一个简短名称以便引用,可以执行命令:
git remote add [shortname] [url]
例如:
- $ git remote
- origin
- $ git renote add pb https://github.com/paulboone/ticgit
- $ git renote -v
- origin https://github.com/schacon/ticgit (fetch)
- origin https://github.com/schacon/ticgit (push)
- pb https://github.com/paulboone/ticgit (fetch)
- pb https://github.com/paulboone/ticgit (push)
现在你可以在命令行中使用pb字符串替代完整的URL。比如,要获取Paul拥有而你还没有的全部数据,可以执行git fetch pb命令:
- $ git fetch pb
- renote: Counting objects: 43, done.
- renote: Compressing objects: 100% (36/36), done.
- renote: Total 43 (delta 10),reused 31 (delta 5)
- Unpacking objects: 100% (43/43), done.
- Fron https ://github. con/paulboone/ticgit
- * [new branch] master -> pb/master
- * [new branch] ticgit -> pb/ticgit
现在,你可以在本地用pb/master的名称访问到Paul的master分支,还可以把它和你的一个分支合并,或是检出一个本地分支便于检查其中的更改。
正如上面所见,要从远程项目获取数据,可以执行:
$ git fetch [remote-nane]
这条命令会从远程仓库中获取所有本地仓库没有的数据。在执行上述命令后,你就可以在本地引用远程仓库包含的所有分支,并可以在任何时候合并或检查这些分支。
当你克隆仓库时,克隆命令会自动添加远程仓库的地址并取名为“origin”。 当随后执行git fetch origin时, 会获取到所有自上一次克隆( 或获取)之后被推送到服务器端的新增的变更数据。请注意,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]命令。如果给该命令提供一个仓库的短名称,比如origin,就会看到如下输出:
- $ git remote show origin
- * renote origin
- Fetch URL: https://github.com/schacon/ticgit
- Push URL: https://github.com/schacon/ticgit
- HEAD branch: master
- Remote branches:
- master tracked
- dev-branch tracked
- Local branch configured for 'git pull':
- master merges with renote master
- Local ref configured for 'git push':
- master pushes to master (up to date)
上述命令列出了远程仓库的URL地址以及每个分支的跟踪信息。这条命令会输出一些很有帮助的信息,比如告诉你在naster分支上执行git pull会获取到所有的远程引用,然后自动合并入master分支。它还显示了拉取下来的所有远程引用的信息。
可以用git remote rename来重命名远程仓库。如果想要把pb重命名为paul,可以用git remote rename命令来实现,如下所示。
- $ git remote rename pb paul
- $ git remote
- origin
- paul
值得一提的是, 上述操作也会更改远程分支的名称。先前的pb/master分支现在变成了paul/
master。
有时出于某种原因,需要删除某个远程仓库地址,比如当你迁移了服务器地址,或是不再使用某一仓库镜像,又或是某个参与者退出协作时,就可以使用git remote rm命令,如下所示。
- $ git remote rm paul
- $ git remote
- origin
就像大多数版本控制系统一样,Git可以把特定的历史版本标记为重要版本。其典型应用场景是标出发布版本(v1.0等)。在本节中,你可以学到如何列举所有可用的标签,如何创建新的标签以及不同标签之间的差异。
在Git中,列举可用标签的操作很简单,只需键入git tag即可:
- $ git tag
- v0.1
- v1.3
这条命令会按字母顺序列出所有的标签。列举的顺序先后和标签的重要性无关。
你还可以按照某个特定匹配模式搜索标签。举例来说,Git的源代码仓库包括超过500个标签。
如果你只想查看1.8.5系列的标记版本,可以执行以下命令。
- $ git tag -l "v1.8.5*"
- v1.8.5
- v1.8.5-rc0
- v1.8.5-rc1
- v1.8.5-rc2
Git使用的标记主要有两种类型:轻量( lightweight )标签和注释( annotated)标签。
轻量标签很像是一个不变的分支——它只是一个指向某次提交的指针。
注释标签则会作为完整的对象存储在Git数据库中。Git会计算其校验和,除此之外还包含其他信息,比如标记者( tagger)的名字、邮箱地址和标签的创建时间,还有标记消息( tagging message),另外还可以利用GNU Privacy Guard ( GPG )对它们进行签名和验证。一般推荐创建注释标签,这样可以包含上述所有信息。但如果你需要的只是一个临时标签,或者由于某些原因不需要包含那些额外信息,也可以用轻量标签。
创建注释标签很简单,只需要执行带有-a选项的tag命令即可:
- $ git tag -a v1.4 -m "my version 1.4"
- $ git tag
- v0.1
- v1.3
- v1.4
-m选项指定了标记信息,它会伴随着标签一起被存储。如果你没有为注释标签指定标记消息。Git会打开文本编辑器以便你进行输入。
执行git show命 令可以看到标签数据以及对应的提交:
- $ git show v1.4
- tag v1.4
- Tagger: Ben Straub <ben@straub.cc>
- Date:
- Sat May 3 20:19:12 2014 -0700
- my version 1.4
- commit ca82a6dff817ec66f44342007202690a93763949
- Author: Scott Chacon <schacon@gee-mail. com>
- Date: Mon Mar 17 21:52:11 2008 -0700
- changed the verison nunber
上述命令的输出显示了标记者信息、提交被标记的日期以及注释消息,最后是提交信息。
另一种用来标记提交的方法是使用轻量标签。这种标签基本上就是把提交的校验和保存到文件中,除此之外,不包含其他任何信息。创建一个轻量标签时不需要使用-a. -s或-m选项:
- $ git tag v1.4 - lw
- $ git tag
- v0.1
- V1.3
- v1.4
- v1.4-1w
- V1.5
如果你现在在这个标签上执行git show,除了提交信息之外,不会看到别的标签信息。
- $ git show v1.4-lw
- comnit ca82a6df f817ec66f44342007202690a93763949
- Author: Scott Chacon <schacon@gee-nail.com>
- Date:
- Mon Mar 17 21:52:11 2008 -0700
- changed the verison number
默认情况下,git push命令不会把标签传输到远程服务器上。在创建了标签之后,你必须明确地将标签推送到共享服务器上。这个过程有点像推送分支,对应的命令是git push origin [tagnane]。
- $ git push origin v1.5
- Counting objects: 14, done.
- Delta compression using up to 8 threads.
- Compressing objects: 100% (12/12), done .
- Writing objects: 100% (14/14), 2.05 KiB | 0 bytes/s, done. .
- Total 14 (delta 3), reused 0 (delta 0)
- To git@github. com: schacon/simplegtt.git
- * [new tag]
- V1.5 -> V1.5
如果你有很多标签需要一次性推送, 可以使用git push命令的--tags选项。这会把所有服务器上还没有的标记都推送过去。
- $ git push origin --tags
- Counting objects: 1, done.
- Writing objects: 100% (1/1), 160 bytes |日bytes/s, done.
- Total 1 (delta 0), reused θ (delta 0)
- To git@github . com: schacon/sinplegit.git
- 曲[new tag]
- v1.4 -> V1.4
- * [new tag]
- v1.4-1w -> v1.4-1w
执行完上述命令后,如果其他人此时对仓库执行克隆或拉取操作,他们也能够得到所有的标签。
你是无法在Git中真正检出一个标签的,这是因为标签无法移动。如果想将某个版本的仓库放入像是标签的工作目录中,可以使用git checkout -b [br anchnane] [tagname ]在特定标签上创建一个新的分支:
- $ git checkout -b version2 v2.0.0
- Switched to a new branch 'version2'
如果你执行上面的操作并完成了提交,那么version2分支会和你的v2.0.0标签略有不同,因为它携带了新的变更,所以要小心操作。
Copyright © 2003-2013 www.wpsshop.cn 版权所有,并保留所有权利。