Ukrywanie tylko etapowych zmian w git - czy to możliwe?

364

Czy istnieje sposób, aby ukryć tylko zmiany etapowe? Scenariusz, z którym mam problemy, polega na tym, że pracowałem nad kilkoma błędami w danym momencie i wprowadziłem kilka nieetapowych zmian. Chciałbym mieć możliwość stopniowania tych plików indywidualnie, tworzenia plików .patch i przechowywania ich do momentu zatwierdzenia kodu. W ten sposób, po zatwierdzeniu, mogę ukryć całą (bieżącą) sesję, usunąć ten błąd i przesłać kod.

Czy robię to w niewłaściwy sposób? Czy nie rozumiem, w jaki sposób git może działać na inne sposoby, aby uprościć mój proces?

MrDuk
źródło
Tak, prawdopodobnie robisz coś źle, aby dostać się do tej sytuacji. Wciąż przydatne pytanie. Naprawdę powinieneś schować lub rozgałęzić się przed rozpoczęciem kolejnej poprawki. Styczna odpowiedź stackoverflow.com/a/50692885 jest prawdopodobnie lepszym sposobem radzenia sobie z tym w git. Zabawa ze skrytką często powoduje dziwne rzeczy w moim obszarze roboczym, jeśli wyciągnąłem commits z góry.
Samuel Åslund,

Odpowiedzi:

469

Tak, jest to możliwe dzięki DOUBLE STASH

  1. Ustaw wszystkie pliki, które chcesz ukryć.
  2. Uruchom git stash --keep-index. To polecenie utworzy skrytkę ze WSZYSTKIMI zmianami ( etapowymi i nieetapowymi ), ale pozostawi zmiany etapowe w katalogu roboczym (nadal w stanie przemieszczonym).
  3. Biegać git stash push -m "good stash"
  4. Teraz twój "good stash"ma tylko wystawił plików .

Teraz, jeśli potrzebujesz plików niestacjonarnych przed skrytką, po prostu zastosuj pierwszą skrytkę ( tę utworzoną za pomocą--keep-index ), a teraz możesz usunąć pliki, które zostały schowane "good stash".

Cieszyć się

Bartłomiej Semańczyk
źródło
Ukrywa zmiany w podmodułach, nawet jeśli nie są one ustawione. Czy jest na to jakiś sposób?
rluks,
1
w jakiś sposób wszystkie nowe pliki (nawet zainscenizowane) zostały wyłączone.
Aurimas,
8
@Aurimas, aby ukryć nowe pliki, musisz użyć -uprzełącznika.
Gyromite
2
po ponownym zastosowaniu pierwszego skrytki i przywróceniu wszystkich zmian, podczas gdy być może zainteresuje Cię tylko opcja zmiany scen, użyj git stash apply --indexopcji. Spróbuje to utrzymać stan un (inscenizowany). Łatwiej jest teraz usunąć niechciane zmiany z działającego drzewa.
otomo
Chociaż nie musiałem robić dokładnie tego, co powiedziała ta odpowiedź, znajomość flagi --keep-index była bardzo pomocna.
Aaron Krauss
128

W najnowszym git możesz użyć --patchopcji

git stash push --patch  

git stash save --patch   # for older git versions

I git poprosi cię o każdą zmianę w twoich plikach, aby dodać lub nie w skrytce.
Po prostu odpowiadasz ylubn

UPD
Alias ​​dla DOUBLE STASH :

git config --global alias.stash-staged '!bash -c "git stash --keep-index; git stash push -m "staged" --keep-index; git stash pop stash@{1}"'

Teraz możesz wyeksponować pliki, a następnie uruchomić git stash-staged.
W rezultacie pliki tymczasowe zostaną zapisane w skrytce .

Jeśli nie chcesz zachować przemieszczanych plików i chcesz przenieść je do przechowalni. Następnie możesz dodać kolejny alias i uruchomić git move-staged:

