Есть ли способ получить корневой каталог git в одной команде?

670

Mercurial имеет способ печати корневого каталога (который содержит .hg) через

hg root

Есть ли что-то эквивалентное в git, чтобы получить каталог, содержащий каталог .git?

wojo
источник
Хороший сценарий Эмиль. Я сделал сценарий доступным в сети и разрешил добавить файл / каталог в качестве аргумента. github.com/Dieterbe/git-scripts/commit/…
Dieter_be
Также для всех, кто любопытствовал или искал, bzr rootчасто пользовался Базаром
Кристофер Айвз
Обратите внимание на «gcd»: Git-Aware «cd» Относительно корня репозитория с автозаполнением на jeetworks.org/node/52 .
Миха Виденманн
1
Рассмотрите мой ответ нижеgit rev-parse --git-dir , как объяснено в этом комментарии
VonC
1
@MichaWiedenmann: ссылка мертва.
d33tah

Ответы:

1114

Да:

git rev-parse --show-toplevel

Если вы хотите реплицировать команду Git более напрямую, вы можете создать псевдоним :

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

и теперь git rootбудет функционировать так же, как hg root.


Примечание . В подмодуле будет отображаться корневой каталог подмодуля, а не родительский репозиторий. Если вы используете Git> = 2.13 или выше, есть способ, чтобы подмодули могли показать корневой каталог суперпроекта. Если ваш мерзавец старше этого, посмотрите этот другой ответ.

baudtack
источник
148
Я всегда определяю, git config --global alias.exec '!exec 'чтобы я мог делать такие вещи, как git exec make. Это работает, потому что псевдонимы оболочки всегда выполняются в каталоге верхнего уровня.
Даниэль Брокман
8
Это то, что hg rootделает. Он распечатывает каталог верхнего уровня вашего извлеченного хранилища. Он не переключает вас на это (и на самом деле он не мог этого сделать из-за того, как взаимодействует вся концепция текущего каталога и вашей оболочки).
Всевышний
14
Небольшое предостережение с этим решением - оно будет следовать любым символическим ссылкам. Таким образом, если вы находитесь в системе ~/my.proj/foo/barи ~/my.projимеете символическую ссылку ~/src/my.proj, приведенная выше команда переместит вас в ~/src/my.proj. Может быть проблемой, если то, что вы хотите сделать после этого, не зависит от дерева.
Франси Пенов
3
Как я могу использовать это из Git Hook? В частности, я делаю хук после слияния, и мне нужно получить фактический корневой каталог локального репозитория git.
Дерек
12
Это решение (и многие другие на этой странице, которые я пробовал) не работает внутри git hook. Чтобы быть более точным, это не работает, когда вы находитесь в .gitкаталоге проекта .
Деннис
97

manСтраница git-config(под псевдонимом ) говорит:

Если расширение псевдонима начинается с восклицательного знака, оно будет рассматриваться как команда оболочки. [...] Обратите внимание, что команды оболочки будут выполняться из каталога верхнего уровня репозитория, который необязательно может быть текущим каталогом.

Итак, в UNIX вы можете сделать:

git config --global --add alias.root '!pwd'
Скотт Линдсей
источник
2
Итак, если у вас есть команда «git root», как вы можете поместить ее в псевдоним? Если я добавлю его в my .zshrc, и я определю `alias cg =" cd $ (git root) ", то часть $ () будет вычислена в исходное время и всегда будет указывать на ~ / dotfiles, так как именно там находится мой zshrc ,
zelk
2
@cormacrelf Вы не помещаете это в псевдоним оболочки. Вы можете поместить это в функцию оболочки или скрипт.
Конрад Мейер
4
@cormacrelf Поместите его в одинарные кавычки вместо двойных кавычек, тогда он будет расширен не во время определения, а во время выполнения.
Clacke
3
@ Mechanicalsnail Правда? Так что вы ожидаете от внешнего репо?
Алоис Махдал
1
@AloisMahdal на самом деле для меня это не провал вне репо, он просто сообщает CWD.
Джастинпиттс
91

Был --show-toplevelдобавлен только недавно git rev-parseили почему никто не упоминает об этом?

Со git rev-parseстраницы руководства :

   --show-toplevel
       Show the absolute path of the top-level directory.
marc.guenther
источник
1
Спасибо за указание на это; без вашей помощи мне было бы трудно догадаться git-rev-parse- из-за названия, предполагающего, что он обрабатывает спецификации ревизий. Кстати, я был бы рад видеть git --work-treeработу, похожую на git --exec-path[=<path>]: «Если путь не указан, git напечатает текущую настройку»; по крайней мере, IMO, было бы логично найти такую ​​функцию.
imz - Иван Захарьящев
4
В частности, вы можете использовать псевдоним root = rev-parse --show-toplevelв вашем gitconfig.
Механическая улитка
2
иными словами, вы можете сделать, git config --global alias.root "rev-parse --show-toplevel"а затем git rootсможете сделать работу
неполярность
@RyanTheLeach git rev-parse --show-toplevelработает, когда я пробовал это в подмодуле. Он печатает корневой каталог подмодуля git. Что это печатает для вас?
wisbucky
2
Я был смущен, почему этот ответ дублировал верхний ответ. Оказывается, что главный ответ был изменен с --show-cdupдо --show-top-levelв феврале 2011 года (после того, как этот ответ был представлен).
wisbucky
54

Как насчет " git rev-parse --git-dir"?

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

--git-dirВариант представляется на работу.

Со страницы руководства git rev-parse :

--git-dir

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

Вы можете увидеть это в действии в этом git setup-shсценарии .

Если вы находитесь в папке подмодулей, с Git> = 2.13 , используйте :

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

Если вы используете git rev-parse --show-toplevel, убедитесь, что это с Git 2.25+ (первый квартал 2020 года) .

VonC
источник
3
Ой, подождите, это было близко, но оно получает фактическое .git dir, а не основание git repo. Кроме того, каталог .git может быть в другом месте, так что это не то, что я искал точно.
Wojo
2
Хорошо, теперь я вижу то, что вы на самом деле искали. --show-cdup более уместен тогда. Я оставляю свой ответ, чтобы проиллюстрировать разницу между двумя вариантами.
VonC
2
Также кажется, что эта команда дает относительный путь, .gitесли вы уже находитесь в корневом каталоге. (По крайней мере, на msysgit.)
Блэр Холлоуэй
2
+1, это единственный ответ, который отвечает на первоначальный вопрос «получить каталог, содержащий каталог .git?», Забавно видеть, что сам OP упоминает «каталог .git может быть где-то еще».
FabienAndre
1
Это единственное решение, которое работает в Windows AND и дает анализируемый результат при использовании подмодулей git (например, c: \ repositories \ myrepo \ .git \ modules \ mysubmodule). Это делает его наиболее надежным решением, особенно в скрипте многократного использования.
chriskelly
37

Чтобы написать простой ответ здесь, чтобы мы могли использовать

git root

чтобы сделать работу, просто настройте свой мерзавец с помощью

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

и тогда вы можете добавить следующее к вашему ~/.bashrc:

alias cdroot='cd $(git root)'

так что вы можете просто использовать, cdrootчтобы перейти к верхней части вашего репо.

nonopolarity
источник
Очень хорошо! В конечном итоге удобно!
Scravy
Отличный ответ, спасибо!
AVarf
26

Если вы уже находитесь на верхнем уровне или не в репозитории git cd $(git rev-parse --show-cdup), вы вернетесь домой (просто cd). cd ./$(git rev-parse --show-cdup)это один из способов исправить это.

Карл
источник
7
Другой вариант заключается в том, чтобы процитировать его cd "$(git rev-parse --show-cdup)". Это работает, потому что cd ""уносит вас в никуда, а не обратно $HOME. В любом случае, лучше всего вызывать вызовы $ (), если они выводят что-то с пробелами (хотя в этом случае эта команда не будет).
ctrueden
Этот ответ хорошо работает, даже если вы изменили каталог для своего git-репо по символической ссылке . Некоторые из других примеров не работают должным образом, когда ваше git-репо находится под символической ссылкой, поскольку они не разрешают корневой каталог git относительно bash $PWD. Этот пример разрешит корень git относительно $PWDвместо realpath $PWD.
Дэмиен Ó Келлай
16

Чтобы вычислить абсолютный путь к текущему корневому каталогу git, скажем, для использования в сценарии оболочки, используйте эту комбинацию readlink и git rev-parse:

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

git-rev-parse --show-cdupдает вам правильное число "..", чтобы добраться до корня с вашего cwd, или пустую строку, если вы находитесь в корне. Затем добавьте «./» для обработки пустого регистра строки и используйте readlink -fдля перевода на полный путь.

Вы также можете создать git-rootкоманду в своем PATH в качестве сценария оболочки, чтобы применить эту технику:

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

(Выше можно вставить в терминал, чтобы создать git-root и установить биты выполнения; фактический скрипт находится в строках 2, 3 и 4.)

И тогда вы сможете запустить, git rootчтобы получить корень вашего текущего дерева. Обратите внимание, что в сценарии оболочки используйте «-e», чтобы заставить оболочку завершиться, если rev-parse завершился неудачно, чтобы вы могли правильно получить статус выхода и сообщение об ошибке, если вы не находитесь в каталоге git.

Эмиль Сит
источник
2
Ваши примеры сломаются, если путь к каталогу git "root" содержит пробелы. Всегда используйте "$(git rev-parse ...)"вместо хаков, как ./$(git rev-parse ...).
Микко Ранталайнен
readlink -fне работает то же самое на BSD. Смотрите это ТАК для обходных путей. Ответ Python вероятно , будет работать без установки каких - либо: python -c 'import os, sys; print(os.path.realpath(sys.argv[1]))' "$(git rev-parse --show-cdup)".
Bluu
16

Как уже отмечали другие, суть решения заключается в использовании git rev-parse --show-cdup. Тем не менее, есть несколько крайних случаев:

  1. Когда cwd уже является корнем рабочего дерева, команда выдает пустую строку.
    На самом деле он выдает пустую строку, но подстановка команды удаляет разрыв задней строки. Конечный результат - пустая строка.

    В большинстве ответов предлагается добавлять к выводу выходные данные, ./так что пустой вывод становится "./"до его подачи cd.

  2. Когда для GIT_WORK_TREE задано местоположение, которое не является родительским для cwd, выходные данные могут быть абсолютным путем.

    Предвосхищение ./неправильно в этой ситуации. Если a ./добавляется к абсолютному пути, он становится относительным путем (и они ссылаются на одно и то же местоположение, только если cwd является корневым каталогом системы).

  3. Вывод может содержать пробелы.

    Это действительно применимо только во втором случае, но это легко исправить: используйте двойные кавычки вокруг подстановки команд (и любые последующие использования значения).

Как уже отмечалось в других ответах, мы можем это сделать cd "./$(git rev-parse --show-cdup)", но это нарушается в случае второго ребра (и случае третьего ребра, если мы исключим двойные кавычки).

Многие оболочки обрабатываются cd ""как неиспользуемые, поэтому для этих оболочек мы могли бы сделать это cd "$(git rev-parse --show-cdup)"(двойные кавычки защищают пустую строку в качестве аргумента в случае первого ребра и сохраняют пробелы в случае третьего ребра). POSIX говорит, что результат cd ""не определен, поэтому лучше не делать этого предположения.

Решение, которое работает во всех вышеперечисленных случаях, требует некоторого теста. Сделано явно, это может выглядеть так:

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

Нет cdсделано для первого краевого случая.

Если cd .для первого граничного случая допустимо выполнение , то условное выражение может быть выполнено в расширении параметра:

cdup="$(git rev-parse --show-cdup)" && cd "${cdup:-.}"
Крис Джонсен
источник
Почему бы вам просто не использовать "git config --global --add alias.root '! Pwd'" и псевдоним оболочки gitroot = 'cd git root', который использует ответ выше, который вы используете?
Джейсон Аксельсон
1
Использование псевдонима может быть невозможным, например, если вы хотите написать его и не можете использовать собственный псевдоним.
синеватый
1
Субмодули являются еще одним угловым случаем.
Райан Лич
14

На всякий случай, если вы подаете этот путь к самому Git, используйте :/

# 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

Короткие решения, которые работают с подмодулями, в хуках и внутри .gitкаталога

Вот краткий ответ, который больше всего захочется:

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

Это будет работать в любом месте рабочего дерева git (в том числе внутри .gitкаталога), но предполагается, что каталог (ы) хранилища вызываются .git(что является значением по умолчанию). С подмодулями это пойдет в корень самого внешнего содержащего репозитория.

Если вы хотите добраться до корня текущего субмодуля, используйте:

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

Чтобы легко выполнить команду в корне вашего подмодуля, [alias]в вашем .gitconfig, добавьте:

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

Это позволяет вам легко делать такие вещи, как git sh ag <string>

Прочные решение , которое поддерживает по- разному поименованные или внешние .gitили $GIT_DIRкаталоги.

Обратите внимание, что $GIT_DIRможет указывать куда-то внешнее (и не называться.git ), отсюда и необходимость дальнейшей проверки.

Поместите это в свой .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"
}

Выполнить его ввода git_root(после перезагрузки оболочки: exec bash)

Том Хейл
источник
Этот код проверяется кодом в функции Robust bash, чтобы найти корень репозитория git . Ищите там обновления.
Том Хейл,
Ницца. Более сложный, чем то, что я предложил 7 лет назад ( stackoverflow.com/a/958125/6309 ), но все еще +1
VonC
1
Более короткое решение в том же духе: (root=$(git rev-parse --git-dir)/ && cd ${root%%/.git/*} && git rev-parse && pwd)но это не распространяется на внешние $GIT_DIRs, имена которых отличаются от.git
Том Хейл,
Интересно, если принять во внимание несколько рабочих деревьев, которые теперь возможны в git 2.5+ ( stackoverflow.com/a/30185564/6309 )
VonC
Ссылка на ваш комментарий «Надежная функция bash для поиска корня ...» теперь дает 404.
ErikE
8

Чтобы немного изменить ответ «git config»:

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

и очистить путь. Очень хорошо.

Брюс К
источник
7

Если вы ищете хороший псевдоним для этого, плюс не взрывайтесь, cdесли вы не в git dir:

alias ..g='git rev-parse && cd "$(git rev-parse --show-cdup)"'
Дэн Хеберден
источник
6

Этот псевдоним оболочки работает независимо от того, находитесь ли вы в подкаталоге git или на верхнем уровне:

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

обновлен для использования современного синтаксиса вместо обратных галочек:

alias gr='[ ! -z $(git rev-parse --show-toplevel) ] && cd $(git rev-parse --show-toplevel || pwd)'
MERM
источник
5
alias git-root='cd \`git rev-parse --git-dir\`; cd ..'

Все остальное терпит неудачу в некоторый момент или идет в домашний каталог или просто терпит неудачу. Это самый быстрый и короткий способ вернуться к GIT_DIR.

Tocororo
источник
Похоже, что 'git rev-parse --git-dir' - самое чистое решение.
Конюшня
3
Это терпит неудачу, когда $GIT_DIRотсоединяется от рабочего дерева с помощью .git-Files и gitdir: SOMEPATH. Следовательно, это не работает и для подмодулей, где $GIT_DIRсодержится .git/modules/SUBMODULEPATH.
Тино
5

Вот скрипт, который я написал, который обрабатывает оба случая: 1) хранилище с рабочим пространством, 2) пустое хранилище.

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

git-root (исполняемый файл на вашем пути):

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

Надеюсь, это полезно.

jdsumsion
источник
1
Кит, спасибо за предложение, я включил сценарий.
jdsumsion
Я на самом деле проголосовал за лучший ответ, потому что понял, что git execидея более полезна в не голых репозиториях. Однако этот сценарий в моем ответе правильно обрабатывает случай «голый» или «не голый», который может быть кому-то полезен, поэтому я оставляю этот ответ здесь.
jdsumsion
1
Однако это не удается в git submodules, где $GIT_DIRсодержится что-то вроде /.git/modules/SUBMODULE. Также вы предполагаете, что .gitкаталог является частью рабочего дерева в не голом случае.
Тино
4
$ git config alias.root '!pwd'
# then you have:
$ git root
FractalSpace
источник
Этот псевдоним потерпит неудачу как глобальный. Используйте это вместо этого (в ~ / .gitconfig): [alias] findroot = "!f () { [[ -d ".git" ]] && echo "Found git in [pwd ]" && exit 0; cd .. && echo "IN pwd" && f;}; f"
FractalSpace
почему понизить? Пожалуйста, выделите любую ошибку или предложите улучшение.
FractalSpace
1
У меня git config --global alias.root '!pwd'работает. Я не смог обнаружить ни одного случая, когда он действует иначе, чем неглобальный вариант. (Unix, git 1.7.10.4) Кстати: findrootвам требуется a, /.gitчтобы избежать бесконечной рекурсии.
Тино
4

Начиная с Git 2.13.0 , он поддерживает новую опцию для отображения пути корневого проекта, которая работает даже при использовании изнутри подмодуля:

git rev-parse --show-superproject-working-tree
Хорди Вилалта Прат
источник
git-scm.com/docs/… . Интересно. Должно быть, я пропустил это. +1
VonC
3
Предупреждение: «Ничего не выводится, если текущий репозиторий не используется в качестве подмодуля каким-либо проектом».
VonC
Спасибо за комментарий! Я этого не заметил.
Джорди Вилалта Прат
2

Предварительно настроенные псевдонимы оболочки в оболочках

Если вы используете оболочку оболочки, возможно, уже существует псевдоним оболочки:

  • $ grtв о-о-о (68k) ( cd $(git rev-parse --show-toplevel || echo "."))
  • $ git-rootв презто (8.8к) (отображает путь к корню рабочего дерева)
  • $ g.. zimfw (1k) (изменяет текущий каталог на верхний уровень рабочего дерева.)
Hotschke
источник
1

Я хотел бы остановиться на превосходном комментарии Дэниела Брокмана.

Определение git config --global alias.exec '!exec 'позволяет вам делать что-то вроде, git exec makeпотому что, как man git-configговорится:

Если расширение псевдонима начинается с восклицательного знака, оно будет рассматриваться как команда оболочки. [...] Обратите внимание, что команды оболочки будут выполняться из каталога верхнего уровня репозитория, который необязательно может быть текущим каталогом.

Также удобно знать, что $GIT_PREFIXэто будет путь к текущему каталогу относительно каталога верхнего уровня репозитория. Но, зная, что это только полдела ™. Расширение переменной оболочки делает его довольно сложным в использовании. Поэтому я предлагаю использовать bash -cтак:

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

другие команды включают в себя:

git exec pwd
git exec make
Бруно Броноски
источник
1

На случай, если кому-то понадобится POSIX-совместимый способ сделать это без использования gitисполняемого файла:

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

Если вы просто хотите, чтобы функциональность была частью сценария, удалите shebang и замените последнюю git_root_recurse_parentстроку на:

git_root() {
    (git_root_recurse_parent)
}
swalog
источник
Предостережение: если это не вызывается в git-репо, то рекурсия вообще не заканчивается.
AH
@AH Второе ifутверждение должно было проверить, изменили ли вы каталог в рекурсии. Если он остается в том же каталоге, он предполагает, что вы застряли где-то (например /), и тормозит рекурсию. Ошибка исправлена ​​и теперь должна работать как положено. Спасибо за указание на это.
Сулог
0

Пришлось решить это самому сегодня. Решил это в C # так, как мне было нужно для программы, но, думаю, это можно переписать. Считайте, что это общественное достояние.

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;

}
Хилке Бонс
источник