目录 搜索
Guides gitattributes giteveryday gitglossary gitignore gitmodules gitrevisions gittutorial gitworkflows Administration git archive git bundle git clean git filter-branch git fsck git gc git instaweb git reflog Basic Snapshotting git add git commit git diff git mv git reset git rm git status Branching and Merging git branch git checkout git log git merge git mergetool git stash git tag Debugging git bisect git blame git grep Email git am git format-patch git request-pull git send-email External Systems git fast-import git svn Getting and Creating Projects git clone git init Git git annotate git archimport git bisect-lk2009 git check-attr git check-mailmap git check-ref-format git checkout-index git cherry git citool git column git credential git credential-cache git credential-store git cvsexportcommit git cvsimport git cvsserver git diff-files git diff-tree git difftool git fast-export git fetch-pack git fmt-merge-msg git get-tar-commit-id git gui git http-backend git http-fetch git http-push git imap-send git index-pack git interpret-trailers git ls-remote git ls-tree git mailinfo git mailsplit git merge-file git merge-index git merge-one-file git merge-tree git mktag git mktree git name-rev git notes git p4 git pack-objects git pack-redundant git pack-refs git parse-remote git patch-id git prune git prune-packed git quiltimport git receive-pack git remote-ext git remote-fd git remote-testgit git repack git replace git rerere git send-pack git sh-i18n git sh-setup git shell git show-branch git show-index git stripspace git unpack-file git unpack-objects git upload-archive git upload-pack git var git verify-commit git verify-tag git whatchanged git worktree Inspection and Comparison git describe git shortlog git show Miscellaneous api credentials api index gitcli gitcore tutorial gitcredentials gitcvs migration gitdiffcore githooks gitk gitnamespaces gitremote helpers gitrepository layout gitsubmodules gittutorial 2 gitweb gitweb.conf pack format User Manual Patching git apply git cherry-pick git rebase git revert Plumbing Commands git cat-file git check-ignore git commit-tree git count-objects git diff-index git for-each-ref git hash-object git ls-files git merge-base git read-tree git rev-list git rev-parse git show-ref git symbolic-ref git update-index git update-ref git verify-pack git write-tree Server Admin git daemon git update-server-info Setup and Config git git config git help Sharing and Updating Projects git fetch git pull git push git remote git submodule
文字

Git是一个快速分布式版本控制系统。

本手册旨在让具有基本UNIX命令行技能的人员可读,但以前没有Git知识。

存储库和分支以及探索Git历史解释了如何使用git读取和研究项目 - 阅读这些章节以了解如何构建和测试特定版本的软件项目,搜索回归等。

需要进行实际开发的人员还需要阅读使用Git进行开发并与其他人共享开发。

更多章节涵盖更多专业主题。

综合参考文档可通过手册页或git-help [1]命令获得。例如,对于git clone <repo>命令,您可以使用:

$ man git-clone

要么:

$ git help clone

使用后者,您可以使用您选择的手动查看器;请参阅git-help [1]了解更多信息。

有关Git命令的简要概述,另请参见附录A:Git快速参考,不作任何说明。

最后,请参阅附录B:本手册的注释和待办事项列表,了解可帮助您完善本手册的方法。

如何获得git存储库

在阅读本手册时,让Git存储库进行试验会很有用。

获取其中一个的最好方法是使用git-clone [1]命令下载现有存储库的副本。如果你还没有一个项目,这里有一些有趣的例子:

        # Git itself (approx. 40MB download):$ git clone git://git.kernel.org/pub/scm/git/git.git
        # the Linux kernel (approx. 640MB download):$ git clone git://git.kernel.org/pub/scm/linux/kernel/git/torvalds/linux.git

对于大型项目来说,初始克隆可能非常耗时,但您只需要克隆一次。

clone命令将创建一个以项目命名的新目录(gitlinux上面的示例中)。在你进入这个目录之后,你会看到它包含了一个名为工作树的项目文件的副本,以及一个名为的特殊顶级目录.git,其中包含有关项目历史的所有信息。

如何检出不同版本的项目

Git最好被认为是存储文件集合历史的工具。它将历史记录存储为项目内容的相关快照的压缩集合。在Git中,每个这样的版本都被称为提交。

这些快照不一定都是从最老到最新排列成一行,相反,工作可以沿着并行的发展路线同时进行,称为分支,这可能会合并和分化。

单个Git存储库可以跟踪多个分支上的开发。它通过保存引用每个分支上最新提交的头的列表来实现这一点;git-branch[1]命令会显示分支头的列表:

$ git branch* master

新克隆的存储库包含一个单独的分支头,默认名为“master”,工作目录初始化为该分支头引用的项目的状态。

大多数项目也使用标签。像heads一样,标签是对项目历史的引用,可以使用git-tag [1]命令列出:

$ git tag -l
v2.6.11v2.6.11-tree
v2.6.12v2.6.12-rc2
v2.6.12-rc3
v2.6.12-rc4
v2.6.12-rc5
v2.6.12-rc6
v2.6.13...

预计标签总是指向相同版本的项目,而标签预计会随着开发的进展而提前。

创建一个指向其中一个版本的新分支头,并使用git-checkout [1]检出它:

$ git checkout -b new v2.6.13

然后工作目录反映了项目标签为v2.6.13时的内容,而git-branch[1]显示了两个分支,并标有当前签出分支的星号:

$ git branch
  master* new

如果您更愿意看到版本2.6.17,则可以修改当前分支,使其指向v2.6.17

$ git reset --hard v2.6.17

请注意,如果当前分支头是您对历史中某个特定点的唯一引用,那么重置该分支可能使您无法找到用于指向的历史记录;所以请谨慎使用此命令。

Understanding history: commits

项目历史的每一个变化都由一个提交表示。git-show[1]命令显示当前分支上最近的提交:

$ git show
commit 17cf781661e6d38f737f15f53ab552f1e95960d7
Author: Linus Torvalds <torvalds@ppc970.osdl.org.(none)>Date:   Tue Apr 19 14:11:06 2005 -0700

    Remove duplicate getenv(DB_ENVIRONMENT) call

    Noted by Tony Luck.diff --git a/init-db.c b/init-db.c
