Suponiendo que el repositorio remoto tiene una copia de la rama de desarrollo (su descripción inicial lo describe en un repositorio local, pero parece que también existe en el remoto), debería poder lograr lo que creo que quiere, pero el enfoque es un poco diferente de lo que has imaginado.
La historia de Git se basa en un DAG de commits. Las ramas (y las "referencias" en general) son solo etiquetas transitorias que apuntan a compromisos específicos en el DAG de compromiso en continuo crecimiento. Como tal, la relación entre ramas puede variar con el tiempo, pero la relación entre confirmaciones no lo hace.
---o---1 foo
\
2---3---o bar
\
4
\
5---6 baz
Parece que baz
se basa en (una versión anterior de) bar
? ¿Pero qué pasa si borramos bar
?
---o---1 foo
\
2---3
\
4
\
5---6 baz
Ahora parece que baz
se basa en foo
. Pero la ascendencia de baz
no cambió, simplemente eliminamos una etiqueta (y la confirmación pendiente resultante). ¿Y si agregamos una nueva etiqueta en 4
?
---o---1 foo
\
2---3
\
4 quux
\
5---6 baz
Ahora parece que baz
se basa en quux
. Aún así, la ascendencia no cambió, solo cambiaron las etiquetas.
Sin embargo, si estuviéramos preguntando "¿es commit 6
un descendiente de commit 3
?" (suponiendo 3
y 6
son nombres de confirmación SHA-1 completos), la respuesta sería "sí", independientemente de si las etiquetas bar
y quux
están presentes o no.
Por lo tanto, puede hacer preguntas como "¿el commit forzado es descendiente de la punta actual de la rama de desarrollo ?", Pero no puede preguntar de manera confiable "¿cuál es la rama padre del commit forzado?".
Una pregunta en su mayoría confiable que parece acercarse a lo que quieres es:
Para todos los ancestros del commit empujado (excluyendo la punta actual de desarrollo y sus antepasados), que tienen la punta actual de desarrollo como padre:
- ¿existe al menos una de estas confirmaciones?
- ¿son todos estos commits commits monoparentales?
Que podría implementarse como:
pushedrev=...
basename=develop
if ! baserev="$(git rev-parse --verify refs/heads/"$basename" 2>/dev/null)"; then
echo "'$basename' is missing, call for help!"
exit 1
fi
parents_of_children_of_base="$(
git rev-list --pretty=tformat:%P "$pushedrev" --not "$baserev" |
grep -F "$baserev"
)"
case ",$parents_of_children_of_base" in
,) echo "must descend from tip of '$basename'"
exit 1 ;;
,*\ *) echo "must not merge tip of '$basename' (rebase instead)"
exit 1 ;;
,*) exit 0 ;;
esac
Esto cubrirá algo de lo que desea restringir, pero tal vez no todo.
Como referencia, aquí hay un historial de ejemplo extendido:
A master
\
\ o-----J
\ / \
\ | o---K---L
\ |/
C--------------D develop
\ |\
F---G---H | F'--G'--H'
| |\
| | o---o---o---N
\ \ \ \
\ \ o---o---P
\ \
R---S
El código anterior podría ser utilizado para rechazar H
y S
aceptando al mismo tiempo H'
, J
, K
, o N
, pero sería también aceptar L
y P
(que implican fusiones, pero no fusionar la punta del desarrollo ).
También a rechazar L
y P
, puede cambiar la pregunta y pedir
Para todos los ancestros del commit empujado (excluyendo la punta actual de desarrollo y sus ancestros):
- ¿Hay algún compromiso con dos padres?
- en caso negativo, ¿tiene al menos una de estas confirmaciones la punta actual de desarrollar su (único) padre?
pushedrev=...
basename=develop
if ! baserev="$(git rev-parse --verify refs/heads/"$basename" 2>/dev/null)"; then
echo "'$basename' is missing, call for help!"
exit 1
fi
parents_of_commits_beyond_base="$(
git rev-list --pretty=tformat:%P "$pushedrev" --not "$baserev" |
grep -v '^commit '
)"
case "$parents_of_commits_beyond_base" in
*\ *) echo "must not push merge commits (rebase instead)"
exit 1 ;;
*"$baserev"*) exit 0 ;;
*) echo "must descend from tip of '$basename'"
exit 1 ;;
esac