C'è un modo per ottenere la directory radice git in un comando?


670

Mercurial ha un modo di stampare la directory principale (che contiene .hg) tramite

hg root

Esiste qualcosa di equivalente in git per ottenere la directory che contiene la directory .git?


Buona sceneggiatura Emil. Rendo lo script disponibile online e ho consentito la possibilità di aggiungere un file / directory come argomento. github.com/Dieterbe/git-scripts/commit/…
Dieter_be

Anche per chiunque fosse curioso o alla ricerca bzr rootveniva usato molto a Bazaar
Kristopher Ives il

Si prega di prendere nota di 'gcd': Git-Aware 'cd' relativo alla radice del repository con completamento automatico su jeetworks.org/node/52 .
Micha Wiedenmann,


1
@MichaWiedenmann: il link è morto.
d33tah,

Risposte:


1114

Sì:

git rev-parse --show-toplevel

Se vuoi replicare il comando Git più direttamente, puoi creare un alias :

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

e ora git rootfunzionerà come hg root.


Nota : in un sottomodulo verrà visualizzata la directory principale del sottomodulo e non il repository principale. Se stai usando Git> = 2.13 o versioni successive, c'è un modo in cui i sottomoduli possono mostrare la directory root del superprogetto. Se il tuo git è più vecchio di quello, vedi questa altra risposta.


148
Definisco sempre in git config --global alias.exec '!exec 'modo da poter fare cose del genere git exec make. Questo funziona perché gli alias di shell vengono sempre eseguiti nella directory di livello superiore.
Daniel Brockman,

8
Questo è ciò che hg rootfa. Stampa la directory di livello superiore del repository estratto. Non ti passa ad esso (e, in effetti, non potrebbe farlo a causa dell'interazione dell'intero concetto di directory corrente e shell).
Onnipotente l'

14
Piccola avvertenza con questa soluzione: seguirà tutti i collegamenti simbolici. Quindi, se sei dentro ~/my.proj/foo/bared ~/my.projè collegato a symlink ~/src/my.proj, il comando sopra ti sposterà su ~/src/my.proj. Potrebbe essere un problema, se quello che vuoi fare dopo non è agnostico.
Franci Penov,

3
Come posso usarlo da un hook Git? In particolare, sto eseguendo un hook post-merge e ho bisogno di ottenere la directory root effettiva del repository git locale.
Derek,

12
Questa soluzione (e molte altre in questa pagina che ho provato) non funziona all'interno di un hook git. Per essere più precisi, non funziona quando ci si trova nella .gitdirectory del progetto .
Dennis,

97

La manpagina per git-config(sotto Alias ) dice:

Se l'espansione dell'alias è preceduta da un punto esclamativo, verrà trattata come un comando shell. [...] Notare che i comandi della shell verranno eseguiti dalla directory di livello superiore di un repository, che potrebbe non essere necessariamente la directory corrente.

Quindi, su UNIX puoi fare:

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

2
Quindi, se hai un comando "git root", come puoi metterlo in un alias? Se lo inserisco in my .zshrce definisco `alias cg =" cd $ (git root) ", la parte $ () viene valutata al momento del sorgente e punta sempre a ~ / dotfiles, poiché è lì che si trova il mio zshrc .
zelk,

2
@cormacrelf Non lo metti in un alias di shell. Puoi metterlo in una funzione di shell o in uno script.
Conrad Meyer,

4
@cormacrelf Mettilo tra virgolette singole anziché doppie, quindi non verrà espanso al momento della definizione, ma in fase di esecuzione.
clacke

3
@Mechanicalsnail Davvero? Quindi cosa ti aspetteresti che faccia un repo esterno?
Alois Mahdal,

1
@AloisMahdal in realtà per me non fallisce al di fuori di un repository, riporta semplicemente il CWD.
justinpitts

91

È --show-toplevelstato aggiunto solo di recente git rev-parseo perché nessuno ne parla?

Dalla git rev-parsepagina man:

   --show-toplevel
       Show the absolute path of the top-level directory.

1
Grazie per averlo sottolineato; senza il tuo aiuto, sarebbe difficile per me indovinare, a git-rev-parsecausa del suo nome che suggerisce che si tratta di elaborare le specifiche di revisione. A proposito, sarei felice di vedere git --work-treelavori simili a git --exec-path[=<path>]: "Se non viene fornito alcun percorso, git stamperà l'impostazione corrente"; almeno, IMO, sarebbe un posto logico per cercare una tale funzionalità.
imz - Ivan Zakharyaschev,

4
In particolare, puoi alias root = rev-parse --show-toplevelin gitconfig.
Lumaca meccanica

2
in altre parole, puoi fare git config --global alias.root "rev-parse --show-toplevel"e poi git rootsarai in grado di fare il lavoro
polarità

@RyanTheLeach git rev-parse --show-toplevelfunziona quando l'ho provato in un sottomodulo. Stampa la directory principale del sottomodulo git. Cosa stampa per te?
Wisbucky,

2
Ero confuso perché questa risposta ha duplicato la risposta migliore. Si scopre che la risposta principale è stata modificata da --show-cdupa --show-top-levelfebbraio 2011 (dopo che questa risposta è stata inviata).
Wisbucky

54

Che ne dici di " git rev-parse --git-dir"?

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

L' --git-diropzione sembra funzionare.

Dalla pagina di manuale di git rev-parse :

--git-dir

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

Puoi vederlo in azione in questo git setup-shscript .

Se ti trovi in ​​una cartella di sottomodulo, con Git> = 2.13 , usa :

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

Se stai usando git rev-parse --show-toplevel, assicurati che sia con Git 2.25+ (Q1 2020) .


3
Oh aspetta, questo è stato vicino ma ottiene l'attuale dir .git, non la base del repository git. Inoltre, la directory .git potrebbe essere altrove, quindi questo non è esattamente quello che stavo cercando.
Wojo

2
Bene, ora vedo cosa stavi effettivamente cercando. --show-cdup è più appropriato allora. Lascio la mia risposta per illustrare la differenza tra le due opzioni.
VonC,

2
Sembra anche che questo comando fornisca un percorso relativo per .gitse sei già nella directory principale. (Almeno, lo fa su msysgit.)
Blair Holloway il

2
+1, questa è l'unica risposta che risponde alla domanda originale "ottenere la directory che contiene la directory .git?", Divertente vedere che l'OP stesso menziona "la directory .git potrebbe essere altrove".
FabienAndre,

1
Questa è l'unica soluzione che funziona su Windows e fornisce un risultato analizzabile quando si usano i sottomoduli git (ad es. C: \ repository \ myrepo \ .git \ modules \ mysubmodule). Ciò la rende la soluzione più affidabile, specialmente in uno script riutilizzabile.
chriskelly,

36

Per scrivere una semplice risposta qui, in modo che possiamo usare

git root

per fare il lavoro, configura semplicemente il tuo git usando

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

e quindi potresti voler aggiungere quanto segue al tuo ~/.bashrc:

alias cdroot='cd $(git root)'

in modo che tu possa semplicemente usare cdrootper andare in cima al tuo repository.


Molto bella! In definitiva conveniente!
scravy

Un'ottima risposta, grazie!
AVarf

26

Se sei già al livello superiore o no in un repository git cd $(git rev-parse --show-cdup)ti porterà a casa (solo cd). cd ./$(git rev-parse --show-cdup)è un modo per risolverlo.


7
Un'altra opzione è quella di citarlo: cd "$(git rev-parse --show-cdup)". Questo funziona perché cd ""non ti porta da nessuna parte, piuttosto che indietro $HOME. Ed è buona prassi citare comunque le invocazioni $ (), nel caso in cui emettano qualcosa con spazi (non che questo comando in questo caso, però).
ctrueden,

Questa risposta funziona bene anche se hai cambiato la directory nel tuo repository git tramite un collegamento simbolico . Alcuni degli altri esempi non funzionano come previsto quando il repository git si trova in un collegamento simbolico in quanto non risolvono la directory principale di git rispetto a bash $PWD. Questo esempio risolverà la radice di git rispetto a $PWDinvece di realpath $PWD.
Damien Ó Ceallaigh,

16

Per calcolare il percorso assoluto della directory radice di git corrente, dire per l'uso in uno script di shell, utilizzare questa combinazione di readlink e git rev-parse:

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

git-rev-parse --show-cdupti dà il numero giusto di ".." per accedere alla radice dal tuo CWD, o la stringa vuota se sei alla radice. Quindi anteporre "./" per gestire il caso stringa vuoto e utilizzare readlink -fper tradurre in un percorso completo.

Puoi anche creare un git-rootcomando nel tuo PERCORSO come script di shell per applicare questa tecnica:

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

(Quanto sopra può essere incollato in un terminale per creare git-root e impostare i bit di esecuzione; lo script effettivo è nelle righe 2, 3 e 4.)

E poi saresti in grado di correre git rootper ottenere la radice del tuo albero attuale. Si noti che nello script della shell, usare "-e" per far uscire la shell se il rev-parse fallisce in modo da poter ottenere correttamente lo stato di uscita e il messaggio di errore se non ci si trova in una directory git.


2
I tuoi esempi non funzioneranno se il percorso della directory "root" git contiene spazi. Usa sempre "$(git rev-parse ...)"invece di hack come ./$(git rev-parse ...).
Mikko Rantalainen,

readlink -fnon funziona allo stesso modo su BSD. Vedi questo SO per soluzioni alternative. La risposta Python probabilmente funzionerà senza installare nulla: python -c 'import os, sys; print(os.path.realpath(sys.argv[1]))' "$(git rev-parse --show-cdup)".
Bluu,

16

Come altri hanno notato, il nocciolo della soluzione è usare git rev-parse --show-cdup. Tuttavia, ci sono alcuni casi limite da affrontare:

  1. Quando il cwd è già la radice dell'albero di lavoro, il comando produce una stringa vuota.
    In realtà produce una linea vuota, ma ordina la sostituzione della striscia dall'interruzione di riga finale. Il risultato finale è una stringa vuota.

    La maggior parte delle risposte suggerisce di anteporre l'uscita in ./modo che un'uscita vuota diventi "./"prima di essere alimentata cd.

  2. Quando GIT_WORK_TREE è impostato su una posizione che non è il genitore del CWD, l'output può essere un percorso assoluto.

    Prepararsi ./è sbagliato in questa situazione. Se a ./è anteposto a un percorso assoluto, diventa un percorso relativo (e si riferiscono alla stessa posizione solo se il cwd è la directory principale del sistema).

  3. L'output può contenere spazi bianchi.

    Questo si applica davvero solo nel secondo caso, ma ha una soluzione semplice: usa le doppie virgolette attorno alla sostituzione del comando (e tutti gli usi successivi del valore).

Come hanno notato altre risposte, possiamo fare cd "./$(git rev-parse --show-cdup)" , ma questo si interrompe nel caso del secondo bordo (e nel caso del terzo bordo se lasciamo le virgolette doppie).

Molte shell trattano cd ""come no-op, quindi per quelle shell potremmo farlo cd "$(git rev-parse --show-cdup)"(le doppie virgolette proteggono la stringa vuota come argomento nel primo caso limite e preservano gli spazi bianchi nel caso terzo limite). POSIX afferma che il risultato di cd ""non è specificato, quindi potrebbe essere meglio evitare di fare questo assunto.

Una soluzione che funziona in tutti i casi precedenti richiede un test di qualche tipo. Fatto esplicitamente, potrebbe apparire così:

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

No cdviene fatto per il primo caso limite.

Se è accettabile eseguire cd .il primo caso limite, è possibile eseguire il condizionale nell'espansione del parametro:

cdup="$(git rev-parse --show-cdup)" && cd "${cdup:-.}"

Perché non usi semplicemente "git config --global --add alias.root '! Pwd'" e un alias di shell gitroot = 'cd git root' che usa la risposta sopra di te?
Jason Axelson

1
L'uso di un alias potrebbe non essere possibile, ad esempio se si desidera scriverlo e non si può fare affidamento su un alias personalizzato.
azzurrato

1
I sottomoduli sono un altro caso angolare.
Ryan The Leach

14

Nel caso in cui stai alimentando questo percorso con Git stesso, usa :/

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

12

Soluzioni brevi che funzionano con sottomoduli, ganci e all'interno di .git directory

Ecco la risposta breve che molti vorranno:

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

Funzionerà ovunque in un albero di lavoro git (incluso all'interno della .gitdirectory), ma presuppone che vengano chiamate le directory del repository .git(che è l'impostazione predefinita). Con i sottomoduli, questo andrà alla radice del repository contenente più esterno.

Se vuoi arrivare alla radice del sottomodulo corrente usa:

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

Per eseguire facilmente un comando nella tua radice del sottomodulo, sotto [alias]nella tua .gitconfig, aggiungi:

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

Questo ti permette di fare facilmente cose come git sh ag <string>

Soluzione robusta che supporta nomi diversi o esterni .gito$GIT_DIR directory.

Nota che $GIT_DIRpotrebbe puntare da qualche parte esterno (e non essere chiamato.git ), quindi la necessità di ulteriori controlli.

Metti questo nel tuo .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"
}

Esegui digitando git_root(dopo il riavvio del guscio: exec bash)


Questo codice è in fase di revisione del codice nella funzione bash robusta per trovare la radice di un repository git . Guarda lì per gli aggiornamenti.
Tom Hale,

Bello. Più complesso di quello che ho proposto 7 anni fa ( stackoverflow.com/a/958125/6309 ), ma comunque +1
VonC

1
La soluzione più breve in tal senso è: (root=$(git rev-parse --git-dir)/ && cd ${root%%/.git/*} && git rev-parse && pwd)ma questo non copre i messaggi esterni $GIT_DIRche sono chiamati diversi da.git
Tom Hale

Mi chiedo se questo prendere in considerazione le molteplici worktrees che sono ora possibili in git 2.5+ ( stackoverflow.com/a/30185564/6309 )
VonC

Il tuo commento link a "Funzione bash robusta per trovare il root ..." ora dà un 404.
ErikE

8

Per modificare la risposta "git config" solo un po ':

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

e ripulisci il percorso. Molto bella.


7

Se stai cercando un buon alias per farlo, oltre a non saltare in aria cdse non sei in una git dir:

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


6

Questo alias di shell funziona sia in un sottodirio git, sia al livello superiore:

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

aggiornato per utilizzare la sintassi moderna anziché i backtick:

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

5
alias git-root='cd \`git rev-parse --git-dir\`; cd ..'

Tutto il resto fallisce a un certo punto o andando nella home directory o semplicemente fallendo miseramente. Questo è il modo più rapido e breve per tornare al GIT_DIR.


Sembra che 'git rev-parse --git-dir' sia la soluzione più pulita.
Stabledog,

3
Questo fallisce, quando $GIT_DIRviene staccato dalla workingtree usando .git-Files e gitdir: SOMEPATH. Di conseguenza, ciò fallisce anche per i sottomoduli, dove $GIT_DIRcontiene .git/modules/SUBMODULEPATH.
Tino,

5

Ecco uno script che ho scritto che gestisce entrambi i casi: 1) repository con uno spazio di lavoro, 2) repository nudo.

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

git-root (file eseguibile nel tuo percorso):

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

Speriamo che sia utile


1
Keith, grazie per il suggerimento, ho incluso la sceneggiatura.
jdsumsion,

In realtà ho votato a favore della risposta migliore perché ho capito che l' git execidea è più utile nei repository non nudi. Tuttavia, questo script nella mia risposta gestisce correttamente il caso nudo o non nudo, che potrebbe essere utile a qualcuno, quindi lascio questa risposta qui.
jdsumsion,

1
Tuttavia, ciò non riesce in git submodules dove $GIT_DIRcontiene qualcosa di simile /.git/modules/SUBMODULE. Inoltre, si assume che la .gitdirectory faccia parte del worktree nel caso non nudo.
Tino,

4
$ git config alias.root '!pwd'
# then you have:
$ git root

Questo alias fallirà come globale. Usa questo invece (in ~ / .gitconfig): [alias] findroot = "!f () { [[ -d ".git" ]] && echo "Found git in [pwd ]" && exit 0; cd .. && echo "IN pwd" && f;}; f"
FractalSpace

perché downvote? Si prega di evidenziare eventuali errori o suggerire invece miglioramenti.
FractalSpace

1
Per me git config --global alias.root '!pwd'funziona. Non sono stato in grado di individuare alcun caso in cui si comporta diversamente dalla variante non globale. (Unix, git 1.7.10.4) A proposito: il tuo findrootrichiede un /.gitper evitare una ricorsione infinita.
Tino,

4

A partire da Git 2.13.0 , supporta una nuova opzione per mostrare il percorso del progetto root, che funziona anche quando viene utilizzato dall'interno di un sottomodulo:

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

git-scm.com/docs/… . Interessante. Quello devo essermelo perso. +1
VonC,

3
Avviso però: "Non emette nulla se il repository corrente non viene utilizzato come sottomodulo da alcun progetto."
VonC,

Grazie per il commento! Non me ne ero accorto.
Jordi Vilalta Prat,

2

Alias ​​Shell preconfigurati in Frame Shell

Se si utilizza un framework di shell, potrebbe essere già disponibile un alias di shell:

  • $ grtin oh-my-zsh (68k) ( cd $(git rev-parse --show-toplevel || echo "."))
  • $ git-rootin prezto (8.8k) (visualizza il percorso della radice dell'albero di lavoro)
  • $ g.. zimfw (1k) (cambia la directory corrente al livello superiore dell'albero di lavoro.)

1

Volevo ampliare l'eccellente commento di Daniel Brockman.

La definizione git config --global alias.exec '!exec 'ti consente di fare cose come git exec makeperché, come man git-configafferma:

Se l'espansione dell'alias è preceduta da un punto esclamativo, verrà trattata come un comando shell. [...] Notare che i comandi della shell verranno eseguiti dalla directory di livello superiore di un repository, che potrebbe non essere necessariamente la directory corrente.

È anche utile sapere che $GIT_PREFIXsarà il percorso della directory corrente relativa alla directory di livello superiore di un repository. Ma sapendo che è solo metà della battaglia ™. L'espansione variabile della shell rende piuttosto difficile l'utilizzo. Quindi suggerisco di usare bash -ccosì:

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

altri comandi includono:

git exec pwd
git exec make

1

Nel caso in cui qualcuno abbia bisogno di un modo conforme a POSIX per farlo, senza necessità git eseguibile:

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 vuoi solo la funzionalità come parte di uno script, rimuovi lo shebang e sostituisci l'ultima git_root_recurse_parentriga con:

git_root() {
    (git_root_recurse_parent)
}

Avvertenza: se questo NON viene chiamato in un repository git, la ricorsione non termina affatto.
AH,

@AH La seconda ifistruzione doveva verificare se hai cambiato directory nella ricorsione. Se rimane nella stessa directory, presume che tu sia bloccato da qualche parte (ad esempio /) e frena la ricorsione. Il bug è stato corretto e ora dovrebbe funzionare come previsto. Grazie per segnalarlo.
swalog,

0

Ho dovuto risolvere questo da solo oggi. Risolto in C # perché ne avevo bisogno per un programma, ma immagino che possa essere riscritto con esattezza. Considera questo dominio pubblico.

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;

}
Utilizzando il nostro sito, riconosci di aver letto e compreso le nostre Informativa sui cookie e Informativa sulla privacy.
Licensed under cc by-sa 3.0 with attribution required.