index 65898fa..b002dc6 100644--- a/init-db.c+++ b/init-db.c
@@ -7,7 +7,7 @@

 int main(int argc, char **argv) {-        char *sha1_dir = getenv(DB_ENVIRONMENT), *path;+        char *sha1_dir, *path;
         int len, i;         if (mkdir(".git", 0755) < 0) {

正如你所看到的,一个提交表明谁做出了最新的改变,他们做了什么以及为什么。

每个提交都有一个40位十六进制的id,有时称为“对象名称”或“SHA-1 id”,显示在git show输出的第一行。您通常可以使用较短的名称(如标签或分支名称)引用提交,但此较长的名称也可能有用。最重要的是,它是这个提交的一个全球唯一的名称:所以如果你告诉其他人的对象名称(例如在电子邮件中),那么你可以保证这个名称将引用他们在你的仓库中提交的同一个提交假设他们的存储库有这个提交)。由于对象名称是作为提交内容的散列来计算的,因此可以保证,提交不会更改,而其名称也不会更改。

实际上,在Git概念中,我们将看到存储在Git历史记录中的所有内容(包括文件数据和目录内容)都存储在名称为内容哈希的对象中。

Understanding history: commits, parents, and reachability

每次提交(除了项目中的第一次提交)还有一个父提交,它显示了在提交之前发生了什么。继父母之后,最终会让你回到项目的开始阶段。

但是,提交并不构成一个简单的列表;Git允许发展路线发散并重新聚合,两条发展重新聚合的点被称为“合并”。因此,表示合并的提交可以有多个父代,每个父代表代表最近一次提交到该点的开发线上的提交。

看看这是如何工作的最好方法是使用gitk[1]命令; 现在在Git存储库上运行gitk并寻找合并提交将有助于理解Git如何组织历史记录。

在下文中,我们说如果提交X是提交Y的祖先,那么提交X就是来自提交Y的“可达”。相等地,可以说Y是X的后代,或者存在由提交提交的父项链Y提交X.

Understanding history: History diagrams

我们有时会使用下面的图表来表示Git历史记录。提交被显示为“o”,并且它们之间的连接与用 -  /和绘制的线条之间的链接。时间从左到右:

         o--o--o <-- Branch A        /
 o--o--o <-- master
        \
         o--o--o <-- Branch B

如果我们需要谈论某个特定的提交,可以用另一个字母或数字替换字符“o”。

Understanding history: What is a branch?

当我们需要精确时,我们将使用“分支”一词来表示一条开发线,“分支头”(或者“头”)意味着对分支上最近提交的引用。在上面的例子中,名为“A”的分支头是指向一个特定提交的指针,但我们将引向该点的三个提交的行称为“分支A”的一部分。

但是,如果不会造成混淆,我们通常只是在分支机构和分支机构使用“分支”一词。

Manipulating branches

创建,删除和修改分支是快速而简单的;以下是这些命令的摘要:

git branch

列出所有分支。

git branch <branch>

创建一个名为的新分支<branch>,引用历史上与当前分支相同的点。

git branch <branch> <start-point>

创建一个名为<branch>referencing 的新分支<start-point>,它可以用任何你喜欢的方式指定,包括使用分支名称或标签名称。

git branch -d <branch>

删除分支<branch>; 如果分支未在其上游分支中完全合并或包含在当前分支中,则此命令将失败并显示警告。

git branch -D <branch>

<branch>不管其合并状态如何,都要删除该分支。

git checkout <branch>

使当前分支<branch>,更新工作目录以反映引用的版本<branch>

git checkout -b <new> <start-point>

创建一个新的分支<new>引用<start-point>,并检查出来。

特殊符号“HEAD”总是可以用来指代当前分支。事实上,Git使用目录HEAD中的一个文件.git来记住哪个分支是当前的:

$ cat .git/HEAD
ref: refs/heads/master

Examining an old version without creating a new branch

git checkout命令通常期望分支头,但也会接受任意提交;例如,您可以检出由标签引用的提交:

$ git checkout v2.6.17Note: checking out 'v2.6.17'.You are in 'detached HEAD' state. You can look around, make experimental
changes and commit them, and you can discard any commits you make in thisstate without impacting any branches by performing another checkout.If you want to create a new branch to retain commits you create, you maydo so (now or later) by using -b with the checkout command again. Example:

  git checkout -b new_branch_name

HEAD is now at 427abfa... Linux v2.6.17

HEAD然后引用提交的SHA-1而不是分支,并且git分支显示你不再在分支上:

$ cat .git/HEAD
427abfa28afedffadfca9dd8b067eb6d36bac53f
$ git branch* (detached from v2.6.17)
  master

在这种情况下,我们说HEAD是“分离的”。

这是检查特定版本的简单方法,无需为新分支创建一个名称。如果你决定,你仍然可以为这个版本创建一个新的分支(或标签)。

Examining branches from a remote repository

您克隆时创建的“master”分支是您从克隆的存储库中的HEAD副本。但是,该存储库可能还有其他分支机构,并且您的本地存储库会保留跟踪每个远程分支的分支,称为远程跟踪分支,您可以使用-rgit-branch[1]选项查看远程分支:

$ git branch -r
  origin/HEAD
  origin/html
  origin/maint
  origin/man
  origin/master
  origin/next
  origin/pu
  origin/todo

在这个例子中,“origin”被称为远程存储库,简称“remote”。从我们的角度来看,这个存储库的分支被称为“远程分支”。上面列出的远程跟踪分支是在克隆时基于远程分支创建的,并将由git fetch(因此git pull)和git push。有关详细信息,请参阅使用git获取更新存储库。

您可能想要在您自己的分支上构建这些远程跟踪分支之一,就像您使用标记一样:

$ git checkout -b my-todo-copy origin/todo

您也可以origin/todo直接退出查看或编写一次性补丁。看到分离的head。

请注意,名称“origin”只是Git默认使用的名称,用于引用您从中克隆的存储库。

Naming branches, tags, and other references

分支,远程跟踪分支和标签都是对提交的引用。所有引用都以斜杠分隔的路径名称开头refs;我们迄今使用的名字实际上是简写:

  • 该分支test是短暂的refs/heads/test

  • 标签v2.6.18是简短的refs/tags/v2.6.18

  • origin/master是简短的refs/remotes/origin/master

例如,如果存在标签和具有相同名称的分支,则全名偶尔是有用的。

(新创建的refs实际上是存储在.git/refs目录下的,在它们的名字给出的路径下。但是,出于效率的原因,它们也可能被打包在一个文件中;请参阅git-pack-refs [1])。

作为另一个有用的捷径,可以仅使用该存储库的名称来引用存储库的“HEAD”。因此,例如,“origin”通常是存储库“origin”中HEAD分支的快捷方式。

有关Git检查引用的完整路径列表,以及它用于确定在具有多个具有相同简写名称的引用时要选择哪个的顺序,请参阅gitrevisions [7]的“指定修订”部分。

Updating a repository with git fetch

在克隆存储库并对自己进行一些更改后,您可能希望检查原始存储库以获取更新。

git-fetch命令不带任何参数,会将所有远程跟踪分支更新为原始存储库中找到的最新版本。它不会触及你自己的任何分支,甚至不会触及你为克隆创建的“master”分支。

Fetching branches from other repositories

您还可以使用git-remote[1]跟踪您从克隆的存储库以外的分支:

$ git remote add staging git://git.kernel.org/.../gregkh/staging.git
$ git fetch staging...From git://git.kernel.org/pub/scm/linux/kernel/git/gregkh/staging * [new branch]      master     -> staging/master * [new branch]      staging-linus -> staging/staging-linus * [new branch]      staging-next -> staging/staging-next

staging这种情况下,新的远程跟踪分支将以您提供的git remote add简写名称进行存储:

$ git branch -r
  origin/HEAD -> origin/master
  origin/master
  staging/master
  staging/staging-linus
  staging/staging-next

如果git fetch <remote>稍后运行,则该名称的远程跟踪分支<remote>将被更新。

如果您检查.git/config文件,您会看到Git添加了一个新节:

$ cat .git/config...[remote "staging"]
        url = git://git.kernel.org/pub/scm/linux/kernel/git/gregkh/staging.git
        fetch = +refs/heads/*:refs/remotes/staging/*
...

这就是Git跟踪远程分支的原因。您可以通过.git/config使用文本编辑器进行编辑来修改或删除这些配置选项。(有关详细信息,请参阅git-config [1]的“CONFIGURATION FILE”部分。)

Git最好被认为是存储文件集合历史的工具。它通过存储文件层次结构内容的压缩快照以及显示这些快照之间关系的“commits”来实现。

Git为探索项目的历史提供了非常灵活和快速的工具。

我们从一个专用工具开始,该工具对于找到将错误引入到项目中的提交很有用。

How to use bisect to find a regression

假设你的项目的2.6.18版本可以工作,但是“master”版本崩溃。有时候找到这种回归的最好方法就是对项目历史进行强力搜索,以找到造成问题的特定提交。git-bisect [1]命令可以帮助你做到这一点:

$ git bisect start
$ git bisect good v2.6.18$ git bisect bad master
Bisecting: 3537 revisions left to test after this[65934a9a028b88e83e2b0f8b36618fe503349f8e] BLOCK: Make USB storage depend on SCSI rather than selecting it [try #6]

如果你在这一点上运行git branch,你会发现Git暂时将你移到了“(无分支)”。HEAD现在与任何分支分离并直接指向可从“主”访问但不从v2.6.18访问的提交(提交ID为65934 ...)。编译并测试它,看它是否崩溃。假设它确实崩溃。然后:

$ git bisect bad
Bisecting: 1769 revisions left to test after this[7eff82c8b1511017ae605f0c99ac275a7e21b867] i2c-core: Drop useless bitmaskings

检出一个较旧的版本。继续这样,在每个阶段告诉Git它给你的版本是好还是坏,并且注意每次剩下的测试修订版本大约减半。

经过大约13次测试(在这种情况下),它会输出有罪提交的提交ID。然后,您可以使用git-show[1]检查提交,找出是谁编写的,并使用提交ID将错误报告发送给他们。最后,运行

$ git bisect reset

让你回到你以前的分支。

请注意,git bisect每个点为您提供的版本只是一个建议,如果您认为这是一个好主意,您可以自由尝试不同的版本。例如,偶尔你可能会出现一个违反了无关的承诺;运行

$ git bisect visualize

它会运行gitk并用一个标记“bisect”标记它所选择的提交。选择一个安全的提交附近,注意它的提交ID,并检查出来:

$ git reset --hard fb47ddb2db...

然后测试,适当运行bisect goodbisect bad,然后继续。

不是关于git bisect visualize以及git reset --hard fb47ddb2db...,你可能只想告诉Git你想跳过当前的提交:

$ git bisect skip

然而,在这种情况下,Git最终可能无法告诉第一个跳过的提交和后来的错误提交之间的第一个错误。

如果你有一个测试脚本可以从不好的commit中分辨出好的,那么也有自动化二分过程的方法。有关此git bisect功能和其他功能的更多信息,请参见git-bisect[1] 。

Naming commits

我们已经看到了几种命名提交方式:

  • 40位十六进制对象名称

  • 分支名称:指给定分支头部的提交

  • 标签名称:指给定标签指向的提交(我们已经看到分支和标签是引用的特例)。

  • HEAD:指当前分支的负责人

还有更多;请参阅gitrevisions[7]手册页的“指定修订”一节以获取修订名称的完整列表。一些例子:

$ git show fb47ddb2 # the first few characters of the object name
                    # are usually enough to specify it uniquely
$ git show HEAD^    # the parent of the HEAD commit
$ git show HEAD^^   # the grandparent
$ git show HEAD~4   # the great-great-grandparent

回想一下,合并提交可能有多个父代;默认情况下,^~按照提交列出的第一个父,但你也可以选择:

$ git show HEAD^1   # show the first parent of HEAD
$ git show HEAD^2   # show the second parent of HEAD

除HEAD外,还有其他几个提交的特殊名称:

合并(稍后将讨论)以及诸如git reset更改当前签出提交的操作通常将ORIG_HEAD设置为HEAD在当前操作之前具有的值。

git fetch操作始终将最后获取的分支的头部存储在FETCH_HEAD中。例如,如果您运行时git fetch未指定本地分支作为操作的目标

$ git fetch git://example.com/proj.git theirbranch

提取的提交仍然可以从FETCH_HEAD获得。

当我们讨论合并时,我们还会看到特殊名称MERGE_HEAD,它指向我们正在合并到当前分支中的另一个分支。

git-rev-parse[1]命令是一个低级命令,它偶尔用于将提交的某个名称转换为该提交的对象名称:

$ git rev-parse origin
e05db0fd4f31dde7005f075a84f96b360d05984b

Creating tags

我们也可以创建一个标签来引用特定的提交;运行完之后

$ git tag stable-1 1b2e1d63ff

您可以使用stable-1引用提交1b2e1d63ff。

这会创建一个“轻量级”标签。如果您还想在标签中加入评论,并可能以加密方式对其进行签名,那么您应该创建一个标签对象;有关详细信息,请参阅git-tag[1]手册页。

Browsing revisions

git-log[1]命令可以显示提交列表。它自己显示所有从父提交可访问的提交;但您也可以提出更具体的要求:

$ git log v2.5..        # commits since (not reachable from) v2.5$ git log test..master        # commits reachable from master but not test
$ git log master..test        # ...reachable from test but not master
$ git log master...test        # ...reachable from either test or master,
                        #    but not both
$ git log --since="2 weeks ago" # commits from the last 2 weeks
$ git log Makefile      # commits which modify Makefile
$ git log fs/                # ... which modify any file under fs/$ git log -S'foo()'        # commits which add or remove any file data
                        # matching the string 'foo()'

当然,你可以将所有这些结合起来;以下查找自v2.5以来提交的提交Makefile或其中的任何文件的提交fs

$ git log v2.5.. Makefile fs/

你也可以让git log来显示补丁:

$ git log -p

请参阅--prettygit-log [1]手册页中的选项以获取更多显示选项。

请注意,git日志从最近的提交开始并通过父项向后工作;然而,由于Git历史可以包含多个独立的开发线,因此提交的特定顺序可能有些随意。

Generating diffs

您可以使用git-diff[1]在任何两个版本之间生成差异:

$ git diff master..test

这会在两个分支的尖端之间产生差异。如果你想从他们的共同祖先找到差异来测试,你可以使用三个点而不是两个:

$ git diff master...test

有时你想要的是一组补丁; 为此你可以使用git-format-patch[1]:

$ git format-patch master..test

将生成一个带有补丁的文件,用于从测试可达的每个提交,但不是来自主站。

Viewing old file versions

您可以通过首先检查正确的版本来查看旧版本的文件。但有时候,查看单个文件的旧版本而不检查任何内容会更方便;这个命令做到这一点:

$ git show v2.5:fs/locks.c

在冒号可以是任何提交提交命令之前,并且可以是由Git跟踪的文件的任何路径。

Examples

计算分支上的提交数量

假设你想知道你已经提交了几次的mybranch,因为它从origin中分出:

$ git log --pretty=oneline origin..mybranch | wc -l

或者,您可能经常会看到这种事情用下级命令git-rev-list [1]完成,它只列出所有提交的SHA-1:

$ git rev-list origin..mybranch | wc -l

检查两个分支是否指向相同的历史记录

假设你想检查两个分支是否指向历史中的同一个点。

$ git diff origin..master

会告诉你这两个分支的项目内容是否相同;然而,从理论上讲,相同的项目内容可能是由两条不同的历史路线实现的。您可以比较对象名称:

$ git rev-list origin
e05db0fd4f31dde7005f075a84f96b360d05984b
$ git rev-list master
e05db0fd4f31dde7005f075a84f96b360d05984b

或者你可以回想一下,...操作员选择从一个引用或另一个引用可访问的所有提交,但不能同时提交;所以

$ git log origin...master

当两个分支相等时将不返回提交。

查找包含给定修正的第一个标记版本

假设你知道提交e05db0fd修复了某个问题。您希望找到包含该修补程序的最早标签发布。

当然,可能有多个答案 - 如果历史记录在提交e05db0fd后分支,那么可能会有多个“最早的”标签发布。

自e05db0fd以来,您可以直观地检查提交:

$ gitk e05db0fd..

或者你可以使用git-name-rev[1],它会根据它发现的指向提交的后代之一的任何标记为提交提供一个名称:

$ git name-rev --tags e05db0fd
e05db0fd tags/v1.5.0-rc1^0~23

git-describe[1]命令的作用与此相反,它使用给定提交所基于的标记来命名修订:

$ git describe e05db0fd
v1.5.0-rc0-260-ge05db0f

但是这有时可以帮助您猜测给定提交后可能会出现哪些标记。

如果你只是想验证给定的标签版本是否包含给定的提交,你可以使用git-merge-base[1]:

$ git merge-base e05db0fd v1.5.0-rc1
e05db0fd4f31dde7005f075a84f96b360d05984b

merge-base命令找到给定提交的共同祖先,并且在其中一个是另一个的后代的情况下总是返回一个或另一个; 所以上面的输出显示e05db0fd实际上是v1.5.0-rc1的祖先。

或者,请注意

$ git log v1.5.0-rc1..e05db0fd

当且仅当v1.5.0-rc1包含e05db0fd时才会产生空输出,因为它仅输出从v1.5.0-rc1无法访问的提交。

作为另一种选择,git-show-branch[1]命令列出了可从其参数访问的提交,并在左侧显示了一个显示,指示可以从哪个参数提交。所以,如果你运行类似的东西

$ git show-branch e05db0fd v1.5.0-rc0 v1.5.0-rc1 v1.5.0-rc2! [e05db0fd] Fix warnings in sha1_file.c - use C99 printf format ifavailable ! [v1.5.0-rc0] GIT v1.5.0 preview  ! [v1.5.0-rc1] GIT v1.5.0-rc1   ! [v1.5.0-rc2] GIT v1.5.0-rc2...

然后一行代码,比如

+ ++ [e05db0fd] Fix warnings in sha1_file.c - use C99 printf format ifavailable

显示e05db0fd可以从v1.5.0-rc1和v1.5.0-rc2自身获得,而不是从v1.5.0-rc0获得。

显示给定分支独有的提交

假设您希望看到所有提交的commits都可以从名为master的branch head中获得,但是不会从存储库中的任何其他head上提交。

我们可以用git-show-ref[1]列出这个仓库中的所有head:

$ git show-ref --heads
bf62196b5e363d73353a9dcf094c59595f3153b7 refs/heads/core-tutorial
db768d5504c1bb46f63ee9d6e1772bd047e05bf9 refs/heads/maint
a07157ac624b2524a059a3414e99f6f44bebc1e7 refs/heads/master
24dbc180ea14dc1aebe09f14c8ecf32010690627 refs/heads/tutorial-21e87486ae06626c2f31eaa63d26fc0fd646c8af2 refs/heads/tutorial-fixes

我们只能得到分支机构的名称,并在标准实用程序cut和grep的帮助下删除master

$ git show-ref --heads | cut -d' ' -f2 | grep -v '^refs/heads/master'refs/heads/core-tutorial
refs/heads/maint
refs/heads/tutorial-2refs/heads/tutorial-fixes

然后,我们可以要求查看所有可以从主服务器获得的提交,但不能从这些其他的提交者提交:

$ gitk master --not $( git show-ref --heads | cut -d' ' -f2 |
                                grep -v '^refs/heads/master' )

Obviously, endless variations are possible; for example, to see all commits reachable from some head but not from any tag in the repository:

$ gitk $( git show-ref --heads ) --not  $( git show-ref --tags )

(有关提交选择语法的解释,请参阅gitrevisions [7] --not。)

Creating a changelog and tarball for a software release

git-archive[1]命令可以从任何版本的项目中创建一个tar或zip存档;例如:

$ git archive -o latest.tar.gz --prefix=project/ HEAD

将使用HEAD生成一个gzipped tar归档文件,其中每个文件名前都是project/。如果可能的话,输出文件格式是从输出文件扩展名中推断出来的,详情参见git-archive[1]。

版本高于1.7.7的Git不知道tar.gz格式,您需要明确使用gzip:

$ git archive --format=tar --prefix=project/ HEAD | gzip >latest.tar.gz

如果你正在发布一个新版本的软件项目,你可能需要同时做一个更新日志以包含在发布公告中。

例如,Linus Torvalds通过标记新内核版本,然后运行:

$ release-script 2.6.12 2.6.13-rc6 2.6.13-rc7

其中release-script是一个外壳脚本,如下所示:

#!/bin/sh
stable="$1"last="$2"new="$3"echo "# git tag v$new"echo "git archive --prefix=linux-$new/ v$new | gzip -9 > ../linux-$new.tar.gz"echo "git diff v$stable v$new | gzip -9 > ../patch-$new.gz"echo "git log --no-merges v$new ^v$last > ../ChangeLog-$new"echo "git shortlog --no-merges v$new ^v$last > ../ShortLog"echo "git diff --stat --summary -M v$last v$new > ../diffstat-$new"

然后他在验证他们看起来确定后剪切并粘贴输出命令。

查找提交给定内容的文件的提交

有人给你一个文件的副本,并询问哪个提交修改了一个文件,以便它在提交之前或之后包含给定的内容。你可以找到这个:

$  git log --raw --abbrev=40 --pretty=oneline |
        grep -B 1 `git hash-object filename`

弄清楚为什么这个作品留给(高级)学生练习。git-log[1],git-diff-tree[1]和git-hash-object[1]手册页可能会有帮助。

Telling git your name

在创建任何提交之前,您应该向Git介绍自己。最简单的方法是使用git-config[1]:

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

这会将以下内容添加到.gitconfig您的主目录中指定的文件中:

[user]
        name = Your Name Comes Here
        email = you@yourdomain.example.com

有关配置文件的详细信息,请参阅git-config[1]的“CONFIGURATION FILE”部分。该文件是纯文本,所以你也可以用你最喜欢的编辑器编辑它。

Creating a new repository

从头开始创建一个新的存储库非常简单:

$ mkdir project
$ cd project
$ git init

如果你有一些初始内容(比如tarball):

$ tar xzvf project.tar.gz
$ cd project
$ git init
$ git add . # include everything below ./ in the first commit:$ git commit

How to make a commit

创建一个新的提交需要三个步骤:

  1. 使用您最喜爱的编辑器对工作目录进行一些更改。

  2. 告诉Git你的变化。

  3. 使用您在步骤2中告诉Git的内容创建提交。

在实践中,您可以根据需要多次交错并重复步骤1和2:为了跟踪步骤3中要执行的操作,Git将树的内容快照保存在一个称为“索引“。

在开始时,索引的内容将与HEAD的内容相同。因此git diff --cached命令显示了HEAD和索引之间的差异,因此该点不应该输出。

修改索引很简单:

要使用新文件或修改文件的内容更新索引,请使用

$ git add path/to/file

要从索引和工作树中删除文件,请使用

$ git rm path/to/file

每一步之后,您都可以验证

$ git diff --cached

总是显示HEAD和索引文件之间的区别 - 如果您现在创建了提交,那么这就是您要提交的内容

$ git diff

显示了工作树和索引文件之间的区别。

请注意,git add总是只将文件的当前内容添加到索引;除非您git add再次在文件上运行,否则对相同文件的进一步更改将被忽略。

当你准备好了,就运行

$ git commit

Git会提示你提交一个提交消息,然后创建新的提交。检查以确保它看起来像你期望的一样

$ git show

作为一种特殊的捷径,

$ git commit -a

将使用您修改或删除的任何文件更新索引并创建提交,全部一步完成。

许多命令对于跟踪您即将提交的内容很有用:

$ git diff --cached # difference between HEAD and the index; what
                    # would be committed if you ran "commit" now.$ git diff            # difference between the index file and your
                    # working directory; changes that would not
                    # be included if you ran "commit" now.$ git diff HEAD            # difference between HEAD and working tree; what
                    # would be committed if you ran "commit -a" now.$ git status            # a brief per-file summary of the above.

您还可以使用git-gui[1]创建提交,查看索引和工作树文件中的更改,并单独选择包含在索引中的差异hunk(通过右键单击diff hunk并选择“Stage Hunk For承诺”)。

Creating good commit messages

虽然不是必需的,但最好先用一个简短的(少于50个字符)行来概述变化,然后再用空行和更彻底的描述来开始提交消息。直到提交消息中的第一个空行的文本被视为提交标题,并且该标题在整个Git中使用。例如,git-format-patch[1]将提交转换为电子邮件,并使用主题行上的标题和正文中的其余提交。

Ignoring files

一个项目通常会生成你想用Git跟踪的文件。这通常包括由构建过程生成的文件或编辑器创建的临时备份文件。当然,not使用Git跟踪文件只是一个not调用git add它们的问题。但是,如果有这些没有跟踪的文件,它很快就会变得烦人。例如他们git add .几乎没用,而且他们一直在输出git status

你可以通过创建一个.gitignore在你的工作目录的顶层调用的文件来告诉Git忽略某些文件,其内容如下:

# Lines starting with '#' are considered comments.# Ignore any file named foo.txt.foo.txt
# Ignore (generated) html files,*.html
# except foo.html which is maintained by hand.!foo.html
# Ignore objects and archives.*.[oa]

See gitignore[5] for a detailed explanation of the syntax. You can also place .gitignore files in other directories in your working tree, and they will apply to those directories and their subdirectories. The .gitignore files can be added to your repository like any other files (just run git add .gitignore and git commit, as usual), which is convenient when the exclude patterns (such as patterns matching build output files) would also make sense for other users who clone your repository.

如果希望排除模式仅影响某些存储库(而不是某个给定项目的每个存储库),则可以将它们放入名为repository的文件中.git/info/exclude,或存放在core.excludesFile配置变量指定的任何文件中。一些Git命令也可以直接在命令行上使用排除模式。有关详细信息,请参阅gitignore[5]。

How to merge

你可以使用git-merge[1]重新加入两个不同的开发分支:

$ git merge branchname

将分支中的开发合并branchname到当前分支中。

合并是通过将branchname自己的历史记录分叉以来对当前分支中的最新提交进行的更改和更改进行合并而完成的。合并结果完全合并时,工作树将被合并结果覆盖,或者当此合并导致冲突时,合并结果被半合并结果覆盖。因此,如果您有未提交的更改触及与受合并影响的文件相同的文件,Git将拒绝继续。大多数情况下,你会希望在你合并之前提交你的修改,如果你不合并,那么git-stash[1]可以在你合并的时候去掉这些修改,然后重新应用它们。

如果更改足够独立,Git将自动完成合并并提交结果(或者在快进时重用现有提交,请参见下文)。另一方面,如果存在冲突 - 例如,如果在远程分支和本地分支中以两种不同方式修改相同的文件,则会发出警告; 输出可能看起来像这样:

$ git merge next 100% (4/4) done
Auto-merged file.txtCONFLICT (content): Merge conflict in file.txt
Automatic merge failed; fix conflicts and then commit the result.

冲突标记留在有问题的文件中,手动解决冲突后,可以使用内容更新索引并运行Git commit,就像通常在创建新文件时一样。

如果你使用gitk检查结果提交,你会看到它有两个父母,一个指向当前分支的顶部,另一个指向另一个分支的顶部。

Resolving a merge

当合并未自动解析时,Git会将索引和工作树置于特殊状态,为您提供帮助解决合并所需的所有信息。

有冲突的文件会在索引中专门标记,所以直到您解决问题并更新索引时,git-commit[1]将失败:

$ git commit
file.txt: needs merge

此外,git-status[1]会将这些文件列为“unmerged”,并且冲突文件将添加冲突标记,如下所示:

<<<<<<< HEAD:file.txt
Hello world=======Goodbye>>>>>>> 77976da35a11db4580b80ae27e8d65caf5208086:file.txt

您只需编辑文件来解决冲突,然后

$ git add file.txt
$ git commit

请注意,提交消息已经为您填充了关于合并的一些信息。通常情况下,您可以直接使用此默认消息,但如果需要,您可以添加其他注释。

以上是你需要知道解决一个简单的合并。但Git还提供了更多信息来帮助解决冲突:

Getting conflict-resolution help during a merge

Git能够自动合并的所有更改都已添加到索引文件中,所以git-diff[1]仅显示冲突。它使用一种不寻常的语法:

$ git diff
diff --cc file.txt
index 802992c,2b60207..0000000--- a/file.txt+++ b/file.txt
@@@ -1,1 -1,1 +1,5 @@@++<<<<<<< HEAD:file.txt +Hello world++=======+ Goodbye++>>>>>>> 77976da35a11db4580b80ae27e8d65caf5208086:file.txt

回想一下,在我们解决这个冲突后将会犯下的承诺将有两个父母而不是通常的父母:一个父母将是HEAD,即当前分支的尖端;另一个将是另一个分支的尖端,它暂时存储在MERGE_HEAD中。

在合并过程中,索引包含每个文件的三个版本。这三个“文件阶段”中的每一个代表文件的不同版本:

$ git show :1:file.txt        # the file in a common ancestor of both branches
$ git show :2:file.txt        # the version from HEAD.$ git show :3:file.txt        # the version from MERGE_HEAD.

当您要求git-diff[1]显示冲突时,它会在工作树中冲突的合并结果与阶段2和阶段3之间运行三向差异,以便仅显示其内容来自两侧的混合,混合(在其他话说,当一个大块的合并结果只来自第二阶段时,那部分并不相互冲突,没有被显示出来。

上面的diff显示了file.txt的工作树版本与阶段2和阶段3版本之间的差异。因此,而不是由单个的每一行前述的+-,它现在使用两列:第一列被用于第一个亲本和工作目录副本,并且所述第二对第二个亲本和工作目录副本之间的差异之间的差异。(有关格式的详细信息,请参阅git-diff-files[1]的“组合差异格式”部分)。

在以明显的方式解决冲突之后(但在更新索引之前),差异将如下所示:

$ git diff
diff --cc file.txt
index 802992c,2b60207..0000000--- a/file.txt+++ b/file.txt
@@@ -1,1 -1,1 +1,1 @@@- Hello world -Goodbye++Goodbye world

这表明我们的解决方案从第一个父母中删除了“Hello world”,从第二个父母中删除了“Goodbye”,并添加了“Goodbye world”,这在以前都没有。

一些特殊的差异选项允许将工作目录与以下任何阶段进行比较:

$ git diff -1 file.txt                # diff against stage 1$ git diff --base file.txt        # same as the above
$ git diff -2 file.txt                # diff against stage 2$ git diff --ours file.txt        # same as the above
$ git diff -3 file.txt                # diff against stage 3$ git diff --theirs file.txt        # same as the above.

git-log[1]和gitk[1]命令还为合并提供了特殊帮助:

$ git log --merge
$ gitk --merge

这些将显示仅存在于HEAD或MERGE_HEAD上的所有提交,以及哪些提交触及未合并的文件。

您也可以使用git-mergetool[1],它可以让您使用外部工具(如Emacs或kdiff3)合并未合并的文件。

每次解决文件中的冲突并更新索引时:

$ git add file.txt

该文件的不同阶段将被“折叠”,之后git diff将(默认情况下)不再显示该文件的差异。

Undoing a merge

如果您陷入困境并决定放弃并丢掉整个烂摊子,您可以随时通过返回到预合并状态

$ git reset --hard HEAD

或者,如果您已经完成了想要丢弃的合并,

$ git reset --hard ORIG_HEAD

然而,在某些情况下,这最后一条命令可能会很危险 - 如果该提交本身已被合并到另一个分支中,则永远不会丢弃已提交的提交,因为这样做可能会混淆进一步的合并。

Fast-forward merges

有一种特殊情况没有在上面提到,对待的方式不同。通常情况下,合并会导致合并提交,其中有两位父母,一位指向合并的两条开发线中的每一条。

但是,如果当前分支是另一个分支的祖先,那么当前分支中的每个提交都已经包含在另一个分支中,那么Git只执行一个“快进”。当前分支的头部向前移动以指向合并分支的头部,而不创建任何新的提交。

Fixing mistakes

如果你搞错了工作树,但还没有犯下错误,你可以将整个工作树返回到上次提交状态

$ git reset --hard HEAD

如果您稍后希望自己没有做出承诺,则有两种根本不同的方法可以解决该问题:

  1. 您可以创建一个新的提交,撤消旧提交所做的任何操作。如果你的错误已经公开,这是正确的。

  2. 您可以返回并修改旧的提交。如果您已经公开历史,您绝不应该这样做;Git通常不会期望项目的“历史”发生变化,并且无法正确执行历史更改的分支的重复合并。

Fixing a mistake with a new commit

创建一个新的提交以恢复先前的更改非常简单;只要通过git-revert[1]命令引用坏提交;例如,恢复最近的提交:

$ git revert HEAD

这将创建一个新的提交,以解除HEAD中的更改。您将有机会编辑新提交的提交消息。

您还可以恢复先前的更改,例如倒数第二个更改:

$ git revert HEAD^

在这种情况下,Git将尝试撤销旧的更改,同时保留自此之后所做的任何更改。如果最近的更改与要恢复的更改重叠,那么您将被要求手动修复冲突,就像解决合并一样。

Fixing a mistake by rewriting history

如果有问题的提交是最近的提交,并且你还没有公开提交,那么你可以使用它来销毁git reset

或者,您可以编辑工作目录并更新索引以修复您的错误,就像您要创建新的提交一样,然后运行

$ git commit --amend

它将用一个新的提交来代替旧的提交,这个提交包含了你的改变,让你有机会首先编辑旧的提交信息。

同样,你也不应该对已经被合并到另一个分支的提交做这件事;在这种情况下使用git-revert[1]。

在历史中还可以取代进一步的提交,但这是一个高级话题,可以留给另一章。

Checking out an old version of a file

在撤消先前的错误更改的过程中,您可能会发现使用git-checkout[1]检出特定文件的旧版本很有用。我们之前使用过git checkout切换分支,但如果给出路径名称,则它具有完全不同的行为:命令

$ git checkout HEAD^ path/to/file

用它在提交HEAD^中的内容替换path/to/file,并且更新索引以匹配。它不会改变分支。

如果您只想查看旧版本的文件,而无需修改工作目录,则可以使用git-show[1]来执行此操作:

$ git show HEAD^:path/to/file

它将显示给定版本的文件。

Temporarily setting aside work in progress

当你处理一些复杂的事情时,你会发现一个不相关但显而易见的小问题。您想在继续之前解决它。您可以使用git-stash[1]保存工作的当前状态,并在修复错误之后(或者,在不同的分支上执行此操作后再选择,然后返回),清除正在进行的更改。

$ git stash save "work in progress for foo feature"

该命令会将更改保存到stash,并重置您的工作树和索引以匹配当前分支的提示。然后,你可以像往常一样进行修复。

... edit and test ...$ git commit -a -m "blorpl: typofix"

之后,你可以回到你正在处理的git stash pop上:

$ git stash pop

Ensuring good performance

在大型软件仓库中,Git依靠压缩来保持历史信息不占用磁盘或内存中的太多空间。一些Git命令可能会自动运行git-gc[1],所以你不必担心手动运行它。但是,压缩大型存储库可能需要一段时间,因此您可能需要gc明确调用,以避免在不方便时自动压缩。

Ensuring reliability

Checking the repository for corruption

git-fsck[1]命令在存储库上运行一些自我一致性检查,并报告任何问题。这可能要花点时间。

$ git fsck
dangling commit 7281251ddd2a61e38657c827739c57015671a6b3
dangling commit 2706a059f258c6b245f298dc4ff2ccd30ec21a63
dangling commit 13472b7c4b80851a1bc551779171dcb03655e9b5
dangling blob 218761f9d90712d37a9c5e36f406f92202db07eb
dangling commit bf093535a34a4d35731aa2bd90fe6b176302f14f
dangling commit 8e4bec7f2ddaa268bef999853c25755452100f8e
dangling tree d50bb86186bf27b681d25af89d3b5b68382e4085
dangling tree b24c2473f1fd3d91352a624795be026d64c8841f...

您将看到悬挂对象上的信息消息。它们是仍然存在于存储库中的对象,但不再被任何分支引用,并且可以(并且将在)一段时间后被删除gc。您可以运行git fsck --no-dangling以禁止这些消息,并仍然查看实际错误。

Recovering lost changes

Reflogs

假设你用一个分支来修改分支git reset --hard,然后意识到这个分支是你历史上那个点的唯一参考。

幸运的是,Git还保存了一个日志,称为“reflog”,它包含了每个分支的所有以前的值。因此,在这种情况下,您仍然可以使用旧的历史记录,例如,

$ git log master@{1}

这列出了从master分支头的以前版本可访问的提交。这种语法可以用于接受提交的任何Git命令,而不仅仅用于git log。其他一些例子:

$ git show master@{2}                # See where the branch pointed 2,$ git show master@{3}                # 3, ... changes ago.$ gitk master@{yesterday}        # See where it pointed yesterday,$ gitk master@{"1 week ago"}        # ... or last week
$ git log --walk-reflogs master        # show reflog entries for master

为HEAD保留一个单独的reflog,所以

$ git show HEAD@{"1 week ago"}

将显示一周前HEAD指出的内容,而不是一周前当前分支指出的内容。这可以让你看到你已经签出的历史。

reflog默认保存30天,之后可能会被修剪。有关详细信息,请参阅git-reflog[1]和git-gc[1]以了解如何控制此修剪,并参阅gitrevisions[7]的“指定修订”部分。

请注意,reflog历史与正常的Git历史非常不同。虽然普通历史由同一项目中的每个存储库共享,但不共享reflog历史记录:它仅告诉您本地存储库中的分支如何随时间变化。

Examining dangling objects

在某些情况下,reflog可能无法救你。例如,假设您删除了一个分支,然后意识到您需要它包含的历史记录。reflog也被删除; 但是,如果您还没有修剪存储库,那么您仍然可以在git fsck报告的悬挂对象中找到丢失的提交。有关详细信息,请参阅Dangling objects。

$ git fsck
dangling commit 7281251ddd2a61e38657c827739c57015671a6b3
dangling commit 2706a059f258c6b245f298dc4ff2ccd30ec21a63
dangling commit 13472b7c4b80851a1bc551779171dcb03655e9b5...

例如,您可以检查其中一个dangling提交,

$ gitk 7281251ddd --not --all

它听起来像这样:它表示您希望查看由悬挂提交描述的提交历史记录,而不是所有现有分支和标记描述的历史记录。因此,您可以准确获取从该提交丢失的历史记录。(并注意到它可能不只是一次提交:我们只报告“行末”是悬而未决的,但可能会有一整个深刻而复杂的提交历史被删除。)

如果您决定重新创建历史记录,则始终可以创建一个指向它的新参考,例如新的分支:

$ git branch recovered-branch 7281251ddd

其他类型的悬空物体(斑点和树木)也是可能的,悬挂物体可能会出现在其他情况下。

Getting updates with git pull

克隆一个存储库并自行进行一些更改后,您可能希望检查原始存储库以获取更新并将其合并到您自己的工作中。

我们已经看到如何通过git-fetch[1]保持远程跟踪分支的最新状态,以及如何合并两个分支。因此,您可以将来自原始存储库的主分支的更改与以下内容合并:

$ git fetch
$ git merge origin/master

但是,git-pull[1]命令提供了一种方法来完成这一步:

$ git pull origin master

实际上,如果您已master签出,则此分支已配置git clone为从源存储库的HEAD分支中获取更改。通常你可以用简单的方法完成上述工作

$ git pull

该命令将从远程分支获取更改到远程跟踪分支origin/*,并将默认分支合并到当前分支。

更一般地说,从远程追踪分支创建的分支将默认从该分支中提取。请参阅git-config[1]中的branch.<name>.remotebranch.<name>.merge选项的说明以及--trackgit-checkout[1]中的选项的讨论,以了解如何控制这些默认值。

除了保存键击之外,git pull还可以通过生成默认提交消息来记录您从中提取的分支和存储库,从而帮助您。

(但是请注意,在快进的情况下不会创建这样的提交;相反,您的分支只会更新为指向上游分支的最新提交。)

git pull命令也可以.作为“remote”存储库给出,在这种情况下,它仅合并到当前存储库的分支中;所以命令

$ git pull . branch
$ git merge branch

大致相当。

Submitting patches to a project

如果您只是做了一些更改,最简单的提交方式可能只是将它们发送为电子邮件中的补丁:

首先,使用git-format-patch[1]; 例如:

$ git format-patch origin

将在当前目录中生成一系列编号的文件,其中一个用于当前分支中的每个补丁但不包含在其中origin/HEAD

git format-patch可以包含一个初始的“cover letter”。您可以在提交消息之后但补丁本身之前format-patch放置的三条虚线之后插入对各个补丁的评论。如果您使用git notes跟踪您的cover letter的材料,git format-patch --notes将以类似的方式包含提交的笔记。

然后,您可以将这些导入到您的邮件客户端并手动发送。但是,如果您需要立即发送很多内容,则可能更愿意使用git-send-email[1]脚本来自动执行此过程。首先请咨询您项目的邮件列表以确定他们提交补丁的要求。

Importing patches to a project

Git还提供了一个称为git-am[1](am代表“应用邮箱”)的工具,用于导入通过电子邮件发送的一系列修补程序。只需将所有包含修补程序的消息按顺序保存到一个邮箱文件中patches.mbox,然后运行即可

$ git am -3 patches.mbox

Git将按顺序应用每个补丁;如果发现任何冲突,它将停止,并且您可以按照“解决合并”中所述修复冲突。(该-3选项告诉Git执行合并;如果您希望仅中止并保持树和索引不变,则可以省略该选项。)

一旦索引更新了冲突解决的结果,而不是创建一个新的提交,只需运行

$ git am --continue

Git将为您创建提交并继续应用邮箱中的其余修补程序。

最终结果将是一系列提交,原始邮箱中的每个补丁都有一个提交,每个补丁都包含作者身份和提交日志消息。

Public git repositories

提交项目更改的另一种方式是告诉该项目的维护人员使用git-pull [1]从存储库中提取更改。在“获取更新git pull” 一节中,我们将其描述为从“master”存储库获取更新的方式,但它在另一方面也可以工作。

如果你和维护者都在同一台机器上有帐户,那么你可以直接从对方的存储库中取出更改;接受存储库URL作为参数的命令也将接受本地目录名称:

$ git clone /path/to/repository
$ git pull /path/to/other/repository

或一个ssh URL:

$ git clone ssh://yourhost/~you/repository

对于开发人员很少的项目,或者同步一些私人存储库,这可能就是您所需要的。

但是,更常见的做法是维护一个单独的公共存储库(通常位于不同的主机上)以供其他人从中提取更改。这通常更方便,并且允许您将正在进行的私人工作从公开可见的工作中清理出来。

您将继续在个人存储库中执行日常工作,但会定期将更改从个人存储库“推送”到公共存储库中,从而允许其他开发人员从该存储库中提取。因此,在另一个开发人员使用公共存储库的情况下,更改流程如下所示:

                       you push
 your personal repo ------------------> your public repo^                                     ||                                     || you pull                            | they pull|                                     ||                                     |       |               they push             V
 their public repo <------------------- their repo

我们在下面的章节解释如何做到这一点。

Setting up a public repository

假设您的个人存储库位于该目录中~/proj。我们首先创建一个新的版本库并告诉git daemon它是公开的:

$ git clone --bare ~/proj proj.git
$ touch proj.git/git-daemon-export-ok

生成的目录proj.git包含一个“bare”的git仓库 - 它只是.git目录的内容,没有任何文件检出。

接下来,复制proj.git到您计划托管公共存储库的服务器。您可以使用scp,rsync或其他最方便的方法。

Exporting a Git repository via the Git protocol

这是首选的方法。

如果其他人管理服务器,他们应该告诉你将存储库放在哪个目录中,以及git://它将显示在哪个URL上。然后,您可以跳到下面的“推送对公共存储库的更改”部分。

否则,你需要做的就是启动git-daemon[1]; 它将侦听端口9418.默认情况下,它将允许访问任何类似Git目录的目录,并包含魔术文件git-daemon-export-ok。将某些目录路径作为git daemon参数传递将进一步限制对这些路径的导出。

您也可以运行git daemon作为inetd服务;有关详细信息,请参阅git-daemon[1]手册页。(特别参见示例部分。)

Exporting a git repository via HTTP

Git协议提供了更好的性能和可靠性,但在设置了Web服务器的主机上,HTTP输出可能更容易设置。

您只需将新创建的裸Git存储库放置在由Web服务器导出的目录中,并进行一些调整即可为Web客户端提供他们所需的一些额外信息:

$ mv proj.git /home/you/public_html/proj.git
$ cd proj.git
$ git --bare update-server-info
$ mv hooks/post-update.sample hooks/post-update

(关于最后两行的解释,请参阅git-update-server-info[1]和githooks[5]。)

宣传 proj.git的URL。其他任何人都应该能够从该URL克隆或拉出,例如使用如下命令行:

$ git clone http://yourserver.com/~you/proj.git

(另请参阅setup-git-server-over-http,以获得使用WebDAV的稍微更复杂的设置,该设置还允许推送HTTP。)

Pushing changes to a public repository

请注意,上述两种技术(通过http或git导出)允许其他维护人员获取您的最新更改,但他们不允许写入访问权限,您需要使用私有存储库中创建的最新更改来更新公共存储库。

最简单的方法是使用git-push[1]和ssh;更新名为分支名称master的最新状态的远程分支master,运行

$ git push ssh://yourserver.com/~you/proj.git master:master

要不就

$ git push ssh://yourserver.com/~you/proj.git master

与此同时git fetch将会complain,如果这不会导致快进;有关处理此案例的详细信息,请参阅以下部分。

请注意,push的目标通常是裸仓库。您也可以推送到具有检出工作树的存储库,但默认情况下拒绝更新当前检出分支的推送以防止混淆。有关详细信息,请参阅git-config[1]中的receive.denyCurrentBranch选项的说明。

与此同时git fetch,您还可以设置配置选项以保存输入;所以,例如:

$ git remote add public-repo ssh://yourserver.com/~you/proj.git

将以下内容添加到.git/config

[remote "public-repo"]
        url = yourserver.com:proj.git
        fetch = +refs/heads/*:refs/remotes/example/*

这可以让你用同样的方法来完成

$ git push public-repo master

看到的remote.<name>.urlbranch.<name>.remote以及remote.<name>.push在git-config[1]的信息的选项的解释。

What to do when a push fails

如果推送不会导致远程分支的快进,那么它将失败并出现如下错误:

error: remote 'refs/heads/master' is not an ancestor of
 local  'refs/heads/master'.
 Maybe you are not up-to-date and need to pull first?error: failed to push to 'ssh://yourserver.com/~you/proj.git'

例如,如果您:

  • use git reset --hard to remove already-published commits, or

  • use git commit --amend to replace already-published commits (as in Fixing a mistake by rewriting history), or

  • use git rebase to rebase any already-published commits (as in Keeping a patch series up to date using git rebase).

无论如何,您可以强制git push执行更新,方法是在分支名称前加一个加号:

$ git push ssh://yourserver.com/~you/proj.git +master

请注意+标志的添加。或者,您可以使用-f标志来强制执行远程更新,如下所示:

$ git push -f ssh://yourserver.com/~you/proj.git master

通常,只要公共存储库中的分支头被修改,就会修改它以指向它之前指向的提交的后代。通过在这种情况下强行推进,你打破了这个惯例。(请参阅重写历史记录的问题。)

尽管如此,对于那些需要简单的方式来发布正在进行的补丁系列的人来说,这是一种常见的做法,只要您警告其他开发人员,您打算如何管理该分支,这是一个可以接受的折衷方案。

当其他人有权推送到同一个存储库时,推送也可能以这种方式失败。在这种情况下,正确的解决方案是在第一次更新工作后重试推送:通过拉取或通过提取后跟转置;有关更多信息,请参阅下一节和gitcvs-migration[7]。

Setting up a shared repository

另一种协作方式是使用与CVS中常用的模型相似的模型,其中有几个具有特殊权限的开发人员全部从一个共享存储库进行推送和提取。有关如何设置的说明,请参阅gitcvs-migration[7]。

然而,虽然Git对共享存储库的支持没有任何问题,但这种操作模式通常并不推荐,这仅仅是因为Git支持的协作模式 - 通过交换补丁和从公共存储库中提取 - 相比中央共享存储库:

  • Git快速导入和合并补丁的能力允许单个维护人员即使以非常高的速率处理传入的更改。当这变得太多时,git pull为维护人员提供一个简单的方法,将这项工作委派给其他维护人员,同时允许对传入更改进行可选审查。

  • 由于每个开发人员的存储库都具有相同的项目历史记录副本,因此没有存储库是特殊的,对于另一个开发人员来说,接受项目维护,无论是通过双方同意还是由于维护人员无响应或难以处理。

  • 缺乏一个“committers”的中心组意味着更少需要正式决定谁是“in”和谁是“out”。

Allowing web browsing of a repository

gitweb cgi脚本为用户提供了一种简单的方式来浏览项目的修订版本,文件内容和日志,而无需安装Git。像RSS/Atom提要和责备/注解详细信息的功能可以选择启用。

git-instaweb[1]命令提供了一种使用gitweb开始浏览存储库的简单方法。使用instaweb时的默认服务器是lighttpd。

See the file gitweb/INSTALL in the Git source tree and gitweb[1] for instructions on details setting up a permanent installation with a CGI or Perl capable server.

如何获得具有最小历史记录的git存储库

当一个人只对近期的项目历史感兴趣并从上游获取完整的历史记录是昂贵的时,浅层克隆及其截断的历史记录非常有用。

通过指定git-clone[1]--depth开关创建一个浅层克隆。稍后可以使用git-fetch[1] --depth开关更改深度,或使用恢复完整历史记录--unshallow

只要合并基地在近期的历史中,合并在一个浅层克隆中就行得通。否则,就像合并无关的历史一样,可能不得不导致巨大的冲突。此限制可能会使此类存储库不适合在基于合并的工作流程中使用。

Examples

维护Linux子系统维护者的主题分支

这里描述了Tony Luck如何使用Git作为Linux内核的IA64体系结构的维护者。

他使用两个公共分支机构:

  • 最初放置补丁的“test”树,以便在与其他正在进行的开发集成时可以获得一些曝光。这棵树可供Andrew随时随地pulling into - mm。

  • 一个“release”树,测试补丁移动到其中以进行最终的完整性检查,并作为将它们上游发送到Linus的工具(通过向他发送“pull”请求)。

他还使用一组临时分支(“topic branches”),每个分支都包含一组逻辑修补程序。

要设置,首先克隆Linus的公共树创建你的工作树:

$ git clone git://git.kernel.org/pub/scm/linux/kernel/git/torvalds/linux.git work
$ cd work

Linus的树将存储在名为origin/master的远程跟踪分支中,并且可以使用git-fetch[1]更新;您可以使用git-remote[1]来跟踪其他公共树,以设置“remote”和git-fetch[1]以使其保持最新状态;请参阅存储库和分支。

现在创建你将要工作的分支;这些从最初的origin/master分支开始,并且应该设置(使用--trackgit-branch[1]选项)来默认从Linus合并更改。

$ git branch --track test origin/master
$ git branch --track release origin/master

这些可以很容易地保持最新使用git-pull[1]。

$ git checkout test && git pull
$ git checkout release && git pull

重要的提示!如果您在这些分支中有任何本地更改,则此合并将在历史记录中创建一个提交对象(没有本地更改,Git只会执行“fast-forward”合并)。许多人不喜欢Linux历史中产生的“noise”,所以你应该避免在release分支中这样反复地做,因为当你要求Linus从发行版分支中提取时,这些嘈杂的提交将成为永久历史的一部分。

一些配置变量(参见git-config[1])可以很容易地将两个分支推送到公共树上。(请参阅设置公共存储库。)

$ cat >> .git/config <<EOF[remote "mytree"]
        url =  master.kernel.org:/pub/scm/linux/kernel/git/aegl/linux.git
        push = release
        push = test
EOF

然后你可以使用git-push[1]推送测试和释放树:

$ git push mytree

或者使用以下命令仅推送其中一个测试和发布分支:

$ git push mytree test

要么

$ git push mytree release

现在来申请社区中的一些补丁。考虑一个分支持有这个补丁(或相关补丁组)的简短名称,并从最近的Linus分支的稳定标签创建一个新分支。为你的分支选择一个稳定的基础将会:1)帮助你:避免包含不相关的和可能经过轻微测试的变化2)帮助未来git bisect寻找问题的寻找bug的人

$ git checkout -b speed-up-spinlocks v2.6.35

现在您应用补丁(es),运行一些测试并提交更改(s)。如果修补程序是一个多部分系列,那么您应该将每个修补程序作为单独的提交应用于此分支。

$ ... patch ... test  ... commit [ ... patch ... test ... commit ]*

当您对这种变化的状态感到满意时,您可以将其合并到“test”分支中,以准备公布:

$ git checkout test && git merge speed-up-spinlocks

在这里你不太可能会有任何冲突......但是如果你在这一步上花了一段时间,并且也从上游拉了新版本,你可能会这样。

过了一段时间,当足够的时间过去并且测试完成后,您可以将同一分支拉入release树中,准备向上游。这是您看到将每个补丁(或补丁系列)保留在其自己的分支中的价值的地方。这意味着可以release以任何顺序将补丁移动到树中。

$ git checkout release && git merge speed-up-spinlocks

过了一段时间,你会有很多分支机构,尽管你为每个分支选择了精心挑选的名字,但是你可能会忘记它们的用途,或者它们处于什么状态。为了提醒你有什么变化具体分支,使用:

$ git log linux..branchname | git shortlog

要查看它是否已经合并到测试或发布分支中,请使用:

$ git log test..branchname

要么

$ git log release..branchname

(如果这个分支还没有合并,你会看到一些日志条目,如果它已经合并,那么将不会有输出。)

一旦一个补丁完成了一个伟大的循环(从测试到释放,然后由Linus拉动,最后回到本地origin/master分支),不再需要此更改的分支。您在以下情况下输出时会检测到这一点

$ git log origin..branchname

是空的。此时可以删除分支:

$ git branch -d branchname

有些更改很微不足道,因此不必创建单独的分支,然后合并到每个测试和发布分支中。对于这些更改,直接应用于release分支,然后将其合并到test分支中。

在推进你的工作给mytree之后,你可以使用git-request-pull[1]准备一个“please pull”请求消息发送给Linus:

$ git push mytree
$ git request-pull origin mytree release

以下是一些可以进一步简化这一切的脚本。

==== update script ====# Update a branch in my Git tree.  If the branch to be updated
# is origin, then pull from kernel.org.  Otherwise merge
# origin/master branch into test|release branchcase "$1" intest|release)
        git checkout $1 && git pull . origin        ;;origin)
        before=$(git rev-parse refs/remotes/origin/master)
        git fetch origin
        after=$(git rev-parse refs/remotes/origin/master)        if [ $before != $after ]
        then
                git log $before..$after | git shortlog
        fi        ;;*)
        echo "usage: $0 origin|test|release" 1>&2
        exit 1        ;;esac
==== merge script ====# Merge a branch into either the test or release branch

pname=$0usage(){
        echo "usage: $pname branch test|release" 1>&2
        exit 1}git show-ref -q --verify -- refs/heads/"$1" || {
        echo "Can't see branch <$1>" 1>&2
        usage}case "$2" intest|release)        if [ $(git log $2..$1 | wc -c) -eq 0 ]
        then
                echo $1 already merged into $2 1>&2
                exit 1
        fi
        git checkout $2 && git pull . $1        ;;*)
        usage        ;;esac
==== status script ====# report on status of my ia64 Git tree

gb=$(tput setab 2)rb=$(tput setab 1)restore=$(tput setab 9)if [ `git rev-list test..release | wc -c` -gt 0 ]then
        echo $rb Warning: commits in release that are not in test $restore
        git log test..release
fifor branch in `git show-ref --heads | sed 's|^.*/||'`do        if [ $branch = test -o $branch = release ]
        then                continue
        fi

        echo -n $gb ======= $branch ====== $restore " "
        status=        for ref in test release origin/master        do                if [ `git rev-list $ref..$branch | wc -c` -gt 0 ]
                then
                        status=$status${ref:0:1}
                fi
        done        case $status in
        trl)
                echo $rb Need to pull into test $restore                ;;
        rl)
                echo "In test"                ;;
        l)
                echo "Waiting for linus"                ;;        "")
                echo $rb All done $restore                ;;        *)
                echo $rb "<$status>" $restore                ;;
        esac
        git log origin/master..$branch | git shortlog
