Terminaciones de línea en mal estado en Git: ¿cómo rastrear los cambios desde otra rama después de una gran corrección de final de línea?

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

Pregunta

Estamos trabajando con un motor PHP de terceros que recibe actualizaciones periódicas. Las versiones se mantienen en una rama separada en git, y nuestra bifurcación es la rama maestra.

De esta manera podremos aplicar parches a nuestra bifurcación a partir de las nuevas versiones del motor.

Mi problema es que, después de muchos compromisos con nuestra sucursal, me di cuenta de que la importación inicial del motor se realizó con terminaciones de línea CRLF.

Convertí todos los archivos a LF, pero esto hizo una gran confirmación, con 100k líneas eliminadas y 100k líneas agregadas, lo que obviamente rompe lo que pretendíamos hacer: fusionar fácilmente los parches de las versiones de fábrica de ese motor de terceros.

¿Qué debo saber? ¿Cómo puedo arreglar esto? Ya tengo cientos de commits en nuestro fork.

Lo que sería bueno es, de alguna manera, hacer una confirmación de finalización de línea después de la importación inicial y antes de bifurcar nuestra propia bifurcación, y eliminar esa gran confirmación de final de línea más adelante en la historia.

Sin embargo, no tengo idea de cómo hacer esto en Git.

¡Gracias!

¿Fue útil?

Solución

Finalmente logré resolverlo.

La respuesta es:

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

Después de que todas las terminaciones de línea se corrigieron en todos los árboles en todas las confirmaciones, realicé un rebase interactivo y eliminé todas las confirmaciones que arreglaban terminaciones de línea.

Ahora mi repositorio está limpio y fresco, listo para ser empujado :)

Nota para los visitantes: ¡no hagan esto si su repositorio ha sido empujado / clonado porque arruinará mucho las cosas!

Otros consejos

En el futuro, evite este problema con la configuración core.autocrlf , documentada en git config --help :

  

core.autocrlf

     

Si es verdadero, hace que git convierta CRLF al final de las líneas en archivos de texto a LF al leer del sistema de archivos, y convierta a la inversa al escribir en el sistema de archivos. La variable se puede establecer en input , en cuyo caso la conversión ocurre solo mientras se lee del sistema de archivos, pero los archivos se escriben con LF al final de las líneas. Un archivo se considera " texto " ( es decir, estar sujeto al mecanismo autocrlf ) según el atributo crlf del archivo, o si crlf no está especificado, basado en el contenido del archivo. Consulte gitattributes .

¿Viste git rebase ?

Deberá volver a basar el historial de su repositorio de la siguiente manera:

  • confirmar las correcciones del terminador de línea
  • iniciar el rebase
  • deje primero la confirmación de importación de terceros
  • aplique las correcciones del terminador de línea
  • aplique sus otros parches

Sin embargo, lo que debe comprender es que esto romperá todos los repositorios posteriores, aquellos que se clonan desde su repositorio principal. Idealmente, comenzarás desde cero con esos.


Actualización : uso de muestra:

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

Comenzará una sesión de rebase para los últimos 3 commits.

estamos evitando este problema en el futuro con:

1) todos usan un editor que elimina los espacios en blanco al final, y guardamos todos los archivos con LF.

2) si 1) falla (puede hacerlo, alguien lo guarda accidentalmente en CRLF por cualquier motivo) tenemos un script previo a la confirmación que verifica los caracteres 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 

Este script usa GNU grep y funciona en Mac OS X, sin embargo, debe probarse antes de usarlo en otras plataformas (tuvimos problemas con Cygwin y BSD grep)

3) En caso de que encontremos algún error de espacio en blanco, utilizamos el siguiente script en archivos erróneos:

#!/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

Este script usa GNU grep y funciona en Mac OS X, sin embargo, debe probarse antes de usarlo en otras plataformas (tuvimos problemas con Cygwin y BSD grep)

3) En caso de que encontremos algún error de espacio en blanco, utilizamos el siguiente script en archivos erróneos:

<*> 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 --

Este script usa GNU grep y funciona en Mac OS X, sin embargo, debe probarse antes de usarlo en otras plataformas (tuvimos problemas con Cygwin y BSD grep)

3) En caso de que encontremos algún error de espacio en blanco, utilizamos el siguiente script en archivos erróneos:

<*>

Una solución (no necesariamente la mejor) sería usar git-filter- rama para reescribir el historial para usar siempre las terminaciones de línea correctas. Esta debería ser una mejor solución que el rebase interactivo, al menos para un mayor número de confirmaciones; También podría ser más fácil tratar con fusiones usando git-filter-branch.

Eso, por supuesto, supone que el historial no se publicó (el repositorio no se clonó).

Licenciado bajo: CC-BY-SA con atribución
No afiliado a StackOverflow
scroll top