简体中文 ▾ 主题 ▾ 最新版本 ▾ gittutorial 上次更新于 2.54.0

名称

gittutorial - Git 教程入门

概要

git *

描述

本教程介绍了如何将新项目导入 Git,对其进行更改,以及如何与其他开发者共享这些更改。

如果你主要感兴趣的是使用 Git 来获取项目(例如,为了测试最新版本),你可能更愿意从《Git 用户手册》的前两章开始阅读。

首先,请注意你可以获取类似 git log --graph 命令的文档,方法如下:

$ man git-log

$ git help log

使用后者,你可以使用你选择的任何手册查看器;更多信息请参阅 git-help[1]

在进行任何操作之前,最好先通过姓名和公开电子邮箱地址向 Git 进行自我介绍。最简单的方法是:

$ git config --global user.name "Your Name Comes Here"
$ git config --global user.email you@yourdomain.example.com

导入新项目

假设你有一个包含初始工作的 tar 包 project.tar.gz。你可以按照以下步骤将其置于 Git 版本控制之下。

$ tar xzf project.tar.gz
$ cd project
$ git init

Git 会回复:

Initialized empty Git repository in .git/

你现在已经初始化了工作目录——你可能会注意到创建了一个名为 .git 的新目录。

接下来,使用 git add 命令告诉 Git 对当前目录下所有文件的内容进行快照(注意那个 .):

$ git add .

此快照现已存储在 Git 称为“索引”(index)的临时暂存区中。你可以使用 git commit 将索引中的内容永久存储到仓库中:

$ git commit

这会提示你输入提交信息。你现在已经将项目的第一版存储在 Git 中了。

进行更改

修改一些文件,然后将更新后的内容添加到索引中:

$ git add file1 file2 file3

你现在可以提交了。你可以使用带有 --cached 选项的 git diff 查看即将提交的内容:

$ git diff --cached

(如果不使用 --cachedgit diff 会显示你所做但尚未添加到索引的任何更改。)你还可以使用 git status 获取当前状况的简要摘要:

$ git status
On branch master
Changes to be committed:
  (use "git restore --staged <file>..." to unstage)

	modified:   file1
	modified:   file2
	modified:   file3

如果你需要进行任何进一步调整,请立即进行,然后将任何新修改的内容添加到索引中。最后,通过以下命令提交更改:

$ git commit

这会再次提示你输入描述更改的信息,然后记录项目的一个新版本。

或者,你不必提前运行 git add,而是可以使用:

$ git commit -a

它会自动发现任何已修改(但不是新创建)的文件,将它们添加到索引中,并一步完成提交。

关于提交信息的说明:虽然不是强制要求的,但最好在提交信息的第一行使用一个简短的(不超过 50 个字符)行来总结更改,后跟一个空行,然后再进行更详细的描述。提交信息中第一个空行之前的文本被视为提交标题,该标题会在整个 Git 中使用。例如,git-format-patch[1] 会将提交转换为电子邮件,它会使用标题作为主题行,并将提交的其余部分用作正文。

Git 跟踪的是内容而非文件

许多版本控制系统提供一个 add 命令,告诉系统开始跟踪对新文件的更改。Git 的 add 命令的功能更简单且更强大:git add 既用于新文件也用于新修改的文件,在这两种情况下,它都会对给定文件进行快照并将该内容暂存在索引中,以便包含在下一次提交中。

查看项目历史

在任何时候,你都可以使用以下命令查看更改历史:

$ git log

如果你还想查看每一步的完整差异(diff),请使用:

$ git log -p

通常,更改概览有助于了解每一步的情况:

$ git log --stat --summary

管理分支

单个 Git 仓库可以维护多个开发分支。要创建一个名为 experimental 的新分支,请使用:

$ git branch experimental

如果你现在运行:

$ git branch

你会得到所有现有分支的列表:

  experimental
* master

