LINUX.ORG.RU

Скачать все версии одного файла

 


0

1

Есть проект на Гитхабе, там файл со справочной таблицей в CSV, старые версии которой могут быть полезны. Может ли git как-то выкачать все коммиты для заданного файла, не качая всё остальное? Нагугленные рецепты требуют клонировать всё.

Адрес не называю, чтобы не не начали обсуждать проект, и тему не потёрли как оффтопик.

Пока предложили следующее:

git clone --filter=blob:none --no-checkout https://github.com/{owner}/{project}
cd {project}
git sparse-checkout set <path/to/file>
git checkout

Хеши коммитов: git log --pretty=format:"%h" path/to/file > hashes.txt

И файл для каждого: git show $hash:path/to/file > $hash.txt

Но извлечение по одному файлу по хешу через git show получается довольно медленно — ~55 файлов в минуту. Хотелось бы побыстрее.
UPD: Чем старее коммит, тем медленнее. 30 000-е извлекаются со скоростью ~14 файлов в минуту, хотя файлы на порядок меньше объёмом.

UPD2: Создание *.patch файлов: git format-patch --root -o /path/to/patches-directory path/to/file — работает гораздо быстрее и надёжнее. Но для больших объёмов нужно увеличить параметр ядра vm.max_map_count: sysctl -w vm.max_map_count=... от рута. (По умолчанию 65530.)

★★★★★

Последнее исправление: question4 (всего исправлений: 9)

В три шага:

  1. выкачать структуру репы: git clone --filter=blob:none --no-checkout <url>
  2. сделать репу "разряженной": git sparse-checkout set <path/to/file>
  3. вытянуть нужные блобы из репы: git checkout
r--r--r--
()
Последнее исправление: r--r--r-- (всего исправлений: 1)
Ответ на: комментарий от dataman

gh repo read-file <файл> --repo <владелец/репозиторий> --ref <тег или коммит> --output <файл>

Версия 2.93.0. Пишет:

unknown flag: --repo

Usage:  gh repo <command> [flags]

Available commands:
  archive
  autolink
  clone
  create
  delete
  deploy-key
  edit
  fork
  gitignore
  license
  list
  rename
  set-default
  sync
  unarchive
  view

Я правильно понимаю, что если до последнего коммита, --ref не нужен?

question4 ★★★★★
() автор топика
Ответ на: комментарий от question4

Версия 2.93.0. Пишет:

Команды read-* добавили в v2.95.0:

Two new preview commands read repository contents without cloning:

Read a single file to stdout

gh repo read-file README.md –repo cli/cli

Read from a specific branch, tag, or commit

gh repo read-file go.mod –ref v2.94.0 –repo cli/cli

Write a file to disk (use –clobber to overwrite)

gh repo read-file README.md –output ./README.md –repo cli/cli

List the entries in a directory

gh repo read-dir script –repo cli/cli

Я правильно понимаю, что если до последнего коммита, –ref не нужен?

Да.

dataman ★★★★★
()
Последнее исправление: dataman (всего исправлений: 1)
Ответ на: комментарий от r--r--r--

выкачать структуру репы: git clone –filter=blob:none –no-checkout

Выкачало 300 мегабайт. Как я понял, общий объём — много гигабайт.

сделать репу «разряженной»: git sparse-checkout set <path/to/file>

Ошибка:

fatal: не найден git репозиторий (или один из родительских каталогов): .git

И в каком виде надо задавать URL и путь? Правильные ли URL вида https://github.com/ayutaz/piper-plus и путь к файлу docs/api-reference/phoneme-mapping.md ? (несвязанный проект для примера) Я пробовал и https://github.com/ayutaz/piper-plus/blob/dev/docs/api-reference/phoneme-mapping.md, и blob/dev/docs/api-reference/phoneme-mapping.md, и docs/api-reference/phoneme-mapping.md — одинаковая ошибка.

question4 ★★★★★
() автор топика
Ответ на: комментарий от dataman

Команды read-* добавили в v2.95.0:

Обновился. Но эта команда качает только указанный коммит. А мне нужны все. Одним файлом, или 10 000 файлов, или 10 000 дифов — неважно.

question4 ★★★★★
() автор топика
Ответ на: комментарий от r--r--r--

Спасибо, разобрался. 2-ю команду надо запускать в директории, где .git, а не где 1-ю.

git log -- path/to/file работает.

question4 ★★★★★
() автор топика
Последнее исправление: question4 (всего исправлений: 1)
Ответ на: комментарий от dataman

Все коммиты или версии?

Именно все коммиты. А их много. Через API gh api -XGET 'repos/{owner}/{repo}/commits' -f path=path/to/dir/or/file -F per_page=1000 даёт только последние 300, вместо 1000.

question4 ★★★★★
() автор топика

Я бы уже на питоне скрипт написал, который просто проходил по ветке и сравнивал хэш нужного файла. Хэш поменялся - создал папку с коммитом в имени и скопировал туда файл. Быстрее будет, чем возня с этим gh.

skyman ★★★★★
()
Ответ на: комментарий от skyman

Я бы уже на питоне скрипт написал, который просто проходил по ветке и сравнивал хэш нужного файла.

