Как передать программно сгенерированный список файлов в `git filter-branch`?

Я разделяю часть git-репо, чтобы создать новый репо, и пытаюсь использовать git filter-branch вести историю файлов, которые перемещаются в новый проект. Я знаю о --subdirectory-filter но это не очень хорошее решение, потому что файлы, которые я извлекаю, не отображаются точно в один подкаталог. Лучший вариант, который я нашел, это --index-filter, используется следующим образом:

git filter-branch -f --index-filter 'git read-tree --empty && git reset -q "${GIT_COMMIT}" -- <list of files>' --prune-empty -f

Кажется, это работает, за исключением того, что я хотел бы иметь возможность программно генерировать список файлов для хранения, чтобы я мог итеративно уточнить этот список. В настоящее время я пытаюсь получить список файлов, которые я хочу сохранить в другом файле, и добавить его к строке, представляющей команду, которая будет выполняться для каждого коммита, следующим образом:

tmp=$(cat ~/to_keep.txt) && git filter-branch -f --index-filter 'git read-tree --empty && git reset -q "${GIT_COMMIT}" -- '$tmp --prune-empty -f

К сожалению, это приводит к

fatal: bad flag '--prune-empty' used after filename

Кажется, даже простое отображение файлов вызывает проблемы:

tmp=$(echo a.txt b.txt) && git filter-branch -f --index-filter 'git read-tree --empty && git reset -q "${GIT_COMMIT}" -- '$tmp --prune-empty -f
fatal: ambiguous argument 'b.txt': unknown revision or path not in the working tree.
Use '--' to separate paths from revisions, like this:
'git <command> [<revision>...] -- [<file>...]'

Я также попытался объединить строки ранее:

tmp1=$(echo a.txt b.txt) && tmp2='git read-tree --empty && git reset -q "${GIT_COMMIT}" -- ' && tmp3=${tmp2}${tmp1} && git filter-branch -f --index-filter $tmp3 --prune-empty -f
fatal: ambiguous argument 'read-tree': unknown revision or path not in the working tree.
Use '--' to separate paths from revisions, like this:
'git <command> [<revision>...] -- [<file>...]'

Я предполагаю, что это просто конкатенация не происходит, как я ожидаю в оболочке. Кто-нибудь знает, как я могу сделать эту работу? Было бы здорово, если бы вы могли объяснить, что означают и эти ошибки. Благодарю.

1 ответ

Решение

Каждый аргумент к различным ...-filters должен быть одной строкой. Эта строка сохраняется как переменная оболочки:

    --index-filter)
            filter_index="$OPTARG"
            ;;

В соответствующий момент сценарий ответвления фильтра (находится в git-core подкаталог, например, /usr/libexec/git-core или же /usr/local/libexec/git-core) Является ли это:

    eval "$filter_index" < /dev/null ||
            die "index filter failed: $filter_index"

(за исключением коммит-фильтра, который запускается с /bin/sh -c "$filter_commit" ...).

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

Самый простой способ сделать это - начать с исходной команды:

git filter-branch -f --index-filter \
    'git read-tree --empty && git reset -q "${GIT_COMMIT}" -- <list of files>' \
    --prune-empty -f

(который работает, когда у вас есть статический список) и измените его, чтобы извлечь динамический список из ~/to_keep.txt, Я разделил оригинал на три строки частично для целей отображения, а также потому, что теперь мы можем сосредоточиться только на средней строке.

[Редактировать, чтобы исправить проблему новой строки, отмеченную в комментарии. Давайте сделаем псевдоним или функцию оболочки, xc, переводит переводы строк в пробелы]

xc() {
    tr '\n' ' '
}

"git read-tree --empty && git reset -q \"\${GIT_COMMIT}\" -- $(xc < ~/to_keep.txt)" \

или же:

'git read-tree --empty && git reset -q "${GIT_COMMIT}" -- '"$(xc < ~/to_keep.txt)" \

или, как вы пытались (но с одним изменением):

'git read-tree --empty && git reset -q "${GIT_COMMIT}" -- '"$tmp" \

(установив tmp=$(xc < ~/to_keep.txt)).

Обратите внимание, что ни одна из этих правильных вещей, если любое из имен файлов содержит пробелы. Например, предположим, что файл называется a file (со встроенным бланком). eval будет разбивать аргументы в пробелах, а git reset Команда получит имена a а также file как два отдельных аргумента.

Пока у вас нет таких имен файлов, вам не нужно беспокоиться об этом.

Еще одна потенциальная проблема, если этот список файлов становится очень длинным. Вы можете столкнуться с ограничениями ядра на количество аргументов, которые могут быть отправлены в один файл. Вы должны быть в состоянии использовать xargs чтобы решить это (и, в этом отношении, с некоторой работой и использованием -0, для обработки пробелов в именах файлов).

Другие вопросы по тегам