¿guardar automáticamente los cambios de guardar / pop en git rebase?


Mi flujo de trabajo de git usa rebase mucho. Siempre obtengo los cambios de upstream (el repositorio principal desde el que me bifurcé) y luego los merge a mis ramas, y luego los rebase para eliminar los commits de fusión y las divisiones de árbol inútiles (para mí :D).

Una cosa en este flujo de trabajo que me molesta es:

$ 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

Así que aquí tenemos 4 comandos, 1=rebase fallido, 2=stash, 3=rebase, 4=stash pop. cualquier cosa menos 3 es un trabajo sin sentido.

Entonces, la pregunta es: ¿Cuál es la forma más recomendada de automatizarlo? un alias para ejecutar git stash/rebase / pop cada vez? ¿alguna configuración de git que obliga a rebase a esconderse o lo trata como otro commit para volver a aplicar después? algo más?

Author: Visruth, 2014-11-25

5 answers

Editar: A partir de la versión 1.8.4 de Git, pero con un importante error lateral corregido en la versión 2.0.1 de Git, git rebase ahora tiene --autostash. Puede configurar git rebase para usar --autostash también de forma predeterminada, con git config --global rebase.autoStash true. Tenga en cuenta la siguiente frase de la documentación :

Sin embargo, use con cuidado: el alijo final la aplicación después de un rebase exitoso podría resultar en no trivial conflicto.

(Todavía prefiero solo hacer commits.)

TL; DR respuesta: solo haz un commit (luego deshazlo más tarde)

Puede ayudarle a darse cuenta de que git stash es realmente solo git commit (en una forma más complicada, que confirma el índice primero, luego el árbol de trabajo: cuando aplica un alijo, puede mantener la separación de índice y árbol de trabajo, o combinarlos en solo un cambio de árbol de trabajo).

Lo que hace especial a un stash es que las confirmaciones que hace-las dos o, con -u o -a, incluso tres confirmaciones-se hacen de una forma inusual (como una fusión commit que no es realmente una fusión) y no se coloca en ninguna rama (en su lugar, la referencia especial refs/stash se utiliza para retenerlos y encontrarlos).

Dado que no están en una rama, rebase no los toca, y en su flujo de trabajo, es el git stash pop que trae los cambios del árbol de trabajo a su nuevo árbol de trabajo. Sin embargo, si haces tu propio commit (normal), en una rama, y rebases e incluyes ese commit, este commit normal será rebases junto con cualquier otro. Vamos a llegar a un último problema en un momento; por ahora, vamos a dibujar esto, como una serie de commits que hacen (o no) se vuelven a basar:

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

En este punto, esto es lo que tienes:{[101]]}

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

Aquí, A, B, y C son tus commits (asumiré que has hecho 3), todos en la rama master. El i-w hanging off commit C es tu stash, que no está en la rama, pero sigue siendo un two-commit "git stash bag" y en realidad está adjunto a tu último commit (C). El @ se compromete (podría be just one) son los nuevos commits de upstream.

(Si has hecho no commits, tu stash-bag cuelga de commit *, y tu rama actual apunta a commit *, de modo que git rebase no tiene más trabajo que hacer que mover tu puntero de rama actual hacia adelante. Todo funciona igual, en este caso, pero asumiré que hay algunas confirmaciones.)

Ahora corres git rebase upstream/master. Esto copia tus commits a nuevos commits, con nuevos IDs y nuevos IDS padre, para que se sitúen encima del last @. El stash-bag no se mueve, por lo que el resultado se ve así:

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

Ahora se usa git stash pop, que restaura el material i/w a medida que cambia el árbol de trabajo, borrando la etiqueta stash (más precisamente, explotándola para que stash@{1}, si existe, sea ahora stash, y así sucesivamente). Eso libera las últimas referencias a la cadena A - B - C original, y significa que tampoco necesitamos el bit i-w, lo que nos permite volver a dibujar esto como el mucho más simple:

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

Ahora vamos a dibujar lo que sucede si, en lugar de git stash save, solo tienes que hacer un git commit -a (o git add y git commit sin-a) para crear una confirmación real D. Comienza con:

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

Ahora tú git rebase upstream/master, que copia A a través de D para colocarlos al final de la última @, y tienes esto:

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

