web-dev-qa-db-de.com

automatisch speichern/Pop-Änderungen auf Git Rebase speichern?

mein git workflow verwendet rebase viel. Ich hole immer Upstream-Änderungen ab (das Haupt-Repo, von dem ich gegabelt habe), und dann zu meinen Zweigen zusammenführen.

eine Sache in diesem Workflow, die mich nervt, ist:

$ git rebase upstream/master
Cannot rebase: You have unstaged changes.
Please commit or stash them.

$ git stash
Saved working directory and index state WIP on cc: abc1234 Merge remote-tracking branch 'upstream/master' into local_branch
HEAD is now at abc1234 Merge remote-tracking branch 'upstream/master' into local_branch

$ git rebase upstream/master
First, rewinding head to replay your work on top of it...
Applying: awesome code change

$ git stash pop

hier haben wir 4 Befehle, 1 = fehlgeschlagene Rebase, 2 = Stash, 3 = Rebase, 4 = Stash-Pop. alles andere als 3 ist einfach nur gedankenlos.

Die Frage ist also: Welche Methode wird am meisten empfohlen, um sie zu automatisieren? ein Alias, um git stash/rebase/pop jedes Mal auszuführen? einige git config, die rebase zwingt, es zu speichern oder als ein anderes Commit zu behandeln, um es danach erneut anzuwenden? etwas anderes?

29
gcb

Bearbeiten: Ab Git Version 1.8.4, aber mit einem wichtigen Nebenfehler, der in Git Version 2.0.1 behoben wurde, hat git rebase jetzt --autostash. Sie können git rebase so konfigurieren, dass --autostash ebenfalls standardmäßig mit git config --global rebase.autoStash true verwendet wird. Bitte beachten Sie den folgenden Satz aus der Dokumentation :

Gehen Sie jedoch vorsichtig vor: Die endgültige Stash-Anwendung nach einem erfolgreichen Rebase kann zu nicht trivialen Konflikten führen.

(Ich ziehe es immer noch vor, nur Commits zu machen.)

TL; DR antworte: mache einfach ein Commit (dann mache es später wieder rückgängig)

Es kann Ihnen helfen zu erkennen, dass git stash wirklich nur git commit ist (in einer komplizierteren Form, die zuerst den Index und dann den Arbeitsbaum festschreibt - wenn Sie einen Stash anwenden, Sie kann die Trennung von Index und Arbeitsbaum beibehalten oder zu einer Änderung des Arbeitsbaums zusammenfassen).

Das Besondere an einem Stash ist, dass die von ihm ausgeführten Commits - die zwei oder mit -u oder -a sogar drei - in einer ungewöhnlichen Form ausgeführt werden (als Merge-Commit, das nicht der Fall ist) wirklich eine Zusammenführung) und nicht in einem Zweig platziert (stattdessen wird die spezielle Referenz refs/stash verwendet, um sie beizubehalten und zu finden).

Da sie sich nicht in einem Zweig befinden, werden sie von rebase nicht berührt. In Ihrem Workflow ist es der git stash pop, der die Änderungen des Arbeitsbaums in Ihren neuen Arbeitsbaum überträgt. Wenn Sie jedoch ein eigenes (normales) Commit für einen Zweig erstellen und dieses Commit erneut verwenden und einschließen, wird dieses normale Commit zusammen mit allen anderen Commits neu basiert. Wir kommen gleich zu einem letzten Problem. Lassen Sie uns dies zunächst als eine Reihe von Commits aufstellen, die neu basieren (oder nicht):

... do some work ...
... make some commits ...
... more work ...
... do something that causes upstream/master to update, such as git fetch upstream
$ git stash

An dieser Stelle haben Sie Folgendes:

... - o - * - A - B - C     <-- HEAD=master
           \          |\
            \         i-w   <-- stash
             \
              @[email protected]@         <-- upstream/master

Hier sind A, B und C Ihre Commits (ich nehme an, Sie haben 3 gemacht), alle in Zweig master. Der i-w hängt von commit C ab und ist Ihr Versteck, das sich nicht auf dem Zweig befindet, aber immer noch ein Zwei-Commit "git stash bag" und ist tatsächlich an Ihr letztes Commit angehängt (C). Die @ Commits (es könnte nur einen geben) sind die neuen Upstream-Commits.

