我目前正在學習 GitHub Actions。我的目標是為一個小型私有網路專案(網站)構建一個部署管道。除此之外,我注意到了一些我不明白的事情。
最好我用一個例子來解釋它。但首先我的設定:
- 遠程存盤庫“Project_X”在 GitHub 上
- GH Action 會在每次推送時啟動到遠程服務器的 SSH 連接以初始化
git pull那里。 - 完畢!
到目前為止,這作業正常。
現在我測驗了如果我的最后一次推送包含錯誤并且我想撤消它會發生什么。以便頁面繼續運行,我可以進行錯誤修復。所以我輸入了 local: git reset --hard hash_from_prev_commit。在本地,提交被重置。隨著git push -f遠程存盤庫也進行了更新。但是在遠程服務器上它沒有被重置。GitHub 操作輸出:
out: /www/htdocs/w019db06
out: On branch master
out: Your branch is up-to-date with 'origin/master'.
out: nothing to commit, working directory clean
err: From github.com:Project-X/git-workflow-test
err: ffd263c...e5a9ec8 master -> origin/master (forced update)
out: Already up-to-date.
==============================================
? Successfully executed commands to all host.
為什么會這樣,我必須做什么才能使更改在遠程服務器上生效?
uj5u.com熱心網友回復:
TL; 博士
您需要運行第三個存盤庫,git fetch然后是git reset --hard,如下所示:
git fetch origin
git reset --hard origin/master
如果您愿意在master此處對名稱進行硬編碼。
長
讓我在這里總結一下,有三個存盤庫。讓我們給他們起個名字:阿爾弗雷德、芭芭拉和貓女。存盤庫 A (Alfred) 位于您的筆記本電腦上。存盤庫 B (Barbara) 在 GitHub 上。存盤庫 C 位于您的服務器上,無論它是什么。
為簡單起見,由于您在筆記本電腦上運行某些命令而運行的某個 Git 行程在A上執行的操作是“Git A”;那些在 B(在 GitHub 上)是“Git B”,那些出現在 C 上的是“Git C”。這應該有助于保持在每個存盤庫中每臺機器上發生的事情。git whatever
這一行,加上下一行,我還不會參考:
ffd263c...e5a9ec8 master -> origin/master (forced update)
由 Git C 列印,因為 Git C 正在運行git pull. 但是git pull意味著:運行git fetch,然后運行第二個 Git 命令。
背景(有點長,如果你喜歡可以略過)
現在,在我們繼續之前,我們需要再做一些說明:
Git 是關于commits 的。這與分支名稱或檔案無關:分支名稱幫助某些 Git 存盤庫查找提交,并且提交包含檔案,但我們真正關心的是提交。
提交是共享的:如果存盤庫 A、B 和 C 相互交叉連接,它們最終都會以相同的提交結束。
提交被編號,帶有丑陋的哈希 ID。通常我喜歡使用單個大寫字母來代替這些,只要我從字母表中開始更深入地開始(A、B 和 C 是各種機器上的存盤庫,所以我想避免使用那些字母)。
每個提交包含,作為其元資料的一部分,一些先前提交或提交的原始哈希 ID 。大多數提交只有一個先前的哈希 ID,它將提交形成為向后看的鏈:
... <-F <-G <-Hwhere
H代表鏈中最新提交的哈希 ID 。提交H,是最新款,是結束鏈:有這點后,沒有更多的提交。如第 1 條所述,Git 存盤庫將使用分支名稱來幫助它查找提交。但是分支名稱不共享:每個 Git 存盤庫都有自己的分支名稱。
現在,有點懶惰,我傾向于像這樣繪制提交:
...--F--G--H <-- master (HEAD)
Here we see how the current branch name—in this case master—points to the last commit in the chain. The process of making a new commit is done, in Git, by checking out some commit (H), using some branch name (master), which makes that branch the current branch and that particular commit the current commit. Then we act on the files in our working tree, use git add to "stage" them, and run git commit, and Git builds a new commit I, that points back to the current commit:
...--F--G--H <-- master (HEAD)
\
I
As the last step of git commit, Git writes I's actual hash ID—some big ugly hexadecimal number—into the name master:
...--F--G--H
\
I <-- master (HEAD)
and now the chain of commits ends not at H but at I.
When we use git push or git fetch, our Git calls up some other Git and either sends them our new commits (git push) or gets any new commits from them (git fetch). This is how the commits get shared. But after that, things get a little weirder: if we're using git fetch, our Git updates, not a branch name, but rather a remote-tracking name. For instance, if we got a new commit J from their Git, we would now have:
...--F--G--H--I <-- master (HEAD)
\
J <-- origin/master
We can now add commit J to our branch (our master, which is independent of their master) using various Git operations, to get:
...--F--G--H--I--J <-- master (HEAD), origin/master
For various reasons, git push is different: after we send our new commit to some other repository, we ask them (politely) if they will please, if it's OK, set their branch name to some particular commit. For instance, if they have only commits up through H, we can send them commit I, or commits I-J, that add on to their H like this, and then ask them to set their master.
The problem
Let's review what kicks all this off. You, on your laptop—on Git A—are going to run:
git reset --hard HEAD~
or equivalent. This moves your branch name master back one commit. But we don't get there all at once.
Let's say you started with:
...--G--H <-- master (HEAD), origin/master
You then added commit I in your repository A:
...--G--H <-- origin/master
\
I <-- master (HEAD)
You then ran git push origin master. That sent new commit I to repo B, and then asked them to make their master point to I, which they did:
...--G--H--I <-- master (HEAD), origin/master
You then had repo B tickle repo C, which ran git pull origin master or just git pull (both do the same thing). Repo C called up repo B and got what to repo C was new: commit I:
...--G--H <-- master (HEAD)
\
I <-- origin/master
The second command that your git pull ran on C was git merge. This discovered that there was no need for a true merge, so it just added commit I to its master:
...--G--H--I <-- master (HEAD), origin/master
So now, repos A and C match (down to both having an origin/master name pointing to commit I, as copied from repo B and renamed). Repo B has its master pointing to commit I.
And now we reset commit I away on repo A. (Whew, finally.) Let's draw that:
...--G--H <-- master (HEAD)
\
I <-- origin/master
Repo A still remembers that repo B is using commit I as repo B's master. That's true!
You might now run git push origin master from A and get an error about it not being a fast-forward:
! rejected ... (non-fast-forward)
That's because your Git talked with their Git, found that you had no commits to send, and ended with a polite request that they set their master to point to H. That's a polite request that they forget commit I, and they say no.
So you resort to git push -f or equivalent. This changes the final step of git push from a polite request, please set your master to point to H, to a forceful command: Set your master to point to H! If they obey, this happens in repo B:
...--G--H <-- master (HEAD)
\
I ???
Note that repo B has no name for commit I any more. You can still access it by raw hash ID (GitHub allow you to do this with their APIs and web interface), but you have to know the hash ID.
Now, this git push onto GitHub triggers Repo A to do a git pull, which—again—is just git fetch followed by a second Git command, in this case, git merge. Repo C currently has this:
...--G--H--I <-- master (HEAD), origin/master
They run git fetch origin and see that, in repo B (on GitHub), master selects commit H. This means that they can make their origin/master point to commit H, but only by doing a forced update: it's a "non-fast-forward", in Git jargon.
So, they do that. Now they have this:
...--G--H <-- origin/master
\
I <-- master (HEAD)
That is, Repo C still has commit I. It's right there at the end of their master.
Their Git now runs its second step for git pull, which is to run git merge origin/master (more or less—it actually works off the raw hash ID at this point, internally). That tells them to add commit H into their chain that ends at commit I. Commit H is already in this chain, so they print:
Already up-to-date.
and then do nothing.
What's wrong is now obvious (well, as clear as things get in Git)
The problem here is that git merge—the default second command of git pull—is just wrong. You don't want to merge new commits in, on repo C. You want to switch to the latest commit on some branch on repo B. That's recorded in origin/master, in the case of repo B's master.
The git pull command can be told to run a different second command: instead of git merge, you can have it use git rebase. But that's equally wrong, or maybe even "wronger" (if that's a thing). You don't want either of those.
Git isn't a deployment tool
The ultimate problem here is that you're treating Git as a deployment tool, and it just isn't one. It can be used as one, much as a screwdriver can be used as a chisel, or a drill press can be used to tighten screws. It's just an abuse of the tool, to some extent. You need to be sure you know exactly what you're doing.
轉載請註明出處,本文鏈接:https://www.uj5u.com/net/352842.html
標籤:混帐 github 部署 github-actions
上一篇:用于匯出的git存檔的替代方法
