Warm tip: This article is reproduced from serverfault.com, please click
git

其他-Git再次“一切都最新”

(其他 - Git 'Everything up-to-date' push again)

发布于 2020-11-28 14:54:05

我知道有很多问题在问同样的问题,但是我没有找到针对我的特定问题的准确答案。

我只是在服务器上设置了一个新的远程git repo并使用了:

git push --all origin

但是,并不是所有的东西都在远程服务器上运行,因此我想再次推送它。无论我尝试了什么,我总是会收到相同的消息:

Everything up-to-date

编辑:

我的意思是git statusHEAD detached at f6b6299 nothing to commit, working tree clean

如上所述,我在遥控器(hint: The 'hooks/post-receive' hook was ignored because it's not set as executable.上弄乱了东西

第一次推送成功: Enumerating objects: 121, done. Counting objects: 100% (121/121)...

但是,在修复上述错误并尝试再次推送后,我收到了Everything up-to-date消息。我想再次推送它,但似乎不可能。

我在这里想念的是什么以及如何将其再次推到遥控器上?

Questioner
Radical_Activity
Viewed
0
torek 2020-11-29 00:51:17

啊哈,这很重要:

我得到这个git状态:

HEAD detached at f6b6299
nothing to commit, working tree clean.

的方式git push工作很简单,但也有很多的细节管理。你很可能想在自己的存储库中创建一个新的分支名称,或更新一些现有的分支名称,以标识commit f6b6299如果不了解你的具体情况,很难给出更具体的答案。

如上所述,我在遥控器(hint: The 'hooks/post-receive' hook was ignored because it's not set as executable.上弄乱了东西

这是一个单独的问题,必须单独解决。无法直接在Git本身中解决此问题。

龙:知道些什么 git push

我们首先要了解Git本身,这是最基本的知识:Git就是有关commits的一切那些刚接触Git的人经常认为它与文件有关。不是:它是关于提交的。提交包含文件,这是文件输入图片的位置,但重点是提交。或者,他们可能认为Git与分支有关。不是:它仍然是关于提交的。分支允许我们查找提交,这就是分支名称输入图片的地方。当然,能够找到提交非常重要!但这实际上仍然是提交本身的全部。

关于提交要了解的内容

每个Git提交都有编号。每个人都有一个唯一的号码,但号码不是连续的。我们不能仅仅对它们进行计数:我们不能将它们列出为提交#1,提交#2等。相反,我们得到了这些大的丑陋的哈希ID。它们看起来是随机的,但实际上完全是确定性的。一串数字和字母,例如f6b6299(但更长:这个缩写为完整唯一编号的唯一前缀)实际上是一个十六进制数字,给出了该特定提交的原始编号。

这个数字在每个Git存储库中都是相同的,因此我可以通过查看我是否有一个以数字开头的提交f6b6299(它必须与整个事物完全匹配,来确定我是否有提交)。需要更长的版本)。任何Git存储库都可以通过检入其数据库来判断是否具有此提交。Git存储库中的主数据库中充满了由这些数字索引的内容:这就是Git存储提交(以及随之而来的支持对象)的方式。

有时,你会将Git连接到其他Git。这两个Git具有一种Git-sex,可以在其中交换提交(尽管是单向的:一个Git是发送方,另一个是接收方)。这些是git push与之相对的地方git fetch,但是我们稍后再讨论。

无论如何,回到提交:每个都存储两件事:

  • 提交存储你或其他人提交提交时Git知道的每个文件的完整快照。这些文件以特殊的只读,仅Git,压缩和重复数据删除格式存储。重复数据删除解决了人们的反对意见,即当你只更改数千个文件中的一个文件时进行新提交会浪费大量空间。它将这样做,除了如果有5000个文件并且你更改了一个文件之外,新提交仅重用4999个文件。

    这是完全安全的,因为提交中的所有文件永远都是只读的。 任何部分的任何承诺都不能更改。这是因为提交的唯一哈希ID包含了提交数据和元数据的每一位。如果你确实确实提取了提交,进行了一些修改然后放回去,那么你得到的是带有新的唯一哈希ID的新提交。现有提交保持不变。

  • 同时,每个提交还存储一些元数据,即有关提交本身的信息。元数据包括你的姓名(或进行提交的人的姓名),电子邮件地址以及一些日期和时间标记等。它包括你使用看到的日志消息git log,这是你解释为什么进行提交的机会

    不过,对于Git本身而言至关重要的是,Git向此元数据添加了自己的内容。每个提交都存储一些较早提交的原始哈希ID 大多数提交仅存储其直接父级的一个哈希ID。(在此,我们将不讨论此规则的例外情况。)

这是什么对我们来说意味着此处是很简单的:它意味着每次提交,实际上,点向后到紧前提交。让我们用单个大写字母代表实际的,随机外观的哈希ID来绘制此代码:

... <-F <-G <-H

此处H最新提交的哈希ID 例如,可能是f6b6299无论是什么,我们都可以将其记在纸上,白板上或其他东西上,以便我们知道。然后,我们可以在各种Git命令中键入该丑陋的哈希ID,例如git logGit将使用该哈希ID在其“包含所有提交的所有Git对象”的大型数据库中进行查找,以查看其是否具有该哈希ID(并检查它是否也是提交,因为还有其他一些对象类型)。

当然,你的Git仓库实在没有这个承诺,所以Git的发现它。因此,Git可以提取该提交附带的所有文件:它们是提交中的数据。同时,Git还可以提取所有元数据,因此它可以显示你进行了提交,以及何时进行。它可以显示你的日志消息。但是对于Git而言,最重要的是,它可以找到该提交之前即将发生的提交实际哈希ID我们称为)G