Как эти хеши извлекать из удалённого хранилища средствами git-а?

question4 ★★★★★
() автор топика
Ответ на: комментарий от question4

Так а они реально нужны в количестве нескольких тысяч почти одинаковых файлов? Можно же в любой момент переключиться на нужный коммит $git checkout <commit> и увидеть соответствующую версию нужного файла…

PeleWin
()
Ответ на: комментарий от PeleWin

Так а они реально нужны в количестве нескольких тысяч почти одинаковых файлов?

Тоже этого не понимаю. :)

dataman ★★★★★
()
Ответ на: комментарий от PeleWin

Так а они реально нужны в количестве нескольких тысяч почти одинаковых файлов?

Да. Интересуют данные, которые когда-то там были, и затем были удалены.

question4 ★★★★★
() автор топика
Ответ на: комментарий от question4

1000 мало. Там 60 000 :)

$ cat gh-list-commits.sh:

#!/usr/bin/env bash
set -euo pipefail

owner="${1:?usage: $0 <owner> <repo> [batch_size]}"
repo="${2:?usage: $0 <owner> <repo> [batch_size]}"
batch="${3:-100}"

cursor= 
hasNext=true
while $hasNext; do
  query_template='query($o:String!,$r:String!,$c:String){
    repository(owner:$o,name:$r){
      defaultBranchRef{target{...on Commit{
        history(first:BATCHSIZE,after:$c){
          pageInfo{hasNextPage endCursor}
          nodes{oid}
        }
      }}}
    }
  }'
  query="${query_template/BATCHSIZE/$batch}"
  result=$(gh api graphql \
    -f query="$query" \
    -f o="$owner" -f r="$repo" \
    -F c="${cursor:-null}" \
    --jq '.')
  echo "$result" | jq -r '.data.repository.defaultBranchRef.target.history.nodes[] | "\(.oid)"'
  hasNext=$(echo "$result" | jq -r '.data.repository.defaultBranchRef.target.history.pageInfo.hasNextPage')
  cursor=$(echo "$result" | jq -r '.data.repository.defaultBranchRef.target.history.pageInfo.endCursor')
done
dataman ★★★★★
()
Ответ на: комментарий от dataman

Этот скрипт использует gh, чтобы запрашивать хеши коммитов порциями по 100 штук? И если подождать, должен выдать на stdout их все? Спасибо.

Но боюсь, запрашивать каждую версию с удалённого сервера по хешу будет очень долго. Локально скачанные извлекаются со скоростью от ~150 кб в секунду самые свежие, до ~4 кб в секунду 30 000 коммитов назад. Когда я создавал тему, рассчитывал что есть какая-нибудь функция для массового извлечения диффов.

question4 ★★★★★
() автор топика

Есть подозрение, что если git show слишком медленно, то git тут в принципе не помощник и быстрее и проще всего будет воспользоваться апи github.

Lrrr ★★★★★
()
Ответ на: комментарий от r--r--r--

если тебе нужны именно diff, а не история коммитов.

Мне нужны все строки, которые когда-либо бывали в этом файле.

git format-patch --root -o /path/to/patches-directory

Выводит патчи для всех файлов, а мне нужно

git format-patch --root -o /path/to/patches-directory path/to/file

Спасибо.

question4 ★★★★★
() автор топика
Последнее исправление: question4 (всего исправлений: 1)
Ответ на: комментарий от anonymous

git log --pretty=format:"%H:path/to/file" | xargs git rev-parse | xargs -n1 git cat-file -p

Судя по размеру, получается раза в полтора медленнее, чем при скачивании по одному файлу по хешам. За 6,5 часов в 1 файл скачалось столько же, сколько во много файлов за 4. (Точнее, 250 и 400 минут.)

question4 ★★★★★
() автор топика
Последнее исправление: question4 (всего исправлений: 1)
Ответ на: комментарий от r--r--r--

git format-patch --root -o /path/to/patches-directory

git format-patch --root -o /path/to/patches-directory path/to/file на 7574-м файле падает с ошибкой
fatal: packfile .git/objects/pack/pack-....pack cannot be mapped, check sys.vm.max_map_count and/or RLIMIT_DATA: Невозможно выделить память

Увеличил в 100 раз: sysctl -w vm.max_map_count=6553000. Жду.

P.S. За минуту сгенерировало первые ~28000 патчей (думал, ~15% общего объёма, оказалось 8%), после чего стало качать данные по сети, и скорость резко упала.

P.P.S. В итоге управилось за 16 минут. Ещё раз спасибо.

question4 ★★★★★
() автор топика
Последнее исправление: question4 (всего исправлений: 2)
Ответ на: комментарий от question4

git format-patch --root -o

И похоже, этот метод надёжнее git show. Около сотни файлов не скачались. Через патчи скачалось на 175 уникальных строк больше. (Из 1,7 млн.)

question4 ★★★★★
() автор топика
  • Markdown
Пустая строка (два раза Enter) начинает новый абзац. Знак '>' в начале абзаца выделяет абзац курсивом цитирования.
Внимание: прочитайте описание разметки Markdown.
Используйте Ctrl-Enter для размещения комментария