Git 中的行结尾混乱 - 如何在修复大量行结尾后跟踪另一个分支的更改?
题
我们正在使用定期更新的第三方 PHP 引擎。版本保存在 git 中的单独分支上,我们的分支是主分支。
这样我们就能够将新版本的引擎的补丁应用到我们的分支上。
我的问题是,在对我们的分支进行多次提交后,我意识到引擎的初始导入是用 CRLF 行结尾完成的。
我将每个文件都转换为 LF,但这进行了巨大的提交,删除了 100k 行并添加了 100k 行,这显然破坏了我们的意图:轻松合并来自该第三方引擎的工厂版本的补丁。
我能知道什么?我怎样才能解决这个问题?我已经在我们的分叉上进行了数百次提交。
最好的办法是在初始导入之后和分支我们自己的分支之前以某种方式进行行结尾修复提交,并在历史稍后删除那个巨大的行结尾提交。
但是我不知道如何在 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
在所有提交的所有树中修复所有行结尾后,我做了一个交互式rebase并删除了所有修复行结尾的提交。
现在我的回购清新,准备被推了:)。
请访问者注意:如果您的仓库已被推送/克隆,请不要这样做,因为它会使事情变得非常糟糕!
其他提示
展望未来,使用 core.autocrlf
设置避免此问题, git config --help
:
core.autocrlf
如果为true,则在从文件系统读取时,将文本文件行末尾的git convert
CRLF
转换为LF
,并在写入文件系统时反向转换。该变量可以设置为input
,在这种情况下,转换仅在从文件系统读取时发生,但文件在行末用LF
写出。文件被认为是“文本”。 (即受autocrlf
机制)基于文件的crlf
属性,或者如果crlf
未指定,根据文件的内容。请参阅 gitattributes 。
你看过吗 git rebase
?
您将需要重新建立存储库的历史记录,如下所示:
- 提交行终止符修复
- 开始变基
- 首先保留第三方导入提交
- 应用行终止符修复
- 应用您的其他补丁
但你需要明白的是,这会 休息 所有下游存储库 - 从您的父存储库克隆的存储库。理想情况下,您将从头开始。
更新:示例用法:
target=`git rev-list --max-count=3 HEAD | tail -n1`
get rebase -i $target
将为最后 3 次提交启动变基会话。
我们将来通过以下方式避免这个问题:
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
此脚本使用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;
}
?>
\r
此脚本使用GNU grep,适用于Mac OS X,但在使用其他平台之前应进行测试(我们遇到Cygwin和BSD grep问题)
3)如果我们发现任何空格错误,我们在错误文件上使用以下脚本:
<*>
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)如果我们发现任何空格错误,我们在错误文件上使用以下脚本:
<*>一个解决方案(不一定是最好的解决方案)是使用 git-filter-分支重写历史记录以始终使用正确的行结尾。这应该是交互式rebase的更好解决方案,至少对于大量的提交;使用git-filter-branch也可以更容易地处理合并。
当然,假设历史未发布(存储库未被克隆)。