El único problema es que tiene esta confirmación adicional no deseada D (bueno, D' ahora), en lugar de cambios en el árbol de trabajo no comprometidos. Pero esto se deshace trivialmente con git reset retroceder un commit. Podemos utilizar un --mixed reset-el valor predeterminado-para volver a configurar el índice (área de preparación) también, para "des-añadir" todos los archivos, o si desea que se queden git add-ed, un --soft reset. (Ninguno afecta al gráfico de confirmación resultante, solo el estado del índice es diferente.)

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

Esto es lo que parece:

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

Puedes pensar que esto es ineficiente, pero cuando usas git stash en realidad estás haciendo al menos dos commits, que luego abandonas más tarde cuando git stash pop ellos. La verdadera diferencia es que al hacer confirmaciones temporales, no para publicación, obtienes esas automáticamente rebasadas.

No tengas miedo de confirmaciones temporales

Hay una regla general con git: hacer lotes de confirmaciones temporales, para guardar tu trabajo sobre la marcha. Siempre puedes cambiarlos de base más tarde. Es decir, en lugar de esto:

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

Donde A, B, and C are perfect and final commits at top commit * (from someone else or earlier published stuff), make esto:

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

Donde a1 es una puñalada inicial en A, a2 corrige un error en a1, b1 es un intento inicial de hacer que b funcione, a3 es darse cuenta de que b1 requiere A ser diferente después de todo, b2 corrige un error en b1, a4 corrige un error en el cambio de a3 a a2, y b3 es lo que b1 debería haber hecho; entonces c1 es un intento inicial de C, b4 es otra solución a b1, c2 es un refinamiento, y así sucesivamente.

Digamos que después de c3 crees que está casi listo. Ahora ejecuta git rebase -i origin/master o lo que sea, baraja las líneas pick para obtener a1 a través de a4 en orden, b1 a través de b4 en orden, y c1 a través de c3 en orden, y deja que la rebase se ejecute. Luego arreglas cualquier conflicto y te aseguras de que las cosas siguen siendo correctas, luego ejecutas otro git rebase -i para colapsar las cuatro versiones a en A, y así sucesivamente.

Cuando hayas terminado, parece como si hubieras creado un perfecto A la primera vez (o tal vez con a4 o algún otro dependiendo de qué confirmaciones mantengas y cuáles sueltes y si vuelves a establecer marcas de tiempo en las cosas). Otras personas pueden no querer o necesitar ver tu trabajo intermedio-aunque puedes conservarlo, no combinando commits, si eso es útil. Mientras tanto, nunca necesitas tener cosas no comprometidas que tengas que rebase, porque solo tienes confirmaciones de cosas parciales.

Ayuda a dar estos nombres de confirmaciones, en la línea texto de confirmación, que guiará su trabajo de rebase posterior:

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

Y así sucesivamente.

 32
Author: torek,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/ajaxhispano.com/template/agent.layouts/content.php on line 61
2017-05-28 15:31:06

Una Respuesta Simple: git rebase -i --autosquash --autostash <tree-ish>

-i = interactively rebase

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


Esto lo hará...

  • Guarda automáticamente tus cambios
  • Rebase de forma interactiva desde <tree-ish>
    • Posiciona automáticamente tus squash y arreglos
  • Auto pop stash en el directorio de trabajo después de rebase

tree-ish puede ser un cometer hash o nombre de la sucursal o etiqueta o cualquier identificador.

 15
Author: abhisekp,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/ajaxhispano.com/template/agent.layouts/content.php on line 61
2017-05-27 08:57:33

Puedes usar una herramienta externa llamada git-up, que hace exactamente lo que dices para todas las ramas. Esto también te ayudará a mantener un gráfico de historial limpio.

Lo he usado durante algunos años y funciona bastante bien, especialmente si no eres un experto en git. Si agrega la función de reajuste automático, debe saber cómo recuperarse correctamente de un reajuste fallido (continuar, abortar, ...)

Instalar

Abre un shell y ejecuta:

sudo gem install git-up

Configuración

Abra su archivo de configuración global (~/.gitconfig), y agregue lo siguiente:

[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"

Vea la documentación oficial para más opciones.

Invocación

Si todo está bien configurado, simplemente ejecute:

git up

Esto es (aproximadamente) equivalente a ejecutar lo siguiente:

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
Author: giosh94mhz,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/ajaxhispano.com/template/agent.layouts/content.php on line 61
2015-01-29 17:18:06

Todo el flujo de trabajo en un comando, incluyendo la obtención:

git pull --rebase --autostash [...]
 2
Author: Marc,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/ajaxhispano.com/template/agent.layouts/content.php on line 61
2017-01-25 15:07:49

Respuesta de tjsingleton blogg

Crea un alias de comando de:

Git stash & & git pull reb rebase & & git stash pop

Actualización

Si está usando idea, empujando con un dir de trabajo sucio, se abrirá un diálogo, elija rebase / merge, hará stash, rebase / merge y pop automáticamente.

 1
Author: Sisyphus,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/ajaxhispano.com/template/agent.layouts/content.php on line 61
2015-09-17 15:19:23