git config --global alias.move-staged '!bash -c "git stash-staged;git commit -m "temp"; git stash; git reset --hard HEAD^; git stash pop"'
Eugen Konkov
źródło
17
Technicznie nie odpowiada na pytanie - ale naprawdę fajna technika, która pozwala na selektywne składowanie.
alexreardon
6
Zgadzam się, to jest w porządku, ale pomysł tutaj z pytaniem jest, że JUŻ ZROBIAŁEM całą tę pracę, wprowadzając zmiany, z którymi chcę coś zrobić (rzekomo początkowo do zatwierdzenia, ale teraz chcę ukryć), a nie tylko zrób to wszystko od nowa.
Steven Lu
4
nie działa dla nowo tworzonych plików (działa tylko na zmodyfikowanych plikach)
Derek Liang
@DerekLiang: Nowo utworzone pliki w ogóle nie są śledzone. Prawdopodobnie powinieneś sprawdzić -u|--include-untrackedopcjęgit-stash
Eugen Konkov
2
Z dokumentacji : „ zapisz : ta opcja jest przestarzała na korzyść git stash push . Różni się od„ stash push ”tym, że nie może przyjmować specyfikacji ścieżek, a wszelkie nie-opcje argumenty tworzą komunikat.”
Borjovsky,
53

Stworzyłem skrypt, który ukrywa tylko to, co aktualnie jest wystawiane i pozostawia wszystko inne. To niesamowite, gdy zaczynam wprowadzać zbyt wiele niepowiązanych zmian. Po prostu zrób scenę, która nie jest związana z pożądanym zatwierdzeniem i ukryj właśnie to.

(Podziękowania dla Bartłomieja za punkt wyjścia)

#!/bin/bash

#Stash everything temporarily.  Keep staged files, discard everything else after stashing.
git stash --keep-index

#Stash everything that remains (only the staged files should remain)  This is the stash we want to keep, so give it a name.
git stash save "$1"

#Apply the original stash to get us back to where we started.
git stash apply stash@{1}

#Create a temporary patch to reverse the originally staged changes and apply it
git stash show -p | git apply -R

#Delete the temporary stash
git stash drop stash@{1}
Joe
źródło
7
Dodam, że możesz zmienić skrypt w komendę git, postępując zgodnie z thediscoblog.com/blog/2014/03/29/custom-git-commands-in-3-steps
Petr Bela
3
To jest świetne! Poprawiłem go, aby monitował użytkownika o opis skrytki, jeśli nie wprowadzi go w wierszu poleceń: gist.github.com/brookinc/e2589a8c5ca33f804e4868f6bfc18282
brookinc
1
To nie działa w ogóle z git 2.23.0.
wtorek
Dzięki, przegłosowałem i zmieniłem go w alias tutaj: stackoverflow.com/a/60875067/430128 .
Raman
33

TL; DR Wystarczy dodać parametr -- $(git diff --staged --name-only)git<pathspec>

Oto prosty jednowarstwowy:

git stash -- $(git diff --staged --name-only)

Aby dodać wiadomość po prostu:

git stash push -m "My work in progress" -- $(git diff --staged --name-only)

Testowane w wersjach v2.17.1 i v2.21.0.windows.1

Ograniczenia:

  • Należy pamiętać, że spowoduje to ukrycie każdej pojedynczej rzeczy, jeśli nie zostaną zainscenizowane żadne pliki.
  • Również jeśli masz plik, który jest tylko częściowo przemieszczany (tzn. Tylko niektóre zmienione linie są przemieszczane, a niektóre inne zmienione linie nie są), wtedy cały plik zostanie zablokowany (w tym linie niestabilne).
Somo S.
źródło
6
Myślę, że jest to najlepsza opcja w opisanej sytuacji: łatwa do zrozumienia i bez czarnej magii!
Luis
1
To jest całkiem fajne. Ostatecznie stworzyłem z niego alias!
Kalpesh Panchal
głosuj dalej
Somo S.
@KalpeshPanchal czy możesz udostępnić swój alias? Nie jestem pewien, jak go uniknąć, więc nie interpretuje go poprawnie.
Igor Nadj
2
@IgorNadj Sure! Oto on: github.com/panchalkalpesh/git-aliases/commit/…
Kalpesh Panchal
15

Aby osiągnąć to samo ...

  1. Przygotuj tylko pliki, nad którymi chcesz pracować.
  2. git commit -m 'temp'
  3. git add .
  4. git stash
  5. git reset HEAD~1

Bum. Pliki, których nie chcesz, są przechowywane. Wszystkie potrzebne pliki są gotowe.

Michał
źródło
3
To jest z pewnością najlepsza odpowiedź i najłatwiejsza do zapamiętania
Kevin
9

W tym scenariuszu wolę tworzyć nowe gałęzie dla każdego wydania. Używam przedrostka temp /, więc wiem, że mogę później usunąć te gałęzie.

git checkout -b temp/bug1

Dokonaj edycji plików, które naprawiają błąd 1, i zatwierdź je.

git checkout -b temp/bug2

