分支 A 的代碼比分支 B 少。我想將分支 A 合并到 B 中,這樣 B 最終的代碼就會更少,并且本質上與 A 具有完全相同的代碼。類似于撤消多次提交。問題是我必須通過合并請求來做到這一點。我不能直接推送到 B,它必須通過 A(功能分支)。
拉取請求應該是什么樣子的?當我嘗試將 A 合并到 B 時,它沒有檢測到任何差異 - 為什么會這樣?如果我翻轉拉取請求(B 到 A),它會顯示 B 有但 A 沒有的所有更改。
uj5u.com熱心網友回復:
TL; 博士
你想要一個新的提交,其快照是一個老犯。然后你可以從這個做一個 PR。使用普通的 Git 工具進行這個新提交是很棘手的,但是通過繞過來實作它很容易。不過,我會把它留到很長的部分。
長
我們需要在這里區分拉取請求——GitHub 添加的東西,1超過 Git 所做的事情——和 Git 自己做的事情。一旦我們這樣做了,事情就會變得更清楚一些,盡管因為這是 Git,它們可能仍然相當不清楚。
Git 真的是關于commits 的。Git 與檔案無關,盡管提交包含檔案。Git 也與分支無關,盡管我們(和 Git)使用分支名稱來查找提交。所以 Git 是關于commits 的。這意味著我們需要確切地知道提交是什么以及為我們做什么:
每個提交都有編號。然而,這些數字又大又丑又隨機,用十六進制表示,例如,
e9e5ba39a78c8f5057262d49e261b42a8660d5b9。我們稱這些哈希 ID(或者有時更正式地稱為物件 ID或 OID)。不知道將來的提交將具有什么哈希 ID。但是,一旦進行了提交,該哈希 ID 將參考該提交,而不會在任何地方進行任何其他提交。2 這允許兩個不同的 Git 存盤庫通過比較提交編號來查看它們是否具有相同的提交。(我們不會在這里使用該屬性,但這很重要。)每個提交存盤兩件事:
提交具有每個檔案的完整快照(盡管這些是壓縮的——有時是非常壓縮的——并且通過用于制作提交編號的相同型別的加密技巧,去重復)。
提交也有一些元資料:關于提交本身的資訊,例如誰做出的,什么時候做出的。在這個提交資料中,每個提交都存盤了一個先前提交哈希 ID的串列,通常正好是一個元素長。單個先前提交的哈希 ID 是此提交的父級。
這個我的父母是弗蘭克,弗蘭克是倒鉤的東西將提交粘合到他們的祖先鏈中。當我們使用普通 時git merge,Git 使用祖先鏈來確定要合并的內容。不過,我們不希望在這里進行正常的合并。同時,同樣的父級內容是 Git 如何將提交(快照)轉換為“更改”:找出“我”中發生的更改,如果我的父項已提交feedcab(不能是frank,其中有太多非十六進制字母一)和我 commit ee1f00d,Git比較這兩個提交中的快照。什么都一樣,沒變。不同的檔案確實發生了變化,Git 通過玩一種Spot the Difference來弄清楚游戲——其中發生了什么變化并產生了一個配方:feedcab對這個檔案的版本執行此操作,您將獲得該ee1f00d版本。
現在,實際上沒有人使用原始提交編號來查找提交。您最近一次提交的提交編號是多少?你知道嗎?你在乎嗎? 可能不是:您只需使用main或master或develop或某個名稱來查找它。
這是它的作業原理。假設我們有一個很小的存盤庫,其中只有三個提交。讓我們稱它們為A, B, and C(而不是使用它們真實的哈希 ID,它們又大又丑,反正我們也不認識它們)。這三個提交看起來像這樣:
A <-B <-C <--main
CommitC是我們最新的。它有一個快照(所有檔案的完整副本)和元資料。它的元資料列出了早期提交的原始哈希 ID B:我們說它C 指向 B. B同時,Commit有一個快照和一些元資料,并且它B的元資料指向A. A有一個快照和元資料,因為A是第一次提交,它的元資料根本沒有列出父級。這是一個孤兒,有點(所有提交都是處女分娩,有點 - 好吧,我們不要再走這條路了)。所以這是動作停止的地方,這就是我們如何知道只有三個提交。
But we find commit C by name: the name main points to C (holds the raw hash ID of C), just like C points to B.
To make a new commit, we check out main, so that C is our current commit. We change stuff, add new files, remove old files, whatever, and use git add and then git commit to make a new snapshot. The new snapshot gets a new random-looking hash ID, but we'll just call it D. D points back to C:
A <-B <-C <--main
\
D
and now git commit pulls off its clever trick: it writes D's hash ID into the name main:
A--B--C--D <-- main
Now main points to D instead of C, and there are now four commits.
Because people use names, not numbers, to find commits, we can go back to some old commit by throwing out our access to the newer commits. We force a name, like main, to point to some older commit, like C or B, and forget that D exists. That's what git reset is about. That's presumably not what you want here though, especially because Git and GitHub like to add new commits, not take them away. A pull request in particular won't let you take a commit away.
No, what you want instead is to make a new commit whose snapshot matches some old commit.
1If you're not using GitHub, perhaps you are using some other site that also adds Pull Requests. This gets a bit tricky since each site that adds them, does it their own way. GitLab, for instance, have something similar but call them Merge Requests (rather a better name, I think).
2This depends on some cryptographic tricks that will eventually fail. The size—the big-and-ugly-ness of the hash ID—pushes the failure off as long as we need, although now it's a bit too small and they're going to get even bigger and uglier soon.
Normal merges
In normal everyday Git usage, we make branch names, and we use those branch names to add commits. I already showed a really simple example. Let's get a little more complicated. We'll start with a small repository, as before:
...--G--H <-- br1 (HEAD)
I've added the HEAD notation here to indicate that this is the name of the branch we have checked out. Let's now add another branch name, br2, that also selects commit H right now:
...--G--H <-- br1 (HEAD), br2
Since we're using commit H via the name br1, any new commits we make now update only the name br1. Let's make two new commits:
I--J <-- br1 (HEAD)
/
...--G--H <-- br2
Now let's check out commit H again, with git switch br2:
I--J <-- br1
/
...--G--H <-- br2 (HEAD)
and make two more commits:
I--J <-- br1
/
...--G--H
\
K--L <-- br2 (HEAD)
We can now run git checkout br1 and then git merge br2, or just run git merge br1 now. Let's do the former: the snapshot we get in the end is the same either way, but other things change a bit, so we have to pick one.
Either way, Git now has to perform a real merge (not a fast-forward fake merge, but a real one). To perform a merge, Git needs to figure out what we changed on br1, and what they (ok, we, but not for the moment) changed on br2. That means Git has to figure out where we both started—and if we just look at the drawing, it's pretty clear: we both started from commit H. We made "our" changes and committed (several times) and got the snapshot that is in J.
The difference from H to J:
git diff --find-renames <hash-of-H> <hash-of-J>
tells Git what we changed on br1.
A similar difference:
git diff --find-renames <hash-of-H> <hash-of-L>
tells Git what they changed on br2. (Note that Git is using the commits here: the branch names, br1 and br2, just served to find the commits. Git then used the history—as recorded in the parents in each commit—to find the best shared starting-point commit H.)
To perform the merge itself, Git now combines the two diff listings. Where we changed some file and they didn't, Git uses our changes. Where they changed a file and we didn't, Git uses their changes. Where we both changed the same file, Git has to combine those changes.
If we both made the exact same change, that's fine. If we touched different lines, that's fine too—although there's an edge case here: if our changes abut, Git declares a merge conflict; but if they overlap exactly, with the same changes, that's OK). If all goes well, so that there are no merge conflicts while combining changes, Git can apply the combined changes to the snapshot from H. This keeps our changes and adds theirs—or, equivalently, keeps their changes and adds ours. Where our changes overlap exactly, Git keeps just one copy of the changes.
The resulting snapshot—H plus both sets of changes—goes into our new merge commit. There's one thing that is special about this new merge commit though. Instead of just the one normal parent, which in this case—on branch br1—would be J, it gets two parents:
I--J
/ \
...--G--H M <-- br1 (HEAD)
\ /
K--L <-- br2
As always, Git updates the current branch name to point to the new merge commit M. The merge is now complete.
git merge -s ours
Let's draw what you want. You are starting with this:
o--o--...--R <-- br-A
/
...--o--*
\
o--o--...--L <-- br-B (HEAD)
You would like to git merge br-A, but keep the snapshot from the commit L at the tip of br-B.
To accomplish what you want in raw Git, you would run:
git switch br-B
git merge -s ours br-A
Git would now find the merge base * (or not bother, really), then ... completely ignore their changes, and make a new merge commit M, on the current branch:
o--o--...--R <-- br-A
/ \
...--o--* \
\ \
o--o--...--L---M <-- br-B (HEAD)
where merge commit M has L and R as its two parents, but uses commit L as the snapshot.
That's easy, in raw Git. But GitHub won't do this! How do we get GitHub to deliver this kind of result?
We have to trick GitHub a bit
Suppose, for argument sake, that we were to git switch br-A—i.e., check out commit R—and then make a new commit whose snapshot is that from commit L? That is, we make:
o--...--R--L' <-- br-A (HEAD)
/
...--o--*
\
o--o--...--L <-- br-B
Commit L' has a different hash ID from commit L, and has different metadata—we made it just now, with our name and email and date and time and so on, and its parent is R—but has the same snapshot as commit L.
If we had Git do a normal merge here, Git would:
git diff --find-renames <hash-of-*> <hash-of-L>
git diff --find-renames <hash-of-*> <hash-of-L'>
to get the two diffs that Git needs to combine. These diffs would show exactly the same changes.
A normal merge will combine these changes by taking one copy of all of the changes. So that's just what we want! The final merge result will be:
o--...--R--L' <-- br-A
/ \
...--o--* M <-- br-B (HEAD)
\ /
o--o--...--L
where I've drawn this in the other style (with M in the middle) for no particular reason. The snapshot in M will match both commits L and L', and branch br-B will end at the new commit, with no changes to any files, but with a new commit on the end.
We can easily make commit L' in Git, and then raise a Pull Request on GitHub by sending commits up through L' on our br-A branch. The PR will merge smoothly, by "changing" nothing at all in br-B, just adding the new merge commit M. So—except for the extra L' commit—we get the same effect as with git merge -s ours run on branch br-B.
Doing this the hard way
The hard way to get snapshot L' added to branch br-A is this:
git switch br-A
git rm -r . # from the top level
git restore -SW --source br-B -- .
git commit -C br-B
for instance. The first step puts us on br-A with commit R checked out. The second one—git rm -r .—removes all files from Git's index / staging-area, and the corresponding files from our working tree. The git restore puts all files back but takes them from --source br-B or commit L, and last step, git commit -C br-B, makes a new commit using the message from commit L. (With -C you can edit this.)
This works fine, it's just a bit slow. To go faster, we can use either of two tricks. Here's the first one, which is probably the one I would actually use:
git switch br-A
git read-tree -u --reset br-B
git commit -C br-B
這消除了有利于 的洗掉和恢復git read-tree,它可以一舉完成。(您可以使用-m而不是,--reset但需要兩個標志之一,這git read-tree是一個棘手的命令,我不喜歡使用太多,所以我從不記得使用哪個:幸運的是,這里無關緊要。)
或者,我們可以這樣做:
git switch br-B # so that we are not on br-A
git branch -f br-A $(git log --no-walk --format=%B br-B | git commit-tree -F - -p br-A br-B^{tree})
如果我沒有打錯字。但是,這使您沒有機會編輯提交訊息。你需要查不出來br-B,直接,你只需要確保,要么你不上 br-A,或者你使用git merge --ff-only來進行提交后向前移動。
如果 GitHub 可以做一個就好了 git merge -s ours
但它不能,所以就是這樣。
uj5u.com熱心網友回復:
測驗變基 A 功能分支(包括清理過的代碼) B 您的開發人員
第一次保存你的開發
git checkout B git add git commit -am "blabla my dev"
然后更新A
git checkout A git pull A
然后在 A 的基礎上重新設定 B
git checkout B git rebase A
此時您可能需要處理一些沖突
轉載請註明出處,本文鏈接:https://www.uj5u.com/shujuku/354942.html
