¿Cómo empujas solo algunos de tus git commits locales?

160

Supongamos que tengo 5 commits locales. Quiero enviar solo 2 de ellos a un repositorio centralizado (usando un flujo de trabajo de estilo SVN). ¿Cómo hago esto?

Esto no funcionó:

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

Eso termina empujando los 5 commits locales.

Supongo que podría hacer git reset para deshacer mis commits, seguido de git stash y luego git push, pero ya tengo mensajes de commit escritos y archivos organizados y no quiero rehacerlos.

Mi sensación es que alguna bandera pasada para presionar o restablecer funcionaría.

Si ayuda, aquí está mi configuración de 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
fuente

Respuestas:

192

Suponiendo que sus confirmaciones están en la rama maestra y desea enviarlas a la rama maestra remota:

$ git push origin master~3:master

Si estabas usando git-svn:

$ git svn dcommit master~3

En el caso de git-svn, también puede usar HEAD ~ 3, ya que espera una confirmación. En el caso de git directo, debe usar el nombre de la rama porque HEAD no se evalúa correctamente en la especificación de referencia.

También podría adoptar un enfoque más largo de:

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

Si tiene el hábito de este tipo de flujo de trabajo, debería considerar hacer su trabajo en una rama separada. Entonces podrías hacer algo como:

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

Tenga en cuenta que la parte "origen maestro: maestro" es probablemente opcional para su configuración.

Ryan Graham
fuente
14
Nota: no tienes que usar master~3. Cualquier referencia al compromiso "hasta" deseado es igualmente válido, como HEAD~3o HEAD~~~, o el SHA específico, o una etiqueta que etiqueta ese compromiso.
Kaz
2
Buen material. Sin embargo, una advertencia: estos ejemplos empujan al origen maestro. Si está copiando y pegando esta solución, puede terminar actualizando accidentalmente la rama maestra. (Por supuesto, siempre debe tener cuidado y verificar dos git push
veces
Parece que esto empuja la confirmación, pero no agrega la rama de forma remota.
Nateowami
@Nateowami para eso necesitarás especificar algo que no sea masterpara el lado remoto de la especificación de referencia, comogit push origin tocommit:newbramch
Ryan Graham
Veo. El nombre de la sucursal ya existía localmente; Supongo que no le gustó eso. Sin embargo, el control remoto aún no tenía el nombre de la sucursal.
Nateowami
16

Lo que hago es trabajar en una sucursal local llamada "trabajo". Esta rama contiene todas las confirmaciones temporales (como soluciones alternativas u opciones de compilación privadas o lo que sea) que no tengo la intención de enviar al repositorio ascendente. Yo trabajo fuera de esa rama, a continuación, cuando quiero cometer me cambio a la rama principal, cereza recoger las confirmaciones apropiadas que yo no quiero cometer, a continuación, empuje principal.

Después de llevar los cambios desde la parte superior a mi rama maestra, yo git checkout worky git rebase master. Eso reescribe todos mis cambios locales para estar al final de la historia.

De hecho, estoy usando git svneste flujo de trabajo, por lo que implica mi operación "push" git svn dcommit. También utilizo tigque es un buen visor de repositorio de GUI en modo de texto, para elegir los commits apropiados para masterizar.

Greg Hewgill
fuente
con git svn dcommit, puede especificar un commit para dcommit hasta, por lo que el efecto deseado es bastante trivial con git-svn.
Ryan Graham el
Hay desventajas en este enfoque (resumido aquí stackoverflow.com/a/881014/1116674 ). Una buena alternativa es crear ramas para cada característica en la que esté trabajando y una workrama. Luego, fusiona ramas específicas masterpara no perder el historial en ellas. Cuando trabajas work, fusionas todas tus ramas en él. Es más costoso, pero podría valer la pena en algunos casos.
Hudon
16

Por defecto, git-push empuja todas las ramas. Cuando haces esto:

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

Te mueves a un HEAD separado (no estás en ninguna rama) y luego empujas todas las ramas, incluido el maestro local (que todavía está donde estaba) al maestro remoto.

La solución manual es:

 git push origin HEAD:master

Si encuentra que el comportamiento predeterminado de empujar todas las ramas es confuso (¡y peligroso!), Agregue esto a su ~ / .gitconfig:

 [remote.origin]
    push = HEAD

Entonces solo se empuja la rama en la que estás. En su ejemplo (una cabeza separada), habría recibido este mensaje de error, en lugar de presionar accidentalmente las confirmaciones incorrectas:

 error: unable to push to unqualified destination: HEAD
Thomas Leonard
fuente
10

Respuesta corta:

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

Ejemplos:

git push fc47b2

git push HEAD~2

Respuesta larga:

Los commits están unidos como una cadena con un mecanismo padre / hijo. Por lo tanto, empujar una confirmación en realidad también empuja todas las confirmaciones principales a esta confirmación que no era conocida por el control remoto. Esto se hace implícitamente cuando se realiza git pushla confirmación actual: todas las confirmaciones anteriores también se envían porque este comando es equivalente a git push HEAD.

Entonces, la pregunta podría reescribirse en Cómo impulsar una confirmación específica y esta confirmación específica podría ser HEAD ~ 2, por ejemplo.

Si las confirmaciones que desea impulsar no son consecutivas, simplemente vuelva a ordenarlas con un git rebase -iantes de la inserción específica .

Tim
fuente
5

1) Use "git rebase" para reordenar sus confirmaciones, si lo desea.

git rebase -i

Este comando mostrará algo como esto en su editor (estoy usando 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) Reordene sus confirmaciones de acuerdo con su elección mediante un simple corte y pegado. Supongamos que el nuevo orden es

elegir 9781434 commitE

seleccione c3d4961 commitC

recoger 4791291 commitA

elija aa1cefc commitD

seleccione a2bdfbd commitB

Realice estos cambios en su editor y presione Ctrl + O (writeOut)

O también puedes usar

git rebase -i HEAD~<commitNumber>

Puede verificar la nueva secuencia con

git log

3) Ahora usa

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

Si solo una rama en remoto (origen) y una en local (maestro), solo use

git push <commit SHA>
git push aa1cefc

Esto empujará commitB y commitD.

Yogesh Yadav
fuente