Następnie możesz wybrać wiśni z odpowiednich oddziałów zgodnie z wymaganiami i przesłać żądanie ściągnięcia.

Szampony
źródło
2
Podczas gdy fajne dźwięki ukryte są miło wiedzieć, w praktyce wydaje się, że takie podejście jest mniej prawdopodobne.
ryanjdillon,
1
Użyj „git cherry-pick tmpCommit”, aby uzyskać tymczasowe zatwierdzenie z powrotem w trybie scalania-zatwierdzenia lub „git merge tmpCommit” + „git reset HEAD ^”, aby uzyskać zmiany bez zatwierdzenia.
Samuel Åslund,
1
Jak pokazuje ta odpowiedź, lepiej jest zapytać bezpośrednio, co chcesz osiągnąć, zamiast tego, jak to osiągnąć za pomocą danej techniki. Tymczasowe gałęzie i pick-cherry są przydatne w skomplikowanych sytuacjach.
Guney Ozsan
jeśli częściowo zainscenizowałeś plik, będziesz musiał ukryć zmiany, zanim wrócisz do oryginalnej gałęzi i ponownie je
wyskoczy
6

Dlaczego nie zatwierdzasz zmiany dla określonego błędu i nie tworzysz łatki z tego zatwierdzenia i jego poprzednika?

# hackhackhack, fix two unrelated bugs
git add -p                   # add hunks of first bug
git commit -m 'fix bug #123' # create commit #1
git add -p                   # add hunks of second bug
git commit -m 'fix bug #321' # create commit #2

Następnie, aby utworzyć odpowiednie łatki, użyj git format-patch:

git format-patch HEAD^^

Spowoduje to utworzenie dwóch plików: 0001-fix-bug-123.patchi0002-fix-bug-321.patch

Lub możesz utworzyć osobne gałęzie dla każdego błędu, abyś mógł indywidualnie scalać lub rozdzielać poprawki błędów, a nawet usuwać je, jeśli się nie uda.

knittl
źródło
2

git stash --keep-index jest dobrym rozwiązaniem ... oprócz tego, że nie działało poprawnie na usuniętych ścieżkach, co zostało naprawione w Git 2.23 (III kwartał 2019)

Zobacz zatwierdzenie b932f6a (16 lipca 2019 r.) Autor: Thomas Gummerer ( tgummerer) .
(Połączone przez Junio ​​C Hamano - gitster- w commit f8aee85 , 25 lipca 2019)

stash: naprawa obsługi usuniętych plików za pomocą --keep-index

git stash push --keep-index ma zachować wszystkie zmiany, które zostały dodane do indeksu, zarówno w indeksie, jak i na dysku.

Obecnie nie działa to poprawnie po usunięciu pliku z indeksu.
Zamiast pozostawić go usuniętego na dysku, ** - keep-index obecnie przywraca plik. **

Napraw to zachowanie, używając opcji „ git checkout” w trybie bez nakładania, który może wiernie przywrócić indeks i działające drzewo.
Upraszcza to również kod.

Pamiętaj, że spowoduje to zastąpienie nieśledzonych plików, jeśli nieśledzony plik ma taką samą nazwę jak plik, który został usunięty z indeksu.

VonC
źródło
2

Przechowywanie tylko indeksu (zmiany etapowe) w Git jest trudniejsze niż powinno. Znalazłem @ Joe odpowiedź do pracy dobrze i odwrócił odmianę drobne go do tego aliasu:

stash-index = "!f() { \
  git stash push --quiet --keep-index -m \"temp for stash-index\" && \
  git stash push \"$@\" && \
  git stash pop --quiet stash@{1} && \
  git stash show -p | git apply -R; }; f"

Pcha zarówno zmiany etapowe, jak i niestacjonarne do tymczasowej skrytki, pozostawiając same zmiany etapowe. Następnie wypycha zmiany etapowe do skrytki, która jest skrytką, którą chcemy zachować. Argumenty przekazywane do aliasu, takie jak --message "whatever"zostaną dodane do tego polecenia ukrywania. Na koniec wyskakuje tymczasowa skrytka, aby przywrócić pierwotny stan i usunąć tymczasową skrytkę, a następnie w końcu „usuwa” ukryte zmiany z katalogu roboczego za pomocą aplikacji łatki odwrotnej.

W przypadku odwrotnego problemu ukrywania tylko niestabilnych zmian (alias stash-working) zobacz tę odpowiedź .

Raman
źródło
1