experimental 分支是你刚刚创建的分支,而 master 分支是自动为你创建的默认分支。星号标出了你当前所在的分支;输入:

$ git switch experimental

切换到 experimental 分支。现在编辑一个文件,提交更改,然后切换回 master 分支:

(edit file)
$ git commit -a
$ git switch master

检查确认你所做的更改不再可见,因为更改是在 experimental 分支上进行的,而你现在回到了 master 分支。

你可以在 master 分支上进行不同的更改:

(edit file)
$ git commit -a

此时两个分支已经分叉,每个分支都有不同的更改。要将 experimental 中的更改合并到 master,请运行:

$ git merge experimental

如果更改没有冲突,就完成了。如果有冲突,有问题的文件中会留下标记显示冲突;

$ git diff

将显示这一点。一旦你编辑了文件以解决冲突:

$ git commit -a

将提交合并的结果。最后:

$ gitk

将显示所得历史记录的精美图形表示。

此时你可以删除 experimental 分支:

$ git branch -d experimental

此命令可确保 experimental 分支中的更改已经存在于当前分支中。

如果你在一个分支 crazy-idea 上开发,然后后悔了,你可以随时删除该分支:

$ git branch -D crazy-idea

分支廉价且易于使用,所以这是一个尝试新想法的好方法。

使用 Git 进行协作

假设 Alice 在 /home/alice/project 中开始了一个带有 Git 仓库的新项目,而 Bob 在同一台机器上有主目录,想要参与贡献。

Bob 首先执行:

bob$ git clone /home/alice/project myrepo

这会创建一个包含 Alice 仓库克隆的新目录 myrepo。这个克隆与原始项目处于同等地位,拥有原始项目历史记录的副本。

然后 Bob 进行了一些更改并提交:

(edit files)
bob$ git commit -a
(repeat as necessary)

当他准备好后,他告诉 Alice 从 /home/bob/myrepo 处的仓库拉取(pull)更改。她执行以下操作:

alice$ cd /home/alice/project
alice$ git pull /home/bob/myrepo master

这会将 Bob 的 master 分支中的更改合并到 Alice 的当前分支中。如果 Alice 同时进行了自己的更改,那么她可能需要手动修复任何冲突。

pull 命令执行两个操作:从远程分支获取更改,然后将它们合并到当前分支。

请注意,通常 Alice 会希望在发起此 pull 之前先提交她的本地更改。如果 Bob 的工作与 Alice 自从他们的历史分叉后所做的工作发生冲突,Alice 将使用她的工作树和索引来解决冲突,现有的本地更改会干扰冲突解决过程(Git 仍会执行获取操作,但会拒绝合并——在这种情况下,Alice 将不得不以某种方式处理掉她的本地更改,然后再次 pull)。

Alice 可以先不合并而只查看 Bob 做了什么,使用 fetch 命令;这允许 Alice 使用特殊符号 FETCH_HEAD 检查 Bob 做了什么,以确定他是否有值得拉取的内容,如下所示:

alice$ git fetch /home/bob/myrepo master
alice$ git log -p HEAD..FETCH_HEAD

即使 Alice 有未提交的本地更改,此操作也是安全的。范围标记 HEAD..FETCH_HEAD 的意思是“显示所有从 FETCH_HEAD 可达但从 HEAD 不可达的内容”。Alice 已经知道所有导致她当前状态(HEAD)的内容,并以此命令查看 Bob 的状态(FETCH_HEAD)中有哪些是她还没见过的。

如果 Alice 想可视化 Bob 自从他们的历史分叉后做了什么,她可以执行以下命令:

$ gitk HEAD..FETCH_HEAD

这使用了我们之前在 git log 中看到的相同的双点范围标记。

Alice 可能想查看他们自从分叉后各自做了什么。她可以使用三点形式而不是双点形式:

$ gitk HEAD...FETCH_HEAD

这意思是“显示所有从其中任何一个可达的内容,但排除从两者都可达的内容”。

