2016-05-06 6 views
1

Ich versuche herauszufinden, warum Git-Rebase bewirkt, dass eine neu erstellte Datei gelöscht wird, wenn der Zweig, den ich neu bin, löschte es. Zum Beispiel:Warum löscht Git-Rebase eine Datei, die im letzten Commit hinzugefügt wurde, wenn sie vom Rebase-Zweig gelöscht wurde?

A1 - A2 - A3 
\ 
    B1 

A2 = add a new file test.txt 
A3 = delete test.txt 
B1 = add the exact same file as A2 

Wenn B1 ausgecheckt ist und ich ausführen git rebase A3 wird test.txt noch gelöscht. Ich erwarte folgendes Ergebnis:

Was bedeuten würde, dass test.txt noch existiert. Warum wird test.txt nach der Rebase gelöscht?

+1

Es sollte nicht sein, wenn ich deine Frage richtig lese. Hast du ein Skript um das zu reproduzieren? (Schreiben Sie etwas, das ein leeres Verzeichnis macht, läuft 'git init', erstellt Dateien und begeht, macht einen Zweig, mehrere Dateien erstellt, usw., und die letzte, läuft das' git rebase' das Problem zu zeigen.) – torek

+0

Sure I‘ Ich schreibe einen wirklich schnell. – maxbart

+0

Ok Sie dieses Skript kopieren und einfügen können http://pastebin.com/mPcmGCT5 edit: aktualisiert das Skript ein Verzeichnis zu machen (erstellt testing_git) – maxbart

Antwort

4

Wow, das war eine schwierige Frage! :-)

Mit Ihrem Skript reproduzierte ich das Problem. Dort werden alle etwas sehr merkwürdig war es aber so, zuerst, ich getrimmt, um die Fütterungsmaterial Schritt, so dass diese (leicht modifiziert) Skript:

#!/bin/sh 
set -e 
if [ -d testing_git ]; then 
    echo test dir testing_git already exists - halting 
    exit 1 
fi 

mkdir testing_git 
cd testing_git 

git init 
touch main.txt 
git add . 
git commit -m "initial commit" 

# setup B branch 
git checkout -b B 
echo hello > test.txt 
git add . 
git commit -m "added test.txt" 

# setup master 
git checkout master 
echo hello > test.txt 
git add . 
git commit -m "added test.txt" 
rm test.txt 
git add . 
git commit -m "remove test.txt" 

Einmal ausgeführt, die Commits Inspektion, erhalte ich diese:

$ git log --graph --decorate | sed 's/@/ /' 
* commit 249e4893ea7458f45fe5cdc496ddc0292a3f03ef (HEAD -> master) 
| Author: Chris Torek <chris.torek gmail.com> 
| Date: Thu May 5 20:28:02 2016 -0700 
| 
|  remove test.txt 
| 
* commit a132dc9e3939b5338f7c784c58da9c83f4902c8d (B) 
| Author: Chris Torek <chris.torek gmail.com> 
| Date: Thu May 5 20:28:02 2016 -0700 
| 
|  added test.txt 
| 
* commit 81c4d9be82094fdb4c88ed0a53bdbd5c3dfd7a5a 
    Author: Chris Torek <chris.torek gmail.com> 
    Date: Thu May 5 20:28:02 2016 -0700 

     initial commit 

Beachten Sie, dass master 's Eltern-Commit ist Zweig B Commit, und es gibt nur drei Commits, nicht vier. Wie kann das sein, wenn das Skript vier git commit Befehle ausführt?

Jetzt ist sleep 2 zum Skript hinzufügen lassen, direkt nach git checkout master, und starten Sie es und sehen, was passiert ...

[edit] 
$ sh testrebase.sh 
[snip output] 
$ cd testing_git && git log --oneline --decorate --graph --all 
* cddbff1 (HEAD -> master) remove test.txt 
* c4ac1b2 added test.txt 
| * fefc150 (B) added test.txt 
|/ 
* 8c07bb6 initial commit 

Whoa, jetzt haben wir vier Commits haben, und eine richtige Filiale!

Warum hat das erste Skript drei Commits gemacht, und das Hinzufügen von sleep 2 es ändern, um vier Commits zu machen?

Die Antwort liegt in der Identität eines Commits. Jedes Commit hat eine (angeblich!) Eindeutige ID, die eine Prüfsumme des Commit-Inhalts darstellt.Hier ist, was in den B war -Verzweigung verpflichten, beim ersten Mal: ​​

$ git cat-file -p B | sed 's/@/ /' 
tree c3cd0188a6a1490204e25547986e49b0b445dec8 
parent 81c4d9be82094fdb4c88ed0a53bdbd5c3dfd7a5a 
author Chris Torek <chris.torek gmail.com> 1462505282 -0700 
committer Chris Torek <chris.torek gmail.com> 1462505282 -0700 

added test.txt 

Wir haben die tree, die parent, zwei (Name, E-Mail, Zeitstempel) verdreifacht für Autor und Committer, eine leere Zeile, und dem Protokollnachricht Das übergeordnete Element ist das erste Commit auf dem Master-Zweig und der Baum ist der Baum, den wir erstellt haben, als wir test.txt (mit seinem Inhalt) hinzugefügt haben.

