remove - ¿Cómo puedo adelantar un solo git commit programáticamente?
git--tags (3)
Enfoques alternativos
Mencionas que estás trabajando en algún tipo de espejo para Git y Darcs. En lugar de arrastrar un árbol de trabajo a través del historial, en lugar de eso, puede mirar los comandos git fast-import y git fast-export para ver si ofrecen una mejor manera de administrar los datos que necesita extraer / proporcionar.
Cómo saber si una sucursal puede avanzar rápidamente hacia su sucursal ascendente
Hay dos partes en esto. Primero, debe saber o determinar qué rama es la "corriente arriba" de la rama actual. Luego, una vez que sepa cómo referirse al flujo ascendente, verificará la capacidad de avanzar rápidamente.
Encontrar el upstream para una rama
Git 1.7.0 tiene una forma conveniente de consultar qué rama se rastrea una rama (su rama "ascendente"). La sintaxis de la especificación del objeto @{upstream}
se puede usar como un especificador de rama. Como un nombre simple, se refiere a la rama en sentido ascendente para la rama que actualmente está prestada. Como un sufijo, se puede usar para encontrar las sucursales en sentido ascendente para las sucursales que no están actualmente vigiladas.
Para Gits anteriores a 1.7.0, tendrá que analizar las opciones de configuración de la rama usted mismo ( branch.name.remote
y branch.name.merge
). Alternativamente, si tiene una convención de nomenclatura estándar, puede usarla para determinar un nombre para la rama ascendente.
En esta respuesta escribiré en upstream
para referirme a la confirmación en la punta de la rama que está en sentido ascendente de la rama actual.
Comprobando la capacidad de avance rápido
Una rama en la confirmación A se puede adelantar para cometer B si y solo si A es un antepasado de B.
gyim muestra una forma de verificar esta condición (enumere todas las confirmaciones a las que se puede acceder desde B y busque A en la lista). Quizás una forma más sencilla de verificar esta condición es verificar que A es la base de fusión de A y B.
can_ff() {
a="$(git rev-parse "$1")" &&
test "$(git merge-base "$a" "$2")" = "$a"
}
if can_ff HEAD local-master/master; then
echo can ff to local-master/master
else
echo CAN NOT ff to local-master/master
fi
Encontrar el número de "cometer detrás"
git rev-list ^HEAD upstream | wc -l
Esto no requiere que HEAD pueda avanzar hacia el flujo ascendente (solo cuenta hasta qué punto está HEAD detrás del flujo ascendente, no cuánta hacia arriba está detrás del HEAD).
Avanzar hacia adelante por un commit
En general, un historial de avance rápido no puede ser lineal. En el DAG de la historia a continuación, el maestro puede avanzar rápidamente hacia arriba , pero tanto A como B son "un compromiso hacia adelante" desde el maestro en el camino hacia la corriente arriba .
---o---o master
|/
| A--o--o--o--o--o--o upstream
/ /
B---o---o---o---o
Puede seguir un lado como si fuera un historial lineal, pero solo hasta el antepasado inmediato de la confirmación de fusión.
Los comandos de revisión de revisión tienen una opción - primer --first-parent
que hace que sea fácil seguir solo las confirmaciones que llevan al primer padre de las confirmaciones de combinación. Combine esto con git reset y podrá efectivamente arrastrar una rama "hacia adelante, una confirmación a la vez".
git reset --hard "$(git rev-list --first-parent --topo-order --reverse ^HEAD upstream | head -1)"
En un comentario sobre otra respuesta, expresas por temor a que se reinicie Git . Si le preocupa dañar una rama, puede usar una rama temporal o usar una CABEZA separada como una rama sin nombre. Mientras tu árbol de trabajo esté limpio y no te importe mover una rama (o la CABEZA desprendida), git reset --hard
no git reset --hard
nada. Si aún está preocupado, debería considerar seriamente el uso de git fast-export donde no tiene que tocar el árbol de trabajo.
Seguir a un padre diferente sería más difícil. Es probable que tengas que escribir tu propio historial para que puedas darle consejos sobre "en qué dirección" querías ir en cada fusión.
Cuando haya avanzado a un punto justo antes de la fusión, el DAG tendrá este aspecto (la topología es la misma que antes, solo se ha movido la etiqueta maestra ):
---o---o--A--o--o--o--o--o master
| /
| o upstream
/ /
B---o---o---o---o
En este punto, si "avanza un compromiso", se moverá a la combinación. Esto también "traerá" (hacer accesible desde el maestro ) todas las confirmaciones desde B hasta la confirmación de fusión. Si asume que “avanzar un compromiso” solo agregará un compromiso al historial DAG, entonces este paso violará ese supuesto.
Probablemente querrá considerar cuidadosamente lo que realmente quiere hacer en este caso. ¿Está bien arrastrar simplemente las confirmaciones adicionales como esta, o debería haber algún mecanismo para "regresar" al padre de B y avanzar en esa rama antes de procesar la confirmación de fusión?
Periódicamente recibo un mensaje de git que se ve así:
Your branch is behind the tracked remote branch ''local-master/master''
by 3 commits, and can be fast-forwarded.
Me gustaría poder escribir comandos en un script de shell que pueda hacer lo siguiente:
¿Cómo puedo saber si mi sucursal actual puede ser reenviada rápidamente desde la sucursal remota que está rastreando?
¿Cómo puedo saber cuántos envíos "detrás" está mi sucursal?
¿Cómo puedo avanzar rápidamente con un solo compromiso, de modo que, por ejemplo, mi sucursal local pase de "atrás por 3 confirmaciones" a "detrás de 2 confirmaciones"?
(Para aquellos que estén interesados, estoy tratando de armar un espejo git / darcs de calidad).
La bifurcación remota se puede reenviar rápidamente a la bifurcación local si la confirmación actual es el antecesor del encabezado de bifurcación remota. En otras palabras, si el "historial de una rama" de la rama remota contiene el compromiso actual (porque si lo hace, está seguro de que los nuevos compromisos se confirmaron "en" el compromiso actual)
Por lo tanto, una forma segura de determinar si la sucursal remota se puede reenviar rápidamente:
# Convert reference names to commit IDs
current_commit=$(git rev-parse HEAD)
remote_commit=$(git rev-parse remote_name/remote_branch_name)
# Call git log so that it prints only commit IDs
log=$(git log --topo-order --format=''%H'' $remote_commit | grep $current_commit)
# Check the existence of the current commit in the log
if [ ! -z "$log" ]
then echo ''Remote branch can be fast-forwarded!''
fi
Tenga en cuenta que el registro de git se llamó sin el parámetro --all (que listaría todas las ramas), por lo que no es posible que la confirmación actual esté en una "rama lateral" y aún esté impresa en la salida.
La cantidad de confirmaciones anteriores a la confirmación actual es igual a la cantidad de filas en $ log antes de $ current_commit.
Si desea adelantar solo una confirmación, tome la fila anterior a la confirmación actual (con grep -B 1, por ejemplo) y reinicie la rama local para esta confirmación.
ACTUALIZACIÓN : puede usar git log commit1..commit2
para determinar el número de confirmaciones de envío rápido:
if [ ! -z "$log" ]
then
# print the number of commits ahead of the current commit
ff_commits=$(git log --topo-order --format=''%H'' /
$current_commit..$remote_commit | wc -l)
echo "Number of fast-forwarding commits: $ff_commits"
# fast-forward only one commit
if [ $ff_commits -gt 1 ]
then
next_commit=$(git log --topo-order --format=''%H'' /
$current_commit..$remote_commit | tail -1)
git reset --hard $next_commit
fi
fi
Por supuesto, puede hacer esto con una llamada de registro de git si guarda el resultado de la primera llamada en un archivo.
Probablemente este no sea el más elegante, pero funciona:
$ git fetch $ git status | sed -n 2p # Your branch is behind ''origin/master'' by 23 commits, and can be fast-forwarded. $ git reset origin/master~22 > /dev/null $ git status | sed -n 2p # Your branch is behind ''origin/master'' by 22 commits, and can be fast-forwarded.