简体中文 ▾ 主题 ▾ 最新版本 ▾ gitfaq 最后更新于 2.53.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 向远程仓库推送数据时所使用的缓冲区大小。如果数据量超过此大小,处理 Git HTTP 支持的 libcurl 将使用分块传输编码(chunked transfer encoding),因为推送数据的大小无法预先获知。

除非你知道远程服务器或中间代理不支持 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 配置使用凭证助手。大多数系统提供与系统凭证管理器集成的标准选择。例如,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 pushgit fetch 命令推送或拉取工作成果时,Git 的表现最好,它并非设计用于跨系统共享工作区。这样做具有潜在风险,在某些情况下可能会导致仓库损坏或数据丢失。

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

重要的是不要使用云同步服务来同步 Git 仓库的任何部分,因为这可能导致损坏,例如对象丢失、文件被修改或添加、引用损坏以及其他各种各样的问题。这些服务往往会在持续的基础上逐个文件进行同步,而不了解 Git 仓库的结构。如果在 Git 仓库正在更新时进行同步,情况尤为糟糕,因为这极有可能导致不完整或部分更新,从而导致数据丢失。

可能发生的损坏示例如引用状态冲突,导致双方分支上最终出现对方所没有的不同提交。这可能导致重要的对象变为无引用状态,并可能被 git gc 裁剪,从而导致数据丢失。

因此,最好使用正常的推送和拉取机制将你的工作推送到另一系统或中央服务器。在 Git 2.51 中,Git 增加了导入和导出储藏(stash)的功能,因此可以通过 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。这既保留了原始修改已被纳入且是一项有价值贡献的历史,也引入了一个新的提交来撤销这些更改,因为原始提交存在问题。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,紧接着执行 merge 或 rebase。请参阅 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 Defender 和 Windows 防火墙之外的 Windows 防病毒和防火墙程序,以及过滤代理,都无法达到此标准,结果导致 Git 无法工作。由于有许多关于此类问题和不良安全记录的报告,我们建议不要使用这些类别的软件和设备。

合并与变基

在合并具有压缩合并(squash merges)的长期分支时会发生什么问题?

总的来说,当多次使用压缩合并来合并两个分支时,可能会出现各种问题。这些问题包括在 git log 输出中、在图形界面中或在使用 ... 符号表示范围时看到额外的提交,以及可能需要反复解决冲突的可能性。

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

当你执行压缩合并时,不会创建合并提交;相反,来自一侧的更改将作为常规提交应用于另一侧。这意味着这些分支的合并基准不会改变,因此当 Git 下次进行合并时,它会考虑它上次考虑的所有更改以及新的更改。这意味着任何冲突都可能需要重新解决。同样,在 git diffgit log 或图形界面中使用 ... 符号的任何操作都会显示自原始合并基准以来的所有更改。

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

如果我在两个分支上进行了更改但在其中一个上撤销了它,为什么合并这些分支时仍包含该更改?

默认情况下,当 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) 系统的一部分进行检查。

如果你的系统上使用了 smudge 或 clean 过滤器,但文件之前在未运行这些过滤器的情况下被提交,那么在任何平台上都可能出现永久修改的文件。要修复此问题,请在工作区干净的情况下运行以下命令:

$ git add --renormalize .

GIT

Git[1] 套件的一部分