请注意,这些范围标记可以同时用于 gitkgit log

在检查了 Bob 的工作后,如果没有紧急事项,Alice 可能决定继续工作而不拉取 Bob 的更改。如果 Bob 的历史确实有 Alice 立刻需要的内容,Alice 可以选择先暂存(stash)她正在进行的工作,执行 pull,最后再在生成的历史记录之上取消暂存(unstash)她的工作。

当你在一个紧密合作的小团队中工作时,反复与同一个仓库交互并不罕见。通过定义远程仓库简写,你可以使这更容易:

alice$ git remote add bob /home/bob/myrepo

有了这个,Alice 可以单独执行 pull 操作的第一部分,使用 git fetch 命令而不将它们与自己的分支合并:

alice$ git fetch bob

与长格式不同,当 Alice 使用通过 git remote 设置的远程仓库简写从 Bob 那里获取时,所获取的内容存储在远程跟踪分支中,在本例中为 bob/master。因此在这之后:

alice$ git log -p master..bob/master

显示 Bob 自从他从 Alice 的 master 分支分叉以来所做的一切更改列表。

检查这些更改后,Alice 可以将这些更改合并到她的 master 分支中:

alice$ git merge bob/master

这个 merge 也可以通过从她自己的远程跟踪分支 pull 来完成,像这样:

alice$ git pull . remotes/bob/master

注意,git pull 总是合并到当前分支,无论命令行上还给出了什么。

稍后,Bob 可以使用以下命令用 Alice 的最新更改更新他的仓库:

bob$ git pull

请注意,他不需要提供 Alice 仓库的路径;当 Bob 克隆 Alice 的仓库时,Git 将她仓库的位置存储在仓库配置中,该位置被用于 pull:

bob$ git config --get remote.origin.url
/home/alice/project

git clone 创建的完整配置可以使用 git config list 查看,git-config[1] 手册页解释了每个选项的含义。)

Git 还保留了 Alice master 分支的原始副本,名称为 origin/master

bob$ git branch -r
  origin/master

如果 Bob 后来决定从不同的主机工作,他仍然可以使用 ssh 协议进行克隆和拉取:

bob$ git clone alice.org:/home/alice/project myrepo

或者,Git 有一种原生协议,也可以使用 http;详情请参阅 git-pull[1]

Git 也可以在类似 CVS 的模式下使用,具有中央仓库,各种用户将更改推送到其中;请参阅 git-push[1]gitcvs-migration[7]

探索历史

Git 历史表示为一系列相互关联的提交。我们已经看到 git log 命令可以列出这些提交。注意,每个 git log 条目的第一行也给出了提交的名称:

$ 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

但还有其他引用提交的方法。你可以使用名称的任何初始部分,只要足够长以唯一标识该提交:

$ git show c82a22c39c	# the first few characters of the name are
			# usually enough
$ git show HEAD		# the tip of the current branch
$ git show experimental	# the tip of the "experimental" branch

每个提交通常都有一个“父”提交,它指向项目的先前状态:

$ git show HEAD^  # to see the parent of HEAD
$ git show HEAD^^ # to see the grandparent of HEAD
$ git show HEAD~4 # to see the great-great grandparent of HEAD

请注意,合并提交可能不止一个父提交:

$ git show HEAD^1 # show the first parent of HEAD (same as HEAD^)
$ git show HEAD^2 # show the second parent of HEAD

你也可以给提交起你自己的名称;在运行:

$ git tag v2.5 1b2e1d63ff

之后,你可以通过名称 v2.5 引用 1b2e1d63ff。如果你打算与他人共享此名称(例如,标识发布版本),则应创建一个“标签”(tag)对象,并可能对其进行签名;详情请参阅 git-tag[1]

任何需要知道提交的 Git 命令都可以接受这些名称中的任何一个。例如:

$ git diff v2.5 HEAD	 # compare the current HEAD to v2.5
$ git branch stable v2.5 # start a new branch named "stable" based
			 # at v2.5
