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?

StackOverflow https://stackoverflow.com/questions/1011985

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!

È stato utile?

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 in LF durante la lettura dal filesystem e converte al contrario quando si scrive nel filesystem. La variabile può essere impostata su input , nel qual caso la conversione avviene solo durante la lettura dal filesystem ma i file vengono scritti con LF alla fine delle righe. Un file è considerato " testo " ( ie essere soggetto al meccanismo autocrlf ) basato sull'attributo crlf del file, o se crlf 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).

Autorizzato sotto: CC-BY-SA insieme a attribuzione
Non affiliato a StackOverflow
scroll top