2009-06-18 11 views
22

Stiamo lavorando con un motore PHP di terze parti che ottiene aggiornamenti regolari. Le versioni sono mantenute su un ramo separato in git, e la nostra forcella è il ramo principale.Terminazioni di riga incasinate in Git: come tenere traccia delle modifiche da un altro ramo dopo un'enorme correzione di fine riga?

In questo modo saremo in grado di applicare patch alla nostra forcella dalle nuove versioni del motore.

Il mio problema è, dopo molti impegni nella nostra filiale, mi sono reso conto che l'importazione iniziale del motore è stata eseguita con terminazioni di linea CRLF.

Ho convertito tutti i file in LF, ma questo ha reso un enorme commit, con 100k linee rimosse e 100k linee aggiunte, che ovviamente rompe ciò che intendevamo fare: facilmente unire le patch dalle versioni di fabbrica di quel motore di terze parti.

Che cosa dovrei sapere? Come posso risolvere questo? Ho già centinaia di commit sulla nostra forcella.

Ciò che sarebbe opportuno è in qualche modo eseguire un commit di terminazioni di riga dopo l'importazione iniziale e prima di diramare la propria fork e rimuovere quella conclusione di riga enorme impegnata in un secondo momento nella cronologia.

Tuttavia non ho idea di come farlo in Git.

Grazie!

risposta

33

Sono finalmente 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 che tutti i fine riga sono stati fissati in tutti gli alberi in tutta la commette, ho fatto un rebase interattivo e rimosso tutti i commit che stavano fissando le terminazioni di linea.

Ora il mio repo è pulito e fresco, pronto per essere spinto :)

Nota per i visitatori: non fare questo se il repo è stato spinto/Cloned perché sarà complicare le cose male!

+1

È fantastico! Tuttavia, il tuo script ha problemi con gli spazi nei nomi dei file. Ho fatto qualcosa di simile invece: trovare. -type f -print0 -a \ (-name '*. [hc]' -o -name '* .p [yl]' \) | xargs -0 fromdos – Enno

+0

D'accordo che è fantastico. Un Q, però: l'approccio qui specifica esplicitamente quali estensioni di file rimuovere da CR.Mi piacerebbe sapere se è possibile invece eliminare i CR da tutti e solo quelle correzioni che l'euristica di rilevamento del testo di Git considererebbe il testo. (L'euristica di Git guarda il contenuto reale del file, non i nomi dei file.) A prima vista, comunque, sembra che giochi ancora meglio con core.autocrlf = true. – Chris

+0

Non proprio quello che stavo chiedendo, ma http://stackoverflow.com/a/3092511 fornisce un approccio che usa il comando "file" di unix per cercare di distinguere tra testo e file non di testo. – Chris

3

Hai guardato git rebase?

Sarà necessario rebase la storia del repository, come segue:

  • commettere il terminatore di linea fissa
  • avviare il rebase
  • lasciare l'importazione di terze parti commettere prima
  • applicare il terminatore di linea fissa
  • applicare le altre patch

Quello che devi capire è che questo sarà interruzione tutti i repository downstream - quelli che sono stati clonati dal repository padre. Idealmente inizierai da zero con quelli.


Aggiornamento: utilizzo di esempio:

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

inizierà una sessione rebase per gli ultimi 3 commit.

+0

"Correggere le correzioni del terminatore di riga": non lo capisco, poiché HEAD è al commit più recente e la conversione LF è un commit nella storia. – keo

+0

fortunatamente nessuno ha clonato questo repository ancora. – keo

+0

c'è una cosa che non è ancora chiara - se faccio un rebase, alcune patch avranno CRLF in loro - come sono state commesse quando i file erano in crlf - come posso affrontarlo? – keo

2

Una soluzione (non necessariamente la migliore) sarebbe quella di utilizzare git-filter-branch per riscrivere la cronologia per utilizzare sempre le terminazioni di linea corrette. Questa dovrebbe essere la soluzione migliore per il rebase interattivo, almeno per un numero maggiore di commit; inoltre potrebbe essere più semplice gestire le unioni usando git-filter-branch.

Ovviamente si presume che la cronologia sia stata non pubblicata (il repository non è stato clonato).

4

Andando avanti, evitare questo problema con l'impostazione core.autocrlf, documentata in git config --help:

core.autocrlf

Se fosse vero, rende git convertire CRLF alla fine di linee in file di testo a LF durante la lettura dal filesystem e la conversione al contrario durante la scrittura sul filesystem. La variabile può essere impostata su input, nel qual caso la conversione avviene solo durante la lettura dal file system, ma i file vengono scritti con LF alla fine delle righe. Un file è considerato "testo" (, ovvero, soggetto al meccanismo autocrlf) in base all'attributo crlf del file o se crlf non è specificato, in base al contenuto del file. Vedi gitattributes.

+0

grazie per il suggerimento! stavamo usando questa funzione, ma si stava comportando veramente buggy allora (forse quei bug sono stati corretti ora) vedere la mia prossima risposta per vedere come abbiamo evitato questo. – keo

2

stiamo evitare questo problema in futuro con:

1) tutti usano un editor che mette a nudo spazi bianchi finali, e noi salvare tutti i file con LF.

2) se 1) fallisce (il possibile - qualcuno salva accidentalmente in CRLF per qualsiasi motivo) abbiamo uno script pre-commit che verifica la presenza di 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 $'\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 -- 

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 troviamo 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; 
    } 
?>