(Wenn Sie keine Commits durchgeführt haben, hängt Ihr Stashbag vom Commit * ab und Ihre aktuellen Verzweigungspunkte, um *, so dass git rebase nichts anderes zu tun hat, als den aktuellen Verzweigungszeiger vorwärts zu bewegen. In diesem Fall funktioniert alles gleich, aber ich gehe davon aus, dass es einige Commits gibt.)

Jetzt führen Sie git rebase upstream/master aus. Dadurch werden Ihre Commits in neue Commits mit neuen IDs und neuen übergeordneten IDs kopiert, sodass sie auf dem letzten @ stehen. Der Stashbag bewegt sich nicht, das Ergebnis sieht also so aus:

... - o - * - A - B - C     [abandoned, except for the stash]
           \          |\
            \         i-w   <-- stash
             \
              @[email protected]@         <-- upstream/master
                   \
                    A'-B'-C'   <-- HEAD=master

Sie verwenden jetzt git stash pop, wodurch das i/w-Material wiederhergestellt wird, wenn sich der Arbeitsbaum ändert, und das stash -Label gelöscht wird (genauer gesagt, indem es so eingefügt wird, dass [email protected]{1}, wenn es existiert, ist es jetzt stash und so weiter). Das gibt die letzten Verweise auf die ursprüngliche A - B - C Kette frei und bedeutet, dass wir auch das i-w Bit nicht benötigen, wodurch wir dies als das viel einfachere neu zeichnen können:

... - @            <-- upstream/master
       \
        A'-B'-C'   <-- HEAD=master plus work tree changes

Zeichnen wir nun, was passiert, wenn Sie anstelle von git stash save nur git commit -a (oder git add und git commit ohne -a) eingeben erstelle ein tatsächliches Commit D. Sie beginnen mit:

... - o-*-A-B-C-D   <-- HEAD=master
         \
          @[email protected]@     <-- upstream/master

Nun haben Sie git rebase upstream/master, der A bis D kopiert, um sie am Ende des letzten @ zu platzieren, und dies haben Sie:

... - o-*[email protected]@[email protected]     <-- upstream/master
               \
                A'-B'-C'-D'   <-- HEAD=master

