LOADING

加载过慢请开启缓存 浏览器默认开启

Git教程(1)

简单的git教程

ProGit极简版

请安装基于Debian的linux发行版(Ubuntu, kali, debian等)

安装

sudo apt install git

初始化

如果你打算使用 Git 来对现有的项目进行管理,你只需要进入该项目目录并输入:

git init

克隆现有的仓库

克隆仓库的命令格式是git clone [url]。比如,要克隆 Git 的可链接库 libgit2,可以用下面的命令:

git clone https://github.com/libgit2/libgit2

这会在当前目录下创建一个名为 “libgit2” 的目录,并在这个目录下初始化一个.git文件夹,从远程仓库拉
取下所有数据放入.git文件夹,然后从中读取最新版本的文件的拷贝。如果你进入到这个新建的libgit2
件夹,你会发现所有的项目文件已经在里面了,准备就绪等待后续的开发和使用。如果你想在克隆远程仓库的时
候,自定义本地仓库的名字,你可以使用如下命令:

git clone https://github.com/libgit2/libgit2 mylibgit

这将执行与上一个命令相同的操作,不过在本地创建的仓库名字变为 mylibgit

使用 Git 时文件的生命周期

检查当前文件状态

git status

我们在项目下创建一个新的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)

跟踪新文件

使用命令git add开始跟踪一个文件。所以,要跟踪README文件,运行:

git add README

此时再运行git status命令,会看到README文件已被跟踪,并处于暂存状态:

On branch master
Changes to be committed:
  (use "git reset HEAD <file>..." to unstage)
    new file: README

暂存已修改文件

现在我们来修改一个已被跟踪的文件。如果你修改了一个名为CONTRIBUTING.md的已被跟踪的文件,然后运行git status命令,会看到下面内容:

On branch master
Changes to be committed:
  (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

文件CONTRIBUTING.md出现在Changes not staged for commit这行下面,说明已跟踪文件的内容发生了变化,但还没有放到暂存区。要暂存这次更新,需要运行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
    modified: CONTRIBUTING.md

假设此时,你想要在CONTRIBUTING.md里再加条注释,重新编辑存盘后,准备好提交。不过且慢,再运行git status看看:

On branch master
Changes to be committed:
  (use "git reset HEAD <file>..." to unstage)
    new file: README
    modified: CONTRIBUTING.md
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

现在CONTRIBUTING.md文件同时出现在暂存区和非暂存区。实际上Git只暂存了你运行git add命令时的版本,如果你现在提交,CONTRIBUTING.md的版本是你最后一次运行git add命令时的那个版本,而不是你运行git commit时,在工作目录中的当前版本。所以,运行了git add之后又作了修订的文件,需要重新运行git add把最新版本重新暂存起来

状态简览

使用git status -s命令或git status --short命令,你将得到一种更为紧凑的格式输出:

 M README
MM Rakefile
A lib/git.rb
M lib/simplegit.rb
?? LICENSE.txt

新添加的未跟踪文件前面有??标记,新添加到暂存区中的文件前面有A标记,修改过的文件前面有M标记。你可能注意到了M有两个可以出现的位置,出现在右边的M表示该文件被修改了但是还没放入暂存区,出现在靠左边的M表示该文件被修改了并放入了暂存区。

忽略文件

我们可以创建一个名为.gitignore的文件,列出要忽略的文件模式,如:

*.[oa]
*~

第一行告诉Git忽略所有以.o.a结尾的文件。一般这类对象文件和存档文件都是编译过程中出现的。第二行告诉Git忽略所有以波浪符~结尾的文件,许多文本编辑软件(比如 Emacs)都用这样的文件名保存副本。

文件.gitignore的格式规范如下:

  • 所有空行或者以开头的行都会被 Git 忽略。

  • 可以使用标准的glob模式匹配.

  • 匹配模式可以以/开头防止递归。

  • 匹配模式可以以/结尾指定目录。

  • 要忽略指定模式以外的文件或目录,可以在模式前加上惊叹号!取反。

所谓的glob模式是指shell所使用的简化了的正则表达式:

  • 星号(*)匹配零个或多个任意字符;

  • [abc] 匹配任何一个列在方括号中的字符(这个例子要么匹配一个 a,要么匹配一个 b,要么匹配一个 c);

  • 问号(?)只匹配一个任意字符;

  • 如果在方括号中使用短划线分隔两个字符,表示所有在这两个字符范围内的都可以匹配(比如 [0-9] 表示匹配所有 0 到 9 的数字)。

  • 使用两个星号(*) 表示匹配任意中间目录,比如a/**/z 可以匹配 a/z, a/b/za/b/c/z等。

GitHub 有一个十分详细的针对数十种项目及语言的 .gitignore 文件列表,你可以在 gitignore 找到它.

查看已暂存和未暂存的修改

假如再次修改README文件后暂存,然后编辑CONTRIBUTING.md文件后先不暂存,运行git status命令将会看到:

On branch master
Changes to be committed:
  (use "git reset HEAD <file>..." to unstage)
    modified: 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

diff --git a/CONTRIBUTING.md b/CONTRIBUTING.md
index 8ebb991..643e24f 100644
--- a/CONTRIBUTING.md
+++ b/CONTRIBUTING.md
@@ -65,7 +65,8 @@ branch directly, things can get messy.
 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 comprehensive chunks if your
patch is
+longer than a dozen lines.
 If you are starting to work on a particular area, feel free to submit a
PR
 that highlights your work in progress (and note in the PR title that it's ...

此命令比较的是工作目录中当前文件和暂存区域快照之间的差异,也就是修改之后还没有暂存起来的变化内容。

若要查看已暂存的将要添加到下次提交里的内容,可以用git diff --cached命令(Git 1.6.1 及更高版本还允许使用git diff --staged,效果是相同的,但更好记些。):

diff --git a/README b/README
new file mode 100644
index 0000000..03902a1
--- /dev/null
+++ b/README
@@ -0,0 +1 @@
+My Project

git diff本身只显示尚未暂存的改动,而不是自上次提交以来所做的所有改动。所以有时候暂存了所有更新过的文件后,运行git diff后却什么也没有,就是这个原因。

提交更新

git commit

这种方式会启动文本编辑器以便输入本次提交的说明。(默认会启用shell的环境变量$EDITOR所指定的软件,一般都是vimemacs。使用git config --global core.editor= 命令设定你喜欢的编辑软件。)

编辑器会显示类似下面的文本信息(本例选用 Vim 的屏显方式展示):

# Please enter the commit message for your changes. Lines starting
# with '#' will be ignored, and an empty message aborts the commit.
# On branch master
# Changes to be committed:
#	new file: README
#	modified: CONTRIBUTING.md
#
~
~
~
".git/COMMIT_EDITMSG" 9L, 283C

默认的提交消息包含最后一次运行git status的输出,放在注释行里,另外开头还有一空行,供你输入提交说明。这些注释行完全可以去掉。(如果想要更详细的对修改了哪些内容的提示,可以用-v选项,这会将你所做的改变的diff输出放到编辑器中从而使你知道本次提交具体做了哪些修改。)退出编辑器时,Git会丢掉注释行,用你输入提交附带信息生成一次提交。

commit命令后添加-m选项,将提交信息与命令放在同一行,如下所示:

git commit -m "Story 182: Fix benchmarks for speed"

提交后它会告诉你,当前是在哪个分支(master)提交的,本次提交的完整 SHA-1 校验和是什么(463dc4f),以及在本次提交中,有多少文件修订过,多少行添加和删改过:

[master 463dc4f] Story 182: Fix benchmarks for speed
 2 files changed, 2 insertions(+)
 create mode 100644 README

跳过使用暂存区域

只要在提交的时候,给git commit加上-a选项,Git 就会自动把所有已经跟踪过的文件暂存起来一并提交,从而跳过git add步骤

移除文件

要从 Git 中移除某个文件,就必须要从已跟踪文件清单中移除(确切地说,是从暂存区域移除),然后提交。可以用git rm命令完成此项工作,并连带从工作目录中删除指定的文件,这样以后就不会出现在未跟踪文件清单中了。

如果只是简单地从工作目录中手工删除文件,运行git status时就会在 “Changes not staged for commit” 部分(也就是 未暂存清单)看到:

On branch master
Your branch is up-to-date with 'origin/master'.
Changes not staged for commit:
  (use "git add/rm <file>..." to update what will be committed)
  (use "git checkout -- <file>..." to discard changes in working
directory)
        deleted: PROJECTS.md
no changes added to commit (use "git add" and/or "git commit -a")

然后再运行git rm记录此次移除文件的操作:

$ git rm PROJECTS.md
rm 'PROJECTS.md'
$
$ git status
On branch master
Changes to be committed:
  (use "git reset HEAD <file>..." to unstage)
    deleted: PROJECTS.md

下一次提交时,该文件就不再纳入版本管理了。如果删除之前修改过且已经放到暂存区域的话,则必须要用强制删除选项-f(译注:即force的首字母)。这是一种安全特性,用于防止误删还没有添加到快照的数据,这样的数据不能被 Git 恢复。

另外一种情况是,我们想把文件从 Git 仓库中删除(亦即从暂存区移除),但仍然希望保留在当前工作目录中。换句话说,你想让文件保留在磁盘,但是并不想让 Git 继续跟踪。当你忘记添加.gitignore文件,不小心把一个很大的日志文件或一堆.a这样的编译生成文件添加到暂存区时,这一做法尤其有用。为达到这一目的,使用--cached选项:

git rm --cached README

git rm 命令后面可以列出文件或者目录的名字,也可以使用 glob 模式:

git rm log/\*.log

注意到星号*之前的反斜杠\,因为 Git 有它自己的文件模式扩展匹配方式,所以我们不用shell来帮忙展开。此命令删除log/目录下扩展名为.log的所有文件。类似的比如:

git rm \*~

该命令为删除以~结尾的所有文件。

移动文件

要在 Git 中对文件改名,可以这么做:

git mv file_from file_to

此时查看状态信息,也会明白无误地看到关于重命名操作的说明:

$ git status
On branch master
Changes to be committed:
  (use "git reset HEAD <file>..." to unstage)
    renamed: README.md -> README

运行git mv就相当于运行了下面三条命令:

mv README.md README
git rm README.md
git add README

查看提交历史

默认不用任何参数的话,git log会按提交时间列出所有的更新,最近的更新排在最上面。这个命令会列出每个提交的 SHA-1 校验和、作者的名字和电子邮件地址、提交时间以及提交说明:

commit ca82a6dff817ec66f44342007202690a93763949
Author: Scott Chacon <schacon@gee-mail.com>
Date: Mon Mar 17 21:52:11 2008 -0700
    changed the version number
commit 085bb3bcb608e1e8451d4b2432f8ecbe6306e7e7
Author: Scott Chacon <schacon@gee-mail.com>
Date: Sat Mar 15 16:40:33 2008 -0700
    removed unnecessary test
commit a11bef06a3f659402fe7563abf99ad00de2209e6
Author: Scott Chacon <schacon@gee-mail.com>
Date: Sat Mar 15 10:31:28 2008 -0700
    first commit

一个常用的选项是-p,用来显示每次提交的内容差异。你也可以加上-2来仅显示最近两次提交。该选项除了显示基本信息之外,还在附带了每次 commit 的变化:

commit ca82a6dff817ec66f44342007202690a93763949
Author: Scott Chacon <schacon@gee-mail.com>
Date: Mon Mar 17 21:52:11 2008 -0700
    changed the version number
diff --git a/Rakefile b/Rakefile
index a874b73..8f94139 100644
--- a/Rakefile
+++ b/Rakefile
@@ -5,7 +5,7 @@ require 'rake/gempackagetask'
 spec = Gem::Specification.new do |s|
     s.platform = Gem::Platform::RUBY
     s.name = "simplegit"
- s.version = "0.1.0"
+ s.version = "0.1.1"
     s.author = "Scott Chacon"
     s.email = "schacon@gee-mail.com"
     s.summary = "A simple gem for using Git in Ruby code."
commit 085bb3bcb608e1e8451d4b2432f8ecbe6306e7e7
Author: Scott Chacon <schacon@gee-mail.com>
Date: Sat Mar 15 16:40:33 2008 -0700
    removed unnecessary test
diff --git a/lib/simplegit.rb b/lib/simplegit.rb
index a0a60ae..47c6340 100644
--- a/lib/simplegit.rb
+++ b/lib/simplegit.rb
@@ -18,8 +18,3 @@ class SimpleGit
     end
 end
-
-if $0 == __FILE__
- git = SimpleGit.new
- puts git.show
-end
\ No newline at end of file

如果你想看到每次提交的简略的统计信息,你可以使用--stat选项。--stat选项在每次提交的下面列出额所有被修改过的文件、有多少文件被修改了以及被修改过的文件的哪些行被移除或是添加了,在每次提交的最后还有一个总结:

commit ca82a6dff817ec66f44342007202690a93763949
Author: Scott Chacon <schacon@gee-mail.com>
Date: Mon Mar 17 21:52:11 2008 -0700
    changed the version number
 Rakefile | 2 +-
 1 file changed, 1 insertion(+), 1 deletion(-)
commit 085bb3bcb608e1e8451d4b2432f8ecbe6306e7e7
Author: Scott Chacon <schacon@gee-mail.com>
Date: Sat Mar 15 16:40:33 2008 -0700
    removed unnecessary test
 lib/simplegit.rb | 5 -----
 1 file changed, 5 deletions(-)
commit a11bef06a3f659402fe7563abf99ad00de2209e6
Author: Scott Chacon <schacon@gee-mail.com>
Date: Sat Mar 15 10:31:28 2008 -0700
    first commit
 README | 6 ++++++
 Rakefile | 23 +++++++++++++++++++++++
 lib/simplegit.rb | 25 +++++++++++++++++++++++++
 3 files changed, 54 insertions(+)

另外一个常用的选项是--pretty。这个选项可以指定使用不同于默认格式的方式展示提交历史。这个选项有一些内建的子选项供你使用。比如用oneline将每个提交放在一行显示,查看的提交数很大时非常有用。另外还有shortfullfuller可以用:

$ git log --pretty=oneline
ca82a6dff817ec66f44342007202690a93763949 changed the version number
085bb3bcb608e1e8451d4b2432f8ecbe6306e7e7 removed unnecessary test
a11bef06a3f659402fe7563abf99ad00de2209e6 first commit

但最有意思的是format,可以定制要显示的记录格式。这样的输出对后期提取分析格外有用 —— 因为你知道输出的格式不会随着Git的更新而发生改变:

$ git log --pretty=format:"%h - %an, %ar : %s"
ca82a6d - Scott Chacon, 6 years ago : changed the version number
085bb3b - Scott Chacon, 6 years ago : removed unnecessary test
a11bef0 - Scott Chacon, 6 years ago : first commit

Table 1. git log --pretty=format常用的选项

选项 说明
%H 提交对象(commit)的完整哈希字串
%h 提交对象的简短哈希字串
%T 树对象(tree)的完整哈希字串
%t 树对象的简短哈希字串
%P 父对象(parent)的完整哈希字串
%p 父对象的简短哈希字串
%an 作者(author)的名字
%ae 作者的电子邮件地址
%ad 作者修订日期(可以用 –date= 选项定制格式)
%ar 作者修订日期,按多久以前的方式显示
%cn 提交者(committer)的名字
%ce 提交者的电子邮件地址
%cd 提交日期
%cr 提交日期,按多久以前的方式显示
%s 提交说明

作者指的是实际作出修改的人,提交者指的是最后将此工作成果提交到仓库的人。所以,当你为某个项目发布补丁,然后某个核心成员将你的补丁并入项目时,你就是作者,而那个核心成员就是提交者

onelineformat与另一个log选项--graph结合使用时尤其有用。这个选项添加了一些ASCII字符串来形象地展示你的分支、合并历史:

$ git log --pretty=format:"%h %s" --graph
* 2d3acf9 ignore errors from SIGCHLD on trap
* 5e3ee11 Merge branch 'master' of git://github.com/dustin/grit
|\
| * 420eac9 Added a method for getting the current branch.
* | 30e367c timeout code and tests
* | 5a09431 add timeout protection to grit
* | e1193f8 support for heads with slashes in them
|/
* d6016bc require time for xmlschema
* 11d191e Merge branch 'defunkt' into local

Table 2. git log的常用选项

选项 说明
-p 按补丁格式显示每个更新之间的差异。
--stat 显示每次更新的文件修改统计信息。
--shortstat 只显示--stat中最后的行数修改添加移除统计。
--name-only 仅在提交信息后显示已修改的文件清单。
--name-status 显示新增、修改、删除的文件清单。
--abbrev-commit 仅显示 SHA-1 的前几个字符,而非所有的 40 个字符。
--relative-date 使用较短的相对时间显示(比如,“2 weeks ago”)。
--graph 显示 ASCII 图形表示的分支合并历史。
--pretty 使用其他格式显示历史提交信息。可用的选项包括onelineshortfullfullerformat(后跟指定格式)。

限制输出长度

-<n>选项表示仅显示最近的若干条提交,其中的n可以是任何整数。不过实践中我们是不太用这个选项的,Git 在输出所有提交时会自动调用分页程序,所以你一次只会看到一页的内容。

另外还有按照时间作限制的选项,比如--since--until。例如,下面的命令列出所有最近两周内的提交:

git log --since=2.weeks

这个命令可以在多种格式下工作,比如说具体的某一天 “2008-01-15”,或者是相对地多久以前 “2 years 1 day 3 minutes ago”。

--author选项显示指定作者的提交,用--grep选项搜索提交说明中的关键字。(请注意,如果要得到同时满足这两个选项搜索条件的提交,就必须用--all-match选项。否则,满足任意一个条件的提交都会被匹配出来)

另一个非常有用的筛选选项是-S,可以列出那些添加或移除了某些字符串的提交。比如说,你想找出添加或移除了某一个特定函数的引用的提交,你可以这样使用:

git log -Sfunction_name

最后一个很实用的git log选项是路径(path),如果只关心某些文件或者目录的历史提交,可以在git log选项的最后指定它们的路径。因为是放在最后位置上的选项,所以用两个短划线(–)隔开之前的选项和后面限定的路径名。

Table 3. 限制git log输出的选项

选项 说明
-(n) 仅显示最近的n条提交
--since, --after 仅显示指定时间之后的提交。
--until, --before 仅显示指定时间之前的提交。
--author 仅显示指定作者相关的提交。
--committer 仅显示指定提交者相关的提交。
--grep 仅显示含指定关键字的提交
-S 仅显示添加或移除了某个关键字的提交

撤消操作

在任何一个阶段,你都有可能想要撤消某些操作。这里,我们将会学习几个撤消你所做修改的基本工具。注意,有些撤消操作是不可逆的。这是在使用 Git 的过程中,会因为操作失误而导致之前的工作丢失的少有的几个地方之一。

有时候我们提交完了才发现漏掉了几个文件没有添加,或者提交信息写错了。此时,可以运行带有--amend选项的提交命令尝试重新提交:

git commit --amend

这个命令会将暂存区中的文件提交。如果自上次提交以来你还未做任何修改(例如,在上次提交后马上执行了此命令),那么快照会保持不变,而你所修改的只是提交信息。

文本编辑器启动后,可以看到之前的提交信息。编辑后保存会覆盖原来的提交信息。

例如,你提交后发现忘记了暂存某些需要的修改,可以像下面这样操作:

git commit -m 'initial commit'
git add forgotten_file
git commit --amend

最终你只会有一个提交 - 第二次提交将代替第一次提交的结果。

取消暂存的文件

接下来的两个小节演示如何操作暂存区域与工作目录中已修改的文件。这些命令在修改文件状态的同时,也会提示如何撤消操作。例如,你已经修改了两个文件并且想要将它们作为两次独立的修改提交,但是却意外地输入了git add *暂存了它们两个。如何只取消暂存两个中的一个呢?

使用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 commit:
  (use "git add <file>..." to update what will be committed)
  (use "git checkout -- <file>..." to discard changes in working
directory)
    modified: CONTRIBUTING.md

(虽然在调用时加上--hard选项可以令git reset成为一个危险的命令(可能导致工作目录中所有当前进度丢失!),但本例中工作目录内的文件并不会被修改。不加选项地调用git reset并不危险 — 它只会修改暂存区域。)

撤消对文件的修改

git checkout -- <file>撤消修改 - 将它还原成上次提交时的样子(或者刚克隆完的样子,或者刚把它放入工作目录时的样子)

远程仓库的使用

查看远程仓库

git remote命令用于查看你已经配置的远程仓库服务器,它会列出你指定的每一个远程服务器的简写。如果你已经克隆了自己的仓库,那么至少应该能看到 origin(这是 Git 给你克隆的仓库服务器的默认名字)

选项-v,会显示需要读写远程仓库使用的 Git 保存的简写与其对应的 URL。如果远程仓库不止一个,该命令会将它们全部列出。例如,与几个协作者合作的,拥有多个远程仓库的仓库看起来像下面这样:

bakkdoor https://github.com/bakkdoor/grit (fetch)
bakkdoor https://github.com/bakkdoor/grit (push)
cho45 https://github.com/cho45/grit (fetch)
cho45 https://github.com/cho45/grit (push)
defunkt https://github.com/defunkt/grit (fetch)
defunkt https://github.com/defunkt/grit (push)
koke git://github.com/koke/grit.git (fetch)
koke git://github.com/koke/grit.git (push)
origin git@github.com:mojombo/grit.git (fetch)
origin git@github.com:mojombo/grit.git (push)

添加远程仓库

运行git remote add <shortname> <url>添加一个新的远程 Git 仓库,同时指定一个你可以轻松引用的简写,例如:

$ git remote
origin
$ git remote add pb https://github.com/paulboone/ticgit
$ git remote -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
remote: Counting objects: 43, done.
remote: Compressing objects: 100% (36/36), done.
remote: Total 43 (delta 10), reused 31 (delta 5)
Unpacking objects: 100% (43/43), done.
From https://github.com/paulboone/ticgit
 * [new branch] master -> pb/master
 * [new branch] ticgit -> pb/ticgit

现在 Paul 的 master 分支可以在本地通过pb/master访问到

从远程仓库中抓取与拉取

从远程仓库中获得数据,可以执行:

git fetch [remote-name]

这个命令会访问远程仓库,从中拉取所有你还没有的数据。执行完成后,你将会拥有那个远程仓库中所有分支的引用,可以随时合并或查看。

如果你使用 clone 命令克隆了一个仓库,命令会自动将其添加为远程仓库并默认以 “origin” 为简写。所以,git fetch origin会抓取克隆(或上一次抓取)后新推送的所有工作。必须注意git fetch命令会将数据拉取到你的本地仓库(它并不会自动合并或修改你当前的工作。当准备好时你必须手动将其合并入你的工作)。

如果你有一个分支设置为跟踪一个远程分支,可以使用git pull命令来自动的抓取然后合并远程分支到当前分支。这对你来说可能是一个更简单或更舒服的工作流程;默认情况下,git clone命令会自动设置本地master分支跟踪克隆的远程仓库的master分支(或不管是什么名字的默认分支)。运行git pull通常会从最初克隆的服务器上抓取数据并自动尝试合并到当前所在的分支。

推送到远程仓库

当你想分享你的项目时,必须将其推送到上游。这个命令很简单:

git push [remote-name] [branchname]

当你想要将master分支推送到origin服务器时(再次说明,克隆时通常会自动帮你设置好那两个名字),那么运行这个命令就可以将你所做的备份到服务器:

git push origin master

只有当你有所克隆服务器的写入权限,并且之前没有人推送过时,这条命令才能生效。当你和其他人在同一时间克隆,他们先推送到上游然后你再推送到上游,你的推送就会毫无疑问地被拒绝。你必须先将他们的工作拉取下来并将其合并进你的工作后才能推送。

查看远程仓库

如果想要查看某一个远程仓库的更多信息,可以使用git remote show [remote-name]命令。

远程仓库的移除与重命名

如果想要重命名引用的名字可以运行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 中列出已有的标签是非常简单直观的。只需要输入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
v1.8.5-rc3
v1.8.5.1
v1.8.5.2
v1.8.5.3
v1.8.5.4
v1.8.5.5

创建标签

Git 使用两种主要类型的标签:轻量标签(lightweight)与附注标签(annotated):

  • 一个轻量标签很像一个不会改变的分支 - 它只是一个特定提交的引用。

  • 附注标签是存储在 Git 数据库中的一个完整对象。它们是可以被校验的;其中包含打标签者的名字、电子邮件地址、日期时间;还有一个标签信息;并且可以使用 GNU Privacy Guard(GPG)签名与验证。

通常建议创建附注标签,这样你可以拥有以上所有信息;但是如果你只是想用一个临时的标签,或者因为某些原因不想要保存那些信息,轻量标签也是可用的。

附注标签

在 Git 中创建一个附注标签是很简单的。最简单的方式是当你在运行tag命令时指定-a选项:

$ 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 version number

输出显示了打标签者的信息、打标签的日期时间、附注信息,然后显示具体的提交信息。

轻量标签

另一种给提交打标签的方式是使用轻量标签。轻量标签本质上是将提交校验和存储到一个文件中 - 没有保存任何其他信息。创建轻量标签,不需要使用-a-s-m选项,只需要提供标签名字:

$ git tag v1.4-lw
$ git tag
v0.1
v1.3
v1.4
v1.4-lw
v1.5

这时,如果在标签上运行git show,你不会看到额外的标签信息。命令只会显示出提交信息:

$ git show v1.4-lw
commit ca82a6dff817ec66f44342007202690a93763949
Author: Scott Chacon <schacon@gee-mail.com>
Date: Mon Mar 17 21:52:11 2008 -0700
    changed the version number

后期打标签

你也可以对过去的提交打标签。假设提交历史是这样的:

$ git log --pretty=oneline
15027957951b64cf874c3557a0f3547bd83b3ff6 Merge branch 'experiment'
a6b4c97498bd301d84096da251c98a07c7723e65 beginning write support
0d52aaab4479697da7686c15f77a3d64d9165190 one more thing
6d52a271eda8725415634dd79daabbc4d9b6008e Merge branch 'experiment'
0b7434d86859cc7b8c3d5e1dddfed66ff742fcbc added a commit function
4682c3261057305bdd616e23b64b0857d832627b added a todo file
166ae0c4d3f420721acbb115cc33848dfcc2121a started write support
9fceb02d0ae598e95dc970b74767f19372d61af8 updated rakefile
964f16d36dfccde844893cac5b347e7b3d44abbc commit the todo
8a5cbc430f1a9c3d00faaeffd07798508422908a updated readme

现在,假设在 v1.2 时你忘记给项目打标签,也就是在 “updated rakefile” 提交。你可以在之后补上标签。要在那个提交上打标签,你需要在命令的末尾指定提交的校验和(或部分校验和):

git tag -a v1.2 9fceb02

可以看到你已经在那次提交上打上标签了:

$ git tag
v0.1
v1.2
v1.3
v1.4
v1.4-lw
v1.5
$ git show v1.2
tag v1.2
Tagger: Scott Chacon <schacon@gee-mail.com>
Date: Mon Feb 9 15:32:16 2009 -0800
version 1.2
commit 9fceb02d0ae598e95dc970b74767f19372d61af8
Author: Magnus Chacon <mchacon@gee-mail.com>
Date: Sun Apr 27 20:43:35 2008 -0700
    updated rakefile
...

共享标签

默认情况下,git push 命令并不会传送标签到远程仓库服务器上。在创建完标签后你必须显式地推送标签到共享服务器上。这个过程就像共享远程分支一样 - 你可以运行git push origin [tagname]

$ 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/simplegit.git
 * [new tag] v1.5 -> v1.5

如果想要一次性推送很多标签,也可以使用带有--tags选项的git push命令。这将会把所有不在远程仓库服务器上的标签全部传送到那里。

$ git push origin --tags
Counting objects: 1, done.
Writing objects: 100% (1/1), 160 bytes | 0 bytes/s, done.
Total 1 (delta 0), reused 0 (delta 0)
To git@github.com:schacon/simplegit.git
 * [new tag] v1.4 -> v1.4
 * [new tag] v1.4-lw -> v1.4-lw

现在,当其他人从仓库中克隆或拉取,他们也能得到你的那些标签。

检出标签

在 Git 中你并不能真的检出一个标签,因为它们并不能像分支一样来回移动。如果你想要工作目录与仓库中特定的标签版本完全一样,可以使用git checkout -b [branchname] [tagname]在特定的标签上创建一个新分支:

$ git checkout -b version2 v2.0.0
Switched to a new branch 'version2'

当然,如果在这之后又进行了一次提交,version2 分支会因为改动向前移动了,那么 version2 分支就会和 v2.0.0 标签稍微有些不同,这时就应该当心了。

Git 别名

在我们结束本章 Git 基础之前,正好有一个小技巧可以使你的 Git 体验更简单、容易、熟悉:别名。我们不会在之后的章节中引用到或假定你使用过它们,但是你大概应该知道如何使用它们。

Git 并不会在你输入部分命令时自动推断出你想要的命令。如果不想每次都输入完整的 Git 命令,可以通过git config文件来轻松地为每一个命令设置一个别名。这里有一些例子你可以试试:

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 commit时,只需要输入git ci。随着你继续不断地使用 Git,可能也会经常使用其他命令,所以创建别名时不要犹豫。在创建你认为应该存在的命令时这个技术会很有用。例如,为了解决取消暂存文件的易用性问题,可以向 Git 中添加你自己的取消暂存别名:

git config --global alias.unstage 'reset HEAD --'

这会使下面的两个命令等价:

git unstage fileA
git reset HEAD -- fileA

这样看起来更清楚一些。通常也会添加一个 last 命令,像这样:

git config --global alias.last 'log -1 HEAD'

这样,可以轻松地看到最后一次提交:

$ git last
commit 66938dae3329c7aebe598c2246a8e6af90d04646
Author: Josh Goebel <dreamer3@example.com>
Date: Tue Aug 26 19:48:51 2008 +0800
    test for current head
    Signed-off-by: Scott Chacon <schacon@example.com>

可以看出,Git 只是简单地将别名替换为对应的命令。然而,你可能想要执行外部命令,而不是一个 Git 子命令。如果是那样的话,可以在命令前面加入 ! 符号。如果你自己要写一些与 Git 仓库协作的工具的话,那会很有用。我们现在演示将 git visual 定义为 gitk 的别名:

git config --global alias.visual '!gitk'