简体   繁体   English

将文件添加到在 Github 分支上推送的 LAST 提交

[英]Add files to LAST commit Pushed on Github Branch

I've just pushed a commit onto Github but forgot to add 2 files to it我刚刚将提交推送到 Github 但忘记向其中添加 2 个文件

How do I take it back and add the files without making second commit?如何在不进行第二次提交的情况下将其取回并添加文件?

Does git commit --amend --no-edit work on a commit already pushed to remote? git commit --amend --no-edit是否适用于已推送到远程的提交?

The comments are correct: you must force-push the result.评论是正确的:您必须强制推送结果。 The reason is that git commit --amend does not actually change a commit, because commits cannot be changed (not even by Git itself).原因是git commit --amend实际上并没有更改提交,因为无法更改提交(即使是 Git 本身也无法更改)。 What git commit --amend does is kick one commit off the end of a branch while making a new and improved commit to put at the end of the branch instead . git commit --amend所做的是将一个提交从分支末尾踢出,同时将新的改进提交放在分支末尾 The other Git repository involved will refuse to kick their commit (your commit, really) off the end of their branch to match.涉及的其他 Git 存储库将拒绝将他们的提交(实际上是您的提交)从他们的分支末尾踢出以匹配。

A picture is worth some words一张图胜过几句话

A picture makes this much clearer, in my opinion.在我看来,一张照片使这一点更加清晰。 Let's draw commits like this:让我们像这样绘制提交:

<-H

These represent the commit itself—the internal Git object and all of its ancillary details—as an uppercase-letter-and-arrow.这些代表提交本身 - 内部 Git object 及其所有辅助细节 - 作为大写字母和箭头。 The actual commit holds two things: a snapshot of all files, and some metadata.实际提交包含两件事:所有文件的快照和一些元数据。 Neither part can be changed at all.任何一部分都不能改变。

The snapshot makes up an archive (much like a tar or rar or winzip archive).快照构成一个存档(很像 tar 或 rar 或 winzip 存档)。 Unlike a standard archive, though, the files are in a special, weird, Git-ized form, where they can be shared (de-duplicated).然而,与标准存档不同的是,这些文件是一种特殊的、奇怪的、Git 化的形式,可以在其中共享(去重)。 Because they're read-only, it's safe for this commit to share, in storage, all the files that exactly match any file in any other commit.因为它们是只读的,所以此提交可以安全地在存储中共享与任何其他提交中的任何文件完全匹配的所有文件。 (In fact, they can even be shared within a single commit, if the contents match.) (事实上,如果内容匹配,它们甚至可以在单个提交中共享。)

The metadata part of a commit holds stuff like your name and email address—as seen in your user.name and user.email setting—and the date-and-time-stamps for when you made the commit.提交的元数据部分包含您的姓名和 email 地址(如您的user.nameuser.email设置中所示)以及提交的日期和时间戳等内容。 It holds your log message, where you write why you made the commit.它保存您的日志消息,您可以在其中写下提交的原因 And, it holds for Git the hash ID of the previous commit: that's our arrow coming out of commit H .并且,对于 Git 来说,它包含上一次提交的 hash ID:这是我们从提交H中出来的箭头。

What makes Git work—well, one part of what makes Git work, at least—is that each commit has one of these arrows, pointing to the previous commit:是什么让 Git 工作——嗯,至少让 Git 工作的部分原因是每个提交都有这些箭头之一,指向上一个提交:

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

H "points to" (contains the raw hash ID of) earlier commit G . H “指向”(包含原始 hash ID)较早的提交G G , being a commit, has both snapshot and metadata, and points to still-earlier commit F . G是一个提交,同时具有快照和元数据,并指向更早的提交F That commit has both snapshot and metadata, and so on.该提交同时具有快照和元数据,等等。

This means that Git needs just the raw hash ID of the last commit in the chain.这意味着 Git 只需要链中最后一次提交的原始 hash ID。 Git stores this last-commit-in-the-chain hash ID in a branch name such as develop or feature/tall or main or whatever: Git 将此链中最后提交 hash ID 存储在分支名称中,例如developfeature/tallmain或其他:

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

The name main points to the last commit H .名称main指向最后一次提交H From there, Git can find all previous commits.从那里,Git 可以找到所有以前的提交。

