git Befehl, um einen Zweig wie einen anderen zu machen

80

Ich versuche, einen Zweig mit Änderungen zu übernehmen und ihn so wiederherzustellen, dass er mit dem Upstream identisch ist, von dem er abweicht. Die Änderungen sind beide lokal und wurden auf Github verschoben, also weder git resetnoch git rebasewirklich realisierbar, da sie die Geschichte ändern, was bei einem Zweig, der bereits verschoben wurde, eine schlechte Sache ist.

Ich habe es auch git mergemit verschiedenen Strategien versucht, aber keine von ihnen macht die lokalen Änderungen rückgängig. Wenn ich also eine Datei hinzugefügt hätte, könnte eine Zusammenführung andere Dateien wieder in Einklang bringen, aber ich habe immer noch diese Datei, die der Upstream nicht hat haben.

Ich könnte einfach einen neuen Zweig außerhalb des Upstreams erstellen, aber ich möchte wirklich eine Zusammenführung, bei der in Bezug auf den Revisionsverlauf alle Änderungen angewendet werden, um meinen Zweig zu übernehmen und ihn wieder mit dem Upstream identisch zu machen, damit ich diese Änderung sicher vorantreiben kann ohne die Geschichte zu zerstören. Gibt es einen solchen Befehl oder eine Reihe von Befehlen?

Arne Claassen
quelle
9
Wenn Sie die Änderungen nicht beibehalten möchten, löschen Sie den Zweig und erstellen Sie ihn neu. "Die Geschichte des Projekts" muss nicht heilig sein. Git ist ein Tool, mit dem Entwickler kommunizieren können. Wenn diese Änderungen nicht helfen, werfen Sie sie weg.
wnoise
+100 @wnoise - besonders wenn die Änderungen bereits zusammengeführt wurden.
Wuputah
7
Es ist mir wichtig, die Geschichte zu bewahren, da sie für die Zusammenarbeit veröffentlicht wurde und ich möglicherweise darauf zurückkommen möchte. Warum sollten Sie die Revisionskontrolle verwenden, wenn Sie nur die neuesten Informationen verwenden?
Arne Claassen
1
Dies ist ein subjektives Argument, aber für mich besteht der Zweck eines VCS nicht darin, alle Details des Projektverlaufs aufzuzeichnen, sondern nur Änderungen am Inhalt (Commits) aufzuzeichnen, damit Sie den Baum / Verlauf basierend manipulieren können bei diesen Commits (Verzweigen, Zusammenführen, erneutes Basieren, Zurücksetzen usw.) und ermöglichen Ihnen das Anzeigen von Berichten basierend auf dem Verlauf (Unterschiede, Protokolle, Schuldzuweisungen usw.). git ist der "dumme Content Tracker" - ich betrachte es als Werkzeug zum Verwalten von Quellcode, nicht als Zeitmaschine.
Wuputah
5
Wie Sie sagten, ist es subjektiv. Es ist mir wichtig, aufgegebene Ansätze überprüfen zu können und zu sehen, welche Entscheidungen zu einem bestimmten Zeitpunkt in der Vergangenheit getroffen wurden. Und es ist mir wichtig, dass meine Entscheidung, etwas aufzugeben, die Zusammenführungspunkte, auf die andere möglicherweise zeigen, nicht zerstört.
Arne Claassen

Antworten:

105

Sie können Ihren Upstream-Zweig devmit einem benutzerdefinierten Zusammenführungstreiber "keepTheirs" zu Ihrem Zweig zusammenführen :
Siehe " " git merge -s theirs"erforderlich - aber ich weiß, dass er nicht vorhanden ist ".
In Ihrem Fall .gitattributeswäre nur eines erforderlich und ein keepTheirsSkript wie:

mv -f $3 $2
exit 0

git merge --strategy=theirs Simulation # 1

Wird als Zusammenführung angezeigt, wobei Upstream das erste übergeordnete Element ist.

Jefromi erwähnt (in den Kommentaren) das merge -s ours, indem er Ihre Arbeit im Upstream (oder in einem temporären Zweig, der vom Upstream ausgeht ) zusammenführt und dann Ihren Zweig schnell zum Ergebnis dieser Zusammenführung vorspult:

git checkout -b tmp origin/upstream
git merge -s ours downstream         # ignoring all changes from downstream
git checkout downstream
git merge tmp                        # fast-forward to tmp HEAD
git branch -D tmp                    # deleting tmp

