英语 ▾ 主题 ▾ 最新版本 ▾ gitfaq 上次更新于 2.46.0

名称

gitfaq - 关于使用 Git 的常见问题

概要

gitfaq

描述

本 FAQ 中的示例假设使用标准的 POSIX shell,如 bashdash,以及用户 A U Thor,他在托管提供商 git.example.org 上拥有帐户 author

配置

user.name 中应该填写什么?

您应该填写您的个人姓名,通常是名字和姓氏的形式。例如,Git 的当前维护者使用 "Junio C Hamano"。这将是您所做的每个提交中存储的名称部分。

此配置对远程服务的身份验证没有任何影响;为此,请参阅 git-config[1] 中的 credential.username

http.postBuffer 实际做什么?

此选项更改 Git 通过 HTTP 或 HTTPS 将数据推送到远程时使用的缓冲区大小。如果数据大于此大小,libcurl(处理 Git 的 HTTP 支持)将使用分块传输编码,因为它无法提前知道推送数据的大小。

将此值保留为默认大小是可以的,除非您知道远程服务器或中间的代理不支持 HTTP/1.1(引入了分块传输编码)或已知在使用分块数据时会崩溃。这通常(错误地)被建议作为通用推送问题的解决方案,但由于几乎所有服务器和代理都至少支持 HTTP/1.1,因此提高此值通常不能解决大多数推送问题。一个不能正确支持 HTTP/1.1 和分块传输编码的服务器或代理在今天的 Internet 上没有多大用处,因为它会破坏大量的流量。

请注意,增加此值将增加 Git 通过 HTTP 或 HTTPS 执行的每个相关推送上使用的内存,因为无论是否全部使用,都会分配整个缓冲区。因此,最好将其保留为默认值,除非您确定需要不同的值。

如何配置不同的编辑器?

如果您没有专门为 Git 指定编辑器,默认情况下它将使用您使用 VISUALEDITOR 环境变量配置的编辑器,或者如果两者都未指定,则使用系统默认编辑器(通常是 vi)。由于有些人觉得 vi 难以使用或更喜欢不同的编辑器,因此可能需要更改使用的编辑器。

如果您想为大多数需要编辑器的程序配置一个通用编辑器,您可以编辑您的 shell 配置(例如,~/.bashrc~/.zshenv)以包含一行将 EDITORVISUAL 环境变量设置为适当值的行。例如,如果您更喜欢编辑器 nano,那么您可以编写以下内容

export VISUAL=nano

如果您想专门为 Git 配置编辑器,您可以设置 core.editor 配置值或 GIT_EDITOR 环境变量。您可以查看 git-var[1] 以了解有关咨询这些选项的顺序的详细信息。

请注意,在所有情况下,编辑器值都将传递给 shell,因此任何包含空格的参数都应适当地用引号引起来。此外,如果您的编辑器通常在调用时与终端分离,您应该使用一个使其不这样做的参数来指定它,否则 Git 将看不到任何更改。一个在 Windows 上解决这两个问题的配置示例是配置 "C:\Program Files\Vim\gvim.exe" --nofork,它用空格引用文件名并指定 --nofork 选项以避免后台运行该进程。

凭据

通过 HTTP 推送时如何指定我的凭据?

最简单的方法是使用通过 credential.helper 配置的凭据助手。大多数系统都提供一个标准选择来与系统凭据管理器集成。例如,Git for Windows 提供 wincred 凭据管理器,macOS 具有 osxkeychain 凭据管理器,而具有标准桌面环境的 Unix 系统可以使用 libsecret 凭据管理器。所有这些都将凭据存储在加密存储中,以确保您的密码或令牌安全。

此外,您可以使用 store 凭据管理器,它存储在您的主目录中的文件中,或者使用 cache 凭据管理器,它不会永久存储您的凭据,但会阻止您在一段时间内被提示输入凭据。

您也可以在提示时直接输入密码。虽然可以将密码(必须经过百分比编码)放在 URL 中,但这不是很安全,并可能导致凭据意外暴露,因此不建议这样做。