To add a new commit on to the chain , as we normally do, we first check out the desired commit-and-branch via the branch name:在链上添加新的提交,就像我们通常做的那样,我们首先通过分支名称检查所需的提交和分支:

...--F--G--H   <-- main (HEAD)

Git extracts all the (read-only, compressed, Git-ified, and de-duplicated / shared) files from H so that we can see them and work on and with them. Git 从H中提取所有(只读、压缩、Git 化和去重/共享)文件,以便我们可以看到它们并使用它们。 we do our work and git add our updates and run git commit , and Git packages up all the files into a new compressed/Git-ified/de-duplicated snapshot in a new commit I :我们完成我们的工作, git add我们的更新并运行git commit ,并且 Git 将所有文件打包到一个新的压缩/Git-ified/de-duplicated 中的新快照I

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

Commit I points back to H because H is our current commit .提交I指向H因为H是我们当前的提交 (Commit I gets its unique hash ID during the writing-out operation; until then, we don't know what its hash ID will be, as that depends on the exact snapshot and the previous commit and the date and time and the log message: basically everything you're going to put in, some time in the future, but we don't know what or when until you've done it.) (提交I在写出操作期间获得其唯一的 hash ID;在此之前,我们不知道其 hash ID 将是什么,因为这取决于确切的快照先前的提交以及日期和时间以及日志消息: 基本上你要投入的所有东西,在未来的某个时间,但在你完成之前,我们不知道什么或什么时候。)

As soon as commit I actually exists inside the repository, Git immediately write's I 's new hash ID, whatever it turns out to be, into the current branch name .一旦提交I实际存在于存储库中, Git 立即I的新 hash ID 写入当前分支名称,无论结果如何 This means we now have:这意味着我们现在有:

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