Dies hat den Vorteil, dass der vorgelagerte Vorfahr als erstes übergeordnetes Element aufgezeichnet wird, sodass die Zusammenführung "diesen veralteten Themenzweig absorbieren" bedeutet, anstatt "diesen Themenzweig zu zerstören und durch vorgelagerten zu ersetzen" .

(Edit 2011):

Dieser Workflow wurde in diesem Blog-Beitrag vom OP gemeldet :

Warum will ich das wieder?

Solange mein Repo nichts mit der öffentlichen Version zu tun hatte, war das alles in Ordnung, aber da ich jetzt die Möglichkeit haben möchte, mit anderen Teammitgliedern und externen Mitwirkenden auf WIP zusammenzuarbeiten, möchte ich sicherstellen, dass meine öffentlichen Zweige vorhanden sind Zuverlässig für andere, um abzweigen und abziehen zu können, dh keine Rebase und Reset mehr für Dinge, die ich auf das Remote-Backup verschoben habe, da es jetzt auf GitHub und öffentlich ist.

So bleibt mir, wie ich vorgehen soll.
In 99% der Fälle wird meine Kopie in den Upstream-Master verschoben, daher möchte ich meinen Master bearbeiten und die meiste Zeit in den Upstream verschieben.
Aber hin und wieder wird das, was ich habe wip, durch das, was in den Upstream fließt, ungültig und ich werde einen Teil von mir aufgeben wip.
An diesem Punkt möchte ich meinen Master wieder mit dem Upstream synchronisieren, aber keine Commit-Punkte auf meinem öffentlich gepushen Master zerstören. Dh ich möchte eine Zusammenführung mit Upstream, die mit dem Änderungssatz endet, der meine Kopie mit Upstream identisch macht .
Und genau das git merge --strategy=theirssollte es tun.


git merge --strategy=theirs Simulation # 2

Shows als Zusammenführung, mit unserer als erstem Elternteil.

(vorgeschlagen von jcwenger )

git checkout -b tmp upstream
git merge -s ours thebranch         # ignoring all changes from downstream
git checkout downstream
git merge --squash tmp               # apply changes from tmp but not as merge.
git rev-parse upstream > .git/MERGE_HEAD #record upstream 2nd merge head
git commit -m "rebaselined thebranch from upstream" # make the commit.
git branch -D tmp                    # deleting tmp

git merge --strategy=theirs Simulation # 3

Dieser Blog-Beitrag erwähnt :

git merge -s ours ref-to-be-merged
git diff --binary ref-to-be-merged | git apply -R --index
git commit -F .git/COMMIT_EDITMSG --amend

Manchmal möchten Sie dies tun, und zwar nicht, weil Sie "Mist" in Ihrer Geschichte haben, sondern weil Sie möglicherweise die Basis für die Entwicklung in einem öffentlichen Repository ändern möchten, in dem eine erneute Basierung vermieden werden sollte .


git merge --strategy=theirs Simulation # 4

(gleicher Blogbeitrag)

Wenn Sie alternativ die lokalen Upstream-Zweige schnell vorspulbar halten möchten, besteht ein möglicher Kompromiss darin, mit dem Verständnis zu arbeiten, dass für Sid / Instable der Upstream-Zweig von Zeit zu Zeit zurückgesetzt / neu basiert werden kann (basierend auf Ereignissen, die letztendlich aus sind Ihrer Kontrolle auf der Seite des vorgelagerten Projekts).
Dies ist keine große Sache, und wenn Sie mit dieser Annahme arbeiten, ist es einfach, den lokalen Upstream-Zweig in einem Zustand zu halten, in dem nur Aktualisierungen im Schnellvorlauf erforderlich sind.

git branch -m upstream-unstable upstream-unstable-save
git branch upstream-unstable upstream-remote/master
git merge -s ours upstream-unstable
git diff --binary ref-to-be-merged | git apply -R --index --exclude="debian/*"
git commit -F .git/COMMIT_EDITMSG --amend

git merge --strategy=theirs Simulation # 5

(vorgeschlagen von Barak A. Pearlmutter ):

git checkout MINE
git merge --no-commit -s ours HERS
git rm -rf .
git checkout HERS -- .
git checkout MINE -- debian # or whatever, as appropriate
git gui # edit commit message & click commit button

git merge --strategy=theirs Simulation # 6

(vorgeschlagen von demselben Michael Gebetsroither ):

Michael Gebetsroither mischte sich ein und behauptete, ich hätte "geschummelt";) und gab eine andere Lösung mit untergeordneten Sanitärbefehlen:

(Es wäre kein Git, wenn es nicht nur mit Git-Befehlen möglich wäre. Alles in Git mit Diff / Patch / Apply ist keine echte Lösung.)

# get the contents of another branch
git read-tree -u --reset <ID>
# selectivly merge subdirectories
# e.g superseed upstream source with that from another branch
git merge -s ours --no-commit other_upstream
git read-tree --reset -u other_upstream     # or use --prefix=foo/
git checkout HEAD -- debian/
git checkout HEAD -- .gitignore
git commit -m 'superseed upstream source' -a

git merge --strategy=theirs Simulation # 7

Die notwendigen Schritte können beschrieben werden als:

  1. Ersetzen Sie Ihren Arbeitsbaum durch Upstream
  2. Übernehmen Sie die Änderungen auf den Index
  3. Fügen Sie Upstream als zweites übergeordnetes Element hinzu
  4. Verpflichten

Der Befehl git read-treeüberschreibt den Index mit einem anderen Baum, um den zweiten Schritt auszuführen , und verfügt über Flags zum Aktualisieren des Arbeitsbaums, um den ersten Schritt auszuführen . Beim Festschreiben verwendet git SHA1 in .git / MERGE_HEAD als zweites übergeordnetes Element, sodass wir dieses füllen können, um ein Zusammenführungs-Festschreiben zu erstellen. Daher kann dies erreicht werden mit:

git read-tree -u --reset upstream                 # update files and stage changes
git rev-parse upstream > .git/MERGE_HEAD          # setup merge commit
git commit -m "Merge branch 'upstream' into mine" # commit
VonC
quelle
7
Sie können immer nur unsere anstelle ihrer verwenden: Überprüfen Sie den anderen Zweig, führen Sie Ihren Zweig darin zusammen und spulen Sie dann Ihren Zweig schnell zum Zusammenführen vor. git checkout upstream; git merge -s ours downstream; git checkout downstream; git merge upstream. (Verwenden Sie bei Bedarf einen temporären Zweig im Upstream.) Dies hat den Vorteil, dass der Upstream-Vorfahr als erstes übergeordnetes Element aufgezeichnet wird, sodass die Zusammenführung "diesen veralteten Themenzweig absorbieren" bedeutet, anstatt "diesen Themenzweig zu zerstören und zu ersetzen" es mit stromaufwärts ".
Cascabel
@Jefromi: ausgezeichneter Punkt, wie immer. Ich habe es in meine Antwort aufgenommen.
VonC
Eine andere Option - wie git merge --strategy = ihre Simulation Nr. 1 - mit der Ausnahme, dass diese den Bereich als erstes übergeordnetes Zusammenführungselement beibehält: git checkout -b tmp origin / upstream git merge -s our downstream # ignoriert alle Änderungen von downstream git checkout downstream git merge --squash tmp # Änderungen von tmp anwenden, aber nicht als Merge. git rev-parse upstream> .git / MERGE_HEAD #record upstream als zweiter Merge-Kopf git commit -m "hat unsere von upstream neu definiert" # macht das Commit. Git Branch -D tmp # Löschen von tmp
jcwenger
1
Wow, wer hätte gedacht, dass --strategy = ihre auf so viele Arten umgesetzt werden könnte. Nun, wenn es nur in der nächsten Version von git sein könnte
Arne Claassen
VonC und sein Wissen sind erstaunlich. Er ist wie der JonSkeet von git. :)
sjas
13

Es klingt für mich so, als müssten Sie nur Folgendes tun:

$ git reset --hard origin/master

Wenn es keine Änderung gibt, um Upstream zu pushen, und Sie einfach möchten, dass der Upstream-Zweig Ihr aktueller Zweig ist, wird dies das tun. Es ist nicht schädlich, dies lokal zu tun, aber Sie verlieren alle lokalen Änderungen **, die nicht an den Master gesendet wurden.

** Tatsächlich sind die Änderungen noch vorhanden, wenn Sie sie lokal festgeschrieben haben, da die Festschreibungen git reflognormalerweise noch mindestens 30 Tage bei Ihnen liegen .

Wuputah
quelle
Dies funktioniert, wenn Sie diese Änderung um jeden Preis benötigen, da dies den Verlauf des Zweigs ändern kann (Sie müssen mit -f pushen). Das kann so konfiguriert werden, dass es blockiert wird. In der Praxis funktioniert es also grundsätzlich nur für Ihre eigenen privaten Repositorys.
Ribamar
13

