Окончания строк перепутаны в Git – как отслеживать изменения из другой ветки после огромного исправления окончания строки?
Вопрос
Мы работаем со сторонним PHP-движком, который регулярно обновляется.Релизы хранятся в отдельной ветке git, а наша ветка — это master ветка.
Таким образом, мы сможем применять к нашему форку патчи из новых выпусков движка.
Моя проблема в том, что после многих коммитов в нашу ветку я понял, что первоначальный импорт движка был выполнен с окончаниями строк CRLF.
Я конвертировал каждый файл в LF, но это потребовало огромного коммита: 100 тысяч строк было удалено и 100 тысяч добавлено, что явно нарушает то, что мы намеревались сделать:легко объединять патчи из заводских версий стороннего движка.
Что мне следует знать?Как я могу это исправить?У меня уже есть сотни коммитов на нашем форке.
Было бы хорошо каким-то образом выполнить фиксацию исправления концов строки после первоначального импорта и перед ветвлением нашей собственной вилки, а затем удалить эту огромную фиксацию окончания строки позже в истории.
Однако я понятия не имею, как это сделать в Git.
Спасибо!
Решение
Наконец мне удалось решить эту проблему.
Ответ:
git filter-branch --tree-filter '~/Scripts/fix-line-endings.sh' -- --all
fix-line-endings.sh содержит:
#!/bin/sh
find . -type f -a \( -name '*.tpl' -o -name '*.php' -o -name '*.js' -o -name '*.css' -o -name '*.sh' -o -name '*.txt' -iname '*.html' \) | xargs fromdos
После того, как все окончания строк были исправлены во всех деревьях во всех коммитах, я выполнил интерактивную перебазировку и удалил все коммиты, которые исправляли окончания строк.
Теперь мой репозиторий чистый и свежий, готовый к публикации :)
Примечание для посетителей:не делайте этого, если ваш репозиторий был отправлен/клонирован, потому что это сильно все испортит!
Другие советы
В дальнейшем избегайте этой проблемы с помощью core.autocrlf
настройка, описанная в git config --help
:
ядро.autocrlf
Если это правда, заставляет git преобразовать
CRLF
в конце строк текстовых файлов дляLF
при чтении из файловой системы и обратном преобразовании при записи в файловую систему.Переменная может быть установлена вinput
, в этом случае преобразование происходит только при чтении из файловой системы, но файлы записываются с помощьюLF
в конце строк.Файл считается «текстовым» (то есть подвергатьсяautocrlf
механизм) на основе файлаcrlf
атрибут, или еслиcrlf
не указано, в зависимости от содержимого файла.Видеть gitattributes.
Вы посмотрели на git rebase
?
Вам нужно будет перебазировать историю вашего репозитория следующим образом:
- зафиксировать исправления терминатора строки
- начать перебазирование
- сначала оставьте стороннюю фиксацию импорта
- применить исправления терминатора строки
- примените другие патчи
Однако вам нужно понять, что это будет перерыв все последующие репозитории — те, которые клонированы из вашего родительского репозитория.В идеале вы начнете с нуля.
Обновлять:пример использования:
target=`git rev-list --max-count=3 HEAD | tail -n1`
get rebase -i $target
Запустит сеанс перебазирования для последних трех коммитов.
мы избегаем этой проблемы в будущем с помощью:
1) все используют редактор, который удаляет конечные пробелы, и мы сохраняем все файлы с помощью LF.
2) если 1) не удается (может - кто-то случайно сохраняет его в CRLF по какой-либо причине), у нас есть сценарий предварительной фиксации, который проверяет наличие символов CRLF:
#!/bin/sh
#
# An example hook script to verify what is about to be committed.
# Called by git-commit with no arguments. The hook should
# exit with non-zero status after issuing an appropriate message if
# it wants to stop the commit.
#
# To enable this hook, rename this file to "pre-commit" and set executable bit
# original by Junio C Hamano
# modified by Barnabas Debreceni to disallow CR characters in commits
if git rev-parse --verify HEAD 2>/dev/null
then
against=HEAD
else
# Initial commit: diff against an empty tree object
against=4b825dc642cb6eb9a060e54bf8d69288fbee4904
fi
crlf=0
IFS="
"
for FILE in `git diff-index --cached $against`
do
fhash=`echo $FILE | cut -d' ' -f4`
fname=`echo $FILE | cut -f2`
if git show $fhash | grep -EUIlq $'\r$'
then
echo $fname contains CRLF characters
crlf=1
fi
done
if [ $crlf -eq 1 ]
then
echo Some files have CRLF line endings. Please fix it to be LF and try committing again.
exit 1
fi
exec git diff-index --check --cached $against --
Этот скрипт использует GNU grep и работает в Mac OS X, однако его следует протестировать перед использованием на других платформах (у нас были проблемы с Cygwin и BSD grep).
3) В случае обнаружения каких-либо ошибок в пробелах мы используем следующий скрипт для ошибочных файлов:
#!/usr/bin/env php
<?php
// Remove various whitespace errors and convert to LF from CRLF line endings
// written by Barnabas Debreceni
// licensed under the terms of WFTPL (http://en.wikipedia.org/wiki/WTFPL)
// handle no args
if( $argc <2 ) die( "nothing to do" );
// blacklist
$bl = array( 'smarty' . DIRECTORY_SEPARATOR . 'templates_c' . DIRECTORY_SEPARATOR . '.*' );
// whitelist
$wl = array( '\.tpl', '\.php', '\.inc', '\.js', '\.css', '\.sh', '\.html', '\.txt', '\.htc', '\.afm',
'\.cfm', '\.cfc', '\.asp', '\.aspx', '\.ascx' ,'\.lasso', '\.py', '\.afp', '\.xml',
'\.htm', '\.sql', '\.as', '\.mxml', '\.ini', '\.yaml', '\.yml' );
// remove $argv[0]
array_shift( $argv );
// make file list
$files = getFileList( $argv );
// sort files
sort( $files );
// filter them for blacklist and whitelist entries
$filtered = preg_grep( '#(' . implode( '|', $wl ) . ')$#', $files );
$filtered = preg_grep( '#(' . implode( '|', $bl ) . ')$#', $filtered, PREG_GREP_INVERT );
// fix whitespace errors
fix_whitespace_errors( $filtered );
///////////////////////////////////////////////////////////////////////////////////////////////
///////////////////////////////////////////////////////////////////////////////////////////////
// whitespace error fixer
function fix_whitespace_errors( $files ) {
foreach( $files as $file ) {
// read in file
$rawlines = file_get_contents( $file );
// remove \r
$lines = preg_replace( "/(\r\n)|(\n\r)/m", "\n", $rawlines );
$lines = preg_replace( "/\r/m", "\n", $lines );
// remove spaces from before tabs
$lines = preg_replace( "/\040+\t/m", "\t", $lines );
// remove spaces from line endings
$lines = preg_replace( "/[\040\t]+$/m", "", $lines );
// remove tabs from line endings
$lines = preg_replace( "/\t+$/m", "", $lines );
// remove EOF newlines
$lines = preg_replace( "/\n+$/", "", $lines );
// write file if changed and set old permissions
if( strlen( $lines ) != strlen( $rawlines )){
$perms = fileperms( $file );
// Uncomment to save original files
//rename( $file, $file.".old" );
file_put_contents( $file, $lines);
chmod( $file, $perms );
echo "${file}: FIXED\n";
} else {
echo "${file}: unchanged\n";
}
}
}
// get file list from argument array
function getFileList( $argv ) {
$files = array();
foreach( $argv as $arg ) {
// is a direcrtory
if( is_dir( $arg ) ) {
$files = array_merge( $files, getDirectoryTree( $arg ) );
}
// is a file
if( is_file( $arg ) ) {
$files[] = $arg;
}
}
return $files;
}
// recursively scan directory
function getDirectoryTree( $outerDir ){
$outerDir = preg_replace( ':' . DIRECTORY_SEPARATOR . '$:', '', $outerDir );
$dirs = array_diff( scandir( $outerDir ), array( ".", ".." ) );
$dir_array = array();
foreach( $dirs as $d ){
if( is_dir( $outerDir . DIRECTORY_SEPARATOR . $d ) ) {
$otherdir = getDirectoryTree( $outerDir . DIRECTORY_SEPARATOR . $d );
$dir_array = array_merge( $dir_array, $otherdir );
}
else $dir_array[] = $outerDir . DIRECTORY_SEPARATOR . $d;
}
return $dir_array;
}
?>
Одним из решений (не обязательно лучшего) было бы использование git-фильтр-ветвь переписать историю, чтобы всегда использовать правильные окончания строк.Это должно быть лучшим решением, чем интерактивное перебазирование, по крайней мере, для большего количества коммитов;также может быть проще справиться со слияниями, используя git-filter-branch.
Это, конечно, при условии, что история была не опубликовано (репозиторий не был клонирован).