¿Hay alguna manera de obtener el directorio raíz de git en un comando?

670

Mercurial tiene una forma de imprimir el directorio raíz (que contiene .hg) a través de

hg root

¿Hay algo equivalente en git para obtener el directorio que contiene el directorio .git?

wojo
fuente
Buen guion Emil. Puse el script a disposición en línea y permití la posibilidad de agregar un archivo / directorio como argumento. github.com/Dieterbe/git-scripts/commit/…
Dieter_be
También para cualquier persona curiosa o que buscaba bzr rootse usaba mucho en Bazar
Kristopher Ives
Tome nota de 'gcd': Git-Aware 'cd' Relativo a la raíz del repositorio con autocompletado en jeetworks.org/node/52 .
Micha Wiedenmann
1
@MichaWiedenmann: el enlace está muerto.
d33tah

Respuestas:

1114

Si:

git rev-parse --show-toplevel

Si desea replicar el comando Git más directamente, puede crear un alias :

git config --global alias.root 'rev-parse --show-toplevel'

y ahora git rootfuncionará igual que hg root.


Nota : en un submódulo, esto mostrará el directorio raíz del submódulo y no el repositorio principal. Si está utilizando Git> = 2.13 o superior, hay una forma en que los submódulos pueden mostrar el directorio raíz del superproyecto. Si su git es más viejo que eso, vea esta otra respuesta.

baudtack
fuente
148
Siempre defino git config --global alias.exec '!exec 'para poder hacer cosas como git exec make. Esto funciona porque los alias de shell siempre se ejecutan en el directorio de nivel superior.
Daniel Brockman el
8
Esto es lo que hg roothace. Imprime el directorio de nivel superior de su repositorio desprotegido. No lo cambia a él (y, de hecho, no pudo hacerlo debido a cómo interactúa todo el concepto de directorio actual y su shell).
Omnifarioso
14
Una pequeña advertencia con esta solución: seguirá cualquier enlace simbólico. Por lo tanto, si está dentro ~/my.proj/foo/bary ~/my.projtiene un enlace simbólico ~/src/my.proj, el comando anterior lo moverá a ~/src/my.proj. Podría ser un problema, si lo que quiera hacer después de eso no es independiente del árbol.
Franci Penov
3
¿Cómo puedo usar esto desde un git hook? Específicamente, estoy haciendo un enlace posterior a la fusión y necesito obtener el directorio raíz real del repositorio git local.
Derek
12
Esta solución (y muchas otras en esta página que he probado) no funcionan dentro de un git hook. Para ser más precisos, no funciona cuando estás dentro del .gitdirectorio del proyecto .
Dennis
97

La manpágina de git-config(bajo Alias ) dice:

Si la expansión de alias tiene como prefijo un signo de exclamación, se tratará como un comando de shell. [...] Tenga en cuenta que los comandos de shell se ejecutarán desde el directorio de nivel superior de un repositorio, que puede no ser necesariamente el directorio actual.

Entonces, en UNIX puedes hacer:

git config --global --add alias.root '!pwd'
Scott Lindsay
fuente
2
Entonces, si tiene un comando "git root", ¿cómo puede ponerlo en un alias? Si lo pongo en mi .zshrc, y defino `alias cg =" cd $ (git root) ", la parte $ () se evalúa en el tiempo de origen, y siempre apunta a ~ / dotfiles, ya que ahí es donde está mi zshrc .
zelk
2
@cormacrelf No lo pones en un alias de shell. Puede ponerlo en una función de shell o script.
Conrad Meyer
44
@cormacrelf Póngalo entre comillas simples en lugar de comillas dobles, luego no se expandirá en el momento de la definición, sino en el tiempo de ejecución.
clacke
3
@Mechanicalsnail ¿En serio? Entonces, ¿qué esperarías que hiciera fuera del repositorio?
Alois Mahdal
1
@AloisMahdal en realidad para mí no falla fuera de un repositorio, simplemente informa el cwd.
justinpitts
91

¿Se --show-toplevelha agregado recientemente git rev-parseo por qué nadie lo menciona?

Desde la git rev-parsepágina del manual:

   --show-toplevel
       Show the absolute path of the top-level directory.
marc.guenther
fuente
1
Gracias por señalar esto; sin su ayuda, sería difícil para mí adivinar git-rev-parse, debido a que su nombre sugiere que se trata de procesar especificaciones de revisión. Por cierto, me gustaría ver un git --work-treetrabajo similar a git --exec-path[=<path>]: "Si no se proporciona ninguna ruta, git imprimirá la configuración actual"; al menos, en mi opinión, sería un lugar lógico para buscar esa característica.
imz - Ivan Zakharyaschev
44
En particular, puede alias root = rev-parse --show-toplevelen su gitconfig.
Caracol mecánico
2
en otras palabras, puede hacer git config --global alias.root "rev-parse --show-toplevel"y luego git rootpodrá hacer el trabajo
polaridad
@RyanTheLeach git rev-parse --show-toplevelfunciona cuando lo probé en un submódulo. Imprime el directorio raíz del submódulo git. ¿Qué imprime para ti?
wisbucky
2
Estaba confundido por qué esta respuesta duplicaba la respuesta principal. Resulta que la respuesta principal se editó de --show-cdupa --show-top-levelen febrero de 2011 (después de que se envió esta respuesta).
wisbucky
54

¿Qué tal " git rev-parse --git-dir"?

F:\prog\git\test\copyMerge\dirWithConflicts>git rev-parse --git-dir
F:/prog/git/test/copyMerge/.git

los --git-dir opción parece funcionar.

Desde la página del manual de git rev-parse :

--git-dir

    Show $GIT_DIR if defined else show the path to the .git directory.

Puedes verlo en acción en este git setup-sh script .

Si está en una carpeta de submódulos, con Git> = 2.13 , use :

git rev-parse --show-superproject-working-tree

Si está utilizando git rev-parse --show-toplevel, asegúrese de que sea con Git 2.25+ (Q1 2020) .

VonC
fuente
3
Oh, espera, esto estuvo cerca, pero obtiene el directorio .git real, no la base del repositorio git. Además, el directorio .git podría estar en otro lugar, así que esto no es exactamente lo que estaba buscando.
wojo 05 de
2
Bien, ahora veo lo que realmente estabas buscando. --show-cdup es más apropiado entonces. Dejo mi respuesta para ilustrar la diferencia entre las dos opciones.
VonC 05 de
2
También parece que este comando proporciona una ruta relativa para .gitsi ya está en el directorio raíz. (Al menos, lo hace en msysgit.)
Blair Holloway el
2
+1, esta es la única respuesta que responde a la pregunta original "¿obtener el directorio que contiene el directorio .git?", Divertido al ver que el OP mismo menciona "el directorio .git podría estar en otra parte".
FabienAndre
1
Esta es la única solución que funciona en Windows Y da un resultado analizable al usar submódulos git (por ejemplo, c: \ repositorios \ myrepo \ .git \ modules \ mysubmodule). Eso la convierte en la solución más robusta, especialmente en un script reutilizable.
chriskelly
36

Para escribir una respuesta simple aquí, para que podamos usar

git root

para hacer el trabajo, simplemente configure su git usando

git config --global alias.root "rev-parse --show-toplevel"

y luego puede agregar lo siguiente a su ~/.bashrc:

alias cdroot='cd $(git root)'

para que puedas usar cdrootpara ir a la parte superior de tu repositorio.

nonopolaridad
fuente
¡Muy agradable! En definitiva conveniente!
scravy
Una gran respuesta, gracias!
AVarf
26

Si ya estás en el nivel superior o no en un repositorio git cd $(git rev-parse --show-cdup)te llevará a casa (solo cd). cd ./$(git rev-parse --show-cdup)es una forma de arreglar eso.

Karl
fuente
77
Otra opción es citar que: cd "$(git rev-parse --show-cdup)". Esto funciona porque cd ""no te lleva a ningún lado, en lugar de regresar $HOME. Y es una buena práctica citar las invocaciones de $ () de todos modos, en caso de que generen algo con espacios (sin embargo, este comando no lo hará en este caso).
ctrueden
Esta respuesta funciona bien incluso si cambió el directorio a su repositorio git a través de un enlace simbólico . Algunos de los otros ejemplos no funcionan como se esperaba cuando su repositorio de git está bajo un enlace simbólico ya que no resuelven el directorio raíz de git en relación con bash $PWD. Este ejemplo resolverá la raíz de git en $PWDlugar de realpath $PWD.
Damien Ó Ceallaigh
16

Para calcular la ruta absoluta del directorio raíz git actual, digamos para usar en un script de shell, use esta combinación de readlink y git rev-parse:

gitroot=$(readlink -f ./$(git rev-parse --show-cdup))

git-rev-parse --show-cduple da el número correcto de ".." para llegar a la raíz desde su cwd, o la cadena vacía si está en la raíz. Luego anteponga "./" para tratar el caso de cadena vacía y use readlink -f para traducir a una ruta completa.

También puede crear un git-rootcomando en su RUTA como un script de shell para aplicar esta técnica:

cat > ~/bin/git-root << EOF
#!/bin/sh -e
cdup=$(git rev-parse --show-cdup)
exec readlink -f ./$cdup
EOF
chmod 755 ~/bin/git-root

(Lo anterior se puede pegar en un terminal para crear git-root y establecer bits de ejecución; el script real está en las líneas 2, 3 y 4.)

Y luego podrás correr git rootpara obtener la raíz de tu árbol actual. Tenga en cuenta que en la secuencia de comandos del shell, use "-e" para hacer que el shell salga si el rev-parse falla, de modo que pueda obtener correctamente el estado de salida y el mensaje de error si no está en un directorio git.

Emil Sit
fuente
2
Sus ejemplos se romperán si la ruta del directorio "raíz" de git contiene espacios. Siempre use en "$(git rev-parse ...)"lugar de hacks como ./$(git rev-parse ...).
Mikko Rantalainen
readlink -fno funciona igual en BSD. Vea este SO para soluciones alternativas. La respuesta Python probablemente funcionará sin instalar nada: python -c 'import os, sys; print(os.path.realpath(sys.argv[1]))' "$(git rev-parse --show-cdup)".
Bluu
16

Como otros han señalado, el núcleo de la solución es usar git rev-parse --show-cdup. Sin embargo, hay algunos casos extremos para abordar:

  1. Cuando cwd ya es la raíz del árbol de trabajo, el comando produce una cadena vacía.
    En realidad, produce una línea vacía, pero la tira de sustitución de comando se separa del salto de línea final. El resultado final es una cadena vacía.

    La mayoría de las respuestas sugieren anteponer la salida ./para que una salida vacía se convierta "./"antes de alimentarla cd.

  2. Cuando GIT_WORK_TREE se establece en una ubicación que no es la principal del cwd, la salida puede ser una ruta absoluta.

    Pretender ./está mal en esta situación. Si a ./se antepone a una ruta absoluta, se convierte en una ruta relativa (y solo se refieren a la misma ubicación si cwd es el directorio raíz del sistema).

  3. La salida puede contener espacios en blanco.

    Esto realmente solo se aplica en el segundo caso, pero tiene una solución fácil: use comillas dobles alrededor de la sustitución de comandos (y cualquier uso posterior del valor).

Como han señalado otras respuestas, podemos hacerlo cd "./$(git rev-parse --show-cdup)", pero esto se rompe en el segundo caso de borde (y el tercer caso de borde si dejamos las comillas dobles).

Muchos shells se tratan cd ""como no operativos, por lo que para esos shells podríamos hacer cd "$(git rev-parse --show-cdup)"(las comillas dobles protegen la cadena vacía como argumento en el primer caso de borde y preservan los espacios en blanco en el tercer caso de borde). POSIX dice el resultado decd "" no está especificado, por lo que puede ser mejor evitar hacer esta suposición.

Una solución que funciona en todos los casos anteriores requiere una prueba de algún tipo. Hecho explícitamente, podría verse así:

cdup="$(git rev-parse --show-cdup)" && test -n "$cdup" && cd "$cdup"

No cdse hace para el primer caso de borde.

Si es aceptable ejecutar cd .para el primer caso límite, entonces el condicional se puede hacer en la expansión del parámetro:

cdup="$(git rev-parse --show-cdup)" && cd "${cdup:-.}"
Chris Johnsen
fuente
¿Por qué no usas "git config --global --add alias.root '! Pwd'" y un shell alias gitroot = 'cd git root' que usa la respuesta anterior?
Jason Axelson
1
Es posible que no sea posible utilizar un alias, por ejemplo, si desea crear un script y no puede confiar en un alias personalizado.
azulado
1
Los submódulos son otro caso de esquina.
Ryan The Leach
14

En caso de que si está alimentando este camino al Git en sí, use :/

# this adds the whole working tree from any directory in the repo
git add :/

# and is equal to
git add $(git rev-parse --show-toplevel)
Nick Volynkin
fuente
12

Soluciones cortas que funcionan con submódulos, en ganchos y dentro del .gitdirectorio

Aquí está la respuesta corta que la mayoría querrá:

r=$(git rev-parse --git-dir) && r=$(cd "$r" && pwd)/ && echo "${r%%/.git/*}"

Esto funcionará en cualquier parte de un árbol de trabajo de git (incluso dentro del .gitdirectorio), pero supone que se llama a los directorios del repositorio .git(que es el valor predeterminado). Con submódulos, esto irá a la raíz del repositorio que contiene más externo.

Si desea llegar a la raíz del submódulo actual, use:

echo $(r=$(git rev-parse --show-toplevel) && ([[ -n $r ]] && echo "$r" || (cd $(git rev-parse --git-dir)/.. && pwd) ))

Para ejecutar fácilmente un comando en la raíz de su submódulo, debajo [alias]de su .gitconfig, agregue:

sh = "!f() { root=$(pwd)/ && cd ${root%%/.git/*} && git rev-parse && exec \"$@\"; }; f"

Esto te permite hacer fácilmente cosas como git sh ag <string>

Solución robusta que admite directorios .gito nombres diferentes o externos $GIT_DIR.

Tenga en cuenta que $GIT_DIRpuede apuntar a algún lugar externo (y no ser llamado .git), de ahí la necesidad de una mayor verificación.

Pon esto en tu .bashrc:

# Print the name of the git working tree's root directory
function git_root() {
  local root first_commit
  # git displays its own error if not in a repository
  root=$(git rev-parse --show-toplevel) || return
  if [[ -n $root ]]; then
    echo $root
    return
  elif [[ $(git rev-parse --is-inside-git-dir) = true ]]; then
    # We're inside the .git directory
    # Store the commit id of the first commit to compare later
    # It's possible that $GIT_DIR points somewhere not inside the repo
    first_commit=$(git rev-list --parents HEAD | tail -1) ||
      echo "$0: Can't get initial commit" 2>&1 && false && return
    root=$(git rev-parse --git-dir)/.. &&
      # subshell so we don't change the user's working directory
    ( cd "$root" &&
      if [[ $(git rev-list --parents HEAD | tail -1) = $first_commit ]]; then
        pwd
      else
        echo "$FUNCNAME: git directory is not inside its repository" 2>&1
        false
      fi
    )
  else
    echo "$FUNCNAME: Can't determine repository root" 2>&1
    false
  fi
}

# Change working directory to git repository root
function cd_git_root() {
  local root
  root=$(git_root) || return 1  # git_root will print any errors
  cd "$root"
}

Ejecutarlo mediante la tipificación git_root(después de reiniciar el shell: exec bash)

Tom Hale
fuente
Este código se está revisando en la función Robust bash para encontrar la raíz de un repositorio git . Mire allí para actualizaciones.
Tom Hale
Agradable. Más complejo que lo que propuse hace 7 años ( stackoverflow.com/a/958125/6309 ), pero aún así +1
VonC
1
La solución más corta en este sentido es: (root=$(git rev-parse --git-dir)/ && cd ${root%%/.git/*} && git rev-parse && pwd)pero esto no cubre los correos electrónicos externos $GIT_DIRque se nombran de otra manera.git
Tom Hale,
Me pregunto si esto tiene en cuenta los múltiples árboles de trabajo que ahora son posibles en git 2.5+ ( stackoverflow.com/a/30185564/6309 )
VonC
Su enlace de comentario a "Robusta función bash para encontrar la raíz ..." ahora da un 404.
ErikE
8

Para enmendar la respuesta "git config" solo un poco:

git config --global --add alias.root '!pwd -P'

y limpiar el camino. Muy agradable.

Bruce K
fuente
7

Si está buscando un buen alias para hacer esto y no explotar cdsi no está en un directorio git:

alias ..g='git rev-parse && cd "$(git rev-parse --show-cdup)"'
Dan Heberden
fuente
6

Este alias de shell funciona tanto si está en un subdirectorio git como en el nivel superior:

alias gr='[ ! -z `git rev-parse --show-toplevel` ] && cd `git rev-parse --show-toplevel || pwd`'

actualizado para usar la sintaxis moderna en lugar de backticks:

alias gr='[ ! -z $(git rev-parse --show-toplevel) ] && cd $(git rev-parse --show-toplevel || pwd)'
MERM
fuente
5
alias git-root='cd \`git rev-parse --git-dir\`; cd ..'

Todo lo demás falla en algún momento, ya sea yendo al directorio de inicio o simplemente fallando miserablemente. Esta es la forma más rápida y rápida de volver al GIT_DIR.

tocororo
fuente
Parece que 'git rev-parse --git-dir' es la solución más limpia.
Stabledog
3
Esto falla cuando $GIT_DIRse separa del .gitárbol de trabajo usando -Files y gitdir: SOMEPATH. En consecuencia, esto también falla para los submódulos, donde $GIT_DIRcontiene .git/modules/SUBMODULEPATH.
Tino
5

Aquí hay un script que he escrito que maneja ambos casos: 1) repositorio con un espacio de trabajo, 2) repositorio desnudo.

https://gist.github.com/jdsumsion/6282953

git-root (archivo ejecutable en su ruta):

#!/bin/bash
GIT_DIR=`git rev-parse --git-dir` &&
(
  if [ `basename $GIT_DIR` = ".git" ]; then
    # handle normal git repos (with a .git dir)
    cd $GIT_DIR/..
  else
    # handle bare git repos (the repo IS a xxx.git dir)
    cd $GIT_DIR
  fi
  pwd
)

Espero que esto sea útil.

jdsumsion
fuente
1
Keith, gracias por la sugerencia, he incluido el guión.
jdsumsion
De hecho, voté por la respuesta principal porque me di cuenta de que la git execidea es más útil en repositorios no descubiertos. Sin embargo, este script en mi respuesta maneja el caso desnudo versus no desnudo correctamente, lo que podría ser útil para alguien, así que dejo esta respuesta aquí.
jdsumsion
1
Sin embargo, esto falla dentro de git submodules donde $GIT_DIRcontiene algo como /.git/modules/SUBMODULE. También supone que el .gitdirectorio es parte del árbol de trabajo en el caso no desnudo.
Tino
4
$ git config alias.root '!pwd'
# then you have:
$ git root
FractalSpace
fuente
Este alias fallará como global. Use esto en su lugar (en ~ / .gitconfig): [alias] findroot = "!f () { [[ -d ".git" ]] && echo "Found git in [pwd ]" && exit 0; cd .. && echo "IN pwd" && f;}; f"
FractalSpace
¿Por qué rechazar? Destaque cualquier error o sugiera una mejora en su lugar.
FractalSpace
1
Para mi git config --global alias.root '!pwd'funciona. No pude detectar ningún caso en el que actúe de manera diferente a la variante no global. (Unix, git 1.7.10.4) Por cierto: se findrootrequiere /.gitpara evitar una recursión sin fin.
Tino
4

Desde Git 2.13.0 , admite una nueva opción para mostrar la ruta del proyecto raíz, que funciona incluso cuando se utiliza desde un submódulo:

git rev-parse --show-superproject-working-tree
Jordi Vilalta Prat
fuente
git-scm.com/docs/… . Interesante. Debo haberme perdido ese. +1
VonC
3
Advertencia: "No genera nada si el repositorio actual no se utiliza como submódulo en ningún proyecto".
VonC
¡Gracias por el comentario! No me había dado cuenta de eso.
Jordi Vilalta Prat
2

Alias ​​de Shell preconfigurados en Frameworks de Shell

Si usa un marco de shell, es posible que ya haya un alias de shell disponible:

  • $ grten oh-my-zsh (68k) ( cd $(git rev-parse --show-toplevel || echo "."))
  • $ git-rooten prezto (8.8k) (muestra la ruta a la raíz del árbol de trabajo)
  • $ g.. zimfw (1k) (cambia el directorio actual al nivel superior del árbol de trabajo).
Hotschke
fuente
1

Quería ampliar el excelente comentario de Daniel Brockman.

La definición le git config --global alias.exec '!exec 'permite hacer cosas como git exec makeporque, como man git-configdice:

Si la expansión del alias tiene el prefijo de un signo de exclamación, se tratará como un comando de shell. [...] Tenga en cuenta que los comandos de shell se ejecutarán desde el directorio de nivel superior de un repositorio, que puede no ser necesariamente el directorio actual.

También es útil saber que $GIT_PREFIXserá la ruta al directorio actual en relación con el directorio de nivel superior de un repositorio. Pero, sabiendo que es solo la mitad de la batalla ™. La expansión variable de Shell hace que sea bastante difícil de usar. Entonces sugiero usar bash -casí:

git exec bash -c 'ls -l $GIT_PREFIX'

Otros comandos incluyen:

git exec pwd
git exec make
Bruno Bronosky
fuente
1

En caso de que alguien necesite una forma de hacer esto compatible con POSIX, sin necesidad de gitejecutable:

git-root:

#$1: Path to child directory
git_root_recurse_parent() {
    # Check if cwd is a git root directory
    if [ -d .git/objects -a -d .git/refs -a -f .git/HEAD ] ; then
        pwd
        return 0
    fi

    # Check if recursion should end (typically if cwd is /)
    if [ "${1}" = "$(pwd)" ] ; then
        return 1
    fi

    # Check parent directory in the same way
    local cwd=$(pwd)
    cd ..
    git_root_recurse_parent "${cwd}"
}

git_root_recurse_parent

Si solo desea la funcionalidad como parte de un script, elimine el shebang y reemplace la última git_root_recurse_parentlínea con:

git_root() {
    (git_root_recurse_parent)
}
swalog
fuente
Advertencia: si esto NO se llama en un repositorio de git, la recursión no termina en absoluto.
AH
@AH Second ifSe suponía que declaración verificaría si ha cambiado el directorio en la recursividad. Si permanece en el mismo directorio, se supone que está atascado en algún lugar (p /. Ej. ) Y frena la recursividad. El error está solucionado y ahora debería funcionar como se esperaba. Gracias por mencionarlo.
swalog
0

Tuve que resolver esto yo mismo hoy. Lo resolvió en C # ya que lo necesitaba para un programa, pero supongo que puede reescribirse fácilmente. Considere este dominio público.

public static string GetGitRoot (string file_path) {

    file_path = System.IO.Path.GetDirectoryName (file_path);

    while (file_path != null) {

        if (Directory.Exists (System.IO.Path.Combine (file_path, ".git")))
            return file_path;

        file_path = Directory.GetParent (file_path).FullName;

    }

    return null;

}
Hylke Bons
fuente