The name has changed.名称已更改。 The new commit exists .新的提交存在 No existing commit has been changed at all.根本没有更改任何现有的提交 (This is why commits only point backwards: H can point to G , because G existed when we made H . H can't point to I because until now, I did not exist, and we had no idea what hash ID it would get. But I can point to H , because H exists when we make I .) (这就是为什么提交只能向后指向: H可以指向G ,因为当我们制作HG存在H不能指向I因为直到现在, I都不存在,而且我们不知道它会得到什么 hash ID . 但I可以指向H ,因为当我们做IH存在。)

There's no reason to draw the bend in the graph any more, so we can draw this as:没有理由再在图中绘制弯曲了,因此我们可以将其绘制为:

...--F--G--H--I   <-- main (HEAD)

and fit it all on one line.并将其全部放在一条线上。 (Using git log --graph , you'll see Git draws things vertically, with newer commits towards the top, instead of horizontally with newer commits towards the right. Git also doesn't use single uppercase letters, of course, as that would limit us to 26 or so commits, depending on whose alphabet we use.) (使用git log --graph ,您会看到 Git 垂直绘制事物,较新的提交朝向顶部,而不是水平方向,较新的提交朝向右侧。当然,Z0BCC70105AD279503E651FE7B3F4 也不会使用大写字母将我们限制为 26 次左右的提交,具体取决于我们使用的字母表。)

What git commit --amend does is simple: it does not change the existing commits (it can't). git commit --amend所做的很简单:它不会更改现有的提交(它不能)。 Instead, it attaches the new commit I to H 's parent , like this:相反,它将提交I附加到Hparent ,如下所示:

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

Git writes the new commit's hash ID into the name as usual. Git 像往常一样将新提交的 hash ID 写入名称中。 We can't draw this graph without putting in the kink, but we can draw it like this:我们无法在不添加扭结的情况下绘制此图,但我们可以像这样绘制它:

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

(The --no-edit option tells Git to copy the original commit message, and don't bring up the editor to let you change it. The default is to copy the message but do bring up the editor.) --no-edit选项告诉 Git 复制原始提交消息,并且不要调出编辑器让您更改它。默认是复制消息但确实调出编辑器。)

Why --force or --force-with-lease or similar为什么--force--force-with-lease或类似

We now get to the interesting thing, which is why a force-push is required.现在我们来看看有趣的事情,这就是为什么需要强制推动。

Whenever we make new commits, they exist only in our own Git repository .每当我们进行新的提交时,它们只存在于我们自己的 Git 存储库中。 But eventually we might want to give these commits to someone else.但最终我们可能希望将这些提交交给其他人。 So we have our Git call up some other Git software, somewhere, and send them our new commits—the ones we have that they don't, that they'll need.所以我们有我们的 Git 在某个地方调用一些其他的 Git 软件,并将我们的新提交发送给他们——我们拥有的那些他们没有的,他们需要的。 After we've sent these commits, we have to ask or tell them to create or update a branch name in their Git repository .在我们发送这些提交之后,我们必须要求或告诉他们他们的Git 存储库中创建或更新分支名称

That is, they may well have:也就是说,他们很可能有:

...--F--G--H   <-- main [in their Git repository]

in their repository.他们的存储库中。 We've added new commit I ;我们添加了新的提交I they don't have it.他们没有。

If we've added I to the end of H :如果我们将I添加到H的末尾:

...--F--G--H--I   <-- main [in our Git repository]

we'll send them I and then ask them to change their main to point to I .我们将发送给他们I然后要求他们将main更改为指向I They may check for permissions first (this is outside the Git code proper as it has no authentication model, but is done by hosting providers as an obviously-necessary add-on) and in general they'll say okay to this, because I simply adds on .他们可能会首先检查权限(这在 Git 代码之外,因为它没有身份验证model ,但由托管服务提供商作为明显必要的附加组件完成),通常他们会对此表示同意,因为I只是补充说 Commit H remains reachable .提交H仍然可达

But if we've made new commit I have commit G as its parent:但是,如果我们进行了新的提交I将提交G作为其父级:

          H
         /
...--F--G--I   <-- main [in our Git repository]

and we ask them, politely, to make their name main point to commit I , they will refuse, on the grounds that if they did, they'd "lose" their commit H .我们礼貌地要求他们将他们的名字作为提交Imain点,他们会拒绝,因为如果他们这样做了,他们会“失去”他们的提交H Git finds commits by starting from names, like main , and working backwards, and new commit I doesn't find commit H . Git 从名称开始查找提交,例如main ,然后向后工作,新提交I没有找到提交H Once the commit is "lost", it might actually get removed entirely.一旦提交“丢失”,它实际上可能会被完全删除。 (Whether and when this happens is another, much more complicated question.) So they just say no, I won't do that because it would lose commits , which in Git jargon is reported as a non-fast-forward error. (是否以及何时发生这是另一个更复杂的问题。)所以他们只是说不,我不会这样做,因为它会丢失 commits ,在 Git 行话中被报告non-fast-forward错误。

The thing is, we want them to drop commit H .问题是,我们希望他们放弃提交H Commit I is our new-and-improved replacement for commit H .提交I是我们对提交H的新的和改进的替代品 To get them to understand that we do in fact mean replace commit H with new and improved commit I , we set the force flag on our git push operation.为了让他们明白我们实际上是指用新的和改进的提交I替换提交H ,我们在git push操作上设置了强制标志

There are several different ways to set the force flag.有几种不同的方法来设置强制标志。 One is unconditional, and it just turns the git push request into a command: Here are some commits.一个是无条件的,它只是将git push请求变成命令:这里有一些提交。 And now: I command you , set your branch name _______ to hash ID _______!现在:我命令你,将你的分支名称 _______ 设置为 hash ID _______!

The reason to use --force-with-lease is that it offers a bit of safety.使用--force-with-lease的原因是它提供了一点安全性。 Here, instead of a raw set your branch command, we have a conditional: I believe your name _______ is set to hash ID _______.在这里,我们有一个条件,而不是原始设置您的分支命令:我相信您的名字 _______ 设置为 hash ID _______。 If so, set it to _________ instead, even if that's a non-fast-forward.如果是这样,请将其设置为 _________,即使这是非快进。 Let me know what happened.让我知道发生了什么。 Our Git gets the expected hash ID from our origin/main (assuming git push origin and other reasonable assumptions).我们的 Git 从我们的origin/main获取预期的 hash ID(假设git push origin和其他合理的假设)。 If we're in sync with them when we do this git push --force-with-lease origin main , the hash IDs will match up and, if we have the right permissions, they will obey our command and set their main to point to commit I , just as we want them to.如果我们在执行此操作时与他们同步git push --force-with-lease origin main ,则 hash ID 将匹配,如果我们拥有正确的权限,他们将服从我们的命令并将其main指向承诺I ,就像我们希望他们那样。

声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM