Линейные окончания, испорченные в Git - как отслеживать изменения из другой ветки после огромного финиша окончания строки?
Мы работаем с сторонним движком PHP, который получает регулярные обновления. Релизы хранятся на отдельной ветки в git, а наша вилка - ведущая ветвь.
Таким образом мы сможем применить исправления к нашей вилке из новых версий движка.
Моя проблема заключается в том, что после многих коммитов в нашу ветку я понял, что первоначальный импорт движка был выполнен с завершением строки CRLF.
Я преобразовал каждый файл в LF, но это сделало огромную фиксацию, с удалением 100 тыс. строк и добавлением 100 тыс. строк, что, очевидно, нарушает то, что мы намеревались сделать: легко слить патчи из factory выпусков этого стороннего движка.
Что я могу знать? Как я могу это исправить? У меня уже есть сотни коммитов на нашей вилке.
Что было бы хорошо, так или иначе сделать фиксацию конца строки после первоначального импорта и до того, как разветкить нашу собственную вилку и удалить эту огромную строку, завершающую фиксацию позже в истории.
Однако я понятия не имею, как это сделать в Git.
Спасибо!
Ответы
Ответ 1
Наконец-то мне удалось его решить.
Ответ:
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
После того, как все окончания строк были зафиксированы во всех деревьях во всех коммитах, я сделал интерактивную перезагрузку и удалил все фиксации, которые фиксировали окончания строк.
Теперь мое репо чистое и свежее, готовое к нажатию:)
Примечание для посетителей: не делайте этого, если ваше репо было нажато/клонировано, потому что это будет бесполезно!
Ответ 2
Вперед, избегайте этой проблемы с помощью параметра core.autocrlf
, задокументированного в git config --help
:
core.autocrlf
Если true, то git конвертировать CRLF
в конец строк в текстовых файлах до LF
при чтении из файловой системы и конвертировать в обратном порядке при записи в файловую систему. Переменная может быть установлена на input
, и в этом случае преобразование происходит только при чтении из файловой системы, но файлы выписываются с помощью LF
в конце строк. Файл считается "текстом" (т.е. Подвергается механизму autocrlf
) на основе атрибута CRLF
или если CRLF
не указывается, на основе содержимого файла. См. gitattributes.
Ответ 3
Вы посмотрели git rebase
?
Вам нужно будет заново основать историю вашего репозитория, как показано ниже:
- зафиксировать исправления терминатора строки
- начать перезагрузку
- сначала оставить импорт стороннего импорта
- применить исправления терминатора строки
- примените ваши другие патчи.
Однако вам нужно понять, что это приведет к поломке всех расположенных ниже по потоку репозиториев - тех, которые клонированы из вашего родительского репо. В идеале вы начнете с нуля с ними.
Обновление: использование образца:
target=`git rev-list --max-count=3 HEAD | tail -n1`
get rebase -i $target
Запустит сеанс rebase для последних 3 коммитов.
Ответ 4
Одним из решений (не обязательно лучшим) было бы использовать git-filter-branch, чтобы переписать историю, чтобы всегда использовать правильные окончания строк. Это должно быть лучшим решением для интерактивной переустановки, по крайней мере для большего количества коммитов; также было бы легче справиться с слияниями с помощью git -filter-branch.
Это, конечно, предполагает, что история не была опубликована (репозиторий не был клонирован).
Ответ 5
мы избегаем этой проблемы в будущем:
1) каждый использует редактор, который разделяет конечные пробелы, и мы сохраняем все файлы с LF.
2) если 1) терпит неудачу (он может - кто-то случайно сохраняет его в CRLF по любой причине), у нас есть pre-commit script, который проверяет символы 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 --
Этот script использует GNU grep и работает на Mac OS X, однако его следует протестировать перед использованием на других платформах (у нас были проблемы с Cygwin и BSD grep)
3) Если мы найдем ошибки пробела, мы используем следующие script для ошибочных файлов:
#!/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;
}
?>