Как заменить текст из файлов в истории git?
Я всегда использовал основанный на интерфейсе git-клиент (smartGit) и поэтому не имел большого опыта работы с git-консолью.
Однако теперь я сталкиваюсь с необходимостью замены строки во всех файлах.txt из истории (то есть, не стирая весь файл, а просто подставляя строку). Я нашел следующую команду:
git filter-branch --tree-filter 'git ls-files -z "*.php" |xargs -0 perl -p -i -e "s#(PASSWORD1|PASSWORD2|PASSWORD3)#xXxXxXxXxXx#g"' -- --all
Я попробовал это, и, к сожалению, заметил, что, хотя пароль изменился, все двоичные файлы были повреждены. Изображения и т. Д. Будут повреждены.
Есть ли лучший способ сделать это, чтобы не повредить мои двоичные файлы?
Благодарю.
РЕДАКТИРОВАТЬ:
Я с чем-то перепутал. Фактический код, вызвавший повреждение двоичных файлов:
$ git filter-branch --tree-filter "find . -type f -exec sed -i -e 's/originalpassword/newpassword/g' {} \;"
Код вверху фактически удалил все файлы с моим паролем, как ни странно.
7 ответов
Вы можете избежать касания нежелательных файлов, передав -name "pattern"
в find
,
Это работает для меня:
git filter-branch --tree-filter "find . -name '*.php' -exec sed -i -e \
's/originalpassword/newpassword/g' {} \;"
Я бы порекомендовал использовать BFG Repo-Cleaner, более простую и быструю альтернативу git-filter-branch
специально предназначенный для перезаписи файлов из истории Git.
Вы должны тщательно выполнить следующие шаги здесь: https://rtyley.github.io/bfg-repo-cleaner/ - но основной бит просто так: скачайте JAR BFG (требуется Java 7 или выше) и выполните эту команду:
$ java -jar bfg.jar --replace-text replacements.txt -fi *.php my-repo.git
replacements.txt
Файл должен содержать все замены, которые вы хотите сделать, в таком формате (одна запись на строку - обратите внимание, что комментарии не должны быть включены):
PASSWORD1 # Replace literal string 'PASSWORD1' with '***REMOVED***' (default)
PASSWORD2==>examplePass # replace with 'examplePass' instead
PASSWORD3==> # replace with the empty string
regex:password=\w+==>password= # Replace, using a regex
regex:\r(\n)==>$1 # Replace Windows newlines with Unix newlines
Вся ваша история репозитория будет отсканирована, и .php
Для файлов (размером менее 1 МБ) будут выполнены замены: любая подходящая строка (которая отсутствует в вашем последнем коммите) будет заменена.
Полное раскрытие: я являюсь автором BFG Repo-Cleaner.
С Git 2.24 (4 квартал 2019 г.) git filter-branch
(и BFG) устарела.
Эквивалент был бы, используя newren/git-filter-repo
, и его примерный раздел:
cd repo
git filter-repo --path-glob '*.txt' --replace-text expressions.txt
с expressions.txt
:
literal:originalpassword==>newpassword
Больше информации на
git-filter-repo
/questions/31012680/kak-zamenit-tekst-iz-fajlov-v-istorii-git/55262075#55262075 дает основы, вот еще немного информации.
Установить
Начиная с git 2.5, по крайней мере, он не поставляется с mainline git, поэтому: https://superuser.com/questions/1563034/how-do-you-install-git-filter-repo/1589985#1589985
python3 -m pip install --user git-filter-repo
Советы по использованию
Вот наиболее распространенный подход, который я использую:
git filter-repo --replace-text <(echo 'my_password==>xxxxxxxx') HEAD
где:
- Подстановка процесса в Bash позволяет нам не создавать файл для простых замен
HEAD
влияет только на текущую ветку
Изменить только диапазон коммитов
Как изменить только диапазон коммитов с помощью git filter-repo вместо всей истории ветки?
git filter-repo --replace-text <(echo 'my_password==>xxxxxxxx') HEAD
Заменить с помощью Python API
Для более сложных замен вы можете использовать Python API, см.: Как использовать git filter-repo в качестве библиотеки с интерфейсом модуля Python?
Я создал файл в /usr/local/git/findsed.sh со следующим содержимым:
find . -name 'githubDirToSubmodule.sh' -exec sed -i '' -e 's/What I want to remove//g' {} \;
Я запустил команду:
git filter-branch --tree-filter "sh /usr/local/git/findsed.sh"
Объяснение команд
Когда вы запускаете git filter-branch, он проходит каждую ревизию, которую вы когда-либо фиксировали, одну за другой. --tree-filter запускает скрипт findsed.sh для каждой зафиксированной ревизии, сохраняет его и затем переходит к следующей ревизии.
Команда find находит определенный файл или набор файлов и выполняет (-exec) редактор sed для этого файла. sed - это команда, которая принимает регулярное выражение после s/ и заменяет его строкой между / и /g (пустым в моем примере). {} является ссылкой на путь к файлам, который был задан командой find. Путь к файлу подается в sed, чтобы sed знал, над чем работать. \; просто завершает команду -exec.
Разделение сценария оболочки и команды на отдельные части позволяет меньше усложнять, когда дело доходит до кавычек "" или "".
Особенности
Я успешно реализовал это на Mac, и, очевидно, sed - это конкретная (более старая?) Версия для Mac. Это имеет значение, так как иногда ведет себя по-разному. Убедитесь, что вы выполнили sed -i '', иначе он добавил "-e" в конец файлов, думая, что это то, что я хотел назвать своими файлами резервных копий. -i '' говорит, что не делайте резервные копии файлов, просто редактируйте файлы на месте, и резервный файл не требуется.
Указание -name 'filename.sh' помогло мне избежать еще одной проблемы, которую я не мог решить. Был другой файл с.sh, и этот файл заканчивался без символа новой строки. По какой-то причине sed добавит в конец символ новой строки, несмотря на то, что s/blah/blah/g не соответствует никому в этом файле. Поэтому вместо того, чтобы выяснить эту проблему, я просто сказал находке игнорировать все остальные файлы.
Дополнительные команды, которые работают
Кроме того, я обнаружил, что эти команды работают в файле findsed.sh (только одна команда за раз, а не несколько, поэтому закомментируйте # остальные):
find . -name '.publishNewZenPackFromGithub.sh.swp' -exec rm -f {} \;
find . -name '*' -exec grep -H PassToRemove {} \;
Наслаждайтесь!
Может быть проблема расширения оболочки. Если ветвь фильтра теряет кавычки "*.php"
к тому времени, когда он оценивает команду, он может расширяться до нуля, таким образом git ls-files -z
список всех файлов.
Вы можете проверить источник ветки фильтра или попробовать разные приемы цитирования, но я бы просто сделал однострочный сценарий оболочки, который выполняет ваш древовидный фильтр, и вместо этого пропустил этот сценарий.
Поскольку это появляется в Google дляgit replace text in history
, а поскольку использование инструментов, отличных от git, иногда приносит больше проблем, чем пользы, вот команда, которая заменит многострочный текст на всем пути от${COMMIT}
вперед кHEAD
.
Предупреждение: это НЕ для новичков. Оно использует
git filter-branch
, так что все его предостережения/подводные камни/и т.д. применять. Убедитесь, что вы зафиксировали/создали резервную копию всего, что вам нужно сохранить, чтобы не потерять данные.
С учетом сказанного создайте псевдоним в Bash следующим образом:
git config --global alias.filter-branch-replace-text '!main() { set -eu && if [ -n "${BASH_VERSION+x}" ]; then set -o pipefail; fi && local pattern patternq replacement replacementq commit && pattern="$1" && shift && replacement="$1" && shift && commit="$1" && shift && local sed_binary_flags="" && if [ msys = "${OSTYPE-}" ]; then sed_binary_flags="-b"; fi && patternq="$(printf "%s" "${pattern}" | sed ${sed_binary_flags} "s/'\''/'\''\\\\'\'''\''/g")." && patternq="'\''${patternq%.}'\''" && replacementq="$(printf "%s" "${replacement}" | sed ${sed_binary_flags} "s/'\''/'\''\\\\'\'''\''/g")." && replacementq="'\''${replacementq%.}'\''" && git filter-branch --tree-filter "for path in $(printf "%s\n" "$@" | sed ${sed_binary_flags} -e "s/'\''/'\''\\\\'\'''\''/g" -e "s/\(.*\)/'\''\1'\''/" | tr "\n" " ")"'\''; do if [ -f "${path}" ]; then perl -0777 -i -s -p -e "s/\\Q\$q\\E/\$s/sgm" -- -q='\''"${patternq}"'\'' -s='\''"${replacementq}"'\'' -- "${path}"; fi || break; done'\'' "${commit}~1..HEAD" --; } && main'
и затем вы можете вызвать его из Bash следующим образом:
git filter-branch-replace-text \
$')\r\n{' \
$') /* EOL */\r\n{' \
"${COMMIT}" \
src/*.txt
Обратите внимание, что это выполняет замену буквального текста, а не замены регулярного выражения.
Если вам нужны регулярные выражения, вам нужно удалить\Q
и\E
в команде Perl (которая выполняет экранирование) и правильно экранирует строки, необходимые дляs/$q/$s/sgm
командовать собой.
И если вы хотите красиво напечатать скрипт, вы можете отформатировать его следующим образом:
(f="$(git --no-pager config --get alias.filter-branch-replace-text)" && eval "${f%&&*}" && declare -f "${f%%()*}")