简体中文 ▾ 主题 ▾ 最新版本 ▾ gitfaq 最后更新于 2.53.0

名称

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

概要

gitfaq

描述

本常见问题解答中的示例假定使用标准 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 向远程推送数据时使用的缓冲区大小。如果数据大于此大小,处理 Git HTTP 支持的 libcurl 将使用分块传输编码,因为它事先不知道要推送数据的大小。

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

请注意,增加此值将增加 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 选项以避免将进程后台化。

为什么不提供 commit.signoff 和其他配置变量?

Git 有意不(也永远不会)提供像 commit.signoff 这样的配置变量,以默认自动添加 --signoff。原因是保护签名的法律和意图意义。如果存在更多自动化和广为人知的添加签名的方式,那么以后有人就更容易辩称 "Signed-off-by" 尾部只是出于习惯或自动化添加的,而提交者并没有完全意识到或打算证明他们同意开发者原创证书(DCO)或类似的声明。这可能会损害签名在法律或合同情况下的可信度。

存在 format.signoff,但那是一个历史错误,它不是在此基础上添加更多同类错误的借口。

凭据

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

最简单的方法是通过 credential.helper 配置使用凭据助手。大多数系统提供标准选择,可与系统凭据管理器集成。例如,Windows 版 Git 提供 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 仓库的结构。如果在更新过程中同步仓库,情况会特别糟糕,因为这很可能导致不完整或部分更新,从而导致数据丢失。

可能发生的损坏类型的一个示例是引用状态冲突,导致双方在某个分支上都拥有对方没有的不同提交。这可能导致重要对象变得无法引用,并可能被 git gc 清理,从而导致数据丢失。

因此,最好使用正常的推送和拉取机制将您的工作推送到另一个系统或中央服务器。在 Git 2.51 中,Git 学会了导入和导出贮藏,因此可以通过使用 git stash 贮藏工作区,然后使用 git stash export --to-ref refs/heads/stashes 导出所有贮藏(假设您想导出到 stashes 分支),或者通过在命令末尾添加它们的编号来选择贮藏,从而同步工作区的状态。也可以通过在最初贮藏数据时使用 --include-untracked 参数来包含未跟踪文件,但如果这些文件包含敏感信息,请务必小心不要这样做。

然后,您可以推送 stashes 分支(或您已导出到的任何分支),将其拉取到本地系统(例如使用 git fetch origin +stashes:stashes),并使用 git stash import stashes 在另一个系统上导入贮藏(同样,根据需要更改名称)。可以使用 git stash popgit stash apply 将更改应用于工作区。这是最健壮且最有可能避免意外问题的方法。

话虽如此,在某些情况下,人们仍然倾向于在系统之间共享工作区。如果您这样做,建议的方法是在仓库根目录上使用 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 选项,或者在编辑器打开时直接保存并退出。

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

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

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

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

尝试使用 git update-index 的某些功能,即 assume-unchanged 和 skip-worktree 位,是很诱人的,但它们不适用于此目的,不应这样使用。

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

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

gitignore 文件确保 Git 未跟踪的某些文件保持未跟踪状态。但是,有时特定文件在添加到 .gitignore 之前可能已被跟踪,因此它们仍然保持跟踪状态。要取消跟踪并忽略文件/模式,请使用 git rm --cached <file/pattern> 并向 .gitignore 添加匹配 <file> 的模式。有关详细信息,请参阅 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 将需要 -qsocat 将需要一个超时,例如 -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 防火墙),以及过滤代理都未能达到此标准,因此最终导致 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 钩子阻碍了使用临时提交来暂存进行中的工作或创建修复提交的工作流,因此最好还是将这些类型的检查推送到服务器。

跨平台问题

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

当您将文本文件存储为 UTF-8 时,Git 的工作效果最佳。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 钩子或作为持续集成 (CI) 系统的一部分来检查此类约定。

如果您的系统上正在使用涂抹或清理过滤器,但文件之前没有运行涂抹或清理过滤器就已提交,那么任何平台上也可能出现永久修改的文件。要解决此问题,请在一个干净的工作树上运行以下命令

$ git add --renormalize .

GIT

Git[1] 套件的一部分