Le terminazioni di linea si sono incasinate in Git - come tenere traccia delle modifiche da un altro ramo dopo un'enorme correzione di fine linea?
Domanda
Stiamo lavorando con un motore PHP di terze parti che riceve aggiornamenti regolari. Le versioni sono mantenute su un ramo separato in git e il nostro fork è il ramo principale.
In questo modo saremo in grado di applicare patch al nostro fork dalle nuove versioni del motore.
Il mio problema è che, dopo molti commit nella nostra filiale, mi sono reso conto che l'importazione iniziale del motore è stata effettuata con i finali di linea CRLF.
Ho convertito tutti i file in LF, ma questo ha fatto un grande commit, con 100k linee rimosse e 100k linee aggiunte, che ovviamente interrompe ciò che intendevamo fare: unire facilmente le patch dalle versioni di fabbrica di quel motore di terze parti.
Cosa dovrei sapere? Come posso risolvere questo problema? Ho già centinaia di commit sul nostro fork.
Ciò che sarebbe utile è in qualche modo fare un commit delle terminazioni di riga dopo l'importazione iniziale e prima di ramificare il nostro fork e rimuovere quell'enorme commit di fine riga più avanti nella storia.
Tuttavia non ho idea di come farlo in Git.
Grazie!
Soluzione
Finalmente sono riuscito a risolverlo.
La risposta è:
git filter-branch --tree-filter '~/Scripts/fix-line-endings.sh' -- --all
fix-line-endings.sh contiene:
#!/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
Dopo aver corretto tutti i finali di linea in tutti gli alberi in tutti i commit, ho effettuato un rebase interattivo e rimosso tutti i commit che stavano fissando i finali di linea.
Ora il mio repository è pulito e fresco, pronto per essere spinto :)
Nota per i visitatori: non farlo se il tuo repository è stato spinto / clonato perché rovinerà le cose!
Altri suggerimenti
In futuro, evita questo problema con l'impostazione core.autocrlf
, documentata in git config --help
:
core.autocrlf
Se vero, fa convertire git
CRLF
alla fine delle righe nei file di testo inLF
durante la lettura dal filesystem e converte al contrario quando si scrive nel filesystem. La variabile può essere impostata suinput
, nel qual caso la conversione avviene solo durante la lettura dal filesystem ma i file vengono scritti conLF
alla fine delle righe. Un file è considerato " testo " ( ie essere soggetto al meccanismoautocrlf
) basato sull'attributocrlf
del file, o secrlf
non è specificato, in base al contenuto del file. Vedi gitattributes .
Hai guardato git rebase
?
Dovrai basare nuovamente la cronologia del tuo repository, come segue:
- esegue il commit delle correzioni del terminatore di riga
- avvia il rebase
- lascia prima il commit dell'importazione di terze parti
- applica le correzioni del terminatore di linea
- applica le altre tue patch
Quello che devi capire è che questo interromperà tutti i repository a valle - quelli che sono clonati dal tuo repository principale. Idealmente inizierai da zero con quelli.
Aggiorna : utilizzo di esempio:
target=`git rev-list --max-count=3 HEAD | tail -n1`
get rebase -i $target
Avvia una sessione rebase per gli ultimi 3 commit.
stiamo evitando questo problema in futuro con:
1) tutti usano un editor che rimuove gli spazi bianchi finali e salviamo tutti i file con LF.
2) se 1) fallisce (può - qualcuno lo salva accidentalmente in CRLF per qualsiasi motivo) abbiamo uno script pre-commit che controlla i caratteri 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
Questo script utilizza GNU grep e funziona su Mac OS X, tuttavia dovrebbe essere testato prima dell'uso su altre piattaforme (abbiamo avuto problemi con Cygwin e BSD grep)
3) Nel caso in cui riscontrassimo errori di spazi bianchi, utilizziamo il seguente script su file errati:
#!/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
Questo script utilizza GNU grep e funziona su Mac OS X, tuttavia dovrebbe essere testato prima dell'uso su altre piattaforme (abbiamo avuto problemi con Cygwin e BSD grep)
3) Nel caso in cui riscontrassimo errori di spazi bianchi, utilizziamo il seguente script su file errati:
<*>
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 --
Questo script utilizza GNU grep e funziona su Mac OS X, tuttavia dovrebbe essere testato prima dell'uso su altre piattaforme (abbiamo avuto problemi con Cygwin e BSD grep)
3) Nel caso in cui riscontrassimo errori di spazi bianchi, utilizziamo il seguente script su file errati:
<*>Una soluzione (non necessariamente la migliore) sarebbe quella di utilizzare git-filter- branch per riscrivere la cronologia per utilizzare sempre i finali di riga corretti. Questa dovrebbe essere una soluzione migliore del rebase interattivo, almeno per un numero maggiore di commit; inoltre potrebbe essere più semplice gestire le fusioni usando git-filter-branch.
Ciò ovviamente presuppone che la cronologia non sia stata non pubblicata (il repository non è stato clonato).