24

我们正在使用定期更新的第 3 方 PHP 引擎。版本保存在 git 中的一个单独分支上,我们的 fork 是主分支。

通过这种方式,我们将能够从新版本的引擎中将补丁应用到我们的 fork。

我的问题是,在多次提交到我们的分支之后,我意识到引擎的初始导入是使用 CRLF 行结尾完成的。

我将每个文件都转换为 LF,但这带来了巨大的提交,删除了 100k 行并添加了 100k 行,这显然破坏了我们的意图:轻松合并来自该 3rd 方引擎的工厂版本的补丁。

我该知道什么?我怎样才能解决这个问题?我的分叉上已经有数百个提交。

好的是在初始导入之后和分支我们自己的分叉之前以某种方式进行行尾修复提交,并在历史后期删除那个巨大的行尾提交。

但是我不知道如何在 Git 中做到这一点。

谢谢!

4

5 回答 5

34

我终于设法解决了。

答案是:

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

在所有提交中的所有树中修复了所有行尾之后,我进行了交互式 rebase 并删除了所有修复行尾的提交。

现在我的回购是干净和新鲜的,可以推送了:)

访客注意:如果您的回购已被推送/克隆,请不要这样做,因为它会严重搞砸!

于 2009-06-29T22:08:31.437 回答
3

你看了git rebase吗?

您将需要重新建立存储库的历史记录,如下所示:

  • 提交行终止符修复
  • 开始变基
  • 先离开第三方导入提交
  • 应用行终止符修复
  • 应用你的其他补丁

但是,您需要了解的是,这将破坏所有下游存储库 - 那些从您的父存储库克隆的存储库。理想情况下,您将从头开始。


更新:示例用法:

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

将为最后 3 次提交启动一个 rebase 会话。

于 2009-06-18T10:41:36.553 回答
3

展望未来,避免使用core.autocrlf设置出现此问题,记录在git config --help

核心.autocrlf

如果为 true,则在从文件系统读取时使 gitCRLF在文本文件的行尾转换为LF,并在写入文件系统时反向转换。该变量可以设置为,在这种情况下,转换仅在从文件系统读取时发生,但文件在行尾input写出。LF一个文件被认为是“文本”(受制于该autocrlf机制)基于文件的crlf属性,或者如果crlf未指定,则基于文件的内容。请参阅gitattributes

于 2010-01-08T18:25:43.843 回答
2

一种解决方案(不一定是最好的)是使用git-filter-branch重写历史以始终使用正确的行尾。这应该是交互式 rebase 的更好解决方案,至少对于大量提交而言;使用 git-filter-branch 处理合并也可能更容易。

这当然是假设历史没有发布(存储库没有克隆)。

于 2009-06-18T12:01:57.247 回答
2

我们在未来通过以下方式避免这个问题:

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;
    }
?>
于 2010-01-09T11:25:40.610 回答