Das einzige Problem ist, dass Sie dieses unerwünschte zusätzliche Commit D (nun, D') anstelle von nicht festgeschriebenen Änderungen am Arbeitsbaum haben. Dies wird jedoch mit git reset trivial rückgängig gemacht, um ein Commit zurückzusetzen. Wir können einen --mixed Reset (Standardeinstellung) verwenden, um den Index (Staging-Bereich) ebenfalls zurückzusetzen, um das Hinzufügen aller Dateien aufzuheben, oder wenn Sie möchten, dass sie git add- ed, ein --soft reset. (Beides wirkt sich nicht auf das resultierende Festschreibungsdiagramm aus, nur der Indexstatus ist unterschiedlich.)

git reset --mixed HEAD^   # or leave out `--mixed` since it's the default

So sieht das aus:

... - o-*[email protected]@[email protected]     <-- upstream/master
               \
                A'-B'-C'      <-- HEAD=master
                        \
                         D'   [abandoned]

Sie denken vielleicht, dass dies ineffizient ist, aber wenn Sie git stash verwenden, machen Sie tatsächlich mindestens zwei Commits, die Sie dann aufgeben später, wenn Sie sie git stash pop. Der wirkliche Unterschied besteht darin, dass Sie durch das Tätigen von temporären, nicht zur Veröffentlichung bestimmten Commits diese automatisch umbasieren lassen.

Hab keine Angst vor vorübergehenden Verpflichtungen

Es gibt eine allgemeine Regel bei git: Machen Sie viele temporäre Commits, um Ihre Arbeit zu speichern, während Sie gehen. Sie können sie später jederzeit wieder entfernen. Das heißt, stattdessen:

... - * - A - B - C   <-- mybranch

wobei A, B und C perfekte und endgültige Commits auf Commit * (von jemand anderem oder früher veröffentlichtem Material) sind diese:

... - * - a1 - a2 - b1 - a3 - b2 - a4 - b3 - c1 - b4 - c2 - c3

wobei a1 ein Anfangsstich bei A ist, a2 einen Fehler in a1 behebt, b1 ein Anfangsstich ist Versuch, b zum Laufen zu bringen, a3 ist von der Erkenntnis abhängig, dass b1 erfordert, dass A doch anders ist, b2 behebt einen Fehler in b1, a4 behebt einen Fehler in der Änderung von a3 zu a2, und b3 ist was b1 hätte tun sollen; dann ist c1 ein anfänglicher Versuch, C, b4 ist ein weiterer Fix für b1, c2 ist eine Verfeinerung , und so weiter.

Nehmen wir an, dass Sie nach c3 denken, dass es größtenteils fertig ist. Führen Sie nun git rebase -i Origin/master oder was auch immer aus, mischen Sie die pick Zeilen, um a1 durch a4 in die richtige Reihenfolge zu bringen, b1 durch b4 in Reihenfolge und c1 durch c3 in Reihenfolge, und lassen Sie die Basis neu starten. Dann beheben Sie alle Konflikte und stellen sicher, dass alles noch stimmt. Anschließend führen Sie einen weiteren git rebase -i aus, um alle vier a Versionen in A zu reduzieren, und so weiter.

Wenn Sie fertig sind, sieht es so aus , als hätten Sie das erste Mal (oder vielleicht mit a4 oder ein anderes, je nachdem, welche Commits Sie behalten und welche Sie verwerfen und ob Sie Zeitstempel in den Dingen neu setzen). Andere Personen möchten oder müssen Ihre Zwischenarbeiten möglicherweise nicht sehen - obwohl Sie sie beibehalten können, können Sie keine Commits kombinieren, wenn dies nützlich ist. In der Zwischenzeit brauchen Sie niemals nicht festgeschriebene Inhalte, die Sie zurücksetzen müssen, weil Sie nur teilweise festgeschriebene Inhalte haben.

Es ist hilfreich, im einzeiligen Commit-Text die folgenden Commit-Namen anzugeben, die Ihre spätere Rebase-Arbeit leiten:

git commit -m 'temp commit: work to enable frabulator, incomplete'

und so weiter.

40
torek

Eine einfache Antwort:git rebase -i --autosquash --autostash <tree-ish>

-i = interactively rebase

https://devdocs.io/git/git-rebase


Dieser Wille... 

  • Automatisches Speichern Ihrer Änderungen
  • Interaktiv von <tree-ish>.__ ausgehend.
    • Positionieren Sie Ihre Kürbisse und Korrekturen automatisch
  • Auto-Pop-Stash im Arbeitsverzeichnis nach Rebase

tree-ish kann ein Festschreibungshash oder ein Zweigname oder ein Tag oder ein beliebiger Bezeichner sein.

15
abhisekp

Sie können ein externes Werkzeug namens git-up verwenden, das genau das tut, was Sie für alle Zweige sagen. Dies hilft auch, ein sauberes Verlaufsdiagramm zu erhalten.

Ich habe es schon einige Jahre gebraucht und es funktioniert ziemlich gut, besonders wenn Sie kein Git-Experte sind. Wenn Sie die Funktion für die automatische Neugestaltung hinzufügen, sollten Sie wissen, wie Sie die fehlerhafte Rebase ordnungsgemäß wiederherstellen können (Fortfahren, Abbruch, ...)

Installieren

Öffnen Sie eine Shell und führen Sie Folgendes aus:

Sudo gem install git-up

Aufbau

Öffnen Sie Ihre globale Konfigurationsdatei (~/.gitconfig) und fügen Sie Folgendes hinzu:

[git-up "fetch"]
    all = true    # up all branches at once, default false
    Prune = true  # Prune deleted remote branches, default false
[git-up "rebase"]
    # recreate merges while rebasing, default: unset
    arguments = --preserve-merges
    # uncomment the following to show changed commit on rebase
    # log-hook = "git --no-pager log --oneline --color --decorate $1..$2"

In der offiziellen Dokumentation finden Sie weitere Optionen.

Aufruf

Wenn alles gut konfiguriert ist, führen Sie einfach Folgendes aus:

git up

Dies entspricht (ungefähr) der Ausführung des folgenden Befehls:

git stash
git fetch --all
[foreach branch]
    git rebase --preserve-merges <branch> <remote>/<branch>
    git merge --ff-only <branch>
[end foreach]
git checkout <prev_branch>
git stash pop
2
giosh94mhz

Der gesamte Workflow in einem Befehl, einschließlich des Abrufs:

git pull --rebase --autostash [...]
2
Marc

Antwort von tjsingleton blogg

machen Sie einen Befehlsalias von:

git stash & & git pull --rebase & & git stash pop

update

Wenn Sie idee verwenden und ein fehlerhaftes Arbeitsverzeichnis verwenden, wird ein Dialogfeld angezeigt, und Sie können Rebase/Merge auswählen. Das Einlagern, Rebazieren/Zusammenführen und Popup erfolgt automatisch.

1
Sisyphus