jak wciskać tylko niektóre lokalne commity?

160

Załóżmy, że mam 5 lokalnych zatwierdzeń. Chcę przesłać tylko 2 z nich do scentralizowanego repozytorium (przy użyciu przepływu pracy w stylu SVN). Jak mam to zrobic?

To nie zadziałało:

git checkout HEAD~3  #set head to three commits ago
git push #attempt push from that head

To kończy się wypchnięciem wszystkich 5 lokalnych zatwierdzeń.

Przypuszczam, że mógłbym zrobić reset git, aby faktycznie cofnąć moje zatwierdzenia, a następnie git stash, a następnie git push - ale mam już napisane komunikaty o zatwierdzeniach i uporządkowane pliki i nie chcę ich powtarzać.

Mam wrażenie, że zadziała jakaś flaga przekazana do naciśnięcia lub zresetowania.

Jeśli to pomoże, oto moja konfiguracja git

[ramanujan:~/myrepo/.git]$cat config 
[core]
        repositoryformatversion = 0
        filemode = true
        bare = false
        logallrefupdates = true
[remote "origin"]
        url = ssh://server/git/myrepo.git
        fetch = +refs/heads/*:refs/remotes/origin/*
[branch "master"]
        remote = origin
        merge = refs/heads/master
ramanujan
źródło

Odpowiedzi:

192

Zakładając, że twoje zatwierdzenia znajdują się w gałęzi głównej i chcesz je wypchnąć do zdalnej gałęzi głównej:

$ git push origin master~3:master

Jeśli używasz git-svn:

$ git svn dcommit master~3

W przypadku git-svn możesz także użyć HEAD ~ 3, ponieważ oczekuje zatwierdzenia. W przypadku prostego gita, musisz użyć nazwy gałęzi, ponieważ HEAD nie jest poprawnie oceniana w refspec.

Możesz również przyjąć dłuższe podejście do:

$ git checkout -b tocommit HEAD~3
$ git push origin tocommit:master

Jeśli masz nawyk tego typu przepływu pracy, powinieneś rozważyć pracę w osobnej branży. Wtedy możesz zrobić coś takiego:

$ git checkout master
$ git merge working~3
$ git push origin master:master

Zauważ, że część „origin master: master” jest prawdopodobnie opcjonalna dla twojej konfiguracji.

Ryan Graham
źródło
14
Uwaga: nie musisz używać master~3. Każde odniesienie do żądanego zatwierdzenia „do” jest równie ważne, na przykład HEAD~3lub HEAD~~~, określony SHA lub znacznik, który oznacza to zatwierdzenie.
Kaz,
2
Dobry towar. Jednak ostrzeżenie: te przykłady popychają do mistrza pochodzenia. Jeśli kopiujesz i wklejasz to rozwiązanie, możesz przypadkowo zaktualizować gałąź główną. (Oczywiście zawsze należy być ostrożnym i dwukrotnie sprawdzić swoje polecenie przed wydaniem git push...)
nofinator
Wygląda na to, że wypycha to zatwierdzenie, ale nie dodaje gałęzi zdalnie.
Nateowami
@Nateowami do tego musisz podać coś innego niż masterdla odległej strony refspec, na przykładgit push origin tocommit:newbramch
Ryan Graham
Widzę. Nazwa oddziału już istnieje lokalnie; Przypuszczam, że to się nie podobało. Jednak pilot nie miał jeszcze nazwy oddziału.
Nateowami
16

Pracuję w lokalnym oddziale o nazwie „praca”. Ta gałąź zawiera wszystkie tymczasowe zatwierdzenia (takie jak obejścia, prywatne opcje kompilacji lub cokolwiek innego), których nie zamierzam wypychać do repozytorium nadrzędnego. Pracuję nad tą gałęzią, a kiedy chcę zatwierdzić, przełączam się na gałąź główną, wybieram odpowiednie zatwierdzenia, które chcę zatwierdzić, a następnie wciskam master.

Po ściągnięciu zmian z upstream do mojej gałęzi master, ja git checkout worki git rebase master. To przepisuje wszystkie moje lokalne zmiany na koniec historii.

Właściwie używam git svntego przepływu pracy, więc moja operacja „wypychania” obejmuje git svn dcommit. Używam również tigprzyjemnej przeglądarki repozytorium GUI w trybie tekstowym, aby wybrać odpowiednie commity do opanowania.

Greg Hewgill
źródło
z git svn dcommit, możesz określić zatwierdzenie do dcommit aż do, więc pożądany efekt jest dość trywialny z git-svn.
Ryan Graham
Takie podejście ma wady (podsumowane tutaj stackoverflow.com/a/881014/1116674 ). Dobrą alternatywą jest utworzenie gałęzi dla każdej funkcji, nad którą pracujesz, oraz workgałęzi. Następnie scalasz określone gałęzie w, masteraby nie stracić historii. Podczas pracy workscalasz w nią wszystkie swoje gałęzie. Jest to bardziej kosztowne, ale w niektórych przypadkach może być tego warte.
Hudon
16

Domyślnie git-push wypycha wszystkie gałęzie. Kiedy to robisz:

 git checkout HEAD~3  #set head to three commits ago
 git push #attempt push from that head

Przechodzisz do odłączonej HEAD (nie jesteś na żadnej gałęzi), a następnie wypychasz wszystkie gałęzie, w tym lokalny master (który wciąż jest tam, gdzie był) do zdalnego mastera.

Rozwiązanie ręczne to:

 git push origin HEAD:master

Jeśli uważasz, że domyślne zachowanie wypychania wszystkich gałęzi jest mylące (i niebezpieczne!), Dodaj to do swojego ~ / .gitconfig:

 [remote.origin]
    push = HEAD

Wtedy tylko gałąź, na której się znajdujesz, jest przesuwana. W twoim przykładzie (odłączona głowa) otrzymałeś ten komunikat o błędzie, zamiast przypadkowo wciskać niewłaściwe zatwierdzenia:

 error: unable to push to unqualified destination: HEAD
Thomas Leonard
źródło
10

Krótka odpowiedź:

git push <latest commit SHA1 until you want commits to be pushed>

Przykłady:

git push fc47b2

git push HEAD~2

Długa odpowiedź:

Zatwierdzenia są połączone w łańcuch z mechanizmem nadrzędnym / potomnym. Zatem wypychanie zatwierdzenia faktycznie wypycha również wszystkie zatwierdzenia rodzica do tego zatwierdzenia, które nie były znane użytkownikowi zdalnemu. Jest to robione niejawnie, gdy wykonujesz git pushbieżące zatwierdzenie: wszystkie poprzednie zatwierdzenia również są wypychane, ponieważ to polecenie jest równoważne git push HEAD.

Więc pytanie może zostać przepisane do Jak przekazać określone zatwierdzenie, a tym konkretnym zatwierdzeniem może być na przykład HEAD ~ 2.

Jeśli zatwierdzenia, które chcesz wypchnąć, nie następują po sobie, po prostu zmień ich kolejność, dodając git rebase -iprzed konkretnym push .

Tim
źródło
5

1) Użyj "git rebase", aby zmienić kolejność zatwierdzeń, jeśli chcesz.

git rebase -i

To polecenie wyświetli coś takiego w twoim edytorze (używam vim)

pick 4791291 commitA
pick a2bdfbd commitB
pick c3d4961 commitC
pick aa1cefc commitD
pick 9781434 commitE

# Rebase ..............
#
# Commands:
#  p, pick = use commit
#  r, reword = use commit, but edit the commit message
#  e, edit = use commit, but stop for amending
#  s, squash = use commit, but meld into previous commit
#  f, fixup = like "squash", but discard this commit's log message
#  x, exec = run command (the rest of the line) using shell
#
# These lines can be re-ordered; they are executed from top to bottom.
#
# If you remove a line here THAT COMMIT WILL BE LOST.
#
# However, if you remove everything, the rebase will be aborted.
#
# Note that empty commits are commented out




^G Get Help         ^O WriteOut         ^R Read File        ^Y Prev Page                ^K Cut Text         ^C Cur Pos
^X Exit             ^J Justify          ^W Where Is         ^V Next Page            ^U UnCut Text       ^T To Spell

2) Zmień kolejność zatwierdzeń zgodnie z własnym wyborem za pomocą prostej wycinanej wklejanej. Załóżmy, że nowy porządek to

wybierz 9781434 zatwierdzić

wybierz c3d4961 commitC

wybierz 4791291 commitA

wybierz aa1cefc commitD

wybierz a2bdfbd commitB

Wprowadź te zmiany w swoim edytorze i naciśnij ctrl + O (writeOut)

Możesz też użyć

git rebase -i HEAD~<commitNumber>

Możesz sprawdzić nową sekwencję za pomocą

git log

3) Teraz użyj

git push <remoteName> <commit SHA>:<remoteBranchName>

Jeśli tylko jedna gałąź na zdalnym (źródle) i jedna na lokalnym (główna), po prostu użyj

git push <commit SHA>
git push aa1cefc

Spowoduje to wypchnięcie commitB i commitD.

Yogesh Yadav
źródło