stash - ¿Guardar automáticamente cambios guardados/pop en git rebase?
git stash untracked files (5)
mi flujo de trabajo git utiliza rebase mucho. Siempre recojo los cambios en sentido ascendente (el repositorio principal desde el que se bifurcó) y luego me fusiono con mis sucursales, y luego rebase para eliminar inútiles (para mí: D) fusionar confirmaciones y divisiones de árboles.
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 solo 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 tratarlo como otro compromiso para volver a aplicar después? ¿algo más?
Edición: a partir de la versión 1.8.4 de Git, pero con un error secundario importante corregido en la versión 2.0.1 de Git, ahora git rebase
tiene --autostash
. También puede configurar git rebase
para usar --autostash
de forma predeterminada, con git config --global rebase.autoStash true
. Tenga en cuenta la siguiente oración de la documentación :
Sin embargo, úselo con cuidado: la aplicación de almacenamiento final después de una reconfiguración exitosa podría resultar en conflictos no triviales.
(Sigo prefiriendo solo hacer confirmaciones).
TL; respuesta de DR: simplemente haga un compromiso (luego deséchelo más tarde)
Puede ayudarlo a darse cuenta de que el git stash
es realmente git commit
(en una forma más complicada, que primero ejecuta el índice, luego el árbol de trabajo). Cuando aplica un alijo, puede mantener la separación entre el índice y el árbol de trabajo. , o combinarlos en solo un cambio de arbol de trabajo).
Lo que hace que un escondite sea especial es que las confirmaciones que realiza (las dos o, con -u
o -a
, incluso tres confirmaciones) se realizan de forma inusual (como una confirmación de fusión que no es realmente una fusión) y no se colocan en ninguna rama (en cambio, la refs/stash
especial refs/stash
se usa para retener y encontrarlos).
Como no están en una rama, la 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 realiza su propio compromiso (normal), en una rama, y rebase e incluye ese compromiso, este compromiso normal se volverá a basar junto con cualquier otro. Llegaremos a un último problema en un momento; por ahora, dibujemos esto, como una serie de confirmaciones que 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:
... - o - * - A - B - C <-- HEAD=master
/ |/
/ i-w <-- stash
/
@-@-@ <-- upstream/master
Aquí, A
, B
y C
son tus confirmaciones (asumiré que has hecho 3), todas en el master
sucursal. El iw
cuelga de commit C
es su alijo, que no se encuentra en la sucursal, pero sigue siendo un "gash stash bag" de doble commit y está adjunto a su último commit ( C
). Los @comites (puede haber solo uno) son los nuevos confirmaciones anteriores.
(Si no ha realizado ninguna confirmación, su bolsa de alijo se bloquea de la confirmación *
, y su rama actual apunta a la confirmación *
, por lo que git rebase
no tiene trabajo que hacer más que mover el puntero de su rama actual hacia adelante. Todo funciona de la misma manera, en este caso, pero asumiré que hay algunos compromisos.)
Ahora ejecuta git rebase upstream/master
. Esto copia sus confirmaciones a nuevas confirmaciones, con nuevas identificaciones y nuevas identificaciones principales, para que se coloquen encima de la última @
. 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 usas git stash pop
, que restaura las cosas de i / w a medida que cambian los árboles de trabajo, borrando la etiqueta de stash
(más precisamente, haciendo estallar para que stash@{1}
, si existe, ahora sea stash
, etc.) . Eso libera las últimas referencias a la cadena original A - B - C
, y significa que tampoco necesitamos el bit iw
, lo que nos permite volver a dibujar esto como mucho más simple:
... - @ <-- upstream/master
/
A''-B''-C'' <-- HEAD=master plus work tree changes
Ahora dibujemos lo que sucede si, en lugar de git stash save
, solo haces un git commit -a
(o git add
y git commit
sin -a) para crear un commit real D
Empiezas con:
... - o-*-A-B-C-D <-- HEAD=master
/
@-@-@ <-- upstream/master
Ahora git rebase upstream/master
, que copia de A
a D
para git rebase upstream/master
al final de la última @
, y tiene esto:
... - o-*-@-@-@ <-- upstream/master
/
A''-B''-C''-D'' <-- HEAD=master
El único problema es que tiene este compromiso adicional D
no deseado (bueno, D''
ahora), en lugar de cambios no comprometidos en el árbol de trabajo. Pero esto es trivialmente deshecho con git reset
para retroceder un commit. Podemos usar un reinicio --mixed
(el valor predeterminado) para restablecer también el índice (área de --mixed
), a fin de "quitar-agregar" todos los archivos, o si desea que permanezcan git add
, 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
estás haciendo al menos dos confirmaciones, que luego abandonas cuando haces git stash pop
. La verdadera diferencia es que al hacer confirmaciones temporales, no para publicación, se vuelven a clasificar automáticamente.
No tengas miedo de los compromisos temporales.
Hay una regla general con git: hacer muchos compromisos temporales, para guardar tu trabajo a medida que avanzas. Siempre puedes rebaselas más tarde. Es decir, en lugar de esto:
... - * - A - B - C <-- mybranch
donde A
, B
y C
son confirmaciones perfectas y finales sobre comillas *
(de otra persona o material publicado anteriormente), haga 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
se da cuenta de que b1
requiere que A
sea diferente después de todo, b2
corrige un error en b1
, a4
corrige a error en el cambio de a3
a a2
, y b3
es lo que b1
debería haber hecho; entonces c1
es un intento inicial en C
, b4
es otra solución para b1
, c2
es un refinamiento, y así sucesivamente.
Digamos que después de c3
piensas que está casi listo. Ahora ejecuta git rebase -i origin/master
o lo que sea, mezcle las líneas de pick
para poner orden de a1
a4
en orden, de b1
a b4
en orden, y de c1
a c3
en orden, y deje correr la rebase. Luego arreglas cualquier conflicto y te aseguras de que las cosas sigan siendo correctas, luego ejecutas otro git rebase -i
para colapsar las cuatro versiones en A
, y así sucesivamente.
Cuando hayas terminado, parece que creaste una A
perfecta la primera vez (o tal vez con a4
o alguna otra, dependiendo de los compromisos que mantengas y de cuáles abandonas y si vuelves a configurar las marcas de tiempo en las cosas) . Es posible que otras personas no quieran o necesiten ver su trabajo intermedio, aunque puede retenerlo, no combinar las confirmaciones, si eso es útil. Mientras tanto, nunca necesitas tener cosas no comprometidas que debas reabastecer, porque solo tienes confirmaciones de cosas parciales.
Ayuda a dar estos nombres de confirmación, en el texto de confirmación de una línea, que guiará su trabajo posterior de rebase:
git commit -m ''temp commit: work to enable frabulator, incomplete''
y así.
Puede usar una herramienta externa llamada git-up , que hace exactamente lo que dice para todas las sucursales. 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 rebasado automático, debe saber cómo recuperarse correctamente de una rebase fallida (continuar, abortar, ...)
Instalar
Abra un shell y ejecute:
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"
Consulte la documentación oficial para más opciones.
Invocación
Si todo está bien configurado, simplemente ejecuta:
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
Respuesta de tjsingleton blogg
hacer un alias de comando de:
git stash && git pull --rebase && git stash pop
actualizar
Si está utilizando la idea, presionando con un directorio de trabajo sucio, se abrirá un cuadro de diálogo, elija volver a generar / fusionar, hará un alijo, rebase / fusionará y se abrirá automáticamente.
Todo el flujo de trabajo en un comando, incluida la recuperación:
git pull --rebase --autostash [...]
Una respuesta simple: git rebase -i --autosquash --autostash <tree-ish>
-i = interactively rebase
https://devdocs.io/git/git-rebase
Esta voluntad...
- Auto esconder tus cambios
- Rebase interactiva de
<tree-ish>
- Auto posiciona tus squashes y arreglos
- Auto pop stash en el directorio de trabajo después de rebase
tree-ish
puede ser un hash de confirmación o un nombre de rama o una etiqueta o cualquier identificador .