Sie können dies jetzt ziemlich einfach tun:

$ git fetch origin
$ git merge origin/master -s recursive -Xtheirs

Dadurch wird Ihr lokales Repo mit dem Ursprung synchronisiert und der Verlauf bleibt erhalten.


quelle
5
git merge -s recursive -Xtheirsführt Binärdateien nicht automatisch zusammen, sodass Sie in eine Konfliktsituation geraten, die Sie manuell lösen müssen. Workflows, die auf basieren, git merge -s oursleiden nicht darunter.
Stefaan
Dies scheint ein leeres Commit zu erstellen.
Robin Green
Ich entschuldige mich - es funktioniert tatsächlich, aber git showbei einem Zusammenführungs-Commit werden nur Konfliktlösungen angezeigt, und es gibt -Xtheirsoffensichtlich keine Konfliktlösungen, wenn sie verwendet werden.
Robin Green
5

Eine weitere Simulation für git merge -s theirs ref-to-be-merged:

git merge --no-ff -s ours ref-to-be-merged         # enforce a merge commit; content is still wrong
git reset --hard HEAD^2; git reset --soft HEAD@{1} # fix the content
git commit --amend

Eine Alternative zum doppelten Zurücksetzen wäre das Anwenden des umgekehrten Patches:

git diff --binary ref-to-be-merged | git apply -R --index
michas
quelle
Interessante Verwendung des Reverse Patches. +1
VonC
Das Zurücksetzen hat bei mir nicht funktioniert, ich wurde "fatal: mehrdeutiges Argument 'HEAD2': unbekannte Revision oder Pfad nicht im Arbeitsbaum". . (Ja, ich habe getippt HEAD^2) Die Patch-Methode hat funktioniert.
user247702
@Stijn: Höchstwahrscheinlich haben Sie tatsächlich nicht ^richtig eingegeben . Manchmal werden andere Tastenanschläge wie "Strg-C" als "^ C" angezeigt. - Wenn Sie wirklich das richtige "^" eingegeben haben, haben Sie einen schwerwiegenden Fehler in Ihrer Git-Version gefunden.
Michas
@michas Unter Windows wird das Zeichen ^ zum Escapezeichen verwendet. Um es als Literal zu verwenden, müssen Sie es mit sich selbst maskieren. git reset --hard HEAD^^2; git reset --soft HEAD@{1}
Joshua Walsh
4

Es gibt auch einen Weg mit wenig Hilfe des Sanitärbefehls - IMHO der einfachste. Angenommen, Sie möchten "ihre" für 2 Zweige emulieren:

head1=$(git show --pretty=format:"%H" -s foo)
head2=$(git show --pretty=format:"%H" -s bar)
tree=$(git show --pretty=format:"%T" -s bar)
newhead=$(git commit-tree $tree -p $head1 -p $head2 <<<"merge commit message")
git reset --hard $newhead

Dies führt eine beliebige Anzahl von Köpfen (2 im obigen Beispiel) unter Verwendung des Baums eines von ihnen zusammen (Balken im obigen Beispiel, Bereitstellung des Baums "ihrer"), wobei alle Diff / File-Probleme außer Acht gelassen werden (Commit-Tree ist ein Befehl auf niedriger Ebene, also kümmert sich nicht um diese). Beachten Sie, dass der Kopf nur 1 sein kann (entspricht also Kirschpickel mit "ihrem").

Beachten Sie, dass der zuerst angegebene übergeordnete Kopf einige Faktoren beeinflussen kann (siehe z. B. - erster Elternteil des Befehls git-log). Denken Sie also daran.

Anstelle von git-show kann alles andere verwendet werden, das Tree- und Commit-Hashes ausgeben kann - unabhängig davon, was zum Parsen verwendet wird (Cat-File, Rev-List, ...). Sie können alles mit git commit verfolgen - ändern, um die Commit-Nachricht interaktiv zu verschönern.

Michal Soltys
quelle
Dies ist in meinen Augen am einfachsten. Sie verwenden Installationsbefehle, um zu sagen: "Erstellen Sie ein neues Festschreibungsobjekt mit diesem Baum, diesem ersten übergeordneten Element, diesem zweiten übergeordneten Element. Zeigen Sie dann mit dem Kopf auf dieses neue Festschreibungsobjekt." machen. Der Nachteil ist, dass Sie 4 verschiedene Hashes speichern müssen, damit dieser Vorgang funktioniert.
Michael R
2

