comment poussez-vous seulement certains de vos commits git locaux?

160

Supposons que j'ai 5 commits locaux. Je veux en pousser seulement 2 vers un référentiel centralisé (en utilisant un flux de travail de style SVN). Comment puis-je faire cela?

Cela n'a pas fonctionné:

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

Cela finit par pousser les 5 commits locaux.

Je suppose que je pourrais faire git reset pour réellement annuler mes commits, suivi de git stash puis de git push - mais j'ai déjà écrit des messages de commit et des fichiers organisés et je ne veux pas les refaire.

Mon sentiment est qu'un drapeau passé pour pousser ou réinitialiser fonctionnerait.

Si cela aide, voici ma configuration 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
la source

Réponses:

192

En supposant que vos commits se trouvent sur la branche master et que vous souhaitez les pousser vers la branche master distante:

$ git push origin master~3:master

Si vous utilisiez git-svn:

$ git svn dcommit master~3

Dans le cas de git-svn, vous pouvez également utiliser HEAD ~ 3, car il attend un commit. Dans le cas de git simple, vous devez utiliser le nom de la branche car HEAD n'est pas correctement évalué dans la refspec.

Vous pouvez également adopter une approche plus longue de:

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

Si vous prenez l'habitude de ce type de flux de travail, vous devriez envisager de faire votre travail dans une branche distincte. Ensuite, vous pouvez faire quelque chose comme:

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

Notez que la partie "origin master: master" est probablement optionnelle pour votre configuration.

Ryan Graham
la source
14
Remarque: vous ne devez pas utiliser master~3. Toute référence au commit "up to" souhaité est également valide, comme HEAD~3ou HEAD~~~, ou le SHA spécifique, ou une balise qui étiquette ce commit.
Kaz
2
Bon produit. Un avertissement cependant: ces exemples poussent vers le maître d'origine. Si vous copiez et collez cette solution, vous risquez de mettre à jour accidentellement la branche principale. (Bien sûr, vous devez toujours faire attention et revérifier votre commande avant d'émettre un git push...)
nofinator
Il semble que cela pousse le commit, mais n'ajoute pas la branche à distance.
Nateowami
@Nateowami pour cela, vous devrez spécifier autre chose que masterpour le côté distant du refspec, commegit push origin tocommit:newbramch
Ryan Graham
Je vois. Le nom de la branche existait déjà localement; Je suppose que ça n'a pas plu. Cependant, la télécommande n'avait pas encore le nom de la branche.
Nateowami
16

Ce que je fais, c'est travailler sur une branche locale appelée «travail». Cette branche contient tous les commits temporaires (comme les solutions de contournement ou les options de construction privées ou autre) que je n'ai pas l'intention de pousser vers le référentiel en amont. Je travaille loin sur cette branche, puis quand je veux engager je passe à la branche principale, écrémer les commits appropriés que je ne veux engager, puis pousser maître.

Après avoir transféré les modifications de l'amont dans ma branche principale, je git checkout worket git rebase master. Cela réécrit tous mes changements locaux pour être à la fin de l'histoire.

J'utilise actuellement git svnavec ce workflow, donc mon opération "push" implique git svn dcommit. J'utilise également tigqui est une belle visionneuse de référentiel d'interface graphique en mode texte, pour sélectionner les commits appropriés à maîtriser.

Greg Hewgill
la source
avec git svn dcommit, vous pouvez spécifier un commit à dcommit jusqu'à, donc l'effet désiré est assez trivial avec git-svn.
Ryan Graham
Il y a des inconvénients à cette approche (résumés ici stackoverflow.com/a/881014/1116674 ). Une bonne alternative consiste à créer des branches pour chaque fonctionnalité sur laquelle vous travaillez et une workbranche. Ensuite, vous fusionnez des branches spécifiques masterafin de ne pas perdre leur historique. Lorsque vous travaillez avec work, vous fusionnez toutes vos branches dans celui-ci. C'est plus de frais généraux, mais cela peut valoir la peine dans certains cas.
Hudon
16

Par défaut, git-push pousse toutes les branches. Lorsque vous faites ceci:

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

Vous vous déplacez vers une HEAD détachée (vous n'êtes sur aucune branche) puis vous poussez toutes les branches, y compris le maître local (qui est toujours là où il était) vers le maître distant.

La solution manuelle est:

 git push origin HEAD:master

Si vous trouvez que le comportement par défaut de pousser toutes les branches est déroutant (et dangereux!), Ajoutez ceci à votre ~ / .gitconfig:

 [remote.origin]
    push = HEAD

Ensuite, seule la branche sur laquelle vous vous trouvez est poussée. Dans votre exemple (une tête détachée), vous auriez reçu ce message d'erreur, plutôt que de pousser accidentellement les mauvais commits:

 error: unable to push to unqualified destination: HEAD
Thomas Leonard
la source
10

Réponse courte:

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

Exemples:

git push fc47b2

git push HEAD~2

Longue réponse:

Les validations sont liées ensemble comme une chaîne avec un mécanisme parent / enfant. Ainsi, pousser un commit pousse également tous les commits parents à ce commit qui n'était pas connu du distant. Ceci est implicitement fait lors de git pushla validation actuelle: toutes les validations précédentes sont également poussées car cette commande est équivalente à git push HEAD.

Ainsi, la question pourrait être réécrite dans Comment pousser un commit spécifique et ce commit spécifique pourrait être HEAD ~ 2, par exemple.

Si les commits que vous souhaitez pousser ne sont pas consécutifs, il vous suffit de les réorganiser avec un git rebase -iavant le push spécifique .

Tim
la source
5

1) Utilisez "git rebase" pour réorganiser vos commits, si vous le souhaitez.

git rebase -i

Cette commande affichera quelque chose comme ça dans votre éditeur (j'utilise 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) Réorganisez vos commits selon votre choix par simple copier-coller. Supposons que le nouvel ordre soit

choisissez 9781434 commitE

choisissez c3d4961 commitC

choisissez 4791291 commitA

choisissez aa1cefc commitD

choisissez a2bdfbd commitB

Apportez ces modifications dans votre éditeur et appuyez sur ctrl + O (writeOut)

Ou vous pouvez également utiliser

git rebase -i HEAD~<commitNumber>

Vous pouvez vérifier la nouvelle séquence avec

git log

3) Maintenant, utilisez

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

Si une seule branche à distance (origine) et une à local (maître), utilisez simplement

git push <commit SHA>
git push aa1cefc

Cela poussera commitB et commitD.

Yogesh Yadav
la source