Czy absolutnie konieczna jest praca nad kilkoma błędami jednocześnie? I przez „od razu” mam na myśli „edytowanie plików pod kątem wielu błędów jednocześnie”. Ponieważ chyba, że ​​jest to absolutnie potrzebne, pracuję tylko nad jednym błędem na raz w twoim środowisku. W ten sposób możesz korzystać z lokalnych oddziałów i bazy danych, co uważam za znacznie łatwiejsze niż zarządzanie złożoną skrytką / sceną.

Powiedzmy, że master jest w zatwierdzeniu B. Teraz pracuj nad błędem nr 1.

git checkout -b bug1

Teraz jesteś na gałęzi bug1. Wprowadź zmiany, zatwierdź, poczekaj na sprawdzenie kodu. Jest to lokalne, więc nie wpływasz na nikogo innego i powinno być łatwo zrobić łatkę z git diffs.

A-B < master
   \
    C < bug1

Teraz pracujesz nad bug2. Idź z powrotem do opanowania z git checkout master. Utworzyć nowy oddział, git checkout -b bug2. Wprowadź zmiany, zatwierdź, poczekaj na sprawdzenie kodu.

    D < bug2
   /
A-B < master
   \
    C < bug1

Udawajmy, że ktoś inny popełnia E & F na masterie, gdy czekasz na recenzję.

    D < bug2
   /
A-B-E-F < master
   \
    C < bug1

Po zatwierdzeniu kodu możesz zmienić jego podstawę w celu opanowania, wykonując następujące czynności:

git checkout bug1
git rebase master
git checkout master
git merge bug1

Spowoduje to:

    D < bug2
   /
A-B-E-F-C' < master, bug1

Następnie możesz wcisnąć, usunąć lokalny oddział bug1 i gotowe. Jeden błąd na raz w twoim obszarze roboczym, ale przy użyciu lokalnych oddziałów twoje repozytorium może obsłużyć wiele błędów. I to pozwala uniknąć skomplikowanego tańca scenicznego / skrytkowego.

Odpowiedz na pytanie ctote w komentarzach:

Cóż, możesz wrócić do ukrywania każdego błędu i pracować tylko z jednym błędem na raz. Przynajmniej oszczędza to problemu z inscenizacją. Ale po wypróbowaniu tego osobiście uważam to za kłopotliwe. Skrytki są nieco niechlujne na wykresie git log. A co ważniejsze, jeśli coś spieprzysz, nie możesz cofnąć. Jeśli masz brudny katalog roboczy i wyskakujesz skrytkę, nie możesz „cofnąć” tego popu. Znacznie trudniej jest zepsuć już istniejące zatwierdzenia.

Więc git rebase -i .

Po zmianie podstawy jednej gałęzi na drugą możesz to zrobić interaktywnie (flaga -i). Kiedy to zrobisz, możesz wybrać, co chcesz zrobić z każdym zatwierdzeniem. Pro Git to niesamowita książka, która jest również dostępna online w formacie HTML i ma fajną sekcję na temat zmiany bazy i squashingu:

http://git-scm.com/book/ch6-4.html

Dla wygody ukradnę ich przykład. Udawaj, że masz następującą historię zatwierdzeń i chcesz zmienić bazę i zgnieść błąd1 na master:

    F < bug2
   /
A-B-G-H < master
   \
    C-D-E < bug1

Oto, co zobaczysz podczas pisania git rebase -i master bug1

pick f7f3f6d changed my name a bit
pick 310154e updated README formatting and added blame
pick a5f4a0d added cat-file
#
# Commands:
#  p, pick = use commit
#  e, edit = use commit, but stop for amending
#  s, squash = use commit, but meld into previous commit
#
# If you remove a line here THAT COMMIT WILL BE LOST.
# However, if you remove everything, the rebase will be aborted.
#

Aby zmiksować wszystkie zatwierdzenia gałęzi w jednym zatwierdzeniu, zachowaj pierwszy zatwierdzenie jako „pick” i zamień wszystkie kolejne wpisy „pick” na „squash” lub po prostu „s”. Otrzymasz również możliwość zmiany komunikatu zatwierdzenia.

pick f7f3f6d changed my name a bit
s 310154e updated README formatting and added blame
s a5f4a0d added cat-file
#
# Commands:
#  p, pick = use commit
#  e, edit = use commit, but stop for amending
#  s, squash = use commit, but meld into previous commit

Więc tak, zgniatanie jest trochę uciążliwe, ale nadal zalecałbym to nad intensywnym używaniem skrytek.