如何从环境变量中读取密码或令牌?

credential.helper 配置选项也可以采用一个任意的 shell 命令,该命令在标准输出上生成凭据协议。例如,当将凭据传递到容器中时,这很有用。

可以通过以感叹号开头选项值来指定这样的 shell 命令。如果您的密码或令牌存储在 GIT_TOKEN 中,您可以运行以下命令来设置您的凭据助手

$ git config credential.helper \
	'!f() { echo username=author; echo "password=$GIT_TOKEN"; };f'
如何更改我在凭据管理器中保存的密码或令牌?

通常,如果密码或令牌无效,Git 将擦除它并提示输入新密码或令牌。但是,有时这并不总是发生。要更改密码或令牌,您可以擦除现有凭据,然后 Git 将提示输入新凭据。要擦除凭据,请使用类似以下的语法(替换您的用户名和主机名)

$ echo url=https://author@git.example.org | git credential reject
如何使用 HTTP 在同一托管提供商上使用多个帐户?

通常,区分这些帐户的最简单方法是在 URL 中使用用户名。例如,如果您在 git.example.org 上拥有帐户 authorcommitter,则可以使用 URL https://author@git.example.org/org1/project1.githttps://committer@git.example.org/org2/project2.git。这样,当您使用凭据助手时,它会自动尝试查找您的帐户的正确凭据。如果您已经设置了远程,您可以使用类似 git remote set-url origin https://author@git.example.org/org1/project1.git 的命令更改 URL(有关详细信息,请参阅 git-remote[1])。

如何使用 SSH 在同一托管提供商上使用多个帐户?

对于大多数支持 SSH 的托管提供商,单个密钥对唯一标识一个用户。因此,要使用多个帐户,必须为每个帐户创建一个密钥对。如果您使用的是相当现代的 OpenSSH 版本,您可以使用类似 ssh-keygen -t ed25519 -f ~/.ssh/id_committer 的命令创建一个新的密钥对。然后,您可以向托管提供商注册公钥(在本例中为 ~/.ssh/id_committer.pub;请注意 .pub)。

大多数托管提供商使用单个 SSH 帐户进行推送;也就是说,所有用户都推送到 git 帐户(例如,git@git.example.org)。如果您的提供商是这种情况,您可以在 SSH 中设置多个别名,以明确要使用哪个密钥对。例如,您可以在 ~/.ssh/config 中编写类似以下内容,替换正确的私钥文件

# This is the account for author on git.example.org.
Host example_author
	HostName git.example.org
	User git
	# This is the key pair registered for author with git.example.org.
	IdentityFile ~/.ssh/id_author
	IdentitiesOnly yes
# This is the account for committer on git.example.org.
Host example_committer
	HostName git.example.org
	User git
	# This is the key pair registered for committer with git.example.org.
	IdentityFile ~/.ssh/id_committer
	IdentitiesOnly yes

然后,您可以调整您的推送 URL 以使用 git@example_authorgit@example_committer 而不是 git@example.org(例如,git remote set-url git@example_author:org1/project1.git)。

传输

如何在系统之间同步工作树?

首先,确定您是否要这样做。Git 在您使用典型的 git pushgit fetch 命令推送或拉取您的工作时效果最佳,并且并非设计用于跨系统共享工作树。这具有潜在风险,在某些情况下可能会导致存储库损坏或数据丢失。

通常,这样做会导致 git status 需要重新读取工作树中的每个文件。此外,Git 的安全模型不允许跨不受信任的用户共享工作树,因此只有在所有机器上仅由单个用户使用时,同步工作树才是安全的。

重要的是不要使用云同步服务来同步 Git 存储库的任何部分,因为这可能会导致损坏,例如缺少对象、更改或添加的文件、损坏的引用以及各种其他问题。这些服务倾向于持续地逐个文件地同步,并且不了解 Git 存储库的结构。如果他们在更新存储库的过程中同步它,情况会更糟糕,因为这很可能导致不完整或部分更新,从而导致数据丢失。

