Les fins de lignes égarées dans Git - comment suivre les modifications d'une autre branche après un énorme problème de fin de ligne?
Question
Nous travaillons avec un moteur PHP tiers qui reçoit des mises à jour régulières. Les versions sont conservées sur une branche distincte dans git, et notre fork est la branche principale.
Ainsi, nous pourrons appliquer des correctifs à notre fourche à partir des nouvelles versions du moteur.
Mon problème est que, après de nombreux engagements dans notre branche, j'ai réalisé que l'importation initiale du moteur avait été effectuée avec des fins de ligne CRLF.
J'ai converti chaque fichier au format LF, mais cela a créé un énorme commit, avec 100 000 lignes supprimées et 100 000 lignes ajoutées, ce qui rompt évidemment ce que nous voulions faire: fusionner facilement les correctifs des versions d'usine de ce moteur tiers.
Que saurais-je? Comment puis-je réparer cela? J'ai déjà des centaines de commits sur notre fork.
Ce qui serait bien, c’est d’effectuer une correction de fin de ligne après l’importation initiale et avant de créer une branche, et de supprimer cette énorme ligne qui se termine plus tard dans l’historique.
Cependant, je ne sais pas comment faire cela dans Git.
Merci!
La solution
J'ai finalement réussi à le résoudre.
La réponse est:
git filter-branch --tree-filter '~/Scripts/fix-line-endings.sh' -- --all
fix-line-endings.sh contient:
#!/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
Une fois que toutes les fins de ligne ont été corrigées dans toutes les arborescences de tous les commits, j'ai créé une rebase interactive et supprimé toutes les commits qui corrigeaient les fins de ligne.
Maintenant, mon dépôt est propre et frais, prêt à être poussé:)
Note aux visiteurs: ne le faites pas si votre référentiel a été poussé / cloné car cela gâcherait tout!!
Autres conseils
A l'avenir, évitez ce problème avec le paramètre core.autocrlf
, documenté dans git config --help
:
core.autocrlf
Si la valeur est true, git convertit
CRLF
à la fin des lignes dans les fichiers texte enLF
lors de la lecture du système de fichiers, et la conversion est inversée lors de l'écriture sur le système de fichiers. La variable peut être définie surinput
, auquel cas la conversion n'a lieu que lors de la lecture du système de fichiers, mais les fichiers sont écrits avecLF
à la fin des lignes. Un fichier est considéré comme un "texte". ( c'est-à-dire être soumis au mécanismeautocrlf
) en fonction de l'attributcrlf
du fichier, ou sicrlf
n'est pas spécifié, basé sur le contenu du fichier. Voir gitattributes .
Avez-vous examiné git rebase
?
Vous devrez refaire l'historique de votre référentiel, comme suit:
- valider les correctifs de terminaison de ligne
- démarrer la rebase
- laissez d'abord la validation d'importation tierce
- appliquer les correctifs de terminaison de ligne
- appliquez vos autres correctifs
Ce que vous devez comprendre, c’est que cela va casser tous les référentiels en aval - ceux qui sont clonés à partir de votre référant parent. Idéalement, vous partirez de zéro avec ceux-là.
Mise à jour : exemple d'utilisation:
target=`git rev-list --max-count=3 HEAD | tail -n1`
get rebase -i $target
va commencer une session de rebase pour les 3 derniers commits.
nous évitons ce problème à l'avenir avec:
1) Tout le monde utilise un éditeur qui supprime les espaces, et nous sauvegardons tous les fichiers avec LF.
2) si 1) échoue (cela peut arriver - quelqu'un l'enregistre accidentellement dans CRLF pour quelque raison que ce soit), nous avons un script de pré-validation qui recherche les caractères 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
Ce script utilise GNU grep et fonctionne sous Mac OS X. Toutefois, il doit être testé avant de pouvoir être utilisé sur d'autres plates-formes (nous avons eu des problèmes avec Cygwin et BSD grep)
3) Au cas où nous trouverions des erreurs d'espacement, nous utilisons le script suivant sur les fichiers erronés:
#!/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
Ce script utilise GNU grep et fonctionne sous Mac OS X. Toutefois, il doit être testé avant de pouvoir être utilisé sur d'autres plates-formes (nous avons eu des problèmes avec Cygwin et BSD grep)
3) Au cas où nous trouverions des erreurs d'espacement, nous utilisons le script suivant sur les fichiers erronés:
<*>
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 --
Ce script utilise GNU grep et fonctionne sous Mac OS X. Toutefois, il doit être testé avant de pouvoir être utilisé sur d'autres plates-formes (nous avons eu des problèmes avec Cygwin et BSD grep)
3) Au cas où nous trouverions des erreurs d'espacement, nous utilisons le script suivant sur les fichiers erronés:
<*>Une solution (pas nécessairement la meilleure) serait d'utiliser git-filter- branche pour réécrire l’historique afin de toujours utiliser les fins de ligne correctes. Cela devrait être une meilleure solution que la rebase interactive, au moins pour un plus grand nombre de commits; De plus, il pourrait être plus facile de gérer les fusions en utilisant git-filter-branch.
Cela suppose bien sûr que l'historique n'était pas publié (le référentiel n'était pas cloné).