Чи є спосіб отримати кореневий каталог 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.