一种可能发生的腐败的例子是关于引用(refs)状态的冲突,导致双方最终在同一个分支上拥有对方没有的不同的提交。这可能会导致重要的对象变为未引用状态,并可能被 git gc 修剪掉,从而导致数据丢失。

因此,最好使用正常的推送和拉取机制将你的工作推送到另一个系统或中央服务器。然而,这并不总是能保存重要的数据,比如储藏(stashes),所以有些人更喜欢在不同系统之间共享一个工作树。

如果你这样做,推荐的方法是在仓库的根目录下使用 rsync -a --delete-after (最好通过加密连接,例如使用 ssh)。当你这样做时,你应该确保以下几点:

  • 如果你有额外的工作树或单独的 Git 目录,它们必须与主工作树和仓库同时同步。

  • 你能够接受目标目录成为源目录的精确副本,删除任何已存在的数据

  • 仓库(包括所有工作树和 Git 目录)在传输期间处于静止状态(也就是说,没有任何操作正在进行,包括后台操作,如 git gc 以及编辑器调用的操作)。

    请注意,即使有这些建议,以这种方式同步仍然存在一定的风险,因为它绕过了 Git 对仓库的正常完整性检查,所以建议进行备份。你可能还希望在同步后执行 git fsck 来验证目标系统上数据的完整性。

常见问题

我在上次提交中犯了一个错误。我该如何修改它?

你可以对你的工作树进行适当的更改,运行 git add <file>git rm <file>,根据需要暂存它,然后 git commit --amend。你的更改将被包含在提交中,并且你会被提示再次编辑提交消息;如果你希望逐字使用原始消息,你可以额外使用 git commit--no-edit 选项,或者只是在你的编辑器打开时保存并退出。

我做了一个有错误的更改,并且它已经被包含在主分支中。我应该如何撤销它?

处理这个问题的常用方法是使用 git revert。这保留了原始更改被创建并且是一个有价值的贡献的历史,但也引入了一个新的提交,该提交撤销了这些更改,因为原始更改存在问题。revert 的提交消息指示了被 revert 的提交,并且通常会被编辑以包含 revert 的原因的解释。

我如何忽略对已跟踪文件的更改?

Git 不提供这样做的方法。原因是如果 Git 需要覆盖这个文件,例如在 checkout 期间,它不知道对文件的更改是否是珍贵的并且应该保留,或者它们是否是不相关的并且可以安全地销毁。因此,它必须采取安全的方式并始终保留它们。

尝试使用 git update-index 的某些特性,即 assume-unchanged 和 skip-worktree 位,是很诱人的,但这些特性不能正确地用于此目的,并且不应该这样使用。

如果你的目标是修改一个配置文件,那么将一个文件检入到仓库中作为模板或默认设置,然后将其复制到旁边并根据需要进行修改,通常是有帮助的。这个第二个修改过的文件通常会被忽略,以防止意外提交。

我要求 Git 忽略各种文件,但它们仍然被跟踪

一个 gitignore 文件确保某些未被 Git 跟踪的文件保持未跟踪状态。然而,有时特定的文件可能在将它们添加到 .gitignore 之前已经被跟踪,因此它们仍然保持被跟踪状态。要取消跟踪和忽略文件/模式,请使用 git rm --cached <file/pattern> 并添加一个与 <file> 匹配的模式到 .gitignore。有关详细信息,请参阅 gitignore[5]

我如何知道我想要执行 fetch 还是 pull?

fetch 存储来自远程仓库的最新更改的副本,而不修改工作树或当前分支。然后,你可以随意检查、合并、在上面变基或忽略上游更改。pull 包括一个 fetch,然后立即进行合并或变基。请参阅 git-pull[1]

我可以在 Git 中使用代理吗?

是的,Git 支持使用代理。Git 遵循 Unix 上常用的标准 http_proxyhttps_proxyno_proxy 环境变量,并且还可以通过 http.proxy 和类似的 HTTPS 选项进行配置(请参阅 git-config[1])。 http.proxy 和相关选项可以基于每个 URL 模式进行定制。此外,理论上 Git 可以在网络上存在的透明代理正常运行。

