Existe-t-il un moyen d'obtenir le répertoire racine git dans une seule commande?

670

Mercurial a un moyen d'imprimer le répertoire racine (qui contient .hg) via

hg root

Y a-t-il quelque chose d'équivalent dans git pour obtenir le répertoire qui contient le répertoire .git?

wojo
la source
Bon script Emil. Je mets le script à disposition en ligne, et j'autorise la possibilité d'ajouter un fichier / répertoire comme argument. github.com/Dieterbe/git-scripts/commit/…
Dieter_be
Aussi pour toute personne curieuse ou recherchant a bzr rootété beaucoup utilisée dans Bazaar
Kristopher Ives
Veuillez prendre note de 'gcd': 'cd' de Git-Aware relatif à la racine du référentiel avec auto-complétion sur jeetworks.org/node/52 .
Micha Wiedenmann
1
Considérez ma réponse cigit rev-parse --git-dir - dessous , comme expliqué dans ce commentaire
VonC
1
@MichaWiedenmann: le lien est mort.
d33tah

Réponses:

1114

Oui:

git rev-parse --show-toplevel

Si vous souhaitez répliquer la commande Git plus directement, vous pouvez créer un alias :

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

et git rootfonctionnera maintenant comme hg root.


Remarque : dans un sous-module, cela affichera le répertoire racine du sous - module et non le référentiel parent. Si vous utilisez Git> = 2.13 ou supérieur, il existe un moyen pour les sous - modules d'afficher le répertoire racine du superprojet. Si votre git est plus ancien que cela, consultez cette autre réponse.

baudtack
la source
148
Je définis toujours git config --global alias.exec '!exec 'pour pouvoir faire des choses comme git exec make. Cela fonctionne car les alias du shell sont toujours exécutés dans le répertoire de niveau supérieur.
Daniel Brockman
8
C'est ce qui hg rootfait. Il imprime le répertoire de niveau supérieur de votre référentiel extrait. Il ne vous y fait pas basculer (et il ne pouvait pas, en fait, le faire en raison de l'interaction de l'ensemble du concept de répertoire actuel et de votre shell).
Omnifarious
14
Faites attention à cette solution - elle suivra tous les liens symboliques. Donc, si vous êtes dans ~/my.proj/foo/baret ~/my.projauquel vous avez un lien symbolique ~/src/my.proj, la commande ci-dessus vous amènera ~/src/my.proj. Cela pourrait être un problème si tout ce que vous voulez faire après cela n'est pas indépendant de l'arbre.
Franci Penov
3
Comment puis-je l'utiliser à partir d'un crochet git? Plus précisément, je fais un hook post-fusion et j'ai besoin d'obtenir le répertoire racine réel du dépôt git local.
Derek
12
Cette solution (et bien d'autres sur cette page que j'ai essayée) ne fonctionne pas à l'intérieur d'un crochet git. Pour être plus précis, cela ne fonctionne pas lorsque vous êtes dans le .gitrépertoire du projet .
Dennis
97

La manpage pour git-config(sous Alias ) dit:

Si l'extension d'alias est préfixée d'un point d'exclamation, elle sera traitée comme une commande shell. [...] Notez que les commandes shell seront exécutées à partir du répertoire de niveau supérieur d'un référentiel, qui n'est pas nécessairement le répertoire courant.

Donc, sous UNIX, vous pouvez faire:

git config --global --add alias.root '!pwd'
Scott Lindsay
la source
2
Donc, si vous avez une commande "git root", comment pouvez-vous mettre cela dans un alias? Si je le mets dans mon .zshrc, et je définis `alias cg =" cd $ (racine git) ", la partie $ () est évaluée au moment de la source, et pointe toujours vers ~ / dotfiles, car c'est là que se trouve mon zshrc .
zelk
2
@cormacrelf Vous ne le mettez pas dans un alias de shell. Vous pouvez le mettre dans une fonction shell ou un script.
Conrad Meyer
4
@cormacrelf Mettez-le entre guillemets simples au lieu de guillemets doubles, il ne sera pas développé au moment de la définition, mais au moment de l'exécution.
clacke
3
@Mechanicalsnail Vraiment? Alors, que pensez-vous qu'il fasse en dehors du repo?
Alois Mahdal
1
@AloisMahdal en fait pour moi ça ne manque pas en dehors d'un repo, ça rapporte simplement le cwd.
justinpitts
91

A --show-toplevelrécemment été ajouté git rev-parseou pourquoi personne ne le mentionne?

Depuis la git rev-parsepage de manuel:

   --show-toplevel
       Show the absolute path of the top-level directory.
marc.guenther
la source
1
Merci de l'avoir signalé; sans votre aide, il serait difficile pour moi de deviner git-rev-parse- en raison de son nom suggérant qu'il s'agit de traiter les spécifications de révision. BTW, je serais heureux de voir un git --work-treetravail similaire à git --exec-path[=<path>]: "Si aucun chemin n'est donné, git imprimera le paramètre actuel"; au moins, OMI, ce serait un endroit logique pour rechercher une telle fonctionnalité.
imz - Ivan Zakharyaschev
4
En particulier, vous pouvez root = rev-parse --show-toplevelcréer un alias dans votre gitconfig.
Escargot mécanique
2
en d'autres termes, vous pouvez le faire git config --global alias.root "rev-parse --show-toplevel"et ensuite git rootvous pourrez faire le travail
non
@RyanTheLeach git rev-parse --show-toplevelfonctionne quand je l'ai essayé dans un sous-module. Il affiche le répertoire racine du sous-module git. Qu'est-ce que cela imprime pour vous?
wisbucky
2
J'étais confus pourquoi cette réponse reproduisait la première réponse. Il s'avère que la réponse du haut a été modifiée de --show-cdupà --show-top-levelen février 2011 (après la soumission de cette réponse).
wisbucky
54

Et " git rev-parse --git-dir"?

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

L' --git-diroption semble fonctionner.

Depuis la page de manuel de git rev-parse :

--git-dir

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

Vous pouvez le voir en action dans ce git setup-shscript .

Si vous êtes dans un dossier de sous-module, avec Git> = 2.13 , utilisez :

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

Si vous utilisez git rev-parse --show-toplevel, assurez-vous que c'est avec Git 2.25+ (Q1 2020) .

VonC
la source
3
Oh, attendez, c'était proche mais il obtient le dir .git réel, pas la base du dépôt git. De plus, le répertoire .git pourrait être ailleurs, donc ce n'est pas ce que je cherchais exactement.
wojo
2
Bon, je vois maintenant ce que vous cherchiez réellement. --show-cdup est alors plus approprié. Je laisse ma réponse pour illustrer la différence entre les deux options.
VonC
2
Il semble également que cette commande donne un chemin relatif .gitsi vous êtes déjà dans le répertoire racine. (Au moins, il le fait sur msysgit.)
Blair Holloway
2
+1, c'est la seule réponse qui réponde à la question d'origine "obtenir le répertoire qui contient le répertoire .git?", Amusant de voir que l'OP lui-même mentionne "le répertoire .git pourrait être ailleurs".
FabienAndre
1
C'est la seule solution qui fonctionne sur Windows ET donne un résultat analysable lors de l'utilisation de sous-modules git (par exemple c: \ repositories \ myrepo \ .git \ modules \ mysubmodule). Cela en fait la solution la plus robuste, en particulier dans un script réutilisable.
chriskelly
36

Pour écrire une réponse simple ici, afin que nous puissions utiliser

git root

pour faire le travail, configurez simplement votre git en utilisant

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

et puis vous voudrez peut-être ajouter ce qui suit à votre ~/.bashrc:

alias cdroot='cd $(git root)'

afin que vous puissiez simplement utiliser cdrootpour aller en haut de votre repo.

non-polarité
la source
Très agréable! Finalement pratique!
scravy
Une excellente réponse, merci!
AVarf
26

Si vous êtes déjà au niveau supérieur ou pas dans un référentiel git cd $(git rev-parse --show-cdup)vous ramènera à la maison (juste un cd). cd ./$(git rev-parse --show-cdup)est une façon de résoudre ce problème.

Karl
la source
7
Une autre option est de le citer: cd "$(git rev-parse --show-cdup)". Cela fonctionne car cd ""vous ne mène nulle part, plutôt que de revenir $HOME. Et il est préférable de citer les invocations $ () de toute façon, au cas où ils produiraient quelque chose avec des espaces (pas que cette commande le fasse dans ce cas, cependant).
ctrueden
Cette réponse fonctionne bien même si vous avez changé de répertoire pour votre dépôt git via un lien symbolique . Certains des autres exemples ne fonctionnent pas comme prévu lorsque votre dépôt git est sous un lien symbolique car ils ne résolvent pas le répertoire racine de git par rapport à bash $PWD. Cet exemple résoudra la racine de git par rapport à $PWDau lieu de realpath $PWD.
Damien Ó Ceallaigh
16

Pour calculer le chemin absolu du répertoire racine git actuel, par exemple pour une utilisation dans un script shell, utilisez cette combinaison de readlink et git rev-parse:

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

git-rev-parse --show-cdupvous donne le bon nombre de ".." pour accéder à la racine depuis votre cwd, ou la chaîne vide si vous êtes à la racine. Ajoutez ensuite "./" pour traiter le cas de chaîne vide et utilisez readlink -fpour traduire en un chemin complet.

Vous pouvez également créer une git-rootcommande dans votre PATH en tant que script shell pour appliquer cette technique:

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

(Ce qui précède peut être collé dans un terminal pour créer git-root et définir des bits d'exécution; le script réel se trouve aux lignes 2, 3 et 4.)

Et puis vous seriez en mesure d'exécuter git rootpour obtenir la racine de votre arbre actuel. Notez que dans le script shell, utilisez "-e" pour provoquer la fermeture du shell si le rev-parse échoue afin que vous puissiez correctement obtenir l'état de sortie et le message d'erreur si vous n'êtes pas dans un répertoire git.

Emil Sit
la source
2
Vos exemples s'arrêteront si le chemin du répertoire "root" de git contient des espaces. Utilisez toujours "$(git rev-parse ...)"au lieu de hacks comme ./$(git rev-parse ...).
Mikko Rantalainen
readlink -fne fonctionne pas de la même manière sur BSD. Voir cet OS pour des solutions de contournement. La réponse Python fonctionnera probablement sans installer quoi que ce soit: python -c 'import os, sys; print(os.path.realpath(sys.argv[1]))' "$(git rev-parse --show-cdup)".
Bluu
16

Comme d'autres l'ont noté, le cœur de la solution est d'utiliser git rev-parse --show-cdup. Cependant, il y a quelques cas marginaux à résoudre:

  1. Lorsque le cwd est déjà la racine de l'arborescence de travail, la commande renvoie une chaîne vide.
    En fait, il produit une ligne vide, mais la substitution de commande supprime la coupure de ligne de fin. Le résultat final est une chaîne vide.

    La plupart des réponses suggèrent d'ajouter la sortie de ./telle sorte qu'une sortie vide devienne "./"avant d'être alimentée cd.

  2. Lorsque GIT_WORK_TREE est défini sur un emplacement qui n'est pas le parent du cwd, la sortie peut être un chemin d'accès absolu.

    La préparation ./est incorrecte dans cette situation. Si a ./est ajouté à un chemin absolu, il devient un chemin relatif (et ils ne font référence au même emplacement que si le cwd est le répertoire racine du système).

  3. La sortie peut contenir des espaces.

    Cela ne s'applique vraiment que dans le deuxième cas, mais il a une solution simple: utilisez des guillemets doubles autour de la substitution de commande (et toute utilisation ultérieure de la valeur).

Comme d'autres réponses l'ont noté, nous pouvons le faire cd "./$(git rev-parse --show-cdup)", mais cela casse dans le deuxième cas de bord (et le troisième cas de bord si nous laissons les guillemets doubles).

De nombreux shells traitent cd ""comme un no-op, donc pour ces shells, nous pourrions le faire cd "$(git rev-parse --show-cdup)"(les guillemets doubles protègent la chaîne vide comme argument dans le premier cas de bord et préservent les espaces dans le troisième cas de bord). POSIX indique que le résultat de cd ""n'est pas spécifié, il est donc préférable d'éviter de faire cette hypothèse.

Une solution qui fonctionne dans tous les cas ci-dessus nécessite un test quelconque. Fait explicitement, cela pourrait ressembler à ceci:

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

Aucun cdn'est fait pour le premier cas de bord.

S'il est acceptable d'exécuter cd . pour le premier cas de bord, alors le conditionnel peut être fait dans l'expansion du paramètre:

cdup="$(git rev-parse --show-cdup)" && cd "${cdup:-.}"
Chris Johnsen
la source
Pourquoi ne pas simplement utiliser "git config --global --add alias.root '! Pwd'" et un shell alias gitroot = 'cd git root' que la réponse ci-dessus utilise?
Jason Axelson
1
L'utilisation d'un alias peut ne pas être possible, par exemple si vous souhaitez l'écrire et ne pouvez pas vous fier à un alias personnalisé.
blueyed
1
Les sous-modules sont un autre cas d'angle.
Ryan The Leach
14

Au cas où si vous alimentez ce chemin vers le Git lui-même, utilisez :/

# 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
la source
12

Solutions courtes qui fonctionnent avec des sous-modules, dans des crochets et à l'intérieur du .gitrépertoire

Voici la réponse courte que la plupart voudront:

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

Cela fonctionnera n'importe où dans une arborescence de travail git (y compris à l'intérieur du .gitrépertoire), mais suppose que les répertoires du référentiel sont appelés .git(ce qui est la valeur par défaut). Avec les sous-modules, cela ira à la racine du référentiel contenant le plus à l'extérieur.

Si vous souhaitez accéder à la racine de l'utilisation actuelle du sous-module:

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

Pour exécuter facilement une commande dans votre racine de sous-module, sous [alias]dans votre .gitconfig, ajoutez:

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

Cela vous permet de faire facilement des choses comme git sh ag <string>

Solution robuste qui prend en charge des répertoires nommés différemment ou externes .gitou $GIT_DIR.

Notez que cela $GIT_DIRpeut pointer quelque part vers l'extérieur (et ne pas être appelé .git), d'où la nécessité d'une vérification supplémentaire.

Mettez ceci dans votre .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"
}

Exécuter en tapant git_root(après avoir redémarré votre shell: exec bash)

Tom Hale
la source
Ce code est en cours de révision dans la fonction Robust bash pour trouver la racine d'un référentiel git . Regardez là pour les mises à jour.
Tom Hale
Agréable. Plus complexe que ce que j'ai proposé il y a 7 ans ( stackoverflow.com/a/958125/6309 ), mais toujours +1
VonC
1
La solution la plus courte dans ce sens est: (root=$(git rev-parse --git-dir)/ && cd ${root%%/.git/*} && git rev-parse && pwd)mais cela ne couvre pas les $GIT_DIRs externes qui sont nommés autrement que.git
Tom Hale
Je me demande si cela prend en compte les multiples arbres de travail qui sont maintenant possibles dans git 2.5+ ( stackoverflow.com/a/30185564/6309 )
VonC
Votre lien de commentaire vers "Fonction bash robuste pour trouver la racine ..." donne maintenant un 404.
ErikE
8

Pour modifier la "git config" répondez un peu:

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

et obtenir le chemin nettoyé. Très agréable.

Bruce K
la source
7

Si vous êtes à la recherche d'un bon alias pour le faire, sans exploser cdsi vous n'êtes pas dans un répertoire git:

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

Cet alias de shell fonctionne que vous soyez dans un sous-répertoire git ou au niveau supérieur:

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

mis à jour pour utiliser une syntaxe moderne au lieu de backticks:

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

Tout le reste échoue à un moment donné, soit en allant dans le répertoire personnel, soit en échouant lamentablement. C'est le moyen le plus rapide et le plus court pour revenir au GIT_DIR.

tocororo
la source
Il semble que 'git rev-parse --git-dir' soit la solution la plus propre.
Stabledog
3
Cela échoue, lorsqu'il $GIT_DIRest détaché de l'arbre de travail à l'aide de -Files .gitet gitdir: SOMEPATH. Par conséquent, cela échoue également pour les sous-modules, où $GIT_DIRcontient .git/modules/SUBMODULEPATH.
Tino
5

Voici un script que j'ai écrit qui gère les deux cas: 1) référentiel avec un espace de travail, 2) référentiel nu.

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

git-root (fichier exécutable sur votre chemin):

#!/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
)

J'espère que cela vous sera utile.

jdsumsion
la source
1
Keith, merci pour la suggestion, j'ai inclus le script.
jdsumsion
J'ai en fait voté la réponse du haut parce que j'ai réalisé que l' git execidée est plus utile dans les référentiels non dénudés. Cependant, ce script dans ma réponse gère correctement le cas nu contre non nu, ce qui pourrait être utile à quelqu'un, donc je laisse cette réponse ici.
jdsumsion
1
Cependant, cela échoue dans git submodules où $GIT_DIRcontient quelque chose comme /.git/modules/SUBMODULE. Vous supposez également que le .gitrépertoire fait partie de l'arbre de travail dans le cas non nu.
Tino
4
$ git config alias.root '!pwd'
# then you have:
$ git root
FractalSpace
la source
Cet alias échouera en tant que global. Utilisez ceci à la place (dans ~ / .gitconfig): [alias] findroot = "!f () { [[ -d ".git" ]] && echo "Found git in [pwd ]" && exit 0; cd .. && echo "IN pwd" && f;}; f"
FractalSpace
pourquoi downvote? Veuillez mettre en évidence toute erreur ou suggérer une amélioration à la place.
FractalSpace
1
Pour moi ça git config --global alias.root '!pwd'marche. Je n'ai pu repérer aucun cas où il agit différemment de la variante non globale. (Unix, git 1.7.10.4) BTW: Votre findrootrequiert un /.gitpour éviter une récursion sans fin.
Tino
4

Depuis Git 2.13.0 , il prend en charge une nouvelle option pour afficher le chemin du projet racine, qui fonctionne même lorsqu'il est utilisé depuis l'intérieur d'un sous-module:

git rev-parse --show-superproject-working-tree
Jordi Vilalta Prat
la source
git-scm.com/docs/… . Intéressant. J'ai dû manquer celui-là. +1
VonC
3
Attention cependant: "N'affiche rien si le référentiel actuel n'est utilisé comme sous-module par aucun projet."
VonC
Merci pour le commentaire! Je ne l'avais pas remarqué.
Jordi Vilalta Prat
2

Alias ​​de shell préconfigurés dans les cadres de shell

Si vous utilisez un framework shell, un alias shell peut déjà être disponible:

  • $ grten oh-my-zsh (68k) ( cd $(git rev-parse --show-toplevel || echo "."))
  • $ git-rootdans prezto (8.8k) (affiche le chemin d'accès à la racine de l'arbre de travail)
  • $ g.. zimfw (1k) (change le répertoire courant au niveau supérieur de l'arborescence de travail.)
Hotschke
la source
1

Je voulais développer l'excellent commentaire de Daniel Brockman.

La définition git config --global alias.exec '!exec 'vous permet de faire des choses comme git exec makeparce que, comme l' man git-configindique:

Si l'extension d'alias est préfixée d'un point d'exclamation, elle sera traitée comme une commande shell. [...] Notez que les commandes shell seront exécutées à partir du répertoire de niveau supérieur d'un référentiel, qui n'est pas nécessairement le répertoire courant.

Il est également utile de savoir que ce $GIT_PREFIXsera le chemin d'accès au répertoire actuel par rapport au répertoire de niveau supérieur d'un référentiel. Mais, sachant que ce n'est que la moitié de la bataille ™. L'expansion variable du shell le rend plutôt difficile à utiliser. Je suggère donc d'utiliser bash -ccomme ça:

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

les autres commandes incluent:

git exec pwd
git exec make
Bruno Bronosky
la source
1

Dans le cas où quelqu'un aurait besoin d'une méthode compatible POSIX pour le faire, sans avoir besoin d'un gitexécutable:

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 vous souhaitez simplement que la fonctionnalité fasse partie d'un script, supprimez le shebang et remplacez la dernière git_root_recurse_parentligne par:

git_root() {
    (git_root_recurse_parent)
}
swalog
la source
Avertissement: si cela n'est PAS appelé dans un dépôt git, la récursivité ne se termine pas du tout.
AH
@AH La deuxième ifinstruction était censée vérifier si vous avez changé de répertoire dans la récursivité. S'il reste dans le même répertoire, il suppose que vous êtes coincé quelque part (par exemple /) et freine la récursivité. Le bogue est corrigé et devrait maintenant fonctionner comme prévu. Merci de l'avoir signalé.
swalog
0

J'ai dû résoudre ce problème moi-même aujourd'hui. Résolu en C # comme j'en avais besoin pour un programme, mais je suppose qu'il peut être réécrit en toute simplicité. Considérez ce domaine public.

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
la source