Existe uma maneira de obter o diretório raiz do git em um comando?

670

O Mercurial tem uma maneira de imprimir o diretório raiz (que contém .hg) via

hg root

Existe algo equivalente no git para obter o diretório que contém o diretório .git?

wojo
fonte
Bom roteiro Emil. Disponibilizei o script online e permiti adicionar um arquivo / diretório como argumento. github.com/Dieterbe/git-scripts/commit/…
Dieter_be
Também para qualquer um curioso ou busca bzr rootfoi usado muito em Bazaar
Kristopher Ives
Observe o 'gcd': Git-Aware 'cd' relativo à raiz do repositório com preenchimento automático em jeetworks.org/node/52 .
Micha Wiedenmann
1
Considere minha resposta abaixogit rev-parse --git-dir , conforme explicado neste comentário
VonC
1
@MichaWiedenmann: o link está morto.
precisa saber é

Respostas:

1114

Sim:

git rev-parse --show-toplevel

Se você deseja replicar o comando Git mais diretamente, é possível criar um alias :

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

e agora git rootfuncionará exatamente comohg root .


Nota : Em um submódulo, isso exibirá o diretório raiz do submódulo e não o repositório pai. Se você estiver usando Git> = 2.13 ou superior, existe uma maneira de os submódulos mostrarem o diretório raiz do superprojeto. Se o seu git for mais antigo que isso, veja esta outra resposta.

baudtack
fonte
148
Eu sempre defino git config --global alias.exec '!exec 'para que eu possa fazer coisas como git exec make. Isso funciona porque os aliases do shell são sempre executados no diretório de nível superior.
Daniel Brockman
8
Isto é o que hg rootfaz. Ele imprime o diretório de nível superior do seu repositório com check-out. Ele não muda para você (e na verdade não podia fazê-lo por causa de como todo o conceito de diretório atual e seu shell interagem).
Onipotente
14
Faça uma pequena ressalva com esta solução - ela seguirá quaisquer links simbólicos. Portanto, se você estiver dentro ~/my.proj/foo/bare ~/my.projestiver vinculado a ~/src/my.proj, o comando acima o levará a ~/src/my.proj. Pode ser um problema, se o que você quiser fazer depois disso não for independente da árvore.
Franci Penov
3
Como posso usar isso de dentro de um gancho git? Especificamente, estou fazendo um gancho pós-mesclagem e preciso obter o diretório raiz real do repositório git local.
Derek
12
Esta solução (e muitas outras nesta página que eu tentei) não funciona dentro de um gancho git. Para ser mais preciso, não funciona quando você está dentro do .gitdiretório do projeto .
Dennis
97

A manpágina para git-config(em Alias ) diz:

Se a expansão do alias for prefixada com um ponto de exclamação, será tratada como um comando shell. [...] Observe que os comandos do shell serão executados no diretório de nível superior de um repositório, que pode não ser necessariamente o diretório atual.

Portanto, no UNIX, você pode:

git config --global --add alias.root '!pwd'
Scott Lindsay
fonte
2
Então, se você tem um comando "git root", como você pode colocar isso em um alias? Se eu colocá-lo no meu .zshrce definir `alias cg =" cd $ (git root) ", a parte $ () é avaliada no momento da fonte e sempre aponta para ~ / dotfiles, pois é onde meu zshrc está .
Zelk 01/12/12
2
@cormacrelf Você não o coloca em um apelido de shell. Você pode colocá-lo em uma função ou script de shell.
Conrad Meyer
4
@cormacrelf Coloque-o entre aspas simples, em vez de aspas duplas, para não ser expandido no tempo de definição, mas no tempo de execução.
clacke
3
@Mechanicalsnail Sério? Então, o que você espera que faça fora do repositório?
Alois Mahdal
1
@AloisMahdal, na verdade, para mim, não falha fora de um repositório, simplesmente relata o cwd.
Justinpitts
91

Foi --show-topleveladicionado recentemente git rev-parseou por que ninguém menciona isso?

Na git rev-parsepágina do manual:

   --show-toplevel
       Show the absolute path of the top-level directory.
marc.guenther
fonte
1
Obrigado por apontar isso; sem a sua ajuda, seria difícil para mim adivinhar git-rev-parse- por causa do nome que sugere que se trata do processamento de especificações de revisão. Aliás, ficaria feliz em ver um git --work-treetrabalho semelhante a git --exec-path[=<path>]: "Se nenhum caminho for fornecido, o git imprimirá a configuração atual"; pelo menos, na IMO, seria um lugar lógico para procurar esse recurso.
imz - Ivan Zakharyaschev 4/12/12
4
Em particular, você pode root = rev-parse --show-toplevelcriar um alias no seu gitconfig.
Caracol mecânico
2
em outras palavras, você pode fazer git config --global alias.root "rev-parse --show-toplevel"e, em seguida, git rootserá capaz de fazer o trabalho
nonopolarity
O @RyanTheLeach git rev-parse --show-toplevelfunciona quando eu tentei em um submódulo. Ele imprime o diretório raiz do submódulo git. O que é impresso para você?
23618 wisbucky
2
Fiquei confuso por que essa resposta duplicou a resposta principal. Acontece que a resposta principal foi editada de --show-cdupaté --show-top-levelfevereiro de 2011 (após o envio dessa resposta).
23618 wisbucky
54

Que tal " git rev-parse --git-dir"?

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

o --git-dir opção parece funcionar.

Na página de manual do git rev-parse :

--git-dir

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

Você pode vê-lo em ação neste git setup-shscript .

Se você estiver em uma pasta de submódulo, com Git> = 2.13 , use :

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

Se você estiver usando git rev-parse --show-toplevel, verifique se está com o Git 2.25+ (primeiro trimestre de 2020) .

VonC
fonte
3
Ah, espere, isso foi próximo, mas ele obtém o diretório .git real, não a base do repositório git. Além disso, o diretório .git poderia estar em outro lugar, portanto, não era exatamente isso que eu estava procurando.
Wok 5/06
2
Certo, agora vejo o que você estava procurando. --show-cdup é mais apropriado então. Deixo minha resposta para ilustrar a diferença entre as duas opções.
VonC 5/06/09
2
Parece também que este comando fornece um caminho relativo para .gitse você já estiver no diretório raiz. (Pelo menos, ele faz sobre msysgit.)
Blair Holloway
2
+1, esta é a única resposta que responde à pergunta original "obter o diretório que contém o diretório .git?", Divertida ao ver que o próprio OP menciona "o diretório .git pode estar em outro lugar".
FabienAndre
1
Esta é a única solução que funciona no Windows E fornece um resultado analisável ao usar sub-módulos git (por exemplo, c: \ repositories \ myrepo \ .git \ modules \ mysubmodule). Isso a torna a solução mais robusta, especialmente em um script reutilizável.
Chriskelly
36

Para escrever uma resposta simples aqui, para que possamos usar

git root

para fazer o trabalho, basta configurar seu git usando

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

e você pode adicionar o seguinte ao seu ~/.bashrc:

alias cdroot='cd $(git root)'

para que você possa usar apenas cdrootpara ir para o topo do seu repo.

falta de polaridade
fonte
Muito agradável! Finalmente conveniente!
Scravy 29/05/19
Uma ótima resposta, obrigado!
AVarf
26

Se você já está no nível superior ou não em um repositório git cd $(git rev-parse --show-cdup), você será levado para casa (apenas cd). cd ./$(git rev-parse --show-cdup)é uma maneira de consertar isso.

Karl
fonte
7
Outra opção é a citá-lo: cd "$(git rev-parse --show-cdup)". Isso funciona porque cd ""você não leva a lugar algum, em vez de voltar $HOME. E é uma boa prática citar invocações $ () de qualquer maneira, caso elas produzam algo com espaços (embora não seja esse comando nesse caso).
ctrueden
Esta resposta funciona bem mesmo se você alterou o diretório para seu repositório git por meio de um link simbólico . Alguns dos outros exemplos não funcionam como esperado quando seu repositório git está sob um link simbólico, pois eles não resolvem o diretório raiz do git em relação ao bash $PWD. Este exemplo resolverá a raiz do git em relação a em $PWDvez de realpath $PWD.
Damien Ó Ceallaigh
16

Para calcular o caminho absoluto do diretório raiz atual do git, digamos para uso em um script de shell, use esta combinação de readlink e git rev-parse:

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

git-rev-parse --show-cdupfornece o número certo de ".." s para obter a raiz do seu cwd ou a string vazia, se você estiver na raiz. Em seguida, acrescente "./" para lidar com a caixa de cordas vazia e use readlink -f para traduzir para um caminho completo.

Você também pode criar um git-rootcomando no seu PATH como um 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

(O acima pode ser colado em um terminal para criar git-root e definir bits de execução; o script real está nas linhas 2, 3 e 4.)

E então você poderá executar git rootpara obter a raiz da sua árvore atual. Observe que, no script do shell, use "-e" para fazer com que o shell seja encerrado se a análise de revisão falhar, para que você possa obter corretamente o status de saída e a mensagem de erro se não estiver em um diretório git.

Emil Sit
fonte
2
Seus exemplos serão interrompidos se o caminho do diretório "root" do git contiver espaços. Sempre use em "$(git rev-parse ...)"vez de hacks como ./$(git rev-parse ...).
Mikko Rantalainen 29/03
readlink -fnão funciona da mesma maneira no BSD. Veja este SO para soluções alternativas. A resposta Python provavelmente irá funcionar sem instalar nada: python -c 'import os, sys; print(os.path.realpath(sys.argv[1]))' "$(git rev-parse --show-cdup)".
Bluu
16

Como outros observaram, o núcleo da solução é usar git rev-parse --show-cdup. No entanto, existem alguns casos extremos a serem abordados:

  1. Quando o cwd já é a raiz da árvore de trabalho, o comando gera uma sequência vazia.
    Na verdade, ele produz uma linha vazia, mas a substituição de comando retira a quebra de linha à direita. O resultado final é uma sequência vazia.

    A maioria das respostas sugere anexar a saída de ./forma que uma saída vazia se torne "./"antes de ser alimentada cd.

  2. Quando GIT_WORK_TREE é definido como um local que não é o pai do cwd, a saída pode ser um nome de caminho absoluto.

    Anexar ./está errado nessa situação. Se a ./é anexado a um caminho absoluto, ele se torna um caminho relativo (e eles se referem apenas ao mesmo local se o cwd for o diretório raiz do sistema).

  3. A saída pode conter espaço em branco.

    Isso realmente se aplica apenas no segundo caso, mas tem uma solução fácil: use aspas duplas em torno da substituição do comando (e qualquer uso subsequente do valor).

Como outras respostas observaram, podemos fazê-lo cd "./$(git rev-parse --show-cdup)", mas isso ocorre no segundo caso de aresta (e no terceiro caso de aresta se deixarmos de aspas duplas).

Muitos shells são tratados cd ""como não-op, portanto, para aqueles shells que poderíamos fazer cd "$(git rev-parse --show-cdup)"(as aspas duplas protegem a string vazia como argumento no primeiro caso de aresta e preservam espaço em branco no terceiro caso de aresta). POSIX diz que o resultado decd "" não é especificado, portanto, é melhor evitar fazer essa suposição.

Uma solução que funcione em todos os casos acima requer algum tipo de teste. Feito explicitamente, pode ser assim:

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

Não cdé feito o primeiro caso de borda.

Se for aceitável executar cd .para o primeiro caso de borda, o condicional poderá ser feito na expansão do parâmetro:

cdup="$(git rev-parse --show-cdup)" && cd "${cdup:-.}"
Chris Johnsen
fonte
Por que você não usa "git config --global --ad alias.root '! Pwd'" e um apelido de shell gitroot = 'cd git root' que a resposta acima usa?
21430 Jason Axelson
1
O uso de um alias pode não ser possível, por exemplo, se você deseja criar um script e não pode confiar em um alias personalizado.
blueyed
1
Sub-módulos são outro caso de canto.
Ryan The Leach
14

Caso você esteja alimentando esse caminho para o próprio Git, 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
fonte
12

Soluções curtas que funcionam com submódulos, em ganchos e dentro do .git diretório

Aqui está a resposta curta que a maioria deseja:

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

Isso funcionará em qualquer lugar da árvore de trabalho do git (inclusive dentro do .gitdiretório), mas pressupõe que os diretórios do repositório sejam chamados .git(que é o padrão). Com os submódulos, isso irá para a raiz do repositório externo.

Se você deseja chegar à raiz do submódulo atual, use:

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

Para executar facilmente um comando em sua raiz do submódulo, [alias]em sua .gitconfig, adicione:

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

Isso permite que você faça facilmente coisas como git sh ag <string>

Solução robusta que suporta diretórios .gitou nomes diferentes ou externos $GIT_DIR.

Observe que $GIT_DIRpode apontar para algum lugar externo (e não ser chamado .git), daí a necessidade de uma verificação adicional.

Coloque isso no seu .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"
}

Executá-lo digitando git_root(depois de reiniciar o shell: exec bash)

Tom Hale
fonte
Este código está sendo revisto na função Robust bash para encontrar a raiz de um repositório git . Procure por atualizações.
Tom Hale
Agradável. Mais complexo que o que eu propus há 7 anos ( stackoverflow.com/a/958125/6309 ), mas ainda +1
VonC
1
A solução mais curta nesse sentido é: (root=$(git rev-parse --git-dir)/ && cd ${root%%/.git/*} && git rev-parse && pwd)mas isso não cobre $GIT_DIRs externos com nomes diferentes de.git
Tom Hale
Gostaria de saber se esta tomada em conta os múltiplos worktrees que agora são possíveis em git 2.5+ ( stackoverflow.com/a/30185564/6309 )
VonC
O link do seu comentário para "A função robusta do bash para encontrar a raiz ..." agora fornece um 404. #
194 ErikE
8

Para alterar a resposta "git config" um pouco:

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

e limpe o caminho. Muito agradável.

Bruce K
fonte
7

Se você está procurando um bom alias para fazer isso e não exploda cdse não estiver em um dir git:

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

Esse alias do shell funciona se você estiver em um subdiretório git ou no nível superior:

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

atualizado para usar a sintaxe moderna em vez de backticks:

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

Todo o resto falha em algum momento, quer ir para o diretório inicial ou apenas falhar miseravelmente. Esta é a maneira mais rápida e mais curta de voltar ao GIT_DIR.

tocororo
fonte
Parece que 'git rev-parse --git-dir' é a solução mais limpa.
precisa saber é
3
Isso falha quando $GIT_DIRé desanexado da .gitárvore de trabalho usando -Files e gitdir: SOMEPATH. Consequentemente, isso também falha nos submódulos, onde $GIT_DIRcontém .git/modules/SUBMODULEPATH.
Tino
5

Aqui está um script que eu escrevi que lida com os dois casos: 1) repositório com uma área de trabalho, 2) repositório vazio.

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

git-root (arquivo executável no seu caminho):

#!/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 isso seja útil.

jdsumsion
fonte
1
Keith, obrigado pela sugestão, eu incluí o script.
usar o seguinte comando
Na verdade, votei na melhor resposta porque percebi que a git execidéia é mais útil em repositórios não-nua. No entanto, esse script na minha resposta lida corretamente com o caso simples versus o não básico, o que pode ser útil para alguém, então estou deixando essa resposta aqui.
Jsumsion
1
No entanto, isso falha dentro de git submodules onde $GIT_DIRcontém algo parecido /.git/modules/SUBMODULE. Além disso, você assume que o .gitdiretório faz parte da árvore de trabalho no caso não vazio.
Tino
4
$ git config alias.root '!pwd'
# then you have:
$ git root
FractalSpace
fonte
Esse alias falhará como global. Use isso em vez (em ~ / .gitconfig): [alias] findroot = "!f () { [[ -d ".git" ]] && echo "Found git in [pwd ]" && exit 0; cd .. && echo "IN pwd" && f;}; f"
FractalSpace
por que voto negativo? Destaque qualquer erro ou sugira melhoria.
FractalSpace 27/05
1
Para mim git config --global alias.root '!pwd'funciona. Não consegui identificar nenhum caso em que ele atue de maneira diferente da variante não global. (Unix, git 1.7.10.4) BTW: Você findrootrequer a /.gitpara evitar uma recursão sem fim.
Tino
4

Desde o Git 2.13.0 , ele suporta uma nova opção para mostrar o caminho do projeto raiz, que funciona mesmo quando usado dentro de um submódulo:

git rev-parse --show-superproject-working-tree
Jordi Vilalta Prat
fonte
git-scm.com/docs/… . Interessante. Eu devo ter perdido esse. +1
VonC
3
Aviso: "Não gera nada se o repositório atual não for usado como um submódulo por nenhum projeto."
VonC 16/05/19
Obrigado pelo comentário! Eu não tinha notado isso.
Jordi Vilalta Prat
2

Aliases de shell pré-configurados em estruturas de shell

Se você usar uma estrutura de shell, talvez já exista um alias de shell disponível:

  • $ grtem oh-my-zsh (68k) ( cd $(git rev-parse --show-toplevel || echo "."))
  • $ git-rootno prezto (8,8k) (exibe o caminho para a raiz da árvore de trabalho)
  • $ g.. zimfw (1k) (altera o diretório atual para o nível superior da árvore de trabalho.)
Hotschke
fonte
1

Eu queria expandir o excelente comentário de Daniel Brockman.

A definição git config --global alias.exec '!exec 'permite fazer coisas como git exec makeporque, como man git-configafirma:

Se a expansão do alias for prefixada com um ponto de exclamação, será tratada como um comando shell. [...] Observe que os comandos do shell serão executados no diretório de nível superior de um repositório, que pode não ser necessariamente o diretório atual.

Também é útil saber que esse $GIT_PREFIXserá o caminho para o diretório atual em relação ao diretório de nível superior de um repositório. Mas, sabendo que é apenas metade da batalha ™. A expansão de variáveis ​​do Shell torna bastante difícil de usar. Então, eu sugiro usar bash -cassim:

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

outros comandos incluem:

git exec pwd
git exec make
Bruno Bronosky
fonte
1

Caso alguém precise de uma maneira compatível com POSIX, sem a necessidade de gitexecutável:

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

Se você deseja apenas a funcionalidade como parte de um script, remova o shebang e substitua a última git_root_recurse_parentlinha por:

git_root() {
    (git_root_recurse_parent)
}
andorinha
fonte
Advertência: Se isso NÃO for chamado em um repositório git, a recursão não terminará.
AH
A @AH Second ifstatement deveria verificar se você alterou o diretório na recursão. Se ele permanecer no mesmo diretório, assume que você está preso em algum lugar (por exemplo /) e freia a recursão. O bug foi corrigido e agora deve funcionar como esperado. Obrigado por apontar isso.
swalog 12/02/19
0

Hoje tive que resolver isso sozinho. Resolvi em C # conforme eu precisava para um programa, mas acho que ele pode ser reescrito. Considere este domínio 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
fonte