Чи є спосіб отримати кореневий каталог git за допомогою однієї команди?


669

У Mercurial є спосіб друку кореневого каталогу (який містить .hg) через

hg root

Чи є щось еквівалентне в git, щоб отримати каталог, який містить каталог .git?


Хороший сценарій Еміль. Я зробив сценарій доступним в Інтернеті і дозволив додати файл / каталог як аргумент. github.com/Dieterbe/git-scripts/commit/…
— Dieter_be

Також для всіх, хто цікавився чи шукав, багато bzr rootбуло використано на Bazaar
— Крістофер Івз

Будь ласка, зверніть увагу на 'gcd': Git-Aware 'cd' Відносно кореневого сховища з автоматичним заповненням на jeetworks.org/node/52 .
— Micha Wiedenmann


1
@MichaWiedenmann: посилання мертва.
— d33tah

Відповіді:


1111

Так:

git rev-parse --show-toplevel

Якщо ви хочете скопіювати команду Git безпосередньо, ви можете створити псевдонім :

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

і тепер git rootбуде функціонувати так само hg root.


Примітка . У підмодулі це буде відображати кореневий каталог підмодуля, а не батьківське сховище. Якщо ви використовуєте Git> = 2.13 або вище, існує спосіб, що підмодулі можуть відображати кореневий каталог суперпроекту. Якщо ваш git старший за це, дивіться цю іншу відповідь.


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? Зокрема, я роблю гачок після злиття, і мені потрібно отримати фактичну кореневу директорію локального git repo.
— Дерек

12
Це рішення (та багато інших на цій сторінці, які я спробував) не працюють всередині гака. Якщо точніше, це не працює, якщо ви знаходитесь в .gitкаталозі проекту .
— Денніс

97

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

Якщо розширення псевдоніму встановлено префіксом оклику, воно буде розглядатися як команда оболонки. [...] Зауважте, що команди оболонки виконуватимуться з каталогу верхнього рівня репозиторію, який не обов'язково може бути поточним каталогом.

Отже, в UNIX ви можете:

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

2
Отже, якщо у вас є команда "git root", як ви можете це поставити в псевдонімі? Якщо я поміщаю його в свій .zshrcі визначаю `alias cg =" cd $ (git root) ", частина $ () оцінюється в початковий час і завжди вказує на ~ / dotfiles, оскільки саме там мій zshrc .
— zelk

2
@cormacrelf Ви не додаєте його до псевдоніма оболонки. Ви можете помістити його в оболонку або сценарій.
— Конрад Мейєр

4
@cormacrelf Помістіть його в одиничні лапки замість подвійних лапок, тоді він не буде розширений під час визначення, а під час виконання.
— clacke

3
@Mechanicalsnail Справді? То що, як ви очікували, це зробить поза репо?
— Алоїз Магдал

1
@AloisMahdal насправді для мене він не провалюється поза репо, він просто повідомляє cwd.
— justinpitts

90

Була --show-toplevelтільки недавно була доданаgit rev-parse або чому не ніхто згадки про нього?

На git rev-parseчоловіковій сторінці:

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

1
Дякуємо, що вказали на це; без вашої допомоги мені важко буде здогадуватися, git-rev-parseтому що його назва говорить про те, що мова йде про обробку специфікацій версії. BTW, я б радий побачити 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

The --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+ (Q1 2020) .


3
О зачекайте, це було близько, але він отримує фактичний .git dir, а не базу git repo. Крім того, каталог .git може бути в іншому місці, тому це не те, що я саме шукав.
— wojo

2
Правильно, я зараз бачу, що ви насправді шукали. - show-cdup є більш доречним тоді. Я залишаю свою відповідь, щоб проілюструвати різницю між двома варіантами.
— VonC

2
Також виявляється, що ця команда дає відносний шлях, .gitякщо ви вже знаходитесь в кореневій директорії. (Принаймні, це робиться на msysgit.)
— Блер Холлоуей,

2
+1, це єдина відповідь, яка відповідає на оригінальне запитання "отримати каталог, який містить каталог .git?", Забавно побачивши, що в самій ОП згадується "каталог .git може бути в іншому місці".
— FabienAndre

1
Це єдине рішення, яке працює у Windows і дає проаналізований результат при використанні підмодулів git (наприклад, c: \ repositories \ myrepo \ .git \ module \ mysubmodule). Це робить його найбільш надійним рішенням, особливо в сценарії для багаторазового використання.
— chriskelly

36

Тут написати просту відповідь, щоб ми могли використовувати

git root

щоб виконати роботу, просто налаштуйте свій git за допомогою

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

і тоді ви можете додати наступне до свого ~/.bashrc:

alias cdroot='cd $(git root)'

так що ви можете просто використовувати cdrootдля переходу до вершини репо.


Дуже хороший! Зрештою зручно!
— scravy

Чудова відповідь, дякую!
— AVarf

26

Якщо ви вже перебуваєте на найвищому рівні або не знаходитесь у сховищі git, cd $(git rev-parse --show-cdup)вас відвезуть додому (просто компакт-диск). cd ./$(git rev-parse --show-cdup)це один із способів виправити це.


7
Інший варіант полягає в лапки: cd "$(git rev-parse --show-cdup)". Це працює, тому що cd ""бере вас нікуди, а не назад $HOME. І найкраще практикувати цитування викликів $ () у будь-якому випадку, якщо вони виводять щось із пробілами (не в цьому випадку ця команда буде в цьому випадку).
— ctrueden

Ця відповідь спрацює добре, навіть якщо ви змінили каталог на своє git repo через симпосилання . Деякі з інших прикладів не спрацьовують так, як очікувалося, коли ваше git repo знаходиться під символьним посиланням, оскільки вони не вирішують кореневий каталог git відносно bash $PWD. Цей приклад дозволить вирішити корінь git відносно $PWDзамість realpath $PWD.
— Дамієн Ó Ceallaigh

16

Для обчислення абсолютного шляху поточного кореневого каталогу git, скажімо, для використання в скрипті оболонки, використовуйте цю комбінацію readlink та git rev-parse:

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

git-rev-parse --show-cdupдає вам потрібну кількість ".." s, щоб потрапити до кореня з вашого 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", щоб змусити вихід оболонки, якщо ревіз аналізу не виконаний, щоб ви могли правильно отримати статус виходу та повідомлення про помилку, якщо ви не перебуваєте в каталозі git.


2
Ваші приклади зламаються, якщо шлях до каталогу "gut" до кореня містить пробіли. Завжди використовуйте "$(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)".
— Блу

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
— Том Хейл,

Цікаво , якщо це взяти до уваги кілька worktrees, які тепер можна в мерзотника 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 subdir або на найвищому рівні:

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

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

Все інше виходить з ладу в якийсь момент, або переходячи до домашнього каталогу, або просто нещасно виходить з ладу. Це найшвидший і найкоротший спосіб повернутися до GIT_DIR.


Здається, що "git rev-parse - git-dir" - найчистіше рішення.
— Stabledog

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
)

Сподіваємось, це корисно.


1
Кіт, дякую за пропозицію, я включив сценарій.
— jdsumsion

Я насправді відмовився від головної відповіді, тому що зрозумів, що git execідея корисніша у неоголених сховищах. Однак цей скрипт у моїй відповіді правильно обробляє голий проти неоголений випадок, який може бути корисним комусь, тому я залишаю цю відповідь тут.
— jdsumsion

1
Однак це не вдається git submodule, де $GIT_DIRміститься щось подібне /.git/modules/SUBMODULE. Крім того, ви припускаєте, що .gitкаталог є частиною робочого дерева в неоголеному випадку.
— Тіно

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

Цей псевдонім провалиться як глобальний. Використовуйте це замість (у ~ / .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) BTW: Вам findrootпотрібно a, /.gitщоб уникнути нескінченних рекурсій.
— Тіно

4

Оскільки Git 2.13.0 , він підтримує нову опцію, щоб показати шлях кореневого проекту, який працює навіть при використанні всередині підмодуля:

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

git-scm.com/docs/… . Цікаво. Я, мабуть, пропустив цього. +1
— VonC

3
Але попередження: "Виводить нічого, якщо поточний сховище не використовується як підмодуль жодним проектом."
— VonC

Дякуємо за коментар! Я цього не помічав.
— Jordi Vilalta Prat

2

Попередньо налаштовані псевдоніми оболонки в раковинах Shell

Якщо ви використовуєте рамку оболонки, можливо, вже є псевдонім оболонки:

  • $ grtв о-мі-зш (68 к) ( cd $(git rev-parse --show-toplevel || echo "."))
  • $ git-rootin prezto (8,8k) (відображає шлях до робочого кореня дерева)
  • $ g.. zimfw (1k) (змінює поточний каталог на верхній рівень робочого дерева.)

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

Якщо ви просто хочете, щоб функціональність була частиною сценарію, видаліть шебанг і замініть останній git_root_recurse_parentрядок на:

git_root() {
    (git_root_recurse_parent)
}

Caveat: Якщо це НЕ викликається в git repo, тоді рекурсія взагалі не закінчується.
— AH

@AH Другий ifвислів повинен був перевірити, чи ви змінили каталог в рекурсії. Якщо він залишається в одному каталозі, він передбачає, що ви десь застрягли (наприклад /) і гальмує рекурсію. Помилка виправлена, і тепер повинна працювати як очікувалося. Дякуємо, що вказали на це.
— swalog

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;

}
Використовуючи наш веб-сайт, ви визнаєте, що прочитали та зрозуміли наші Політику щодо файлів cookie та Політику конфіденційності.
Licensed under cc by-sa 3.0 with attribution required.