done

通常情况下,提交只能添加到项目中,不会被带走或替换。Git是根据这个假设设计的,违反它会导致Git的合并机制(例如)做错误的事情。

但是,有一种情况可能会违反这一假设。

创建完美的补丁系列

假设您是一个大型项目的贡献者,并且您希望添加一个复杂的功能,并以一种方式将其呈现给其他开发人员,使他们可以轻松读取您的更改,验证它们是否正确并理解为什么你做了每个改变。

如果您将所有更改作为单个修补程序(或提交)呈现,则他们可能会发现一次消化所有内容太多。

如果您向他们展示您的工作的整个历史,并完成错误,更正和死路一条,他们可能会不知所措。

所以理想的做法通常是制作一系列的补丁:

  1. 每个补丁都可以按顺序应用。

  2. 每个修补程序都包含一个逻辑更改,以及一条解释更改的消息。

  3. 没有修补程序引入回归:在应用系列的任何初始部分之后,生成的项目仍然编译并运行,并且没有以前没有的错误。

  4. 整个系列产生与您自己的(可能更加混乱!)开发过程相同的最终结果。

我们将介绍一些工具,可以帮助您做到这一点,解释如何使用它们,然后解释一些因重写历史而可能出现的问题。

使用git rebase保持最新的补丁系列

