远程仓库

本章介绍如何浏览项目的历史版本、如何迅速而简单的撤销错误操作、如何向远程仓库推送(push)以及如何从远程仓库拉取(pull)文件。

1. 查看提交历史

可以用以下命令查看提交历史:

git log

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

git log 有许多选项可以帮助你搜寻你所要找的提交,接下来介绍些最常用的。

一个常用的选项是 -p,用来显示每次提交的内容差异。你也可以加上 -2来仅显示最近两次提交:

git log -p -2

该选项除了显示基本信息之外,还附带了每次 commit 的变化。当进行代码审查,或者快浏览某个搭档提交的 commit 所带来的变化的时候,这个参数就非常有用了。你也可以为 git log 附带一系列的总结性选项。比如说,如果你想看每次提交的简略的统计信息,可以使用 --stat 选项:

git log --stat

--stat 选项在每次提交的下面额外列出所有被修改过的文件、有多少文件被修改了以及被修改过的文件的哪些行被移除或是添加了。在每次提交的最后还有一个总结。

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

git log --pretty=oneline

或者直接使用:

git log --oneline

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

git log --pretty=format:"%h - %an, %ar : %s"

%h 表示提交对象的简短哈希字串,%an 表示作者的名字,%ar 表示作者修订日期,%s 表示提交说明。

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

git log --pretty=format:"%h %s" --graph

1.1 限制输出长度

之前已经看到过 -2 了,它只显示最近的两条提交,实际上,这是 -n 选项的写法。

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

git log --since=2.weeks

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

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

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

git log -Sfunction_name

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

git log -- README

来看一个实际例子,如果要查看 git 仓库中,2008 年 10 月期间,Junio Hamano 提交的但未合并的测试文件,可以用下面的查询命令:

git log --pretty="%h %s" --author=gitster --since="2008-10-01" --before="2008-11-01" --no-merges -- t

2. 撤销操作

在任何一个阶段,你都有可能想要撤销某些操作。注意,有些撤销操作是不可逆的。这是在使用 git 的过程中,会因为操作失误而导致之前的工作丢失的少有的几个地方之一。

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

git commit --amend

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

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

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

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

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

2.1 取消暂存的文件

例如,你已经修改了两个文件并且想要将它们作为两次独立的修改提交,但是却意外的输入了 git add * 暂存了它们两个。如何只取消暂存两个中的一个呢?使用 git reset HEAD <file> 来取消暂存:

git reset HEAD CONTRIBUTING.md

CONTRIBUTING.md 文件已经是修改未暂存的状态了。

友情提醒:

虽然在调用时加上 --hard 选项可以令 git reset 成为一个危险的命令,但在这里工作目录内的文件并不会被修改。不加选项的调用 git reset 并不危险 —— 它只会修改暂存区域。

2.2 撤销对文件的修改

如果并不想保留对 CONTIBUTING.md 文件的修改怎么办?如何方便的撤销修改 —— 将它还原成上次提交时的样子(或者刚克隆完时的样子,或者刚把它放入工作目录时的样子)?使用 git checkout -- <file> 命令:

git checkout -- CONTRIBUTING.md

重要提醒:

你需要知道 git checkout -- [file] 是一个危险的命令,这很重要。你对那个文件做的任何修改都会消失 —— 只是拷贝了另一个文件来覆盖它。除非你确实清楚不想要那个文件了,否则不要使用这个命令。

如果你仍然想保留对那个文件做出的修改,但是现在仍然需要撤销,我们将会在 git分支介绍保存进度与分支,这些通常是更好的做法。

记住,在 git 中任何 已提交的 东西几乎总是可以恢复的。甚至那些被删除的分支中的提交或使用 --amend 选项覆盖的提交也可以恢复。然而,你未提交的东西丢失后很可能再也找不到了。

3. 远程仓库的使用

为了能在任意 git 项目上协作,你需要知道如何管理自己的远程仓库。远程仓库是指托管在因特网或其他网络中的你的项目的版本库。你可以有好几个远程仓库,通常有些仓库只对你只读,有些则可以读写。与他人协作涉及管理远程仓库以及根据需要推送或拉取数据。管理远程仓库包括了解如何添加远程仓库、移除无效的远程仓库、管理不同的远程分支并定义它们是否被跟踪等等。

3.1 查看远程仓库

如果想查看你已配置的远程仓库服务器,可以运行 git remote 命令。

它会列出你指定的每一个远程服务器的简写。如果你已经克隆了自己的仓库,那么至少应该能看到 origin —— 这是 git 给你克隆的仓库的服务器的默认名字:

git clone https://github.com/schacon/ticgit
git remote
origin

你也可以指定选项 -v ,会显示需要读写远程仓库使用的 git 保存的简写与其对应的 URL 。

git remote -v
origin https://github.com/schacon/ticgit (fetch)
origin https://github.com/schacon/ticgit (push)

如果你的远程仓库不止一个,该命令会将它们全部列出。这样我们可以轻松拉取其中任何一个用户的贡献。

3.2 添加远程仓库

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

git remote add pd https://github.com/paulboone/ticgit