使用的哈希ID G,Git可以提取提交G这样就可以获取所有文件的完整快照H,例如,Git可以将其与其中的快照进行比较,以查看更改了什么G不过,这也会获取Git的元数据,其中包括尚早提交的哈希ID F

使用的哈希ID F,Git可以读取该提交。Git可以对链中的每个提交继续这样做,从最新到历史的最开始。这些提交中的每一个,当它遍历这个向后看的链时,都是存储在Git存储库中的历史记录。所有这一切都通过提交哈希ID来实现:我们只需要通过最新的哈希ID保存某个地方就可以开始整个工作

分行名称

这就是分支名称的来源。考虑到我们只要写下最后一个的哈希ID就能找到每个提交,那么,为什么我们不让计算机写下它呢?让我们将最后一次提交的数字(哈希ID)保存在文件或其他内容中。实际上,让我们建立另一个小数据库,其中包含诸如和的名称在此数据库中,让Git存储某个链中最后一次提交的哈希ID developfeature/tall

我们可以这样画:

...--F--G--H   <-- main
         \
          I--J   <-- develop

也就是说,我们的名称数据库包含分支名称(还包括标记名称和其他名称)main,其中包含分支名称,该分支名称包含commit的哈希ID H根据定义,这是该分支上最后一次提交。它还具有develop保存哈希ID的名称J,根据定义,哈希ID分支上最后一次提交develop

我们说名称指向提交,提交指向父母。因此main指向H,它指向返回G,依此类推。同样,develop指向指向J,指向I,指向G提交通过G两个分支上,而提交H仅在上main如果要强调这一点,我们可以像这样绘制同一组提交:

          H   <-- main
         /
...--F--G
         \
          I--J   <-- develop

这表示相同的Git存储库,具有相同的提交和名称。我们只是略有不同地绘制了它,以弄清楚分支的分支位置以及两个分支上的提交。