Mike Monkiewicz
źródło
1
Dzięki za szczegółowy post! To z pewnością rozwiązuje wiele moich problemów - jedynym problemem, jaki widzę, jest to, że nasz obecny zespół poprosił o utrzymanie wszystkich dostaw w jednym zatwierdzeniu. :(
MrDuk
1
Jeśli nie potrzebują lub nie chcą twojej historii pracy w repozytorium produkcyjnym, to w porządku: spraw, aby twój mistrz śledzenia nie miał historii, stosując różnice, a nie łącząc gałęzie. Możesz dowiedzieć się, jak zachować udekorowaną gałąź, która ma rzeczywistą scaloną historię, i wykonać z niej swoją prawdziwą pracę, dzięki czemu łatwo będzie zautomatyzować generowanie poprawnych różnic.
jthill
2
Zauważ, że git checkout master; git checkout -b bug2można to skrócić git checkout -b bug2 master. To samo dotyczy git checkout bug1; git rebase master; git checkout master; git merge bug1, co jest identyczne z git rebase master bug1; git push . bug1:master( pushoczywiście , sztuczka nie jest oczywista)
knittl
1
W głównej odpowiedzi podałem przewodnik dotyczący ukrywania się, aby móc użyć fantazyjnego formatowania
Mike Monkiewicz,
6
Zagłosowałem, ponieważ to nie odpowiada na pierwotne pytanie. Jestem w oddziale, który nad czymś pracuje i właśnie dokonałem zmiany, która moim zdaniem powinna być osobno poświęcona branży integracji. Wszystko, co chcę zrobić, to zmienić etap i ukryć go, abym mógł przejść do innej gałęzi i zatwierdzić osobno, zamiast mojej obecnej gałęzi „praca w toku”. (Ostrzeżenie, rozpacz.) Absurdem jest to, że tak trudno to zrobić; Muszę sobie wyobrazić, że jest to częste zjawisko. (Praca w jednym oddziale i wykrywanie szybkiej zmiany, którą należy wprowadzić, i zapominanie o zmianie w pierwszej kolejności.)
jpmc26,
0

Z twoich komentarzy do odpowiedzi Mike'a Monkiewicza sugeruję użycie prostszego modelu: używaj zwykłych gałęzi programistycznych, ale użyj opcji squash połączenia, aby uzyskać pojedyncze zatwierdzenie w gałęzi master:

git checkout -b bug1    # create the development branch
* hack hack hack *      # do some work
git commit
* hack hack hack *
git commit
* hack hack hack *
git commit
* hack hack hack *
git commit
git checkout master     # go back to the master branch
git merge --squash bug1 # merge the work back
git commit              # commit the merge (don't forget
                        #    to change the default commit message)
git branch -D bug1      # remove the development branch

Zaletą tej procedury jest to, że możesz użyć normalnego przepływu pracy git.

Rudi
źródło
Nie widzę, jak ta odpowiedź mogłaby pomóc. Nie ma to związku z pierwotnym pytaniem.
frapen
0

TL; DR ;git stash-staged

Po utworzeniu aliasu:

git config --global alias.stash-staged '!bash -c "git stash -- \$(git diff --staged --name-only)"'

Tutaj git diffzwraca listę --stagedplików, --name-only
a następnie przekazujemy tę listę pathspecdo git stashkomendy.

Od man git stash:

git stash [--] [<pathspec>...]

<pathspec>...
   The new stash entry records the modified states only for the files
   that match the pathspec. The index entries and working tree
   files are then rolled back to the state in HEAD only for these
   files, too, leaving files that do not match the pathspec intact.

Eugen Konkov
źródło
-1

Aby przyciąć przypadkową zmianę, w szczególności usunięcie wielu plików, wykonaj następujące czynności:

git add <stuff to keep> && git stash --keep-index && git stash drop

innymi słowy, schowaj bzdury i wyrzuć je razem ze skrytką.

Testowane w wersji git 2.17.1

wmax
źródło
opinia bez komentarza nie jest ani pomocna dla mnie, ani dla następnego czytelnika ... zaenks grumpy anon. Chociaż mogę sobie wyobrazić jeden problem z tym linkiem: należy bardzo uważać, aby nie zapomnieć o dodaniu wszystkich pożądanych zmian do indeksu, w przeciwnym razie te ważne zmiany również zostaną usunięte. Ale z drugiej strony, nieostrożne użycie dowolnego narzędzia CLI może być bardzo niebezpieczne dla cennego czasu i pracy w najgorszym przypadku.
wmax,