现在你可以在命令行中使用字符串 pb 来代替整个 URL 。例如,如果你想拉取 Paul 的仓库中有但你没有的信息,可以运行:

git fetch pb

现在 Paul 的 master 分支可以在本地通过 pb/master 访问到 —— 你可以将它合并到自己的某个分支中,或者如果你想要查看它的话,可以检出一个指向该点的本地分支。

3.3 从远程仓库中抓取与拉取

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

git fetch [remote-name]

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

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

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

3.4 推送到远程仓库

当你想分享你的项目时,必须将其推送到上游。这个命令很简单:git push [remote-name] [branch-name]。当你想要将 master 分支推送到 origin 服务器时(再次说明,克隆时通常会自动帮你设置好那两个名字),那么运行这个命令就可以将你所做的备份到服务器:

git push origin master

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

3.5 查看远程仓库

如果想要查看某一个远程仓库的更多信息,可以使用 git remote show [remote-name] 命令。如果想以一个特定的缩写名运行这个命令,例如 origin,会得到类似下面的信息:

git remote show origin
* remote 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 remote master
  Local ref configured for 'git push':
    master pushes to master (up to date)

它同样会列出远程仓库的 URL 与跟踪分支的信息。这些信息非常有用,它告诉你正处于 master 分支,并且如果运行 git pull,就会抓取所有的远程引用,然后将远程 master 分支合并到本地 master 分支。它也会列出拉取到的所有远程引用。

这是一个经常遇到的简单例子。如果你是 git 的重度使用者,那么还可以通过 git remote show 看到更多的信息。

git remote show origin
* remote origin
  URL: https://github.com/my-org/complex-project
  Fetch URL: https://github.com/my-org/complex-project
  Push URL: https://github.com/my-org/complex-project
  HEAD branch: master
  Remote branches:
    master tracked
    dev-branch tracked
    markdown-strip tracked
    issue-43 new (next fetch will store in
remotes/origin)
    issue-45 new (next fetch will store in
remotes/origin)
    refs/remotes/origin/issue-11 stale (use 'git remote prune' to
remove)
  Local branches configured for 'git pull':
    dev-branch merges with remote dev-branch
    master merges with remote master
  Local refs configured for 'git push':
    dev-branch pushes to dev-branch (up to date)
    markdown-strip pushes to markdown-strip (up to date)
    master pushes to master (up to date)

这个命令列出了当你在特定的分支上执行 git push 会自动的推送到哪一个远程分支。它也同样的列出了哪些远程分支不在你的本地,哪些远程分支已经从服务器上移除了,还有当你执行 git pull 时哪些分支会自动合并。

3.6 远程仓库的移除与重命名

如果想要重命名引用的名字可以运行 git remote rename 去修改一个远程仓库的简写名。例如,想要将 pb 重命名为 paul ,可以这样做:

git remote rename pb paul

值得注意的是这同样也会修改你的远程分支名字。那些过去引用 pb/master 的现在会引用 paul/master

如果因为一些原因想要移除一个远程仓库 —— 你已经从服务器上搬走了或不再想使用某一个特定的镜像了,又或者某一个贡献者不再贡献了 —— 可以使用 git remote rm

git remote rm paul

4. 打标签

git 可以给历史中的某一个提交打上标签,以示重要。比较有代表性的是人们会使用这个功能来标记发布节点(v1.0 等等)。

4.1 列出标签

在 git 中列出已有的标签是非常简单直观的。

git tag

这个命令以字母顺序列出标签;但是它们出现的顺序并不重要。

你也可以使用特定的模式查找标签。例如,git 自身的源代码仓库包含标签的数量超过 500 个。如果只对 1.8.5 系列感兴趣,可以运行:

git tag -l 'v1.8.5*'

4.2 创建标签

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

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

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

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

4.2.1 附注标签

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

git tag -a v1.4 -m 'my version 1.4'

-m 选项指定了一条将会存储在标签中的信息。如果没有为附注标签指定一条信息,git 会运行编辑器要求你输入信息。

可以通过 git show 命令看到标签信息与对应的提交信息:

git show v1.4

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

4.2.2 轻量标签

轻量标签本质上是将提交校验和存储到一个文件中 —— 没有保存任何其他信息。创建轻量标签,只需要提供标签名字:

git tag v1.4-lw

这时,如果在标签上运行 git show,只会显示出提交信息。

4.2.3 后期打标签

你也可以对过去的提交打标签。要在哪个提交上打标签,只需要在命令的末尾指定提交的校验和(或部分校验和):

git tag -a v1.2 9fceb02

4.3 共享标签

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

git push origin v1.5

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

git push origin --tags

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

4.4 检出标签

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

git checkout -b version2 v2.0.0

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

5. 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 config --global alias.unstage 'reset HEAD --'

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

git unstage fileA
git reset HEAD -- fileA

可以看出,git 只是简单的将别名替换为对应的命令。然而,你可能想要执行外部命令,而不是一个 git 子命令。如果是那样的话,可以在命令前加入 ! 符号。比如将 git visual 定义为 gitk 的别名:

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