(请注意,我们在这里相当宽松地使用了分支一词。另请参见“分支”到底是什么意思?

HEAD,或者,我们如何知道我们正在使用哪个分支?

假设我们确实有这个:

          H   <-- main
         /
...--F--G
         \
          I--J   <-- develop

如果运行git checkout maingit switch main则将使用名称 main哈希ID为提交H如果运行git checkout developgit switch develop则将使用名称 develop哈希ID为提交J我们想要一种记住使用哪个名称的好方法。我们开工吧:

          H   <-- main (HEAD)
         /
...--F--G
         \
          I--J   <-- develop

在这里,特殊名称HEAD告诉我们我们正在使用哪个分支名称我们只需将其附加到某个分支名称即可。那就是我们使用的名字!如果我们有此设置,并进行新的提交,则Git将通过写出新的提交,为其赋予新的唯一哈希ID并将该哈希ID填充到name中 来处理该过程main新的提交将指向commit H,因为这是我们为了进行新提交而开始的提交,现在,我们将拥有:

          H--K   <-- main (HEAD)
         /
...--F--G
         \
          I--J   <-- develop

在这里,HEAD仍附加到main,但现在的名称 main表示commit K

这里的想法是:

  • 分支名称查找提交;
  • 特殊名称HEAD找到分支名称;
  • 当我们添加新的提交时,它们指向我们用来进行新提交的提交,而Git将提交的ID写入分支名称中。

这就是在分支上的含义特殊名称HEAD附加到该分支名称上。

独立式 HEAD

如果出于某种原因,我们想看看某个历史性的提交怎么办?鉴于:

          H--K   <-- main
         /
...--F--G
         \
          I--J   <-- develop

我们可能要检查提交G我们可以做到的一种方法是附加一个新的分支名称提交G

          H--K   <-- main
         /
...--F--G   <-- inspect
         \
          I--J   <-- develop

然后我们可以使用git checkout inspectgit switch inspect然后进行提交G并查看它,甚至进行新的提交:

          H--K   <-- main
         /
...--F--G   <-- inspect (HEAD)
         \
          I--J   <-- develop

一旦我们进行了新的提交,它将获得一个新的哈希ID:

          H--K   <-- main
         /
...--F--G--L   <-- inspect (HEAD)
         \
          I--J   <-- develop

分支以通常的方式成长。

很好,在Git中很容易做到,但是Git并不需要我们实际使用这个新名称。我们可以代替,只要HEAD直接提交一些也就是说,我们可以这样做:

          H--K   <-- main
         /
...--F--G   <-- HEAD
         \
          I--J   <-- develop

如果这样做,并且现在进行一次提交,则会得到以下信息:

          H--K   <-- main
         /
...--F--G--L   <-- HEAD
         \
          I--J   <-- develop

在Git中,这种操作模式称为分离式HEAD可以通过这种方式工作,但是它会令人烦恼和痛苦,因为分支名称是我们通常存储最后提交哈希ID的位置。

你通常不希望在分离式HEAD模式下工作

假设我们已经提交L没有在纸上或其他任何东西上写下哈希ID,并决定我们要查看develop并运行git checkout developgit switch develop我们会得到这个:

          H--K   <-- main
         /
...--F--G--L   ???
         \
          I--J   <-- develop (HEAD)

特殊名称HEAD不再包含原始哈希ID。它不再分离那么...我们将如何找到承诺L1个


1如果不深入讨论“这里是解决错误的方法”部分,我们将不会。该提交将从视图中消失,我们将再也看不到它。最终,Git会将其丢弃。这就是为什么我们通常不以“分离式HEAD”模式工作的原因-除非是在进行中的重新设置基准期间。但这是针对其他StackOverflow答案的。


如果你处于此模式下并想要保存提交,请给它起一个名字

比方说,我们在这种模式下:

          H--K   <-- main
         /
...--F--G--L   <-- HEAD
         \
          I--J   <-- develop

并且我们希望Git记住commit的哈希ID L显而易见的事情是创建一个指向commit分支名称L我们可以使用任何数量的命令来做到这一点:

git branch save-me

或者:

git checkout -b save-me

或者:

git switch -c save-me

例如。这三个都创建一个新的分支名称,在这种情况下save-me,该分支名称指向commit Lgit branch命令会执行此操作,但是会保持HEAD分离:

          H--K   <-- main
         /
...--F--G--L   <-- HEAD, save-me
         \
          I--J   <-- develop

而checkout和switch命令执行此操作,然后将其附加HEAD到该名称:

          H--K   <-- main
         /
...--F--G--L   <-- save-me (HEAD)
         \
          I--J   <-- develop

因此,它们或多或少都一样好,但是,如果你希望“出现在”新分支上,则可以使用执行该操作的分支。

如果这里已经有一个分支名称,则可以使用它

假设由于某种原因我们处于这种情况下-我们不必担心为什么,只是我们是:

          H   <-- main
         /
...--F--G
         \
          I--J   <-- develop, HEAD

我们可以返回“上”分支develop,即附加HEAD到该分支,而无需更改提交名称develop选择提交J; 名字也是HEAD所以:

git checkout develop         # or git switch develop

将给我们:

          H   <-- main
         /
...--F--G
         \
          I--J   <-- develop (HEAD)

我们HEAD现在再次安装,我们很分支develop,使用承诺J唯一发生变化的是,我们从分离HEAD模式转到了附着HEAD模式,因此新提交K现在将develop通过将新提交的哈希ID写入分支名称来扩展分支。

我们终于准备好谈论 git push

既然你知道分支名称的工作原理,我们就可以看看git push真正的作用。我们让Git调用了其他一些Git,并且两个Git具有我前面提到的Git-sex。假设在两个存储库中,我们都是从相同的提交集和一些相似的分支名称开始的。 他们有:

          H   <-- main (HEAD)
         /
...--F--G
         \
          I--J   <-- develop

当我们运行git clone以复制整个存储库时,我们得到了所有提交,而没有任何分支:

          H   <-- origin/main
         /
...--F--G
         \
          I--J   <-- origin/develop

这些有趣的origin/*名称是远程跟踪名称,而不是分支名称。他们让我们的Git查找提交,但是他们不让我们“接受”它们。如果我们尝试使用git checkout以下名称:

git checkout origin/develop

Git将使我们进入“分离头”模式。

现在,在git clone完成之前,继续在我们的新存储库中创建一个分支名称。-b当我们使用时,它使用的名称就是我们通过选项告诉它的名称git clone -b develop

          H   <-- origin/main
         /
...--F--G
         \
          I--J   <-- develop (HEAD), origin/develop

如果我们不使用告诉它-b,那么我们的Git会使用一个分支名称,该名称基于其他Git推荐的名称。通常是master(截至去年的当前Git和GitHub)或main(截至今天的GitHub,也许还有将来的Git),但是如果你使用的是GitHub,则可以使用此处的Web界面进行设置。所以也许我们得到:

          H   <-- main (HEAD), origin/main
         /
...--F--G
         \
          I--J   <-- origin/develop

现在,我们可以创建自己的分支名称,例如inspect指向G或其他名称:我们唯一的限制是,每当要求Git创建新的分支名称时,我们都必须选择一些现有的提交。2 我们使用分支名称(找到最后的提交)或origin/develop从其他Git分支名称复制而来的远程跟踪名称(例如)找到那些现有的提交或者我们只运行git logorgit log --all --decorate --graph --oneline或其他方法,然后剪切并粘贴原始提交哈希ID。我们根据需要创建和销毁分支名称,但有一个约束条件:我们必须选择一些现有的提交。

当然,我们可以创建新的提交。一旦创建它们,它们就会存在于我们的Git存储库中。现在,我们可以创建新名称来挑选这些新提交。我们可以通过创建一个指向某些现有提交的新分支名称来做到这一点:

git checkout -b newbranch origin/develop

          H   <-- main, origin/main
         /
...--F--G
         \
          I--J   <-- newbranch (HEAD), origin/develop

然后进行新的提交:

          H   <-- main, origin/main
         /
...--F--G
         \
          I--J   <-- origin/develop
              \
               K--L   <-- newbranch (HEAD)

需要注意的是没有origin/newbranch的,因为他们的Git,在GitHub上或什么的,不具有分支的名称newbranch他们也没有提交K-L

因此,现在我们运行git push origin newbranch这指示我们的Git调用他们的Git。简称origin,Git称为远程,它存储了我们的Git用于将消息发送到其Git的URL,以便使它们的存储库发生事情。我们的Git调用他们的Git;他们查看他们的存储库;我们和他们看到他们没有承诺K-L; 并且我们已经告知Git发送这些新提交,因此我们的Git就是这样做的。我们的Git打包了commitKL,以及我们没有的所有文件。

Git使用哈希ID自行解决所有这些问题。由于哈希ID在所有Git存储库中都是通用的,并且它们确实具有提交这一事实意味着它们具有J导致所有提交的所有内容J,因此它们必须具有与所有这些提交一起的所有文件。我们的Git可以把他们的东西尽可能的小,让他们建立的提交KL该店内所有相同的文件和元数据作为我们的提交KL,并因此将具有相同的哈希标识。

发送完这些提交后,我们的Git会要求其Git创建或更新其名称newbranch,以使其指向commit L假设他们服从此请求(默认情况下,我们的Git将其作为有礼貌的请求而非强制命令发送),他们现在将具有:

          H   <-- main (HEAD)
         /
...--F--G
         \
          I--J   <-- develop
              \
               K--L   <-- newbranch

他们的资料库中。他们有我们的新承诺而且有个名字newbranch我们的Gitgit push看到他们同意创建newbranch,从而结束了我们的行动,因此我们的Git创造了我们自己origin/newbranch来记住他们的newbranch,现在我们有了:

          H   <-- main, origin/main
         /
...--F--G
         \
          I--J   <-- origin/develop
              \
               K--L   <-- newbranch (HEAD), origin/newbranch

2这里有一个特殊的模式git checkout --orphan,我们不会进入这里,在这里我们不会选择现有的提交。


git push HEAD分离的效果不佳

假设无论出于何种原因,我们都有这样的分离HEAD情况:

            K--L   <-- HEAD
           /
          H   <-- main, origin/main
         /
...--F--G
         \
          I--J   <-- origin/develop

运行时git push origin,我们将要求Git进行新的提交并设置分支名称。 但是我们没有任何分支名称来标识commit L 因此,除非我们专门指示这样做,否则我们甚至不会发送承诺这样的规范采用如下形式:K-Lgit push

git push origin HEAD:refs/heads/newbranch    # don't do this

使用这种构造很少是一个好主意。不工作! 只是这会在其Git存储库中创建新分支,但不会我们的存储库中创建任何分支名称换句话说,这是造成混乱的秘诀。我们保持在HEAD分离模式,如果我们进行更多的提交,则它们不在任何分支上。

在这种情况下,创建新的分支名称或更新一些现有的分支名称更为明智。例如,我们可以立即创建新名称newbranch并继续使用它:

            K--L   <-- newbranch (HEAD)
           /
          H   <-- main, origin/main
         /
...--F--G
         \
          I--J   <-- origin/develop

现在我们可以git push origin newbranch像往常一样,并且我们和他们将使用相同的分支名称newbranch来标识commit L从明年开始,或者与这些存储库一起工作的其他人,将从今天开始对自己感到更加快乐。

请注意,如果我们希望其他Git使用新的提交,则可能需要将它们放置在它们实际使用的任何分支名称上3 例如,也许我们需要更新它们main如果是这样,我们可能应该先更新我们的 main

git branch temp

将创建一个临时名称来保存commitL的哈希ID:

            K--L   <-- temp, HEAD
           /
          H   <-- main, origin/main
         /
...--F--G
         \
          I--J   <-- origin/develop

之后我们可以git checkout main这样进行快进合并4

git switch main
git merge --ff-only temp

既然一切顺利,我们现在就可以将其全部删除5temp并画出:

            K--L   <-- main (HEAD)
           /
          H   <-- origin/main
         /
...--F--G
         \
          I--J   <-- origin/develop

现在,我们可以安全地运行git push origin main,向K-L他们发送提交,并礼貌地要求他们更新分支名称main以指向commit L如果一切顺利,我们将:

          H--K--L   <-- main (HEAD), origin/main
         /
...--F--G
         \
          I--J   <-- origin/develop

并且,由于他们正在部署main分支(假设这就是他们正在部署的分支),因此他们现在正在使用与我们正在使用的相同的提交,因此也使用了相同的文件。


3询问你自己使用的分支名称,方式以及原因。每个Git存储库都可以出于任何目的使用其喜欢的名称。GitHub,GitLab,Bitbucket等不同的托管系统为使用分支机构提供了不同的自动化系统。有无数种可能性。Git提供工具和机制,而不是预先打包的解决方案。

4快速合并根本不是合并,但在这里通常是正确的选择。

5如果操作顺利,则说明我们处于良好状态,因为所有内容都永久保存在提交中,并且我们有可以查找所有提交的名称。很显然,我们必须弄清楚什么地方出了错,并修复,但因为提交是永久的,只要我们能找到他们反正和安全这样,我们就可以继续进行。