Dann, als wir gingen, um die zweite commit auf der master Zweig machen, machte Git einen neuen Baum aus den neuen Dateien. Dieser Baum war Bit für Bit identisch mit dem, den wir gerade für den Zweig B festgelegt haben, also bekam er die gleiche eindeutige ID (denken Sie daran, dass es nur eine Kopie dieses Baumes im Repo gibt, also ist das korrekt). Dann machte es einen neuen Commit Objekt mit meinem Namen und E-Mail und Zeitstempel wie üblich, und die Protokollmeldung. Aber dieses Commit war Bit-für-Bit identisch mit dem Commit, das wir gerade für den Zweig B gemacht haben, also haben wir die gleiche ID wie zuvor und den Zweig master auf diesen Commit gesetzt.

Mit anderen Worten, wir wiederverwendet das Commit. Wir haben es einfach auf einem anderen Zweig erstellt (so dass master auf denselben Commit wie B zeigt).

Hinzufügen sleep 2 änderte den Zeitstempel auf dem neuen Festschreiben. Nun werden die beiden Commits (in B und master) nicht mehr Bit für Bit identisch:

$ git cat-file -p B | sed 's/@/ /' > bx 
$ git cat-file -p master^ | sed 's/@/ /' > mx 
$ diff bx mx 
3,4c3,4 
< author Chris Torek <chris.torek gmail.com> 1462505765 -0700 
< committer Chris Torek <chris.torek gmail.com> 1462505765 -0700 
--- 
> author Chris Torek <chris.torek gmail.com> 1462505767 -0700 
> committer Chris Torek <chris.torek gmail.com> 1462505767 -0700 

Verschiedene Zeitstempel = unterschiedliche Commits = viel sinnvoller Einrichtung.

Eigentlich die Rebase ausführen, ließ die Datei trotzdem fallen!

Es stellt sich heraus, dass dies beabsichtigt ist. Wenn Sie git rebase ausführen, listet der Setup-Code nicht einfach jedes Commit für Rosinenpicken auf, sondern verwendet git rev-list --right-only, um Commits zu finden, die es fallen lassen sollte.

Da die commit fügt hinzu, dass test.txt im Upstream ist, Git fällt es nur ganz: die hier davon ausgegangen, dass Sie es an jemand Upstream gesendet, nahmen sie es schon, und es gibt keine Notwendigkeit, es wieder zu nehmen .

Lassen Sie uns das reproducer Skript wieder ändern -und wir werden das in der Lage sleep 2 diesmal zu nehmen, die Dinge-so, dass die Änderung master ist anders, und werden nicht entfernt aus der Liste über --cherry-pick --right-only beschleunigt.Wir werden noch test.txt mit der gleichen einzigen Zeile hinzufügen, aber wir werden auch ändern main.txt in dem Autocommit:

# setup master 
git checkout master 
echo hello > test.txt 
echo and also slight difference >> main.txt 
git add . 
git commit -m "added test.txt" 

Wir gehen weiter und biegen auf den endgültigen git checkout B und git rebase master Linien als auch, und dieses Mal, Rebasing Werke wie wir ursprünglich erwartet hatten:

$ git log --oneline --decorate --graph --all 
* c31b13a (HEAD -> B) added test.txt 
* da2ca52 (master) remove test.txt 
* 6972019 added test.txt 
* 0f0d2e8 initial commit 
$ ls 
main.txt test.txt 

Ich hatte nicht erkannt, dass Rebase dies tat; es ist nicht etwas, was ich erwartet habe (obwohl wie die andere Antwort weist darauf hin, es dokumentiert ist), und es bedeutet, dass zu sagen „rebase nur cherry-pick wiederholt wird“ ist nicht ganz richtig: es cherry-pick wiederholt hat, mit speziellem Fälle von Ablegen von Commits.


Eigentlich für nicht-interaktive Fütterungsmaterial, verwendet es diese bemerkenswerte Bit:

git format-patch -k --stdout --full-index --cherry-pick --right-only \ 
--src-prefix=a/ --dst-prefix=b/ --no-renames --no-cover-letter \ 
"$revisions" ${restrict_revision+^$restrict_revision} \ 
>"$GIT_DIR/rebased-patches" 

wo $revisions dehnt sich aus, in diesem Fall zu master...B.

Die --cherry-pick --right-only Optionen git format-patch sind nicht dokumentiert; Man muss wissen, in der git rev-list Dokumentation für sie zu suchen.

Interactive Fütterungsmaterial verwendet eine andere Technik wählt aber noch keine Festschreibungen entfernt, die bereits in den vorgelagerten sind. Dies wird angezeigt, wenn Sie rebase zu rebase -i ändern, in dem die Rebase-Anweisungen aus einer noop Zeile anstelle der erwarteten einzelnen Zeile bestehen.

2

Wie git rebase documentation sagt:

Beachten Sie, dass alle Commits in HEAD, die die gleichen Textänderungen wie eine Festschreibung in HEAD einführen .. <Upstream> entfallen (dh ein Patch bereits mit einem anderen akzeptiert Upstream begehen Nachricht oder Zeitstempel werden übersprungen).

In Ihrem Fall B1 führen Sie die gleiche Änderung wie A2. Wenn Sie also rebase durchführen, wird B1 aus dem Rebase-Prozess weggelassen, da <upstream> bereits diesen Patch haben. Sie können -i Option hinzufügen, um interaktive Rebase zu tun. Damit können Sie sehen, dass B1 nicht in der ToDo-Liste des Rebase-Prozesses aufgeführt ist. Sie können dieses Commit jedoch manuell auswählen, indem Sie pick B1 in die Liste der interaktiven Rebases einfügen.

Verwandte Themen