$ git reset --hard HEAD^ # reset your current branch and working
			 # directory to its state at HEAD^

小心使用最后这个命令:除了丢失工作目录中的任何更改外,它还会从该分支中删除所有后续提交。如果该分支是包含这些提交的唯一分支,它们将丢失。此外,不要在其他开发者会 pull 的公开可见的分支上使用 git reset,因为它会强制其他开发者进行不必要的合并以清理历史。如果你需要撤消已推送的更改,请改用 git revert

git grep 命令可以在你项目的任何版本中搜索字符串,所以:

$ git grep "hello" v2.5

搜索 v2.5 中 "hello" 的所有出现次数。

如果你省略提交名称,git grep 将搜索它在当前目录中管理的所有文件。所以:

$ git grep "hello"

是搜索 Git 所跟踪文件的快速方法。

许多 Git 命令也接受提交集,这些可以通过多种方式指定。以下是一些使用 git log 的示例:

$ git log v2.5..v2.6            # commits between v2.5 and v2.6
$ git log v2.5..                # commits since v2.5
$ git log --since="2 weeks ago" # commits from the last 2 weeks
$ git log v2.5.. Makefile       # commits since v2.5 which modify
				# Makefile

你还可以给 git log 一个“范围”的提交,其中第一个不一定是第二个的祖先;例如,如果 stablemaster 分支的顶端在一段时间前从一个共同的提交分叉,那么:

$ git log stable..master

将列出在 master 分支上但不在 stable 分支上的提交,而:

$ git log master..stable

将显示在 stable 分支上但不在 master 分支上的提交列表。

git log 命令有一个弱点:它必须以列表形式呈现提交。当历史记录中有分叉然后又合并在一起的开发线条时,git log 呈现这些提交的顺序是没有意义的。

大多数有多个贡献者的项目(例如 Linux 内核或 Git 本身)经常合并,而 gitk 在可视化其历史方面做得更好。例如:

$ gitk --since="2 weeks ago" drivers/

允许你浏览过去 2 周内修改过 drivers 目录下文件的任何提交。(注意:你可以通过按住 control 键同时按 "-" 或 "+" 来调整 gitk 的字体。)

最后,大多数接受文件名的命令都可以选择允许你在任何文件名之前加上提交,以指定文件的特定版本:

$ git diff v2.5:Makefile HEAD:Makefile.in

你还可以使用 git show 查看任何此类文件:

$ git show v2.5:Makefile

后续步骤

本教程足以为你项目的基本分布式版本控制提供帮助。然而,要完全理解 Git 的深度和威力,你需要理解它所基于的两个简单概念:

  • 对象数据库(Object database)是用于存储项目历史(文件、目录和提交)的相当优雅的系统。

  • 索引文件(Index file)是目录树状态的缓存,用于创建提交、检出工作目录,以及保存合并中涉及的各种树。

本教程的第二部分解释了对象数据库、索引文件以及你需要充分利用 Git 的其他一些零碎知识。你可以在 gittutorial-2[7] 中找到它。

如果你不想马上继续阅读,此时可能感兴趣的其他几个题外话是:

  • git-format-patch[1], git-am[1]:这些将一系列 git 提交转换为电子邮件补丁,反之亦然,对于像 Linux 内核这样严重依赖电子邮件补丁的项目非常有用。

  • git-bisect[1]:当你的项目中出现回归(regression)时,追踪 bug 的一种方法是搜索历史记录以找到罪魁祸首的那个确切提交。git bisect 可以帮你对该提交执行二分查找。即使在具有大量合并分支的复杂非线性历史的情况下,它也足够智能,可以执行接近最优的搜索。

  • gitworkflows[7]:提供了推荐工作流程的概述。

  • giteveryday[7]:日常 Git 大约 20 个命令。

  • gitcvs-migration[7]:面向 CVS 用户的 Git。

GIT

Git[1] 套件的一部分