git - 如何修复 GitHub [Unrecognized author] 提交消息和链接到错误用户的提交?
问题描述
我被要求在 git hub 上执行此操作
“看起来你没有正确配置你的 git,所以你的提交与匿名用户相关联:”
我是开源和 git 的新手,在对 git 设置进行更改后,任何人都可以帮助我。我需要再次 git push 或者只需要 git commit 。
解决方案
您似乎在这里有多个问题,可能包括:
如何在 Git 中配置用户名和电子邮件地址?
如何将提交/贡献与 GitHub 上的名称相关联?
如何更改许多现有提交的作者姓名和/或电子邮件地址?
StackOverflow 指南说你应该在每个问答中坚持一个问题,所以让我们专注于这个问题:
[GitHub] repo 的所有者要求我再次提交,因为我没有遵守提交消息指南。现在我的问题是在我的本地机器上,我已经有一个我两天前制作的更新文件,所以当我打开我的终端时,我只需要使用 cd 和 git commit -m "---" 选择那个 repo 然后git push 或我应该做的其他事情。那么那个旧的提交会发生什么?我只是完全困惑。
Git 是一个很大的系统,有很多关于它的知识,但是为了让你开始,让我们做一个快速的概述:
Git 是关于提交的。Git 与文件无关,尽管提交存储文件。Git 也不是关于分支的,尽管分支名称可以帮助我们找到提交。提交是 Git 存在的原因:Git 所做的一切都是为了寻找、使用和进行新的提交。
为了实现这一点,Git 本身在很大程度上是一个包含两个数据库的系统。
一个——通常是迄今为止最大的——包含提交对象和其他支持对象。这些对象,包括提交,都有数字,但数字不是简单的计数:它们不会先“提交 #1”,然后是“提交 #2”,然后是 3 和 4,以此类推。相反,每个提交都会获得一个唯一但非常大的数字,以十六进制表示,看起来是随机的(但实际上不是)。
这些看似随机的数字对人类来说是毫无用处的,人类无法保持它们的正确性。因此,Git 提供了第二个通常更小的数据库,它将名称(例如分支名称)映射到提交哈希 ID 号。
这些数据库有一个特别之处。当您使用 时
git clone
,您会制作一个包含所有提交对象和其他对象的大型数据库的副本。您和您的 Git 存储库最终将与他们共享您存储在数据库中的提交——与他们的 Git 存储库和提交数据库。所以提交得到共享。但是您实际上并没有制作名称数据库的副本。相反,您会获得自己的独立名称数据库,其中包含不同的名称。这些名称至少不完全共享。
同样,您要做的是进行新的提交。你已经做了一个。他们不喜欢那一次提交,所以你必须至少再做一次。您还提到您:
已经有一个更新的文件...
我认为这意味着自您上次提交以来已更新。
因为您需要进行另一个新的提交,所以您现在有一个选择:
您可以采用其他存储库所有者不喜欢的现有提交,并制作该提交的新版本和改进版本,该版本仅修复 name-and-email-address 部分。然后,您可以在该提交中添加另一个更新此“更新文件”的提交。这为您提供了三个总提交,您只希望其中两个进入另一个存储库。
或者,您可以从他们不喜欢的提交中获取所有更新,并将这个更新的文件作为另一个更新滚动,并进行一个新的提交,以您以前的方式更新文件,除了它还包括这个新的更新. 当您进行这个新的和改进的提交时,您还将修复 name-and-email-address 部分。这为您提供了两个总提交,您只希望其中一个进入另一个存储库。
这部分是您的选择,是否以及何时为您的一个现有提交进行两次替换(新的和改进的)提交,或者只是为您的一个现有提交进行一次替换(新的和改进的)提交,往往见仁见智。您和其他(开源)存储库的所有者在这里也可能有不同的看法;如果是这样,那是您需要解决的问题。Git 在这里只提供机制,不提供意见。
关于提交的知识
首先,您需要记住您已经在此处阅读过的内容。 每个提交都有一个唯一的编号。 这个大而丑陋的哈希 ID或对象 ID编号对于人类来说很难记住实际数字,但是您应该识别提交哈希 ID 的东西。它们出现在git log
输出中,您可以使用鼠标或其他任何东西来剪切和粘贴它们。由于每个提交都有一个唯一的编号,因此您可以轻松地在您的克隆中找到此提交(如果您的克隆有它)。
例如,如果我git log
在 Git 本身的 Git 存储库中运行,我最终会看到ebf3c04b262aa27fbb97f8a0156c2347fecafafb
. (这是代表 Git 版本 2.32.0 的提交的提交哈希 ID。)这个数字现在永远为那个提交保留,所以你永远不会在任何不是包含提交的存储库的存储库中看到它启动 Git 本身。1 如果我git show
在该编号上运行,我将在 Git 的 Git 存储库的克隆中看到该提交,但bad object
如果我在其他 Git 存储库中尝试这样做,则会收到错误 ( )。
接下来要了解的关于提交的事情是:
提交是永久的(大部分)和只读的(完全)。这意味着您永远无法更改任何提交,并且提交很难摆脱。你可以做到让你找不到它们,一旦提交真的无法找到,并且经过足够的时间,Git 决定根本没有人关心那个提交,然后它就真的消失了。(如果您可以在
git log
不使用提交的原始哈希 ID 的情况下运行,然后找到该提交,它就永远不会消失。我们称这种提交可达,或者更宽松地称为可查找。大多数提交都是可查找的,因此永恒的。)每个提交都包含每个文件的完整快照,就像 tar 或 zip 存档一样。这就是 Git 可以恢复旧版本文件的方式和原因。
但是,提交中的文件不是普通文件。它们采用特殊的只读、仅 Git、压缩和重复数据删除形式。这使得它们只有 Git 本身可以读取它们,实际上没有任何东西可以编写它们——这使得它们对于完成任何实际工作毫无用处。
但是,压缩和重复数据删除非常有用。这意味着即使每个提交都包含每个文件,但新提交(主要使用与旧提交中相同的文件副本)几乎不占用空间。与旧文件相同的文件的新“副本”只是从字面上重新使用旧文件。
除了保存快照之外,每个提交还保存一些元数据:诸如提交作者的姓名和电子邮件地址之类的东西。与快照一样,此元数据在您(或任何人)进行提交时进入提交。从那时起,这一切都是只读的。
这意味着您现有的“错误”提交中包含错误的名称和/或电子邮件地址。你真的无法修复它——没有人也没有任何东西可以改变任何现有的提交——所以你必须做出一个新的和改进的提交,并让你的 Git 使用那个。
其中一个元数据是 Git为自己设置的。每个提交都存储了一些早期提交的原始哈希 ID。我们称这些为此次提交的父母。大多数提交只存储一个父提交哈希 ID。我们称它们为普通提交,以将它们与合并提交(包含两个父提交哈希 ID)或根提交(这是某人在新的空 Git 存储库中进行的第一次提交,没有父提交)区分开来。2
这个父级的东西对 Git 本身至关重要。你通常不需要关心它——Git 会自动为你做这件事——但Git会。它具有将提交链接在一起的效果,尽管是向后的。
假设我们有一个很小的存储库,其中只有三个提交。这三个提交具有大而难看的随机散列 ID,但为了让我们自己更容易,我们将它们称为提交A
、B
和C
,并说我们按此顺序制作它们。根提交也是如此A
,并且根本不向后指向。但是,提交将提交作为其父级:向后指向. Commit是我们最近的提交,并且作为它的父级,所以向后指向.B
A
B
A
C
B
C
B
我们可以这样画出这种情况:
A <-B <-C
注意如何C
指向B
,哪个指向A
。
每个提交都有每个文件的完整快照。通过从和 中提取文件B
, C
Git 现在可以比较两组文件。由于文件是重复数据删除的,Git 可以很容易地分辨出哪些文件是相同的B
。C
所以 Git 可以立即告诉我们哪些文件我们从更改B
为C
. 通过比较这些文件的内容,Git 可以向我们展示一个差异:一个我们可以重现更改的方法。在大多数情况下,该配方将与我们所做的实际更改相匹配(尽管偶尔会出现一些边缘情况,Git 会向我们展示其他一些最终会做同样事情的东西)。
这张图非常简单实用,但有一个大问题:commit 的实际哈希 IDC
不仅仅是一个简单的单个字母C
,比如ebf3c04b262aa27fbb97f8a0156c2347fecafafb
. 这是分支名称的来源。
1从技术上讲,该编号可以在不相关的存储库中重复使用。这些数字本来是要大到始终唯一的,实际上它们通常是唯一的,但是通过部分破坏 SHA-1,人们已经进行了设置,因此 Git 将不得不切换到更大的哈希 ID未来。这就是生活。
2从技术上讲,一个合并提交有两个或多个父提交,并且可以有多个根提交。但这些都是不寻常的情况,在这里你不需要担心。
分支名称帮助我们(和 Git)找到提交
给定一个更大的存储库——比如至少有 8 个提交——我们可能会像这样绘制其中的一部分:
... <-F <-G <-H
由于这些箭头总是向后指向,并且在文本中绘制很烦人,所以我倾向于变得懒惰并像这样绘制它们:
...--F--G--H
这里H
代表最新提交的哈希 ID:例如,我们刚刚在我们的分支上创建的main
那个,或者我们从其他人那里得到的最新一个,或者其他。
我不想尝试记住 Git 哈希 ID,我怀疑你也这样做。但我们不必:我们有一台电脑。我们可以让Git为我们做这件事。让我们在name中存储Git哈希 ID ,指向commit ,如下所示:H
main
main
H
...--F--G--H <-- main
现在,让我们创建一个新的分支名称。在 Git 中,每个分支名称都必须指向某个现有的提交。我们可以选择F
or G
here,但为什么要使用旧的提交呢?有一天我们可能有理由这样做,但我这里没有,所以我也会H
在这里使用。我将feature
提交名称指向H
:
...--F--G--H <-- feature, main
现在我们需要一种方法来记住我们正在使用的分支名称。Git 也会为我们做到这一点,HEAD
只需将特殊名称附加到其中一个分支名称上。因此,如果我们使用 main
,我们会这样绘制:
...--G--H <-- feature, main (HEAD)
(我变得更加懒惰并停止绘图F
,但它仍然存在,就像所有早期的提交一样。我们只是不会费心去寻找它们,尽管git log
会。)
如果我们现在切换到 branch feature
,使用git checkout feature
or git switch feature
,会发生以下情况:
...--G--H <-- feature (HEAD), main
该名称HEAD
现在附加到feature
. 没有其他任何事情发生,因为这两个名称——<code>feature 和main
——现在都选择了同一个提交,即 commit H
。
Git 的索引和你的工作树
我之前提到过,提交中的文件是一种特殊的、只读的、仅限 Git 的、重复数据删除的形式。要实际使用这些文件,Git 必须将它们从提交中复制出来。这些是我们看到和使用的文件。根据我们计算机文件系统的要求,它们存在于目录(或文件夹,如果您更喜欢该术语)中。3 Git 将此称为我们的工作树,因为那是我们真正完成工作的地方。
出于某种原因, Git 4还将每个文件的额外“副本”存储在 Git 所谓的index或staging area中,或者——现在很少见的cache中。我在此处将副本放在引号中,因为该区域中的文件采用 Git 内部的重复数据删除格式。此处的内容充当您提议的 next commit。我不会在这里详细介绍,但这是 Git 迷惑新手的另一种方式。这就是为什么您必须git add
如此频繁地归档,而不是只添加一次新文件并完成它。
所有这一切的最终结果是,实际上每个文件的三个副本始终处于活动状态:
HEAD index work-tree
--------- --------- ---------
README.md README.md README.md
lib.c lib.c lib.c
prog.py prog.py prog.py
管他呢。“HEAD”副本是当前提交中的副本: Git 通过读取HEAD
以查看哪个分支是当前分支来查找当前提交,然后读取分支名称以查找提交。该文件实际上无法更改,尽管您可以从一个提交切换到另一个提交。索引副本可以批量替换,使用git add
. 您的工作树副本是您使用/使用的副本。当你更新它时,你运行git add
让 Git 换入一个新的、预压缩的、预去重的副本(如果是副本,则为“副本”),准备进入下一次提交。
当你从一个提交切换到另一个提交时,Git 必须从它的索引和你的工作树中删除来自这个提交的所有文件。然后,Git 可以插入从您切换到的新提交中产生的所有文件。当您使用git checkout
orgit switch
并且它需要更改提交时会发生这种情况。
3 Git 自己在提交中的内部存储文件不使用文件夹,或者至少不以这种方式使用:Git 使用完全不同的系统,这就是为什么只有 Git 可以读取这些存档文件的原因。
4其他版本控制系统不这样做,这证明 Git不必这样做。但Git 做到了这一点。
我们没有更改提交,所以还没有发生任何事情
我们开始:
...--G--H <-- feature, main (HEAD)
然后切换到feature
:
...--G--H <-- feature (HEAD), main
我们仍在使用commit ,所以 Git还H
没有改变任何东西。
现在让我们修改工作树中的一些文件并运行git add
. 这将更新提议的下一次提交。然后我们就跑git commit
。
该git commit
操作将:
打包所有文件,因为它们现在出现在 Git 的索引中:因为我们
git add
-ed 我们的更改,那些是我们更新的文件,除非索引仍然有我们没有的文件git add
,在这种情况下,它们是提交内容的副本H
.添加新提交的元数据。Git 将从
git config
设置中获取我们的姓名和电子邮件地址。Git 将从计算机的时钟中获取当前日期和时间。而且,对于 Git 本身至关重要的是,Git 将找到 commitH
的真实哈希 ID——实际的大丑数——并将其用作新提交的父级。
Git 现在会将所有这些写出来,这会使用一个新的、唯一的、又大又丑的哈希 ID 进行新的提交,我们将I
简称为:
...--G--H
\
I
现在来了一个特殊的技巧:在进行了新的提交之后,Git 只需将新提交的哈希 ID(无论它是什么)写入当前分支 name中,就像HEAD
附加的任何名称一样。所以现在main
仍然指向现有的提交H
,但feature
指向新的提交I
:
...--G--H <-- main
\
I <-- feature (HEAD)
我们有一个新提交,其父提交是旧提交。我们的新分支名称现在选择新的提交。如果我们现在:
git checkout main
我们会得到:
...--G--H <-- main (HEAD)
\
I <-- feature
Git 将从 commit 中取出文件I
并放入 commit 中的文件H
。我们更新的文件仍然存在,并且可以通过使用名称feature
find commit 找到I
。但是我们看到和使用的文件现在是来自 commit 的文件H
,如名称所示main
。
替换错误的提交
假设 new commit 有问题I
。这可能是快照中的错误文件,或错误的作者姓名或电子邮件地址,或两者兼而有之。我们无法修复错误的提交,但我们总是可以进行新的提交。
如果我们只是签出feature
并进行新的提交,这个新的提交将添加到现有的提交中,就像以前一样:
git checkout feature
... do some work, maybe run `git config` too/instead ...
... git add files if needed ...
git commit
我们会得到:
...--G--H <-- main
\
I--J <-- feature (HEAD)
错误的提交将保留。我们能做什么?
好吧,一个技巧是创建一个新的分支名称,但在提交H
时再次启动它:
...--G--H <-- main, redo (HEAD)
\
I <-- feature
现在我们可以重新做任何我们做的事情I
,但这次做对了,然后添加并提交:
J <-- redo (HEAD)
/
...--G--H <-- main
\
I <-- feature
我们现在可以删除名称feature
:
J <-- redo (HEAD)
/
...--G--H <-- main
\
I ???
CommitI
仍然存在,但我们再也找不到它了。该git log
命令不会向我们显示其哈希 ID。4 如果我们愿意,我们可以重命名redo
为feature
,就好像我们一直都做对了一样。
不过,有一种更简单的方法可以做到这一点,使用git commit --amend
. 该--amend
选项不会更改提交:这实际上是不可能的。它所做的是改变Git 进行新提交的方式。
通常,有:
...--G--H <-- main
\
I <-- feature (HEAD)
一个新的提交添加J
到当前分支的末尾。J
is的父级I
和 namefeature
现在指向J
。
有了--amend
,名称feature
将指向我们的新提交J
,但不是指向I
,我们的新提交J
将指向现有提交H
。Git 只是找到当前提交的父级I
,并将它们用作新提交的父级J
。所以我们得到:
I ???
/
...--G--H <-- main
\
J <-- feature (HEAD)
请注意,这只会“弹出”最后一次提交;如果我们有:
...--H--I--J--K <-- branch (HEAD)
并运行git commit --amend
,我们得到:
K ???
/
...--H--I--J--L <-- branch (HEAD)
提交K
不再有任何方法可以找到它。
要进行更高级的操作,您git rebase
可能需要git rebase -i
. 这意味着复制许多提交,然后使用新的和改进的副本而不是原始副本。但是,它变得非常复杂,所以我们不会在这里讨论。
4这并不完全正确,但它是 Git 最终I
完全放弃提交的方式。
推荐阅读
- haskell - Haskell:用 if-else 语句理解 do 表示法
- javascript - 如何实现 JavaScript 时钟?
- amazon-web-services - 如何监控胶水爬虫执行统计信息?
- python - 如何迭代行并将值分配给新列
- javascript - MomentJS toISOString AM/PM 不正确
- haskell - 尝试在脚手架站点中使用 Yesod.Form.Richtext,由于 Summernote.hs 中的问题,我无法编译
- php - Magento 2.3 中的可售数量不显示
- mysql - MySQL 未启动且意外停止
- javascript - 字符串中的“字符串”是 JavaScript 中的保留字吗?
- amazon-web-services - ClientError: lst 应该至少有三个部分,但只有 1 个部分