假设您mywork在远程跟踪分支上origin创建分支,并在其上创建一些提交:

$ git checkout -b mywork origin
$ vi file.txt
$ git commit
$ vi otherfile.txt
$ git commit...

你没有对mywork进行合并,所以它只是在origin上一个简单的线性补丁序列:

 o--o--O <-- origin
        \
         a--b--c <-- mywork

在上游项目中已经完成了一些更有趣的工作,并且origin已经提出:

 o--o--O--o--o--o <-- origin
        \
         a--b--c <-- mywork

此时,您可以使用pull合并您的更改回来;结果会创建一个新的合并提交,如下所示:

 o--o--O--o--o--o <-- origin
        \        \
         a--b--c--m <-- mywork

但是,如果您希望将历史记录保存在mywork中而不进行任何合并,您可以选择使用git-rebase[1]:

$ git checkout mywork
$ git rebase origin

这将从mywork中删除每个提交,将其临时保存为补丁(位于名为的目录中.git/rebase-apply),更新mywork以指向最新版本的源,然后将每个已保存的补丁应用于新的mywork。结果将如下所示:

 o--o--O--o--o--o <-- origin
                 \
                  a'--b'--c' <-- mywork

在这个过程中,它可能会发现冲突。在这种情况下,它将停止并让你解决冲突;修复冲突之后,使用git add这些内容更新索引,然后运行git commit,而不是运行

$ git rebase --continue

Git将继续应用其他补丁。

在任何时候,您都可以使用--abort选项中止此过程,并将我的作品返回到您开始重新绑定之前的状态:

$ git rebase --abort

如果您需要在分支中重新排序或编辑多个提交,它可能更易于使用git rebase -i,这允许您重新排序和压缩提交,并在重新绑定期间将它们标记为单独编辑。有关详细信息,请参阅使用交互式资料库和重新排序或从补丁系列中选择替代方法。

重写一个提交

我们通过重写历史记录来看到修复错误,您可以使用最近的提交替换

$ git commit --amend

它将用一个新的提交来代替旧的提交,这个提交包含了你的改变,让你有机会首先编辑旧的提交信息。这对于修正上次提交中的拼写错误或调整不良阶段提交的补丁内容非常有用。

如果您需要从历史记录的更深层次修改提交,则可以使用交互式rebase的edit说明。

重新排序或从补丁系列中选择

有时候你想在历史中更深入地编辑一个提交。一种方法是使用git format-patch创建一系列修补程序,然后将状态重置为修补程序之前:

$ git format-patch origin
$ git reset --hard origin

然后在使用git-am[1]再次应用补丁之前,根据需要修改,重新排序或删除补丁:

$ git am *.patch

使用交互式rebase

您还可以使用交互式rebase来编辑补丁系列。这与使用补丁序列重新排序相同format-patch,所以请使用您最喜欢的任何接口。

将您当前的HEAD重新保存在您想要保留的最后一次提交中。例如,如果您想重新排序最后5次提交,请使用:

$ git rebase -i HEAD~5

这将打开你的编辑器,并列出一系列步骤来完成你的rebase。

pick deadbee The oneline of this commit
pick fa1afe1 The oneline of the next commit...# Rebase c0ffeee..deadbee onto c0ffeee
#
# Commands:#  p, pick = use commit
#  r, reword = use commit, but edit the commit message
#  e, edit = use commit, but stop for amending
#  s, squash = use commit, but meld into previous commit
#  f, fixup = like "squash", but discard this commit's log message
#  x, exec = run command (the rest of the line) using shell
#
# These lines can be re-ordered; they are executed from top to bottom.#
# If you remove a line here THAT COMMIT WILL BE LOST.#
# However, if you remove everything, the rebase will be aborted.#
# Note that empty commits are commented out

正如评论中所解释的那样,您可以通过编辑列表来重新排序提交,压缩它们,编辑提交消息等。一旦你满意,保存列表并关闭你的编辑器,并且rebase将开始。

Rebase将停止在pick已被替换的位置edit或当列表中的某个步骤未能机械地解决冲突并需要您的帮助时。当您完成编辑and/or解决冲突时,您可以继续git rebase --continue。如果你认为事情变得太多,你总是可以随心所欲地放弃git rebase --abort。即使rebase完成后,仍然可以使用reflog恢复原始分支。

有关该过程和其他提示的更详细讨论,请参阅git-rebase[1]的“交互模式”部分。

Other tools

还有许多其他工具,如StGit,它们用于维护补丁系列。这些超出了本手册的范围。

重写历史记录的问题

重写分支历史的主要问题与合并有关。假设有人提取你的分支并将它合并到它们的分支中,结果如下所示:

 o--o--O--o--o--o <-- origin
        \        \
         t--t--t--m <-- their branch:

那么假设你修改了最后三个提交:

         o--o--o <-- new head of origin        /
 o--o--O--o--o--o <-- old head of origin

如果我们在一个存储库中一起检查所有这些历史记录,它将如下所示:

         o--o--o <-- new head of origin        /
 o--o--O--o--o--o <-- old head of origin
        \        \
         t--t--t--m <-- their branch:

Git无法知道新头是旧头的更新版本;它将这种情况看作完全一样,如果两位开发人员独立完成了新旧主管的工作并行工作。此时,如果有人试图将新的头部合并到他们的分支中,Git将尝试将两个(旧的和新的)开发线合并在一起,而不是试图用新的替换旧的。结果可能是意想不到的。

您仍然可以选择发布其历史记录被重写的分支,而其他人可以获取这些分支以检查或测试它们,但这些分支不应尝试将这些分支拉入自己的工作中。

对于支持正确合并的真正分布式开发,发布的分支不应该被重写。

Why bisecting merge commits can be harder than bisecting linear history

git-bisect[1]命令可以正确处理包含合并提交的历史记录。但是,当它发现的提交是合并提交时,用户可能需要比平常更努力地弄清为什么该提交引入了问题。

想象一下这段history:

      ---Z---o---X---...---o---A---C---D
          \                       /
           o---o---Y---...---o---B

假设在开发的上层,在Z处存在的一个函数的含义在提交X时发生了变化。从Z到A的提交改变了函数的实现和Z中存在的所有调用站点,以及新的呼叫站点,他们添加,以保持一致。A没有错误。

假设在开发的较低行,有人在提交Y时为该函数添加了一个新的调用站点。从Z到B的提交都假定该函数的旧语义,并且调用者和被调用者彼此一致。B也没有错误。

进一步假设这两条开发线在C处干净地合并,因此不需要冲突解决。