Schwerfällig, aber zur Hölle, was kann möglicherweise schief gehen?

  • Schauen Sie sich den Zweig X an, der wie der Y aussehen soll
  • cp -r .git /tmp
  • Überprüfen Sie Zweig Y. git checkout y
  • rm -rf .git && cp -r /tmp/.git .
  • Commit & Push jeden Unterschied
  • ERLEDIGT.
sscarduzio
quelle
Dies ist die einfachste Brute-Force-Methode, um zwei Zweige identisch zu machen, vorausgesetzt, Sie möchten keinen Zusammenführungsverlauf beibehalten.
Damon D
1

Wechseln Sie in den Remote-Upstream-Zweig und führen Sie eine Zusammenführung git mergemit der auf festgelegten Zusammenführungsstrategie durch ours.

git checkout origin/master
git merge dev --strategy=ours
git commit ...
git push

Der gesamte Verlauf ist weiterhin vorhanden, Sie haben jedoch ein zusätzliches Merge-Commit. Das Wichtigste dabei ist, von der Version zu beginnen, bei der Sie sein möchten, und oursmit dem Zweig zu verschmelzen, auf dem sich Github tatsächlich befindet.

kelloti
quelle
1
Ich brauche das Gegenteil. Das wird meinen Zweig nehmen und ihn in den Upstream integrieren, aber den Upstream-Kopf unverändert lassen. Aber ich muss den Upstream nehmen und in meinen Zweig integrieren, damit mein Kopf wie der Upstream aussieht. Grundsätzlich so etwas --strategy=theirs, außer dass der nächste --strategy=recursive -X=theirsdas nicht ganz macht.
Arne Claassen
--strategy=theirsist genau das Gegenteil von --strategy=ours. Sie beginnen am anderen Ende (beginnen Sie also am Github und führen Sie es in die andere Richtung zusammen).
Kelloti
Es gibt kein --strategy=theirs, was das Problem ist. Das nächste ist --strategy=recursive -X theirsdas Gegenteil, da es die irrelevanten lokalen Änderungen nicht entfernt, wenn sie nicht in Konflikt stehen.
Arne Claassen
Diese beiden sind Gegensätze: git checkout dev; git merge origin/master --strategy=oursundgit checkout origin/master; git merge dev --strategy=ours
Wuputah
2
@Arne: Siehe meinen Kommentar zu VonCs Antwort. Das Vorhandensein der oursStrategie macht es vollständig möglich, eine theirsStrategie zu erstellen.
Cascabel
1

Verwenden Sie git reset BACKWARDS!

Sie können einen Zweig wie jedes andere Commit aussehen lassen git reset, aber Sie müssen dies auf eine Art und Weise tun.

Sie können dies tun, damit ein Zweig beim Festschreiben <old>wie ein Festschreiben aussieht<new>

git reset --hard <new>

um <new>den Inhalt des Arbeitsbaums zu machen .

Dann mach

git reset --mixed <old> 

um den Zweig wieder auf das ursprüngliche Commit zu ändern, aber den Arbeitsbaum im <new> Status zu belassen .

Anschließend können Sie die Änderungen hinzufügen und festschreiben, damit Ihr Zweig genau mit dem Inhalt des Festschreibens übereinstimmt <new>.

Es ist kontraintuitiv, dass Sie ein Von von tun müssen, um vom <old>Staat zum Staat zu gelangen . Mit der Option bleibt der Arbeitsbaum jedoch bei und der Verzweigungszeiger wird auf gesetzt , sodass die Verzweigung beim Festschreiben der Änderungen so aussieht, wie wir es möchten.<new>git reset <new> <old>--mixed<new><old>

Warnung

Verlieren Sie nicht den Überblick über Ihre Commits, z. B. vergessen Sie, was dabei <old>passiert git reset --hard <new>.

Ivan
quelle
0

Ich folgte diesen Rollen:

Holen Sie sich den Ursprung, setzen Sie ihn hart von der Verzweigung zurück, rekursiv von ihrer und erzwingen Sie dann das Drücken auf die Verzweigung

AUF EIGENES RISIKO

git fetch origin
git reset --hard origin/<branch>
git merge origin/<branch> -s recursive -Xtheirs
git push -f <remote> <branch>
esso
quelle