简体中文 ▾ 主题 ▾ 最新版本 ▾ gitfaq 上次更新于 2.46.0

名称

gitfaq - Git 使用常见问题解答

概要

gitfaq

描述

本 FAQ 中的示例假定使用标准的 POSIX shell,如 bashdash,以及名为 author 的用户,该用户在托管提供商 git.example.org 上拥有帐户。

配置

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 选项以避免进程在后台运行。

凭据

在通过 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 存储库的结构。这尤其糟糕,如果它们在更新过程中同步存储库,则极有可能导致不完整或部分的更新,从而导致数据丢失。

可能发生的损坏类型包括对引用的状态的冲突,导致两边都有对方没有的提交。这可能导致重要对象变得未被引用,并可能被 git gc 裁剪,导致数据丢失。

因此,最好使用正常的推送和拉取机制将您的工作推送到另一个系统或中央服务器。但是,这并不总是能保留重要的数据,例如 stashes,因此有些人更喜欢跨系统共享工作目录。

如果您这样做,推荐的方法是使用 rsync -a --delete-after(最好与加密连接一起使用,例如 ssh)在存储库的根目录上。您应该确保在执行此操作时满足以下几点:

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

  • 您已准备好将目标目录作为源目录的精确副本,删除其中已有的任何数据

  • 在传输期间,存储库(包括所有 worktrees 和 Git 目录)处于静止状态(即,没有任何类型的操作正在进行,包括像 git gc 这样的后台操作以及您的编辑器调用的操作)。

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

常见问题

上一次提交时犯了个错误,如何修改?

您可以对工作目录进行适当的更改,运行 git add <file>git rm <file>(根据需要),以暂存更改,然后运行 git commit --amend。您的更改将包含在提交中,您将被提示再次编辑提交消息;如果您想逐字使用原始消息,可以在 git commit 命令中附加 --no-edit 选项,或者在编辑器打开时直接保存并退出。

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

通常处理这种情况的方法是使用 git revert。这会保留原始更改被做出并被视为有价值贡献的历史记录,但同时引入一个新的提交来撤销这些更改,因为原始更改存在问题。revert 的提交消息会指明被 revert 的提交,并且通常会被编辑以包含 revert 原因的解释。

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

Git 没有提供这样的方法。原因在于,如果 Git 需要覆盖此文件,例如在 checkout 期间,它不知道文件中的更改是宝贵的应该保留,还是不相关的可以安全销毁。因此,它必须采取安全措施并始终保留它们。

尝试使用 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 防病毒和防火墙程序(Windows Defender 和 Windows Firewall 除外)以及过滤代理,都无法满足此标准,结果导致 Git 出现问题。由于存在大量问题报告和它们糟糕的安全历史,我们不建议使用这些类别的软件和设备。

合并与变基

用 squash merge 合并长期分支时可能出现哪些问题?

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

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

当您执行 squash merge 时,不会创建合并提交;相反,一个分支的更改将作为常规提交应用到另一个分支。这意味着这些分支的合并基础不会改变,因此当 Git 进行下一次合并时,它会考虑上次考虑的所有更改以及新的更改。这意味着任何冲突都可能需要重新解决。同样,在 git diffgit log 或 GUI 中使用 ... 表示法表示的任何内容都将显示自原始合并基础以来发生的所有更改。

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

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

默认情况下,当 Git 进行合并时,它使用一种称为 ort 的策略,该策略进行巧妙的三向合并。在这种情况下,当 Git 执行合并时,它会考虑三个点:两个头和一个称为合并基础的第三点,它通常是这些提交的共同祖先。Git 完全不考虑这些分支上的历史或单个提交。

结果是,如果两边都有更改,而一边撤销了该更改,则结果是包含该更改。这是因为代码在一边发生了变化,而另一边没有净变化,在这种情况下,Git 会采纳更改。

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

钩子

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

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

通常尝试使用 pre-commit 钩子(或对于提交消息,使用 commit-msg 钩子)来检查这些内容,这对于作为 solo 开发人员并且希望工具提供帮助的人来说非常有用。但是,在开发人员机器上使用钩子作为策略控制无效,因为用户可以使用 --no-verify 绕过这些钩子而不被发现(以及其他各种方式)。Git 假定用户控制其本地存储库,并且不尝试阻止此操作或告发用户。

此外,一些高级用户发现 pre-commit 钩子会阻碍使用临时提交来暂存待处理工作或创建修复提交的工作流程,因此最好将这些类型的检查推送到服务器。

跨平台问题

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

Git 在存储文本文件为 UTF-8 时效果最佳。Windows 上的许多程序支持 UTF-8,但有些不支持,只使用小端 UTF-16 格式,Git 将其检测为二进制。如果您无法在程序中使用 UTF-8,您可以指定一个工作目录编码,指示文件在 checkout 时应使用何种编码,同时仍在存储库中以 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 过滤器,但之前的文件提交时没有运行 smudge 或 clean 过滤器,也可能出现永久修改的文件。要解决此问题,请在其他干净的工作目录中运行以下命令:

$ git add --renormalize .

GIT

Git[1] 套件的一部分