尽管如此,C 语言中的代码已经被破坏,因为添加在开发底层的调用者并没有被转换为开发上层引入的新语义。所以,如果你只知道 D 是坏的,那么 Z 是好的,而 git-bisect [1] 将 C 标识为罪魁祸首,你怎么会发现问题是由于这种语义变化引起的?

git bisect的结果是非合并提交时,通常应该能够通过检查该提交来发现问题。开发人员可以通过将其更改分解为小型独立提交来实现这一点。但是,这对上述案例没有帮助,因为从任何一次单独提交的审查来看问题都不明显; 相反,需要全球发展观。更糟糕的是,有问题的函数中的语义变化可能只是上层开发线变化的一小部分。

另一方面,如果不是在 C 处合并,而是在 A 之上重建了 Z 到 B 之间的历史,那么您将得到这个线性历史记录:

    ---Z---o---X--...---o---A---o---o---Y*--...---o---B*--D*

在 Z 和 D * 之间平分会触及单个犯罪分子 Y * ,并理解为什么 Y * 被破坏可能会更容易。

部分由于这个原因,许多经验丰富的 Git 用户,即使在处理另外一个合并繁重的项目时,也会在发布之前通过重新配置最新的上游版本来保持历史线性。

获取单个分支

除了使用 git-remote [1] ,您还可以选择一次更新一个分支,并以任意名称在本地存储它:

$ git fetch origin todo:my-todo-work

第一个参数,origin只是告诉 Git 从你最初克隆的版本库中获取。第二个参数告诉 Git todo从远程仓库中获取命名的分支,并将其存储在名称的本地refs/heads/my-todo-work

您也可以从其他存储库获取分支; 所以

$ git fetch git://example.com/proj.git master:example-master

将创建一个新的分支,example-master并命名该分支并将master其存储在给定 URL 中的存储库中。如果您已经有一个名为 example-master 的分支,它将尝试快速转向由 example.com 主分支提供的提交。更详细地说:

Git 获取和快进

在前面的示例中,更新现有分支时,git fetch检查以确保远程分支上最近的提交是分支副本上最近提交的后代,然后再更新分支的副本以指向新的分支承诺。Git 把这个过程称为快进。

快进看起来像这样:

 o--o--o--o <-- old head of the branch
           \
            o--o--o <-- new head of the branch

在某些情况下,新头可能实际上不是老头的后代。例如,开发人员可能已经意识到她犯了一个严重的错误,并决定退路,导致如下情况:

 o--o--o--o--a--b <-- old head of the branch
           \
            o--o--o <-- new head of the branch

在这种情况下,git fetch将失败,并发出警告。

在这种情况下,您仍然可以强制 Git 更新到新的头部,如以下部分所述。但是,请注意,在上面的情况,这可能意味着失去标记的提交ab,除非你已经创建了自己的指向它们的参考。

强制 git fetch 执行非快进更新

如果因为分支的新头不是旧头的后代而导致 git 获取失败,则可以使用以下命令强制更新:

$ git fetch git://example.com/proj.git +master:refs/remotes/example/master

请注意+标志的添加。或者,您可以使用-f标志强制更新所有提取的分支,如下所示:

$ git fetch -f origin

请注意,旧版本的示例/主指向的提交可能会丢失,正如我们在前一节中看到的那样。

配置远程跟踪分支

我们在上面看到,这origin只是一个引用您最初从中克隆的存储库的快捷方式。这些信息存储在 Git 配置变量中,您可以使用 git-config [1] 查看这些变量:

$ git config -l
core.repositoryformatversion=0core.filemode=truecore.logallrefupdates=trueremote.origin.url=git://git.kernel.org/pub/scm/git/git.git
remote.origin.fetch=+refs/heads/*:refs/remotes/origin/*
branch.master.remote=origin
branch.master.merge=refs/heads/master

如果您还经常使用其他存储库,则可以创建类似的配置选项以保存输入; 例如,

$ git remote add example git://example.com/proj.git

将以下内容添加到.git/config

[remote "example"]
        url = git://example.com/proj.git
        fetch = +refs/heads/*:refs/remotes/example/*

另外请注意,上述配置可以通过直接编辑文件.git/config而不是使用 git-remote [1] 来执行。

配置远程后,以下三个命令将执行相同的操作:

$ git fetch git://example.com/proj.git +refs/heads/*:refs/remotes/example/*
$ git fetch example +refs/heads/*:refs/remotes/example/*
$ git fetch example

有关上述配置选项的更多详细信息,请参阅 git-config [1],有关 refspec 语法的更多详细信息,请参阅 git-fetch [1]。

Git 建立在少数简单但强大的想法之上。虽然可以在不理解它们的情况下完成任务,但如果你这样做,你会发现 Git 更直观。

我们从最重要的对象数据库和索引开始。

对象数据库

我们已经在“理解历史:提交”中看到,所有提交都存储在40位“对象名称”下。实际上,表示项目历史所需的所有信息都存储在具有这些名称的对象中。在每种情况下,名称都是通过对象内容的 SHA-1 哈希来计算的。SHA-1 哈希是一个加密哈希函数。这对我们意味着什么是不可能找到两个具有相同名称的不同对象。这具有许多优点; 除其他外:

  • 通过比较名称,Git 可以快速确定两个对象是否相同。

  • 由于对象名称在每个存储库中都以相同方式计算,因此存储在两个存储库中的相同内容将始终以相同名称存储。

  • 通过检查对象的名称仍然是其内容的 SHA-1 散列,Git 可以在读取对象时检测错误。

(有关对象格式和 SHA-1 计算的详细信息,请参阅对象存储格式。)

有四种不同类型的对象:“blob”,“tree”,“commit” 和 “tag”。

  • “blob”对象用于存储文件数据。

  • “树”对象将一个或多个“blob”对象绑定到目录结构中。另外,树对象可以引用其他树对象,从而创建目录层次结构。

  • “提交”对象将这样的目录层次结合在一起成为修订的定向非循环图 - 每个提交包含在提交时指定目录层次的恰好一棵树的对象名。另外,一个提交是指“父”提交对象,它描述了我们到达目录层次结构的历史。

  • “标签”对象象征性地标识并且可以用于签署其他对象。它包含另一个对象的对象名称和类型,符号名称(当然!)以及可选的签名。

对象类型更详细一些:

提交对象

“commit” 对象将树的物理状态与描述我们如何到达那里以及为什么联系起来。使用--pretty=raw git-show [1] 或 git-log [1]选项来检查您最喜欢的提交:

$ git show -s --pretty=raw 2be7fcb476
commit 2be7fcb4764f2dbcee52635b91fedb1b3dcf7ab4
tree fb3a8bdd0ceddd019615af4d57a53f43d8cee2bf
parent 257a84d9d02e90447b149af58b271c19405edb6a
author Dave Watson <dwatson@mimvista.com> 1187576872 -0400committer Junio C Hamano <gitster@pobox.com> 1187591163 -0700

    Fix misspelling of 'suppress' in docs

    Signed-off-by: Junio C Hamano <gitster@pobox.com>

正如你所看到的,提交被定义为:

  • 树:树对象的 SHA-1 名称(如下所定义),表示某个时间点的目录内容。

  • 父母:代表项目历史上前一个步骤的一些提交数量的 SHA-1 名称。上面的例子有一个父母; 合并提交可能有多个。没有父母的提交被称为“根”提交,并且表示项目的初始修订。每个项目必须至少有一个根。一个项目也可以有多个根,尽管这并不常见(或者一定是个好主意)。

  • 作者:负责此更改的人员的姓名及其日期。

  • 提交者(committer):实际创建提交的人员的名称及其完成日期。这可能与作者不同,例如,如果作者是编写补丁并将其发送给使用它创建提交的人的人。

  • 描述这个提交的评论。

请注意,提交本身不包含有关实际更改内容的任何信息; 通过比较此提交所涉及的树的内容和与其父代相关的树来计算所有更改。特别是,Git 不会显式记录文件重命名,尽管它可以识别在变化的路径中存在相同文件数据的情况,这表明重命名。(例如,请参阅-M git-diff [1] 的选项)。

提交通常由 git-commit [1] 创建,该提交创建一个提交,它的父代通常是当前的 HEAD ,其树从当前存储在索引中的内容中提取。

树对象

git-show [1] 命令也可以用来检查树对象,但 git-ls-tree [1] 会给你更多的细节:

$ git ls-tree fb3a8bdd0ce100644 blob 63c918c667fa005ff12ad89437f2fdc80926e21c    .gitignore100644 blob 5529b198e8d14decbe4ad99db3f7fb632de0439d    .mailmap100644 blob 6ff87c4664981e4397625791c8ea3bbb5f2279a3    COPYING040000 tree 2fb783e477100ce076f6bf57e4a6f026013dc745    Documentation100755 blob 3c0032cec592a765692234f1cba47dfdcc3a9200    GIT-VERSION-GEN100644 blob 289b046a443c0647624607d471289b2c7dcd470b    INSTALL100644 blob 4eb463797adc693dc168b926b6932ff53f17d0b1    Makefile100644 blob 548142c327a6790ff8821d67c2ee1eff7a656b52    README...

如您所见,树对象包含一个条目列表,每个条目都有一个模式,对象类型,SHA-1 名称和名称,按名称排序。它表示单个目录树的内容。

对象类型可以是 blob ,表示文件的内容,也可以是表示子目录内容的另一棵树。由于树和 blob 与所有其他对象一样,都通过其内容的 SHA-1 哈希来命名,所以两个树具有相同的 SHA-1 名称,当且仅当它们的内容(包括递归地包含所有子目录的内容)相同。这允许 Git 快速确定两个相关树对象之间的差异,因为它可以忽略具有相同对象名的任何条目。

(注意:在存在子模块的情况下,树也可能有提交作为条目,请参阅子模块以获取文档。)

请注意,这些文件都具有模式644或755:Git 实际上只关注可执行位。

Blob对象

您可以使用 git-show [1] 来检查 blob 的内容; 例如,COPYING从上面的树的入口处获取 blob :

$ git show 6ff87c4664

 Note that the only valid version of the GPL as far as this project
 is concerned is _this_ particular version of the license (ie v2, not
 v2.2 or v3.x or whatever), unless explicitly otherwise stated....

“blob”对象只不过是一个二进制数据块。它不涉及其他任何东西或具有任何类型的属性。

由于 blob 完全由其数据定义,因此如果目录树中的两个文件(或存储库的多个不同版本)具有相同的内容,则它们将共享相同的 Blob 对象。该对象完全独立于其在目录树中的位置,重命名文件不会更改与该文件关联的对象。

请注意,可以使用 git-show [1] 和 <revision> :<path> 语法来检查任何树或 blob 对象。这有时对于浏览当前未检出的树的内容非常有用。

相信

如果您从一个源接收到 blob 的 SHA-1 名称,并从另一个源(可能不受信任)接收到它的内容,那么只要 SHA-1 名称同意,仍然可以相信这些内容是正确的。这是因为 SHA-1 的设计使得找到产生相同散列的不同内容是不可行的。

同样,只需要相信顶级树对象的 SHA-1 名称就可以信任它引用的整个目录的内容,并且如果从可信任源接收到提交的 SHA-1 名称,那么您可以轻松验证通过该提交的父母可达到的提交的整个历史记录以及这些提交引用的树的所有内容。

因此,为了在系统中引入一些真正的信任,您唯一需要做的就是对one特殊注释进行数字签名,其中包括顶级提交的名称。您的数字签名向其他人表明您相信该提交,并且提交历史的不可变性告诉其他人可以信任整个历史。

换句话说,您只需发送一封电子邮件即可轻松验证整个存档,该电子邮件会告知用户最高提交的名称( SHA-1 哈希),并使用 GPG / PGP 等数字签名该电子邮件。

为了解决这个问题,Git 还提供了标签对象...

标签对象

标签对象包含对象,对象类型,标签名称,创建标签的人员(“标签者”)的名称以及可能包含签名的消息,如使用 git-cat-file [1 ]:

$ git cat-file tag v1.5.0object 437b1b20df4b356c9342dac8d38849f24ef44f27
type commit
tag v1.5.0tagger Junio C Hamano <junkio@cox.net> 1171411200 +0000GIT 1.5.0-----BEGIN PGP SIGNATURE-----Version: GnuPG v1.4.6 (GNU/Linux)iD8DBQBF0lGqwMbZpPMRm5oRAuRiAJ9ohBLd7s2kqjkKlq1qqC57SbnmzQCdG4ui
nLE/L9aUXdWeTFPron96DLA==2E+0-----END PGP SIGNATURE-----

请参阅 git-tag [1] 命令以了解如何创建和验证标记对象。(请注意,git-tag [1] 也可用于创建“轻量级标签”,它根本不是标签对象,而只是名称以开头的简单引用refs/tags/)。

Git 如何高效地存储对象:打包文件

新创建的对象最初是在一个以该对象的 SHA-1 散列(存储在.git/objects)之后命名的文件中创建的。

不幸的是,一旦项目中有很多对象,这个系统就会变得效率低下。试试这个旧项目:

$ git count-objects6930 objects, 47620 kilobytes

第一个数字是保存在单个文件中的对象的数量。第二个是这些“松散”物体占用的空间量。

通过将这些松散的对象移动到一个“包文件”中,您可以节省空间并使 Git 更快,从而以有效的压缩格式存储一组对象; 包格式的详细信息可以在包格式中找到。

要将松散的对象放入包中,只需运行 git repack:

$ git repack
Counting objects: 6020, done.Delta compression using up to 4 threads.Compressing objects: 100% (6020/6020), done.Writing objects: 100% (6020/6020), done.Total 6020 (delta 4070), reused 0 (delta 0)

这会在 .git / objects / pack / 中创建一个包含所有当前解压缩对象的“包文件”。然后你可以运行

$ git prune

删除包中现在包含的任何“loose”对象。这也将删除所有未引用的对象(例如,当您使用git reset删除提交时可能会创建的对象)。您可以通过查看.git/objects目录或运行来验证松散对象已经消失

$ git count-objects0 objects, 0 kilobytes

尽管目标文件不见了,但引用这些目标的任何命令都可以像以前一样工作。

git-gc [1] 命令为你执行打包,修剪等等,所以通常是你需要的唯一的高级命令。

摇晃的物体

git-fsck [1] 命令有时会抱怨悬挂的对象。他们不是问题。

悬挂对象最常见的原因是您已经重新设计了一个分支,或者您已经从其他重新设计分支的其他人那里获取 - 请参阅重写历史记录和维护补丁系列。在那种情况下,原始分支的老头仍然存在,它所指向的所有东西也是如此。分支指针本身没有,因为你用另一个替换了它。

还有其他一些导致摇晃物体的情况。例如,可能会出现一个“晃来晃去的斑点”,因为你做了git add一个文件,但是在实际提交文件之前将其更改为更大图片的一部分之前,您更改了该文件中的其他内容并提交了更新的内容 -你原来添加的旧状态最终不会被任何提交或树指向,所以它现在是一个悬而未决的 blob 对象。

同样,当 “recursive” 合并策略运行时,发现存在纵横交错合并,因此存在多个合并基础(这很不寻常,但它确实发生),它会生成一个临时中途树(或者甚至可能更多的是,如果你有很多纵横交错的合并和两个以上的合并基地)作为一个临时的内部合并基础,而且这些都是真正的对象,但最终的结果不会最终指向它们,所以它们最终会“悬挂“在你的仓库中。

一般来说,晃动的物体并不值得担心。它们甚至可以是非常有用的:如果你把事情搞砸了,悬挂的物体可能是你如何恢复你的老树(例如,你做了一个 rebase ,并且意识到你真的不想 - 你可以看看悬挂的东西你拥有的物品,并决定将你的头重新设置为一些旧的悬挂状态)。

对于提交,您可以使用:

$ gitk <dangling-commit-sha-goes-here> --not --all

这要求所有历史记录都可以从给定的提交中获得,但不能从任何分支,标签或其他引用中获得。如果你决定这是你想要的东西,你可以随时创建一个新的参考,例如,

$ git branch recovered-branch <dangling-commit-sha-goes-here>

对于斑点和树木,你不能这样做,但你仍然可以检查它们。你可以做

$ git show <dangling-blob/tree-sha-goes-here>

来显示 blob 的内容(或者对于一棵树,基本上是ls那个目录的内容),这可能会给你一些关于那个操作离开那个悬挂对象的想法。

通常,晃动的斑点和树木不是很有趣。它们几乎总是作为一个中途合并库的结果(如果你有手动合并的冲突合并,blob 通常甚至会包含合并冲突标记),或者仅仅是因为你中断了一个git fetch有 ^ C 或类似的东西,把some新对象留在对象数据库中,但只是悬空而没用。

无论如何,一旦你确定你对任何悬挂状态不感兴趣,你可以修剪所有不可访问的对象:

$ git prune

他们将会消失。(你应该只运行git prune在一个静止的仓库上 - 这就像做一个文件系统 fsck 恢复:当文件系统被挂载时你不想这么做,git prune在这种并发访问的情况下不会造成任何伤害存储库,但您可能会收到令人困惑或可怕的消息。)

从存储库损坏中恢复

按照设计,Git 谨慎对待它所信任的数据。但是,即使 Git 本身没有错误,硬件或操作系统错误仍然可能会破坏数据。

针对这些问题的第一道防线是备份。您可以使用克隆来备份 Git 目录,或者仅使用 cp,tar 或其他备份机制。

作为最后的手段,您可以搜索损坏的对象并尝试用手替换它们。在尝试此操作之前备份您的存储库,以防万一您在此过程中更多地破坏了事情。

我们假设问题是单个丢失或损坏的 blob ,这有时是一个可解决的问题。(恢复缺失的树木,特别是提交困难)。

在开始之前,验证是否有损坏,并用 git-fsck [1]找出它的位置; 这可能是耗时的。

假设输出如下所示:

$ git fsck --full --no-dangling
broken link from    tree 2d9263c6d23595e7cb2a21e5ebbb53655278dff8
              to    blob 4b9458b3786228369c63936db65827de3cc06200
missing blob 4b9458b3786228369c63936db65827de3cc06200

现在你知道缺少 blob 4b9458b3,并且树 2d9263c6 指向它。如果你只能找到丢失的 blob 对象的一个副本,可能在其他一些存储库中,你可以将它移动.git/objects/4b/9458b3...并完成。假设你不能。您仍然可以使用 git-ls-tree [1]检查指向它的树,该树可能会输出如下所示的内容:

$ git ls-tree 2d9263c6d23595e7cb2a21e5ebbb53655278dff8100644 blob 8d14531846b95bfa3564b58ccfb7913a034323b8        .gitignore100644 blob ebf9bf84da0aab5ed944264a5db2a65fe3a3e883        .mailmap100644 blob ca442d313d86dc67e0a2e5d584b465bd382cbf5c        COPYING...100644 blob 4b9458b3786228369c63936db65827de3cc06200        myfile...

所以现在你知道丢失的 blob 是一个名为的文件的数据myfile。而且你也可以识别目录 - 让我们假设它已经进入somedirectory。如果你幸运的话,丢失的副本可能与你在工作树中检出的副本相同somedirectory/myfile; 你可以用 git-hash-object [1] 测试这是否正确:

$ git hash-object -w somedirectory/myfile

它将使用 somedirectory / myfile 的内容创建和存储 blob 对象,并输出该对象的 SHA-1 。如果你非常幸运,它可能是4b9458b3786228369c63936db65827de3cc06200,在这种情况下,你猜对了,腐败已经修复!

否则,您需要更多信息。你怎么知道哪个版本的文件已经丢失?

最简单的方法是:

$ git log --raw --all --full-history -- somedirectory/myfile

因为你要求原始输出,你现在会得到类似的东西

commit abc
Author:Date:...:100644 100644 4b9458b... newsha... M somedirectory/myfile


commit xyz
Author:Date:...:100644 100644 oldsha... 4b9458b... M somedirectory/myfile

这告诉你该文件的紧接着的版本是 “newsha” ,并且紧接着的版本是 “oldsha” 。您还知道随着从 oldsha 到 4b9458b 的更改以及从 4b9458b 到 newsha 的更改而提交的提交消息。

如果你已经做了足够小的改变,你现在可以在重建中间状态 4b9458b 的内容时有一个很好的机会。

如果你可以这样做,你现在可以重新创建缺少的对象

$ git hash-object -w <recreated-file>

并且您的存储库又好了!

(顺便说一句,你可以忽略它fsck,并开始做一个

$ git log --raw --all

并在那整个事情中寻找丢失的物体(4b9458b ..)的沙。这取决于你-- Git 确实很多信息,它只是缺少一个特定的 blob 版本。

索引

该索引是一个二进制文件(通常保存在其中.git/index),其中包含路径名的已排序列表,每个路径名都有权限和 blob 对象的 SHA-1 ; git-ls-files [1] 可以显示索引的内容:

$ git ls-files --stage100644 63c918c667fa005ff12ad89437f2fdc80926e21c 0        .gitignore100644 5529b198e8d14decbe4ad99db3f7fb632de0439d 0        .mailmap100644 6ff87c4664981e4397625791c8ea3bbb5f2279a3 0        COPYING100644 a37b2152bd26be2c2289e1f57a292534a51a93c7 0        Documentation/.gitignore100644 fbefe9a45b00a54b58d94d06eca48b03d40a50e0 0        Documentation/Makefile...100644 2511aef8d89ab52be5ec6a5e46236b4b6bcd07ea 0        xdiff/xtypes.h100644 2ade97b2574a9f77e7ae4002a4e07a6a38e46d07 0        xdiff/xutils.c100644 d5de8292e05e7c36c4b68857c1cf9855e3d2f70a 0        xdiff/xutils.h

请注意,在较早的文档中,您可能会看到索引称为“当前目录缓存”或仅“缓存”。它有三个重要的属性:

  1. 该索引包含生成单个(唯一确定的)树对象所需的所有信息。例如,运行 git-commit [1]从索引生成该树对象,将其存储在对象数据库中,并将其用作与新提交关联的树对象。

  2. 索引可以快速比较它定义的树对象和工作树。它通过为每个条目存储一些附加数据(例如上次修改时间)来完成此操作。这些数据并没有显示在上面,也没有存储在创建的树对象中,但它可以用来快速确定工作目录中的哪些文件与索引中存储的文件不同,从而使 Git 不必读取所有的来自这些文件的数据来寻找变化。

  3. 它可以有效地表示关于不同树对象之间合并冲突的信息,允许每个路径名与关于所涉及的树的足够信息相关联,以便您可以在它们之间创建三向合并。我们在合并期间获得了冲突解决的帮助,在合并期间索引可以存储单个文件的多个版本(称为“阶段”)。上面输出的 git-ls-files [1]输出中的第三列是阶段编号,对于存在合并冲突的文件,其值将为0以外的值。

因此,指数是一种临时集结区域,充满了您正在进行的树木工作。

如果您将索引全部删除,只要您具有所描述的树的名称,通常就不会丢失任何信息。

大型项目通常由较小的独立模块组成。例如,嵌入式 Linux 发行版的源代码树将包含发行版中的每个软件,并进行一些本地修改; 电影播放器可能需要针对特定的已知工作版本的解压缩库进行构建; 几个独立的程序可能都共享相同的构建脚本。

通过集中的版本控制系统,通常通过将每个模块包含在一个单一的存储库中来完成。开发人员可以检出所有模块或仅检查需要使用的模块。他们甚至可以在一次提交中跨多个模块修改文件,同时移动或更新 API 和翻译。

Git 不允许部分签出,因此在 Git 中复制这种方法会迫使开发人员保留他们不感兴趣的模块的本地副本。由于 Git 必须扫描每个目录以进行更改,因此在大量结帐中执行操作会比您预期的要慢。如果模块具有大量的本地历史记录,克隆将永远占用。

另一方面,分布式版本控制系统可以更好地与外部资源整合。在集中模型中,外部项目的单个任意快照从其自己的版本控制中导出,然后导入供应商分支的本地版本控制。所有的历史都隐藏起来。通过分布式修订控制,您可以克隆整个外部历史记录,并且更轻松地跟踪开发并重新合并本地更改。

Git 的子模块支持允许存储库作为子目录包含外部项目的签出。子模块保持自己的身份; 子模块支持仅存储子模块存储库位置和提交ID,因此克隆包含项目(“超级项目”)的其他开发人员可以轻松地克隆相同修订版本中的所有子模块。超级项目的部分签出是可能的:您可以告诉 Git 克隆没有,部分或全部子模块。

从 Git 1.5.3 开始,git-submodule [1] 命令可用。使用 Git 1.5.2 的用户可以在存储库中查找子模块提交并手动将其检出; 早期版本根本无法识别子模块。

要查看子模块支持的工作方式,请创建四个以后可用作子模块的示例存储库:

$ mkdir ~/git
$ cd ~/git
$ for i in a b c ddo
        mkdir $i
        cd $i
        git init
        echo "module $i" > $i.txt
        git add $i.txt
        git commit -m "Initial commit, submodule $i"
        cd ..done

现在创建超级项目并添加所有子模块:

$ mkdir super$ cd super$ git init
$ for i in a b c ddo
        git submodule add ~/git/$i $i
done

Note

Do not use local URLs here if you plan to publish your superproject!

查看git submodule创建的文件:

$ ls -a.  ..  .git  .gitmodules  a  b  c  d

git submodule add <repo> <path>命令做了几件事情:

  • 它克隆从当前目录下<repo>给定的子模块,<path>并默认检出主分支。

  • 它将子模块的克隆路径添加到 gitmodules [5]文件中,并将该文件添加到索引中,准备提交。

  • 它将子模块的当前提交 ID 添加到索引,准备提交。

提交超级项目:

$ git commit -m "Add submodules a, b, c and d."

现在克隆超级项目:

$ cd ..$ git clone super cloned
$ cd cloned

子模块目录在那里,但它们是空的:

$ ls -a a.  ..$ git submodule status-d266b9873ad50488163457f025db7cdd9683d88b a-e81d457da15309b4fef4249aba9b50187999670d b-c1536a972b9affea0f16e0680ba87332dc059146 c-d96249ff5d57de5de093e6baff9e0aafa5276a74 d

Note

The commit object names shown above would be different for you, but they should match the HEAD commit object names of your repositories. You can check it by running git ls-remote ../a.

拉下子模块是一个两步过程。首先运行git submodule init以将子模块存储库 URL 添加到.git/config

$ git submodule init

现在使用git submodule update克隆存储库并检出超级项目中指定的提交:

$ git submodule update
$ cd a
$ ls -a.  ..  .git  a.txt

之间的一个主要区别git submodule update,并git submodule addgit submodule update检查发现的特定提交,而不是分支的前端。这就像检查一个标签:头部被分离,所以你不在分支上工作。

$ git branch* (detached from d266b98)
  master

如果您想在子模块内进行更改,并且拥有分离的头部,则应该创建或检出分支,进行更改,在子模块中发布更改,然后更新超级项目以引用新的提交:

$ git checkout master

or

$ git checkout -b fix-up

then

$ echo "adding a line again" >> a.txt
$ git commit -a -m "Updated the submodule from within the superproject."$ git push
$ cd ..$ git diff
diff --git a/a b/a
index d266b98..261dfac 160000--- a/a+++ b/a
@@ -1 +1 @@-Subproject commit d266b9873ad50488163457f025db7cdd9683d88b+Subproject commit 261dfac35cb99d380eb966e102c1197139f7fa24
$ git add a
$ git commit -m "Updated submodule a."$ git push

你必须运行git submodule updategit pull,如果您也想更新子模块。

子模块的陷阱

在将更改发布到引用它的超级项目之前,始终发布子模块更改。如果您忘记发布子模块更改,其他人将无法克隆存储库:

$ cd ~/git/super/a
$ echo i added another line to this file >> a.txt
$ git commit -a -m "doing it wrong this time"$ cd ..$ git add a
$ git commit -m "Updated submodule a again."$ git push
$ cd ~/git/cloned
$ git pull
$ git submodule update
error: pathspec '261dfac35cb99d380eb966e102c1197139f7fa24' did not match any file(s) known to git.Did you forget to 'git add'?Unable to checkout '261dfac35cb99d380eb966e102c1197139f7fa24' in submodule path 'a'

在较旧的 Git 版本中,可能很容易忘记在子模块中提交新文件或修改过的文件,这会导致类似的问题,因为不会推送子模块更改。从 Git 1.7.0 开始,git statusgit diff在超级项目显示子模块中进行修改,当它们包含新的或修改的文件以防止意外提交此类状态时。在生成补丁输出或使用该选项时,git diff还会-dirty在工作树一侧添加--submodule

$ git diff
diff --git a/sub b/sub--- a/sub+++ b/sub
@@ -1 +1 @@-Subproject commit 3f356705649b5d566d97ff843cf193359229a453+Subproject commit 3f356705649b5d566d97ff843cf193359229a453-dirty
$ git diff --submodule
Submodule sub 3f35670..3f35670-dirty:

除了任何超级项目中记录的提交之外,您也不应该将子模块中的分支倒回。

git submodule update如果您在子模块中创建并提交更改而不先检出分支,则运行并不安全。他们将被默默覆盖:

$ cat a.txt
module a
$ echo line added from private2 >> a.txt
$ git commit -a -m "line added inside private2"$ cd ..$ git submodule update
Submodule path 'a': checked out 'd266b9873ad50488163457f025db7cdd9683d88b'$ cd a
$ cat a.txt
module a

Note

The changes are still visible in the submodule’s reflog.

如果您在子模块工作树中有未提交的更改,git submodule update则不会覆盖它们。相反,您会收到关于无法从脏分支切换的常见警告。

许多更高级别的命令最初是作为使用较小核心的低级别 Git 命令的 shell 脚本实现的。当用 Git 做不寻常的事情时,这些仍然很有用,或者只是用来理解其内部工作方式。

对象访问和操作

git-cat-file [1] 命令可以显示任何对象的内容,但更高级别的 git-show [1] 通常更有用。

git-commit-tree [1] 命令允许构建任意父母和树的提交。

A tree can be created with git-write-tree[1] and its data can be accessed by git-ls-tree[1]. Two trees can be compared with git-diff-tree[1].

标签是用 git-mktag [1]创建的,并且签名可以通过 git-verify-tag [1]验证,但是对于两者都使用 git-tag [1]通常更简单。

工作流程

git-commit [1],git-checkout [1] 和 git-reset [1] 等高级操作通过在工作树,索引和对象数据库之间移动数据来工作。Git 提供了分别执行这些步骤的低级操作。

通常,所有 Git 操作都在索引文件上工作。某些操作纯粹在索引文件上工作(显示索引的当前状态),但大多数操作在索引文件和数据库或工作目录之间移动数据。因此有四种主要组合:

工作目录→索引

git-update-index[1]命令用来自工作目录的信息更新索引。您通常只需指定要更新的文件名即可更新索引信息,如下所示:

$ git update-index filename

但为了避免文件名匹配等常见错误,该命令通常不会添加全新条目或删除旧条目,即通常只会更新现有的缓存条目。

为了告知Git,是的,你确实意识到某些文件不再存在,或者应该添加新文件,你应该分别使用--remove--add标志。

注意!一个--remove标志并意味着随后的文件名一定会被删除:如果文件仍然在你的目录结构中存在该指数将与他们的新身份,不会被删除更新。唯一的--remove意思是update-index会考虑删除的文件是一个有效的事情,如果文件真的不再存在,它会相应地更新索引。

作为一种特殊情况,您也可以运行git update-index --refresh,它会刷新每个索引的“统计”信息以匹配当前的统计信息。它将不会更新对象状态本身,它只会更新用于快速测试对象是否仍旧匹配其旧的后备存储对象的字段。

前面介绍的git-add[1]只是git-update-index[1]的一个包装。

索引→对象数据库

用程序将当前索引文件写入“tree”对象

$ git write-tree

没有任何选项 - 它只会将当前索引写入描述该状态的树对象集合中,并且它将返回生成的顶级树的名称。您可以随时使用该树重新生成索引,方法是:

对象数据库→索引

您从对象数据库中读取一个“tree”文件,并使用它填充(并覆盖 - 如果您的索引包含任何未保存的状态,以后可能需要恢复,请不要执行此操作!)当前索引。正常操作就是

$ git read-tree <SHA-1 of tree>

并且您的索引文件现在将等同于您先前保存的树。但是,这只是您的index文件:您的工作目录内容未被修改。

索引→工作目录

您通过“检出”文件从索引更新工作目录。这不是一个很常见的操作,因为通常你只需要保持文件的更新,而不是写入你的工作目录,你会告诉索引文件关于你的工作目录(即git update-index)的变化。

但是,如果您决定跳转到新版本,或者查看其他人的版本,或者只是恢复以前的树,则可以使用读取树来填充索引文件,然后您需要检查结果

$ git checkout-index filename

或者,如果您想查看所有索引,请使用-a

注意!git checkout-index通常拒绝覆盖旧文件,所以如果你有一个旧版本的树已签出,你将需要使用-f标志(before-a标志或文件名)来force签出。

最后,还有一些不完全是从一种表象转移到另一种表象的几率和结果:

将它们联系在一起

为了提交一个你已经实例化的树,你需要git write-tree创建一个引用该树的“提交”对象及其背后的历史记录 - 最值得注意的是历史记录之前的“父”提交。

通常情况下,“提交”有一个父代:在进行特定更改之前树的先前状态。然而,有时它可能有两个或更多的父提交,在这种情况下,我们称之为“合并”,因为这样的提交将两个或更多以前的其他提交所代表的状态汇集(“合并”)。

换句话说,虽然“树”表示工作目录的特定目录状态,但“提交”表示该状态及时,并说明我们如何到达那里。

通过给它提供一个描述提交时的状态的树和一个父母列表来创建一个提交对象:

$ git commit-tree <tree> -p <parent> [(-p <parent2>)...]

然后给出stdin提交的原因(通过从管道或文件重定向,或者只在tty输入)。

git commit-tree将返回表示该提交的对象的名称,并且应将其保存起来以备后用。通常情况下,你会提交一个新的HEAD状态,虽然Git并不在乎你保存关于该状态的注释,但实际上我们倾向于只将结果写到指向的文件中.git/HEAD,以便我们总能看到最后承诺的状态是。

这张图片展示了各个部分如何组合在一起:

                     commit-tree
                      commit obj                       +----+                       |    |                       |    |
                       V    V                    +-----------+                    | Object DB |                    |  Backing  |                    |   Store   |                    +-----------+                       ^
           write-tree  |     |
             tree obj  |     |                       |     |  read-tree                       |     |  tree obj
                             V                    +-----------+                    |   Index   |                    |  "cache"  |                    +-----------+
         update-index  ^
             blob obj  |     |                       |     |
    checkout-index -u  |     |  checkout-index
             stat      |     |  blob obj
                             V                    +-----------+                    |  Working  |                    | Directory |                    +-----------+

检查数据

您可以使用各种帮助器工具检查对象数据库和索引中表示的数据。对于每个对象,您可以使用git-cat-file [1]来检查有关对象的详细信息:

$ git cat-file -t <objectname>

显示对象的类型,一旦你有了类型(通常隐含在你找到对象的地方),你可以使用

$ git cat-file blob|tree|commit|tag <objectname>

以显示其内容。注意!树具有二进制内容,因此有一个特殊的帮手来显示称为内容的内容git ls-tree,它将二进制内容转换为更易读的形式。

查看“提交”对象尤其具有启发性,因为这些对象通常很小且不言自明。特别是,如果你按照顶级提交名称的惯例.git/HEAD,你可以这样做

$ git cat-file commit HEAD

看看最重要的承诺是什么。

合并多棵树

Git可以帮助您执行三路合并,这可以通过多次重复合并过程反过来用于多路合并。通常的情况是,你只做一个三路合并(协调两行历史)并提交结果,但如果你喜欢,你可以一次合并多个分支。

要执行三路合并,首先进行两个你想要合并的提交,找到他们最接近的共同父(第三个提交),然后比较这三个提交对应的树。

要获得合并的“base”,请查找两个提交的共同父项:

$ git merge-base <commit1> <commit2>

这会打印它们都基于的提交的名称。您现在应该查看那些提交的树对象,您可以轻松完成这些操作

$ git cat-file commit <commitname> | head -1

因为树对象信息总是提交对象中的第一行。

一旦你知道了你要合并的三棵树(一棵“original”树,又名普通树,两棵“result”树,即你想要合并的树枝),你可以进行“merge”读入指数。如果它不得不扔掉旧的索引内容,那么就会发生抱怨,所以你应该确保你已经提交了这些内容 - 事实上,你通常会一直对最后一次提交进行合并(这应该与你的内容匹配目前的指数)。

要进行合并,请执行

$ git read-tree -m -u <origtree> <yourtree> <targettree>

它会直接在索引文件中为你完成所有微不足道的合并操作,并且你可以直接写出结果git write-tree

继续合并多棵树

可悲的是,许多合并并非微不足道。如果有文件已被添加,移动或删除,或者如果两个分支都修改了同一个文件,那么您将留下一个包含“合并条目”的索引树。这样的索引树可以NOT写出到一个树形对象中,并且在写出结果之前,您必须使用其他工具来解决任何此类合并冲突。

你可以用git ls-files --unmerged命令检查这样的索引状态。一个例子:

$ git read-tree -m $orig HEAD $target
$ git ls-files --unmerged100644 263414f423d0e4d70dae8fe53fa34614ff3e2860 1        hello.c100644 06fa6a24256dc7e560efa5687fa84b51f0263c3a 2        hello.c100644 cc44c73eb783565da5831b4d820c962954019b69 3        hello.c

git ls-files --unmerged输出的每一行都以blob模式位,blob SHA-1 stage number和文件名开头。该stage number是Git的方式来表达它来自哪个树:第1个阶段对应于$orig树,阶段2的HEAD树,和第3阶段的$target树。

早些时候我们说过,微不足道的合并是在里面完成的git read-tree -m。例如,如果文件不是从改变$origHEAD$target,或者将文件从改变$origHEAD,并$orig$target同样的方式,显然最后的结局是什么是HEAD。什么上面的例子中显示的是该文件hello.c是从改变$origHEAD,并$orig$target不同的方式。你可以通过在你自己的这三个阶段的blob对象上运行你最喜欢的3路合并程序来解决这个问题,例如diff3merge或者Git自己的合并文件,就像这样:

$ git cat-file blob 263414f... >hello.c~1$ git cat-file blob 06fa6a2... >hello.c~2$ git cat-file blob cc44c73... >hello.c~3$ git merge-file hello.c~2 hello.c~1 hello.c~3

hello.c~2如果存在冲突,这会将合并结果留在文件中,并将冲突标记留在文件中。验证合并结果是否合理后,您可以通过以下方式告诉Git该文件的最终合并结果:

$ mv -f hello.c~2 hello.c
$ git update-index hello.c

当路径处于“未合并”状态时,git update-index为该路径运行会告诉Git标记已解决的路径。

以上是Git合并在最低级别的描述,以帮助您理解在概念上发生了什么。在实践中,没有人,甚至没有Git本身,git cat-file为此运行三次。有一个git merge-index程序将阶段提取到临时文件并在其上调用“合并”脚本:

$ git merge-index git-merge-one-file hello.c

这是git merge -s resolve实施更高水平的原因。

本章介绍Git实现的内部细节,可能只有Git开发人员需要了解。

对象存储格式

所有对象都有一个静态确定的“类型”,用于标识对象的格式(即如何使用它以及如何引用其他对象)。目前有四种不同的对象类型:“blob”,“tree”,“commit”和“tag”。

无论对象类型如何,所有对象都共享以下特征:它们全部用zlib deflate,并且具有不仅指定其类型的标题,还提供有关对象中数据的大小信息。值得注意的是,用于命名对象的SHA-1哈希值是原始数据加上此头的哈希值,因此sha1sum file不匹配对象名称file

其结果是,对象的一般一致性总是可以独立于内容或对象的类型进行测试:所有对象可以通过验证被验证了(a)它们的散列文件和(b)所述对象的内容相匹配成功地膨胀为形成一系列字节的字节流<ascii type without space> + <space> + <ascii decimal size> + <byte\0> + <binary object data>

结构化对象可以进一步具有与其他对象验证的结构和连接。这通常是通过git fsck程序来完成的,该程序生成所有对象的完全依赖关系图,并验证它们的内部一致性(除了通过哈希来验证它们的表面一致性外)。

Git源代码的鸟瞰图

新开发人员通过Git的源代码找到自己的方式并非易事。本节为您提供一些指导,以显示从哪里开始。

开始提交的一个好地方是初始提交的内容,其中包括:

$ git checkout e83c5163

最初的修订为Git今天的所有内容奠定了基础,但它足够小,可以一次性阅读。

请注意,自该修订以来,术语已发生变化。例如,该修订版中的自述文件使用“变更集”一词来描述我们现在称之为提交的内容。

此外,我们不再称之为“缓存”,而是“索引”; 但是,该文件仍然被调用cache.h。备注:现在没什么理由要改变它,特别是因为它没有好的单一名称,因为它基本上theallGit C源代码包含的头文件。

如果你掌最初的想法commit,你应该检查出更新的版本和skimcache.hobject.hcommit.h

在早期,Git(在UNIX的传统中)是一些非常简单的程序,你在脚本中使用它们,将一个输出转换成另一个。这对于最初的开发很有利,因为测试新事物更容易。然而,最近许多这些部分已经成为内置的,并且其中一些核心已经被“解放”,即为了性能,便携性的原因而放入libgit.a中,并且避免了代码重复。

到目前为止,你知道索引是什么(并找到相应的数据结构cache.h),并且只有一些对象类型(blob,树,提交和标签)继承了它们的共同结构struct object,这是它们的第一个成员(因此,您可以投射例如(struct object *)commit实现sameas &commit->object,即得到对象名称和标志)。

现在是休息一下让这些信息沉入其中的好时机。

下一步:熟悉对象命名。阅读命名提交。命名对象的方法有很多(不仅仅是修订!)。所有这些都在英寸处理sha1_name.c。只需快速浏览一下该功能即可get_sha1()。许多特殊的处理都是由像get_sha1_basic()或类似的功能完成的。

这只是为了让你进入Git中最受欢迎的部分:the revision walker。

基本上,最初的版本git log是一个shell脚本:

$ git-rev-list --pretty $(git-rev-parse --default HEAD "$@") | \
        LESS=-S ${PAGER:-less}

这是什么意思?

git rev-list是修订步行者的原始版本,其中always打印了对stdout的修订列表。它仍然是功能性的,并且需要,因为大多数新的Git命令从脚本开始使用git rev-list

git rev-parse不再那么重要;它仅用于过滤与脚本调用的不同管道命令相关的选项。

git rev-list所做的大部分都包含在revision.crevision.h。它将选项封装在一个名为struct的结构中rev_info,该结构控制如何以及如何修改步骤等等。

原来的工作git rev-parse现在由该函数采用setup_revisions(),该工具分析修订步行者的修订版和通用命令行选项。这些信息存储在结构中rev_info供以后使用。调用后,您可以执行自己的命令行选项解析setup_revisions()。之后,您必须调用prepare_revision_walk()初始化,然后您可以使用该函数逐个获取提交get_revision()

如果您对修改步行过程的更多细节感兴趣,只需查看第一个实现cmd_log(); 调用git show v1.3.0~155^2~4并向下滚动到该功能(请注意,您不再需要setup_pager()直接调用)。

今天,git log是一个内建的,这意味着它是contained在命令git。内建的源端是

  • 一个叫做cmd_<bla>的函数,通常定义在builtin/<bla.c>(注意老版本的Git用来builtin-<bla>.c代替它),并声明为builtin.h

  • commands[]数组中的一个条目git.c,和

  • 在一个条目BUILTIN_OBJECTSMakefile

有时候,一个源文件中包含多个内建文件。例如,cmd_whatchanged()并且cmd_log()都驻留在builtin/log.c,因为它们共享相当多的代码。在这种情况下,not命名与.c它们所在的文件相同的命令必须列在BUILT_INSMakefile

git log 在C中看起来比在原始脚本中看起来更复杂,但是这允许更大的灵活性和性能。

这里再一次采取暂停是一个很好的观点。

第三课是:研究代码。真的,这是了解Git组织的最佳方式(在了解基本概念之后)。

所以,想想你感兴趣的东西,比如说:“我知道它的对象名称后怎么才能访问一个blob?”。第一步是找到一个你可以做到的 Git 命令。在这个例子中,它是git show或者git cat-file

为了清楚起见,让我们留下来git cat-file,因为它

  • 是水暖,和

  • 即使在最初的提交中也是如此(它实际上只经过了大约20次修改cat-file.c,重新命名为builtin/cat-file.c内建时,然后看到不到10个版本)。

所以,看看builtin/cat-file.c,寻找cmd_cat_file()并看看它做了什么。

        git_config(git_default_config);        if (argc != 3)                usage("git cat-file [-t|-s|-e|-p|<type>] <sha1>");        if (get_sha1(argv[2], sha1))                die("Not a valid object name %s", argv[2]);

让我们跳过明显的细节; 这里唯一真正有趣的部分是打电话给get_sha1()。它试图将argv[2]对象名称解释为对象名称,如果它引用了当前存储库中存在的对象,则会将生成的SHA-1写入变量sha1

这里有两件事很有趣:

  • get_sha1()返回0 success。这可能会让一些新的Git黑客感到惊讶,但是在UNIX中有一个很长的传统可以在出现不同的错误时返回不同的负数 - 而在成功时则返回0。

  • is sha1的函数签名中的变量,但实际上应该是一个指针。该变量将包含给定提交的160位SHA-1。请注意,无论何时传递SHA-1 ,它都是二进制表示形式,而不是以十六进制字符表示的ASCII表示形式,该表示形式作为。get_sha1()unsigned char *unsigned char[20]unsigned char *char *

您将在整个代码中看到这两件事。

现在,为了肉:

        case 0:
                buf = read_object_with_reference(sha1, argv[1], &size, NULL);

这就是你如何阅读一个 blob(实际上,不仅是一个 blob,而且是任何类型的对象)。要知道函数read_object_with_reference()是如何工作的,请找到它的源代码(就像git grep read_object_with | grep ":[a-z]"在Git仓库中一样),然后阅读源代码。

要了解如何使用结果,请阅读cmd_cat_file()

        write_or_die(1, buf, size);

有时候,你不知道在哪里寻找功能。在许多这样的情况下,它有助于搜索输出git log,然后git show进行相应的提交。

例如:如果你知道有一些测试用例git bundle,但不记得它在哪里(是的,你could git grep bundle t/,但没有说明这一点!):

$ git log --no-merges t/

在pager(less)中,只需搜索“bundle”,返回几行,并看到它在提交18449ab0中;现在只需复制该对象名称并将其粘贴到命令行中

$ git show 18449ab0

Voila.

另一个例子:找出为了使某些脚本成为内建的操作:

$ git log --no-merges --diff-filter=A builtin/*.c

你看,Git实际上是了解Git本身来源的最佳工具!

Git explained

备用对象数据库

通过交替机制,存储库可以从另一个对象数据库继承其对象数据库的一部分,这被称为“备用”。

裸仓库

裸存储库通常是一个适当命名的目录,其.git后缀没有在版本控制下的任何文件的本地签出副本。也就是说,通常存在于隐藏.git子目录中的所有Git管理和控制文件都直接出现在repository.git目录中,而不存在和检出其他文件。通常,公共存储库的发布者可以使裸露的存储库可用。

blob object

非类型化的对象,例如文件的内容。

branch

“分支”是一个积极的发展路线。分支上最近的提交被称为该分支的提示。分支的顶端由分支头引用,随着分支上的额外开发,分支头向前移动。单个Git仓库可以跟踪任意数量的分支,但是您的工作树只与其中一个分支(“当前”或“签出”分支)关联,并且HEAD指向该分支。

cache

已过时:索引。

chain

一个对象列表,其中列表中的每个对象都包含对其后继者的引用(例如,提交的后继者可以是其父代之一)。

changeset

BitKeeper / cvsps代表“提交”。由于 Git 不存储更改,但状态,使用 Git 的术语“changesets”确实没有意义。

checkout

使用对象数据库中的树对象或blob更新全部或部分工作树,以及在整个工作树已指向新分支的情况下更新索引和HEAD的操作。

cherry-picking

在SCM专业术语中,“樱桃挑选”意味着从一系列更改(通常为提交)中选择一部分更改,并将它们记录为不同代码库顶部的一系列新更改。在Git中,这由“git cherry-pick”命令执行,以提取现有提交引入的更改,并根据当前分支的提示将其记录为新提交。

clean

如果工作树对应于当前头引用的修订版,则该工作树是干净的。另见“脏”。

commit

作为名词:Git 历史中的单一点; 一个项目的整个历史被表示为一组相互关联的提交。Git 经常在相同的地方使用“commit”一词,而其他版本控制系统使用“revision”或“version”一词。也用作提交对象的简写。

作为一个动词:通过创建一个代表索引当前状态的新提交并推进 HEAD指向新的提交,将项目状态的新快照存储在Git历史记录中的操作。

commit object

一个对象,其中包含有关特定修订版的信息,例如父项,提交者,作者,日期和与所存储的修订版顶部目录相对应的树对象。

commit-ish (also committish)

提交对象或可递归解引用到提交对象的对象。以下是所有提交:提交对象,指向提交对象的标记对象,指向指向提交对象的标记对象的标记对象等。

core Git

Git的基本数据结构和实用程序。只公开有限的源代码管理工具。

DAG

有向无环图。提交对象形成一个有向无环图,因为它们具有父(定向),并且提交对象的图是非循环的(不存在以相同对象开始和结束的链)。

dangling object

即使从其他不可达对象也无法访问的不可访问对象; 一个悬挂对象没有从存储库中的任何引用或对象引用它。

detached HEAD

通常,HEAD 存储分支的名称,并且对历史 HEAD 进行操作的命令表示对通向 HEAD 指向的分支尖端的历史进行操作。但是,Git还允许您检出任意提交,但不一定是任何特定分支的提示。这种状态下的 HEAD被称为“分离”。

请注意,在当前分支的历史记录上运行的命令(例如git commit,在其上创建新的历史记录)在 HEAD 分离时仍然有效。他们更新 HEAD指向更新后的历史记录,而不影响任何分支。更新或查询about当前分支信息(例如git branch --set-upstream-to,设置当前分支集成的远程跟踪分支)的命令显然不起作用,因为在此状态下没有(实际)当前分支要求。

directory

你用“ls”得到的名单:-)

dirty

如果工作树包含尚未提交给当前分支的修改,则称其为“脏”。

evil merge

邪恶的合并是一种合并,它引入了不在任何父项中出现的更改。

fast-forward

快进是一种特殊的合并类型,您可以在其中进行修订,并且您正在“合并”另一个分支的变更,这些变更恰好是您所拥有的后代。在这种情况下,您不必进行新的合并提交,而只需更新其修订。这将经常发生在远程存储库的远程跟踪分支上。

fetch

获取分支意味着从远程存储库获取分支的头部引用,以找出本地对象数据库中缺少哪些对象,并获取它们。另请参阅git-fetch [1]。

file system

Linus Torvalds最初将Git设计为用户空间文件系统,即保存文件和目录的基础架构。这确保了Git的效率和速度。

Git archive

资源库的同义词(适用于拱门人士)。

gitfile

.git工作树根部的纯文件,指向真实存储库的目录。

grafts

嫁接使两个不同的发展路线通过记录提交的虚假血统信息连接在一起。通过这种方式,你可以让Git假装一个提交的父集与创建提交时所记录的不同。通过.git/info/grafts文件进行配置。

请注意,移植机制已过时,可能会导致在库之间传输对象时出现问题; 请参阅git-replace [1]以获得更灵活和更强大的系统来完成同样的任务。

hash

在Git的上下文中,对象名的同义词。

head

对分支顶端提交的命名引用。头文件存储在$GIT_DIR/refs/heads/目录中的文件中,除非使用打包引用。(请参阅git-pack-refs [1]。)

HEAD

当前分支。更详细地说:你的工作树通常是由HEAD引用的树的状态派生的。除了使用分离的HEAD之外,HEAD是对存储库中某个头的引用,在这种情况下,它直接引用任意提交。

head ref

头的同义词。

hook

在正常执行几个Git命令的过程中,对可选脚本进行了标注,以便开发人员添加功能或进行检查。通常,钩子允许预先验证命令并可能中止,并且在完成操作之后允许后通知。钩子脚本可以在$GIT_DIR/hooks/目录中找到,只需.sample从文件名中删除后缀即可启用。在Git的早期版本中,你必须让它们可执行。

index

包含stat信息的文件集合,其内容以对象形式存储。索引是工作树的存储版本。真相被告知,它也可以包含合并时使用的第二个,甚至第三个工作树版本。

index entry

有关特定文件的信息,存储在索引中。如果合并已启动,但尚未完成(即索引包含该文件的多个版本),则索引条目可以取消合并。

master

默认开发分支。无论何时创建Git存储库,都会创建一个名为“master”的分支,并成为活动分支。在大多数情况下,这包含了当地的发展,尽管这纯粹是按照惯例,并不是必需的。

merge

作为动词:将另一个分支的内容(可能来自外部存储库)引入当前分支。在合并分支来自不同的存储库的情况下,首先获取远程分支,然后将结果合并到当前分支中。提取和合并操作的组合称为拉取。合并由一个自动过程执行,该过程识别自分支发散后所做的更改,然后将所有这些更改应用到一起。如果更改有冲突,可能需要手动干预才能完成合并。

作为名词:除非是快进,否则成功的合并会导致创建代表合并结果的新提交,并将合并分支的提示作为父项。这个提交被称为“合并提交”,或者有时候只是一个“合并”。

object

Git中的存储单元。它由SHA-1的内容唯一标识。因此,一个对象不能改变。

object database

存储一组“对象”,单个对象由其对象名称标识。物体通常居住在$GIT_DIR/objects/

object identifier

对象名称的同义词。

object name

对象的唯一标识符。对象名称通常由40个字符的十六进制字符串表示。俗称SHA-1。

object type

描述对象类型的标识符“commit”,“tree”,“tag”或“blob”之一。

octopus

合并两个以上的分支。

origin

默认的上游存储库。大多数项目至少有一个跟踪的上游项目。默认情况下origin用于此目的。新的上游更新将被提取到名为origin / name-of-upstream-branch的远程跟踪分支中,您可以使用它看到git branch -r

pack

一组已压缩到一个文件中的对象(以节省空间或有效传输它们)。

pack index

包中对象的标识符列表和其他信息,以帮助高效地访问包的内容。

pathspec

用于限制Git命令中路径的模式。

Pathspecs用于“git ls-files”,“git ls-tree”,“git add”,“git grep”,“git diff”,“git checkout”等命令行以限制范围对树或工作树的某个子集的操作。有关路径是否与当前目录或顶层相关的信息,请参阅每条命令的文档。pathspec语法如下所示:

  • 任何路径都与自己匹配

  • 直到最后一个斜杠的pathspec表示一个目录前缀。该pathspec的范围仅限于该子树。

  • pathpec的其余部分是路径名剩余部分的模式。相对于目录前缀的路径将使用fnmatch(3)与该模式进行匹配; 特别是,*? can匹配目录分隔符。

例如,Documentation / *。jpg将匹配Documentation子树中的所有.jpg文件,包括Documentation / chapter_1 / figure_1.jpg。

以冒号开头的pathspec :有特殊含义。简而言之,前面的冒号:后面跟着零个或多个“魔术签名”字母(可选地由另一个冒号终止:),其余部分是匹配路径的模式。“魔术签名”由 ASCII 字母组成,既不是字母数字,glob,正则表达式特殊字符也不是冒号。如果模式以不属于“魔术签名”符号集且不是冒号的字符开头,则可以省略终止“魔术签名”的可选冒号。

在长形式中,前面的冒号:后面跟着一个开括号(,一个逗号分隔的零个或多个“魔术字”列表和一个紧密的括号),剩下的就是匹配路径的模式。

只有冒号的pathspec意味着“没有pathspec”。此表单不应与其他pathspec结合使用。

top

即使从子目录中运行命令,魔术字top(魔术签名:)也/会使模式与工作树的根相匹配。

literal

模式中的通配符例如*或被?视为文字字符。

icase

不区分大小写的匹配。

glob

Git 将该模式视为适合 fnmatch(3)使用 FNM_PATHNAME 标志消耗的 shell glob:模式中的通配符不会与路径名中的/匹配。例如,“Documentation / *。html”与“Documentation / git.html”匹配,但不匹配“Documentation / ppc / ppc.html”或“tools / perf / Documentation / perf.html”。

**与全路径匹配的模式中的两个连续星号(“ ”)可能具有特殊含义:

  • 前面的“ **”后跟斜杠意味着所有目录匹配。例如,“ **/foofoo在任何地方与文件或目录“ ”匹配,与模式“ foo” 相同。“ **/foo/bar”与bar直接在目录“ foo” 下的任何地方的文件或目录“ ”匹配。

  • 尾随“ /**”匹配内部的所有内容。例如,“ abc/**.gitignore以无限深度匹配目录“abc”内相对于文件位置的所有文件。

  • 斜杠后跟两个连续的星号,则斜线匹配零个或多个目录。例如,“ a/**/b”匹配“ a/b”,“ a/x/b”,“ a/x/y/b”等。

  • 其他连续的星号被认为是无效的。Glob魔法与字面魔法不相容。

attr

attr:出现空间分隔的“属性要求”列表之后,必须满足所有这些才能使路径被视为匹配; 这是除了通常的非magicitespec模式匹配外。见gitattributes [5]。

路径的每个属性需求都采用以下形式之一:

  • ATTR”要求ATTR设置属性。

  • -ATTR”要求该属性未ATTR设置。

  • ATTR=VALUE”要求将该属性ATTR设置为字符串VALUE

  • !ATTR”要求该属性ATTR未指定。

exclude

在路径匹配任何非排除的pathspec后,它将通过所有排除路径规范(魔术签名:!或其同义词^)运行。如果匹配,路径将被忽略。如果不存在非排除的pathspec,则将排除应用于结果集,就好像在没有任何pathspec的情况下调用一样。

parent

一个提交对象包含一个(可能是空的)在开发线中的逻辑前驱者列表,即它的父代。

pickaxe

术语pickaxe指diffcore例程的一个选项,它帮助选择添加或删除给定文本字符串的更改。有了这个--pickaxe-all选项,它可以用来查看引入或删除的完整变更集,比如说一行文本。参见git-diff [1]。

plumbing

核心Git的可爱名字。

porcelain

程序和程序套件的名字取决于核心Git,提供对核心Git的高级访问。Porcelains公开了比管道更多的SCM界面。

