Окончания строк в Git перепутались - как отследить изменения из другой ветки после огромного исправления окончания строки?

Мы работаем со сторонним движком PHP, который регулярно обновляется. Релизы хранятся в отдельной ветке в git, и наша ветвь является главной.

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

Моя проблема в том, что после множества коммитов в нашей ветке я понял, что первоначальный импорт движка был выполнен с окончаниями строк CRLF.

Я преобразовал каждый файл в LF, но это сделало огромную фиксацию, с удалением 100 тыс. Строк и добавлением 100 тыс. Строк, что, очевидно, нарушает то, что мы намеревались сделать: легко объединять патчи из заводских версий этого стороннего движка.

Что я должен знать? Как я могу это исправить? У меня уже есть сотни коммитов на нашей вилке.

Что было бы хорошо, так это как-то сделать фиксацию окончаний строк после первоначального импорта и до разветвления нашей собственной вилки и удалить эту огромную фиксацию окончания строки позже в истории.

Однако я понятия не имею, как это сделать в Git.

Спасибо!


person keo    schedule 18.06.2009    source источник


Ответы (5)


Наконец-то мне удалось это решить.

Ответ:

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

После того, как все окончания строк были исправлены во всех деревьях во всех коммитах, я провел интерактивную перебазировку и удалил все коммиты, которые исправляли окончания строк.

Теперь мое репо чистое и свежее, готово к отправке :)

Примечание для посетителей: не делайте этого, если ваше репо было отправлено / клонировано, потому что это сильно испортит работу!

person keo    schedule 29.06.2009
comment
Это круто! Однако в вашем сценарии есть проблемы с пробелами в именах файлов. Вместо этого я сделал что-то вроде этого: найти. -type f -print0 -a (-name '. [hc]' -o -name ' .p [yl]') | xargs -0 fromdos - person Enno; 06.03.2010
comment
Согласитесь, это круто. Тем не менее, один вопрос: подход здесь явно указывает, из каких расширений файлов удалять CR. Я хотел бы знать, можно ли вместо этого удалить CR из всех и только тех исправлений, которые эвристика обнаружения текста git будет учитывать текст. (Эвристика Git учитывает фактическое содержимое файла, а не имена файлов.) В любом случае, на первый взгляд кажется, что с core.autocrlf = true он будет работать даже лучше. - person Chris; 29.03.2012
comment
Не совсем то, о чем я спрашивал, но stackoverflow.com/a/3092511 предлагает подход, который использует команду файла unix, чтобы попытаться различить текстовые и нетекстовые файлы. - person Chris; 29.03.2012
comment
Ответы на Как определить если Git обрабатывает файл как двоичный или текстовый? действительно предлагает несколько творческих способов, которыми вы могли бы сделать то, о чем я спросил. - person Chris; 30.03.2012
comment
Между фильтрами .txt и .html отсутствует операнд -o. - person nextgentech; 01.02.2013
comment
@Enno Насколько я могу судить, параметр -print0 должен стоять после соответствующего выражения (прямо перед каналом). Использование его до того, как выражение будет соответствовать ВСЕМ файлам в моем тестировании - person nextgentech; 01.02.2013

В дальнейшем избегайте этой проблемы с помощью параметра core.autocrlf, описанного в git config --help:

core.autocrlf

Если true, заставляет git преобразовывать CRLF в конце строк в текстовых файлах в LF при чтении из файловой системы и преобразовывать в обратном порядке при записи в файловую систему. Переменная может быть установлена ​​в input, и в этом случае преобразование происходит только при чтении из файловой системы, но файлы записываются с LF в конце строк. Файл считается текстовым (т.е. подчиняется механизму autocrlf) на основании атрибута файла crlf или, если crlf не указан, на основе содержимого файла. См. gitattributes.

person Greg Bacon    schedule 08.01.2010
comment
Спасибо за совет! мы использовали эту функцию, но тогда вели себя очень некорректно (возможно, эти ошибки уже были исправлены) см. мой следующий ответ, чтобы узнать, как мы этого избежали. - person keo; 09.01.2010

Вы смотрели git rebase?

Вам нужно будет перестроить историю вашего репозитория следующим образом:

  • зафиксировать исправления терминатора строки
  • начать перебазирование
  • сначала оставьте стороннюю фиксацию импорта
  • применить исправления терминатора линии
  • примените другие патчи

Однако вам нужно понимать, что это сломает все последующие репозитории - те, которые клонированы из вашего родительского репозитория. В идеале с ними нужно начинать с нуля.


Обновление: пример использования:

target=`git rev-list --max-count=3 HEAD | tail -n1`
get rebase -i $target

Запустит сеанс перебазирования для последних 3 коммитов.

person Robert Munteanu    schedule 18.06.2009
comment
зафиксировать исправления терминатора строки: я не совсем понимаю это, так как HEAD находится в самой последней фиксации, а преобразование LF - это фиксация обратно в историю. - person keo; 18.06.2009
comment
К счастью, это репо еще никто не клонировал. - person keo; 18.06.2009
comment
есть одна вещь, которая до сих пор не ясна - если я сделаю перебазирование, в нескольких патчах будет CRLF - поскольку они были зафиксированы, когда файлы были в crlf - как мне с этим справиться? - person keo; 25.06.2009

мы избегаем этой проблемы в будущем с помощью:

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;
    }
?>
person keo    schedule 09.01.2010

Одним из решений (не обязательно лучшим) было бы использование git-filter-branch, чтобы переписать историю, чтобы всегда использовать правильные окончания строк. Это должно быть лучшим решением, чем интерактивная перебазировка, по крайней мере, для большего количества коммитов; также может быть проще иметь дело со слияниями с помощью git-filter-branch.

Это, конечно, при условии, что история не опубликована (репозиторий не был клонирован).

person Jakub Narębski    schedule 18.06.2009