对于 SSH,Git 可以使用 OpenSSH 的 ProxyCommand 支持代理。常用的工具包括 netcatsocat。但是,必须将它们配置为在看到标准输入上的 EOF 时不要退出,这通常意味着 netcat 将需要 -q,而 socat 将需要一个超时,例如 -t 10。这是必需的,因为 Git SSH 服务器知道不再有请求的方式是标准输入上的 EOF,但当发生这种情况时,服务器可能尚未处理最终请求,因此在该点断开连接会中断该请求。

一个在 ~/.ssh/config 中使用 HTTP 代理的配置条目的示例可能如下所示:

Host git.example.org
    User git
    ProxyCommand socat -t 10 - PROXY:proxy.example.org:%h:%p,proxyport=8080

请注意,在所有情况下,为了使 Git 正常工作,代理必须是完全透明的。代理不能以任何方式修改、篡改或缓冲连接,否则 Git 几乎肯定会无法工作。请注意,许多代理,包括许多 TLS 中间盒、Windows 防病毒和防火墙程序(Windows Defender 和 Windows Firewall 除外)以及过滤代理,都无法满足此标准,因此最终会破坏 Git。由于有许多关于问题和它们糟糕的安全历史的报告,我们不建议使用这些类型的软件和设备。

合并和变基

使用 squash 合并合并长期存在的分支时,可能会出现哪些类型的问题?

一般来说,当使用 squash 合并多次合并两个分支时,可能会出现各种问题。这些问题可能包括在 git log 输出、GUI 中或在使用 ... 符号表示范围时看到额外的提交,以及可能需要一次又一次地重新解决冲突。

当 Git 在两个分支之间进行正常的合并时,它会考虑三个点:两个分支和一个称为 *合并基础* 的第三个提交,它通常是提交的公共祖先。合并的结果是合并基础与每个头部之间的更改的总和。当你使用常规合并提交合并两个分支时,这将产生一个新的提交,当再次合并它们时,该提交将最终成为合并基础,因为现在有一个新的公共祖先。Git 不必考虑在合并基础之前发生的更改,因此你不必重新解决之前解决的任何冲突。

当你执行 squash 合并时,不会创建合并提交;相反,来自一侧的更改作为常规提交应用于另一侧。这意味着这些分支的合并基础不会改变,因此当 Git 要执行其下一次合并时,它会考虑它上次考虑的所有更改加上新的更改。这意味着可能需要重新解决任何冲突。类似地,在 git diffgit log 或 GUI 中使用 ... 符号的所有内容都会导致显示自原始合并基础以来的所有更改。

因此,如果你想重复合并两个长期存在的分支,最好始终使用常规合并提交。

如果我在两个分支上都进行了更改,但在一个分支上还原了它,为什么这些分支的合并会包含该更改?

默认情况下,当 Git 进行合并时,它会使用一种称为 ort 策略的策略,该策略会进行复杂的三方合并。在这种情况下,当 Git 执行合并时,它会考虑三个点:两个头部和一个称为 *合并基础* 的第三个点,它通常是这些提交的公共祖先。Git 根本不考虑历史记录或这些分支上发生的单个提交。

因此,如果双方都有更改并且一方已还原该更改,则结果是包含该更改。这是因为代码在一侧发生了更改,而另一侧没有净更改,在这种情况下,Git 采用了该更改。

如果这对你来说是一个问题,你可以改为进行变基,将带有还原的分支变基到另一个分支上。在这种情况下,变基将还原该更改,因为变基会应用每个单独的提交,包括还原。请注意,变基会重写历史记录,因此你应该避免变基已发布的分支,除非你确定你可以接受这一点。有关更多详细信息,请参阅 git-rebase[1] 中的 NOTES 部分。

钩子

我如何使用钩子来防止用户进行某些更改?

进行这些更改的唯一安全位置是在远程仓库(即 Git 服务器)上,通常是在 pre-receive 钩子中或在持续集成 (CI) 系统中。这些是策略可以有效实施的位置。

通常会尝试使用 pre-commit 钩子(或者,对于提交消息,使用 commit-msg 钩子)来检查这些事情,如果你作为一名单独的开发人员工作并且希望工具来帮助你,这很好。但是,在开发人员机器上使用钩子作为策略控制是无效的,因为用户可以在不被注意的情况下使用 --no-verify 绕过这些钩子(以及各种其他方式)。Git 假设用户可以控制其本地仓库,并且不会尝试阻止这一点或告发用户。

此外,一些高级用户发现 pre-commit 钩子会阻碍使用临时提交来暂存正在进行的工作或创建 fixup 提交的工作流程,因此最好无论如何都将这些类型的检查推送到服务器。

跨平台问题

我正在 Windows 上,我的文本文件被检测为二进制文件。

Git 在存储文本文件时最好使用 UTF-8 编码。 Windows 上的许多程序都支持 UTF-8,但有些程序不支持,只使用小端 UTF-16 格式,Git 会将其检测为二进制文件。 如果你的程序不能使用 UTF-8,你可以指定一个工作区树编码,指示你的文件应该以哪种编码检出,同时仍然将这些文件以 UTF-8 格式存储在存储库中。 这样,像 git-diff[1] 这样的工具就可以按预期工作,同时仍然允许你的工具工作。

要做到这一点,你可以使用 working-tree-encoding 属性指定一个 gitattributes[5] 模式。 例如,以下模式将所有 C 文件设置为使用 UTF-16LE-BOM,这在 Windows 上是一种常见的编码

*.c	working-tree-encoding=UTF-16LE-BOM

你需要运行 git add --renormalize 才能使此设置生效。 请注意,如果你正在对一个跨平台使用的项目进行这些更改,你可能希望在每个用户的配置文件或 $GIT_DIR/info/attributes 中的配置文件中进行更改,因为在存储库中的 .gitattributes 文件中进行更改将应用于存储库的所有用户。

有关规范化行尾的信息,请参阅以下条目,有关属性文件的更多信息,请参阅 gitattributes[5]

我在 Windows 上,git diff 显示我的文件末尾有一个 ^M

默认情况下,Git 希望文件以 Unix 行尾符存储。 因此,作为 Windows 行尾符一部分的回车符 (^M) 会被显示出来,因为它被认为是尾随空格。 Git 默认只显示新行上的尾随空格,而不是现有行上的。

你可以将文件以 Unix 行尾符存储在存储库中,并自动将其转换为你平台的行尾符。 要做到这一点,将配置选项 core.eol 设置为 native,并参阅 关于推荐存储设置的问题,了解如何将文件配置为文本或二进制文件。

如果你不想从行尾移除回车符,你也可以使用 core.whitespace 设置来控制此行为。

为什么我有一个文件总是被修改?

在内部,Git 总是将文件名存储为字节序列,并且不执行任何编码或大小写折叠。 然而,Windows 和 macOS 默认都会对文件名执行大小写折叠。 因此,最终可能会出现多个文件或目录,它们的名称仅在大小写上有所不同。 Git 可以很好地处理这个问题,但是文件系统只能存储其中一个文件,所以当 Git 读取另一个文件以查看其内容时,它看起来像是被修改过的。

最好删除其中一个文件,这样你只有一个文件。 你可以使用如下命令来完成此操作(假设有两个文件 AFile.txtafile.txt),且工作区树是干净的

$ git rm --cached AFile.txt
$ git commit -m 'Remove files conflicting in case'
$ git checkout .

这避免了触碰磁盘,但删除了多余的文件。 你的项目可能更喜欢采用命名约定,例如全部使用小写名称,以避免再次出现此问题;可以使用 pre-receive hook 或作为持续集成 (CI) 系统的一部分来检查此类约定。

如果你的系统正在使用 smudge 或 clean 过滤器,但在先前提交文件时没有运行 smudge 或 clean 过滤器,则也可能在任何平台上发生永久修改的文件。 要解决此问题,请在干净的工作区树上运行以下命令

$ git add --renormalize .

GIT

git[1] 套件的一部分

scroll-to-top