per-worktree ref

参考文献是每个工作树,而不是全球。目前只有HEAD和任何以ref开头的ref refs/bisect/,但后来可能会包含其他不寻常的ref。

pseudoref

Pseudorefs是一类文件,在$GIT_DIR这类文件中,为了rev-parse的目的,其行为类似于refs,但是它们被git特别处理。伪代码都具有全部大写的名称,并且始终以包含SHA-1和空白的行开头。所以,HEAD不是一个伪造的,因为它有时是一个符号参考。他们可以选择包含一些额外的数据。MERGE_HEAD并且CHERRY_PICK_HEAD是例子。不像per-worktree refs,这些文件不能是符号参考,也不会有reflog。他们也不能通过正常的参考更新机器进行更新。相反,它们是通过直接写入文件来更新的。然而,他们可以被看作是裁判,所以git rev-parse MERGE_HEAD会工作。

pull

拉分支意味着获取并合并它。另见git-pull [1]。

push

推送分支意味着从远程存储库获取分支的头部引用,查明它是否是分支的本地头部引用的直接祖先,并且在这种情况下,将所有可从本地头部引用访问的对象,以及哪些从远程存储库中丢失,进入远程对象数据库,并更新远程头引用。如果远程头部不是本地头部的祖先,则推送失败。

reachable

所有提交的祖先都被认为是“可达”的。更一般地说,如果我们可以通过跟随标签跟随标签的链向另一个发送另一个对象,那么可以从另一个对象到达另一个对象,向他们的父母或树木承诺,向树木或其包含的斑点承诺。

rebase

要重新应用从一个分支到另一个基地的一系列更改,并将该分支的头部重置为结果。

ref

一个以refs/(例如refs/heads/master)开头的名称指向一个对象名称或另一个引用(后者称为符号引用)。为方便起见,当用作Git命令的参数时,ref有时可以缩写; 有关详细信息,请参阅gitrevisions [7]。参考文件存储在存储库中。

参考命名空间是分层的。不同的子refs/heads/层次结构用于不同的目的(例如,层次结构用于表示本地分支)。

有一些特殊用途的参考文献不是以开头的refs/。最显着的例子是HEAD

reflog

reflog显示参考文献的本地“历史”。换句话说,它可以告诉你this存储库中第3次修订版本是什么,以及当前this存储库中的状态是什么,昨天晚上9点14分。有关详细信息,请参阅git-reflog [1]。

refspec

提取和推送使用“refspec”来描述远程参考和本地参考之间的映射。

remote repository

用于跟踪相同项目但驻留在其他位置的存储库。要与遥控器通信,请参阅提取或推送。

remote-tracking branch

用于跟踪另一个存储库的更改的引用。它通常看起来像refs/remotes/foo/bar(表示它跟踪名为bar远程命名的分支foo),并匹配配置的提取refspec的右侧。远程追踪分支不应包含直接修改或本地提交。

repository

一个ref集合以及一个对象数据库,其中包含所有可从ref访问的对象,可能伴随着来自一个或多个porcelains的元数据。存储库可以通过交替机制与其他存储库共享对象数据库。

resolve

手动修复失败的自动合并留下的操作。

revision

提交的同义词(名词)。

rewind

放弃部分发展,即将头部分配给较早的修订。

SCM

源代码管理(工具)。

SHA-1

“安全散列算法1”; 一个密码散列函数。在使用Git作为对象名称的同义词的上下文中。

shallow clone

大多数情况下,它是浅层存储库的同义词,但该语句使得它更加明确,它是通过运行git clone --depth=...命令创建的。

shallow repository

一个浅仓库有一个不完整的历史记录,其中一些提交被父母烧掉(换句话说,Git被告知假设这些提交没有父母,即使它们被记录在提交对象中)。即使在上游记录的真实历史要大得多,当你仅仅关注项目的最近历史时,这有时也很有用。通过给--depthgit-clone [1]选项创建一个浅仓库,其历史可以通过git-fetch [1]加深。

stash entry

用于临时存储脏工作目录和索引以供将来重用的内容的对象。

submodule

在另一个存储库(后者称为超级项目)中存储单独项目历史的存储库。

superproject

将其工作树中其他项目的存储库作为子模块引用的存储库。超级项目知道所包含子模块的提交对象的名称(但不保存其副本)。

symref

符号引用:它不是包含SHA-1标识本身,而是它的格式ref: refs/some/thing,当被引用时,它递归地解引用这个引用。HEAD是symref的一个主要例子。符号引用通过git-symbolic-ref [1]命令进行处理。

tag

refs/tags/命名空间下的一个ref ,指向一个任意类型的对象(通常是一个标记指向一个标记或一个提交对象)。与头部相比,标签不会被commit命令更新。一个Git标签与一个Lisp标签(在Git的上下文中被称为对象类型)无关。标签通常用于标记提交血统链中的特定点。

tag object

一个包含ref的对象指向另一个对象,它可以像提交对象一样包含消息。它也可以包含一个(PGP)签名,在这种情况下,它被称为“签名标签对象”。

topic branch

一个常规的Git分支,开发人员用它来识别概念上的开发线。由于分支非常简单且便宜,通常需要有几个小分支,每个小分支包含非常明确的概念或小的增量但相关的更改。

tree

无论是工作树还是树对象以及依赖的blob和树对象(即工作树的存储表示)。

tree object

一个包含文件名和模式列表的对象,并引用关联的Blob和/或树对象。树相当于一个目录。

tree-ish (also treeish)

树对象或可递归解引用到树对象的对象。取消引用提交对象会生成与修订版顶部目录对应的树对象。以下是所有树形结构:提交对象,树对象,指向树对象的标记对象,指向指向树对象的标记对象的标记对象等。

unmerged index

包含未合并索引条目的索引。

unreachable object

无法从分支,标记或任何其他参考访问的对象。

upstream branch

合并到问题分支(或分支问题的分支)的默认分支被重新分配到。它通过分支<name> .remote和分支。<名称> .merge进行配置。如果上游分支Aorigin/B有时候我们说“ A被跟踪origin/B”。

working tree

实际检出文件的树。工作树通常包含HEAD提交树的内容,以及您所做的但尚未提交的任何本地更改。

这是主要命令的快速总结; 前面的章节解释了这些如何更详细地工作。

Creating a new repository

从tarball:

$ tar xzf project.tar.gz
$ cd project
$ git init
Initialized empty Git repository in .git/$ git add .$ git commit

从远程存储库:

$ git clone git://example.com/pub/project.git
$ cd project

Managing branches

$ git branch             # list all local branches in this repo
$ git checkout test  # switch working directory to branch "test"$ git branch new     # create branch "new" starting at current HEAD
$ git branch -d new  # delete branch "new"

而不是在当前HEAD(默认)上建立新的分支,请使用:

$ git branch new test    # branch named "test"$ git branch new v2.6.15 # tag named v2.6.15$ git branch new HEAD^   # commit before the most recent
$ git branch new HEAD^^  # commit before that
$ git branch new test~10 # ten commits before tip of branch "test"

同时创建并切换到新分支:

$ git checkout -b new v2.6.15

更新并检查您从克隆的存储库中的分支:

$ git fetch                # update
$ git branch -r                # list
  origin/master
  origin/next  ...$ git checkout -b masterwork origin/master

从不同的存储库获取分支,并在存储库中为其指定一个新名称:

$ git fetch git://example.com/project.git theirbranch:mybranch
$ git fetch git://example.com/project.git v2.6.15:mybranch

保留你经常使用的软件仓库清单:

$ git remote add example git://example.com/project.git
$ git remote                        # list remote repositories
example
origin
$ git remote show example        # get details* remote example
  URL: git://example.com/project.git
  Tracked remote branches
    master
    next    ...$ git fetch example                # update branches from example
$ git branch -r                        # list all remote branches

Exploring history

$ gitk                            # visualize and browse history
$ git log                    # list all commits
$ git log src/                    # ...modifying src/$ git log v2.6.15..v2.6.16  # ...in v2.6.16, not in v2.6.15$ git log master..test            # ...in branch test, not in branch master
$ git log test..master            # ...in branch master, but not in test
$ git log test...master            # ...in one branch, not in both
$ git log -S'foo()'            # ...where difference contain "foo()"$ git log --since="2 weeks ago"$ git log -p                    # show patches as well
$ git show                    # most recent commit
$ git diff v2.6.15..v2.6.16 # diff between two tagged versions
$ git diff v2.6.15..HEAD    # diff with current head
$ git grep "foo()"            # search working directory for "foo()"$ git grep v2.6.15 "foo()"  # search old tree for "foo()"$ git show v2.6.15:a.txt    # look at old version of a.txt

搜索回归:

$ git bisect start
$ git bisect bad                # current version is bad
$ git bisect good v2.6.13-rc2        # last known good revision
Bisecting: 675 revisions left to test after this
                                # test here, then:$ git bisect good                # if this revision is good, or
$ git bisect bad                # if this revision is bad.
                                # repeat until done.

Making changes

确保Git知道责怪谁:

$ cat >>~/.gitconfig <<\EOF[user]
        name = Your Name Comes Here
        email = you@yourdomain.example.com
EOF

选择要包含在下一次提交中的文件内容,然后进行提交:

$ git add a.txt    # updated file
$ git add b.txt    # new file$ git rm c.txt     # old file
$ git commit

或者,一步准备并创建提交:

$ git commit d.txt # use latest content only of d.txt
$ git commit -a           # use latest content of all tracked files

合并

$ git merge test   # merge branch "test" into the current branch
$ git pull git://example.com/project.git master
                   # fetch and merge in remote branch
$ git pull . test  # equivalent to git merge test

分享您的更改

导入或导出补丁:

$ git format-patch origin..HEAD # format a patch for each commit
                                # in HEAD but not in origin
$ git am mbox # import patches from the mailbox "mbox"

在不同的Git存储库中获取分支,然后合并到当前分支中:

$ git pull git://example.com/project.git theirbranch

在合并到当前分支之前,将提取的分支存储到本地分支中:

$ git pull git://example.com/project.git theirbranch:mybranch

在本地分支上创建提交后,使用提交更新远程分支:

$ git push ssh://example.com/project.git mybranch:theirbranch

当远程和本地分支都被命名为“测试”时:

$ git push ssh://example.com/project.git test

常用远程存储库的快捷版本:

$ git remote add example ssh://example.com/project.git
$ git push example test

Repository maintenance

检查腐败情况:

$ git fsck

重新压缩,删除未使用的cruft:

$ git gc

所有列表

这是一个正在进行的工作。

基本要求:

  • 从开始到结束,它必须是可读的,由聪明的人掌握基本的UNIX命令行,但没有任何Git的专门知识。如有必要,应在出现任何其他先决条件时特别提及。

  • 只要有可能,章节标题应该用不需要更多知识的语言清楚地描述他们解释如何去做的任务:例如,“将修补程序导入到项目中”而不是“ git am命令”

考虑如何创建一个清晰的章节依赖关系图,使人们能够阅读重要主题,而不必阅读其中的所有内容。

Scan Documentation/ for other stuff left out; in particular:

  • howto’s

  • some of technical/?

  • hooks

  • list of commands in git[1]

扫描电子邮件档案库中的其他内容

扫描手册页,看看是否有任何假设的背景比本手册提供的更多。

添加更多好的例子。只是食谱示例的整个部分可能是一个好主意; 也许可以将“高级示例”一节作为标准的章节结束部分?

在适当的地方包括词汇表的交叉引用。

添加一个关于使用其他版本控制系统的部分,包括CVS,Subversion以及只是导入一系列发行版tarball。

写一篇关于使用管道和书写脚本的章节。

备用,克隆参考等

上一篇: 下一篇: