Чи можу я використовувати Git для пошуку відповідних імен файлів у сховищі?


76

Просто скажіть, що у мене є файл: "HelloWorld.pm" у декількох підкаталогах у сховищі Git.

Я хотів би виконати команду, щоб знайти повні шляхи всіх файлів, що відповідають "HelloWorld.pm":

Наприклад:

/path/to/repository/HelloWorld.pm
/path/to/repository/but/much/deeper/down/HelloWorld.pm
/path/to/repository/please/dont/make/me/search/through/the/lot/HelloWorld.pm

Як я можу використовувати Git для ефективного пошуку всіх повних шляхів, що відповідають заданому імені файлу?

Я усвідомлюю, що можу це зробити за допомогою команди Linux / Unix find, але я сподівався уникнути сканування всіх підкаталогів, що шукають екземпляри імені файлу.

Відповіді:


114

git ls-filesнадасть вам список усіх файлів у поточному стані сховища (кеш чи індекс). Ви можете передати шаблон, щоб отримати файли, що відповідають цьому шаблону.

git ls-files HelloWorld.pm '**/HelloWorld.pm'

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

git grep some-string -- HelloWorld.pm '**/HelloWorld.pm'

ls-файли також можуть мати шаблон.
Джош Лі

1
Не забудьте використовувати '** / HelloWorld.pm' замість '* / HelloWorld.pm' для пошуку збігів на будь-якій глибині сховища. Приклад OP містить файли різних рівнів.
Джон Рікс,

8
'git ls-files' не містить файлів у сховищі. У ньому перелічені імена файлів в індексі (проміжній області) або робочому дереві. Цілком нормально, щоб ім'я файлу знаходилося десь у сховищі, але не в індексі чи робочому дереві - ім'я файлу може бути в іншій гілці, ніж та, яку ви зараз перевірили, наприклад. Відповідь @GregHewgill тут слід вважати більш правильною.
stevegt

1
(Пропущено вікно редагування коментарів за 5 хвилин ...) Відповіді Уве Гюдера та Діна Холла по суті розширюються на відповіді Грега, перебираючи всі гілки та теги, обробляючи справи з файлами, названими в інших гілках (або видаленими) .
stevegt

1
зауважте, що HelloWorld.pm не буде знайдено у корені вашого проекту. У цьому випадку вам потрібно скористатисяgit ls-files 'HelloWorld.pm' '*/HelloWorld.pm'
Chris Maes,

44

Хм, спочатку питання стосувалось сховища. Репозиторій містить більше 1 коміту (принаймні в загальному випадку), але відповіді, дані перед пошуком, здійснюються лише за допомогою одного коміту.

Оскільки я не зміг знайти відповідь, яка реально шукає всю історію комітів, я написав швидкий скрипт грубої сили git-find-by-name, який враховує (майже) усі коміти.

#! /bin/sh
tmpdir=$(mktemp -td git-find.XXXX)
trap "rm -r $tmpdir" EXIT INT TERM

allrevs=$(git rev-list --all)
# well, nearly all revs, we could still check the log if we have
# dangling commits and we could include the index to be perfect...

for rev in $allrevs
do
  git ls-tree --full-tree -r $rev >$tmpdir/$rev 
done

cd $tmpdir
grep $1 * 

Можливо, існує більш елегантний спосіб.

Зверніть увагу на тривіальний спосіб передавання параметра у grep, тому він буде відповідати частинам імені файлу. Якщо цього не бажано, закріпіть вираз пошуку та / або додайте відповідні параметри grep.

Для глибоких історій результат може бути занадто галасливим, я подумав про сценарій, який перетворює список версій у діапазон, як протилежне тому, що може зробити git rev-list. Але поки що це залишається думкою.


Чудовий сценарій. Однак я не зміг ним скористатися, оскільки мій репозиторій git настільки великий, що скрипт залив мій жорсткий диск :(
Arne Böckmann

@ ArneBöckmann Просто перемістіть команду grep в останній цикл і видаліть все після кожного grep.
Уве Гюдер

9
Ваш код може бути зроблений в однострочнікі: git rev-list --all | xargs -I '{}' git ls-tree --full-tree -r '{}' | grep '.*HelloWorld\.pm$'. Це також вирішує проблему затоплення жорсткого диска.
subhacom

@subhacom ваш oneliner повинен бути прийнятою відповіддю
плита

24

Спробуйте:

git ls-tree -r HEAD | grep HelloWorld.pm

1
Або на Windows:git ls-tree -r HEAD | findstr HelloWorld.pm
Джон Рікс,

man git ls-treeпоказує, що -rозначає "Повторне перетворення в піддерева". Я не знаю, що це означає. Ви можете пояснити, що це означає?
Габріель Стейплз

@JohnRix, востаннє я перевірив, якщо ви використовуєте термінал, наданий Git для Windows , який я настійно рекомендую для Windows, він підтримує загальні команди Linux, такі як конвеєр до grep, запуск bash-скриптів тощо, тому ця відповідь повинна працювати нормально як є. Спробуйте і дайте мені знати. Я повністю кинув Windows для Ubuntu кілька років тому.
Габріель Стейплз,

@GabrielStaples, справедливо чи неправильно, я трохи суттєвий, коли йдеться про альтернативні термінали в Windows (можливо, частково через те, що CygWin відмовив його багато років тому), і, як правило, дотримуюся найнижчого загального знаменника, який буде завжди бути мені доступним. (З іншого боку, випуск WSL 2 для Windows 10 неминучий, і повідомляється, що він буде працювати дуже ефективно, тому, можливо, я нарешті попрощаюся зі старим командним рядком Windows!)
Джон Рікс,

До речі, це -rповинно викликати команду ls-tree для пошуку підкаталогів у сховищі.
Джон Рікс,


4

[Я визнаю, це трохи зловживання коментарями, але я поки що не можу коментувати і думав, що покращу відповідь @ uwe-geuder.]

#!/bin/bash
#
#

# I'm using a fixed string here, not a regular expression, but you can easily
# use a regular expression by altering the call to grep below.
name="$1"

# Verify usage.
if [[ -z "$name" ]]
then
    echo "Usage: $(basename "$0") <file name>" 1>&2
    exit 100
fi  

# Search all revisions; get unique results.
while IFS= read rev
do
    # Find $name in $rev's tree and only use its path.
    grep -F -- "$name" \
        <(git ls-tree --full-tree -r "$rev" | awk '{ print $4 }')
done < \
    <(git rev-list --all) \
    | sort -u

Знову ж, +1 до @ uwe-geuder для чудової відповіді.

Якщо вас цікавить сам BASH:

Якщо ви не гарантуєте розбиття слів у циклі for (як при використанні масиву, подібного до цього for item in "${array[@]}":), я настійно рекомендую використовувати, while IFS= read var ; do ... ; done < <(command)коли вихідні дані команди, які ви переглядаєте, відокремлені новими рядками (або read -d''коли вихідні дані розділені нульовий рядок $'\0'). Хоча git rev-list --allгарантовано використовувати 40-байтові шістнадцяткові рядки (без пробілів), я ніколи не люблю ризикувати. Тепер я можу легко змінити команду git rev-list --allна будь-яку команду, яка створює рядки

Я також рекомендую використовувати вбудовані механізми BASH для введення вхідних даних та фільтрування виводу замість тимчасових файлів.


Не впевнений, чому використовується так багато замін процесів, коли можна просто конвеювати:git rev-list --all | while read rev; do; git ls-tree --full-tree -r $rev | cut -c54- | fgrep -- "$name"; done | sort -u
Саймон Бучан

Файл відлуння сценарію, але не те, яку редакцію він знайшов. Корисно також повторити, $revщоб показати, в яких редакціях він знайдений.
LB2,

2

Сценарій Уве Геудера (@ uwe-geuder) чудовий, але насправді немає необхідності скидати кожен з виходів ls-дерева у свій власний каталог, нефільтрований.

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


сутності можуть змінюватися, і для зручності краще все-таки включити фрагмент коду у свою відповідь, особливо коли він короткий. Рекомендую скопіювати фрагмент коду із суті до відповіді. Просто залиште посилання на суть - це все, щоб цитувати це як джерело на випадок, якщо ви коли-небудь оновите суть, але не цю відповідь.
Габріель Стейплз

Тепер, коли я уважніше розглядаю ваш сценарій, я бачу, що це насправді дуже корисно. Але для вашої відповіді потрібен 1) заголовок: # How to find a long-lost file by searching all commitsта 2) код із суті, безпосередньо вставлений у цю відповідь.
Габріель Стейплз
Використовуючи наш веб-сайт, ви визнаєте, що прочитали та зрозуміли наші Політику щодо файлів cookie та Політику конфіденційності.
Licensed under cc by-sa 3.0 with attribution required.