2009-06-18 15 views
22

Chúng tôi đang làm việc với một công cụ PHP của bên thứ ba nhận các bản cập nhật thường xuyên. Các bản phát hành được giữ trên một nhánh riêng biệt trong git, và ngã ba của chúng tôi là nhánh chính.Kết thúc dòng sai lầm trong Git - làm thế nào để theo dõi các thay đổi từ một chi nhánh khác sau khi một dòng lớn kết thúc sửa chữa?

Bằng cách này, chúng tôi sẽ có thể áp dụng các bản vá cho ngã ba của chúng tôi từ các bản phát hành mới của động cơ.

Vấn đề của tôi là, sau khi nhiều cam kết với chi nhánh của chúng tôi, tôi nhận ra rằng việc nhập khẩu ban đầu của động cơ được thực hiện với kết thúc dòng CRLF.

Tôi đã chuyển đổi mọi tệp thành LF, nhưng điều này đã thực hiện một cam kết lớn, với 100k dòng bị xóa và 100k dòng được thêm vào, rõ ràng phá vỡ những gì chúng tôi dự định làm: dễ dàng hợp nhất các bản vá từ bản phát hành của nhà sản xuất bên thứ ba đó.

Tôi phải làm gì? Làm thế nào tôi có thể sửa lỗi này? Tôi đã có hàng trăm cam kết trên ngã ba của chúng tôi. Điều gì sẽ là tốt bằng cách nào đó làm một kết thúc dòng sửa cam kết sau khi nhập khẩu ban đầu và trước khi phân nhánh ngã ba của chúng ta, và loại bỏ dòng lớn kết thúc cam kết sau này trong lịch sử.

Tuy nhiên tôi không biết làm thế nào để thực hiện điều này trong Git.

Cảm ơn!

Trả lời

33

Cuối cùng tôi đã giải quyết được.

Câu trả lời là:

git filter-branch --tree-filter '~/Scripts/fix-line-endings.sh' -- --all 

fix-line-endings.sh chứa:

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

Sau khi tất cả kết thúc dòng đã được cố định trong tất cả các cây trong tất cả các cam kết, tôi đã làm một rebase tương tác và đã xóa tất cả các cam kết đang sửa kết thúc dòng.

Bây giờ repo của tôi là sạch sẽ và tươi, sẵn sàng để được đẩy :)

Lưu ý khách: không làm điều này nếu repo của bạn đã được đẩy/nhân bản vì nó sẽ điều lộn xộn lên tồi tệ!

+1

Điều này thật tuyệt vời! Tuy nhiên, tập lệnh của bạn có vấn đề với khoảng trống trong tên tệp. Tôi đã làm một cái gì đó như thế này: tìm. - type f -print0 -a \ (-name '*. [hc]' -o -name '* .p [yl]' \) | xargs -0 fromdos – Enno

+0

Đồng ý rằng điều này thật tuyệt vời. Tuy nhiên, một câu hỏi: Cách tiếp cận ở đây chỉ định rõ ràng các phần mở rộng tệp để loại bỏ CR.Tôi muốn biết nếu nó có thể thay vì dải CRs từ tất cả và chỉ những bản sửa lỗi mà chẩn đoán phát hiện văn bản của git sẽ xem xét văn bản. (Git của heuristics nhìn vào nội dung tập tin thực tế, không tên tập tin.) Thoạt nhìn, có vẻ như nó sẽ chơi tốt hơn với core.autocrlf = true. – Chris

+0

Không hoàn toàn là những gì tôi đã hỏi, nhưng http://stackoverflow.com/a/3092511 đưa ra cách tiếp cận sử dụng lệnh "tệp" unix để cố phân biệt văn bản với các tệp không phải văn bản. – Chris

3

Bạn có xem git rebase không?

Bạn sẽ cần phải rebase lịch sử của kho lưu trữ của bạn như sau:

  • cam kết dòng terminator sửa
  • bắt đầu rebase
  • rời nhập khẩu của bên thứ ba cam kết đầu tiên
  • áp dụng sửa lỗi đường thẳng
  • áp dụng các bản vá lỗi khác của bạn

Điều bạn cần hiểu là điều này sẽ ngắt tất cả kho lưu trữ ở hạ lưu - những kho được sao chép từ kho lưu trữ gốc của bạn. Lý tưởng nhất là bạn sẽ bắt đầu từ đầu với những người.


Cập nhật: mẫu sử dụng:

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

sẽ bắt đầu một phiên rebase cho 3 cam kết cuối cùng.

+0

"cam kết sửa lỗi dòng đầu cuối": Tôi không hoàn toàn hiểu điều này, vì HEAD ở lần commit gần đây nhất, và chuyển đổi LF là một cam kết trong lịch sử. – keo

+0

may mắn thay không ai nhân bản repo này. – keo

+0

có một điều vẫn chưa rõ ràng - nếu tôi thực hiện rebase, một vài bản vá sẽ có CRLF trong chúng - vì chúng đã được cam kết khi các tệp nằm trong crlf - làm thế nào để xử lý? – keo

2

Một giải pháp (không nhất thiết là giải pháp tốt nhất) sẽ sử dụng git-filter-branch để ghi lại lịch sử để luôn sử dụng kết thúc dòng chính xác. Điều này sẽ là giải pháp tốt hơn mà rebase tương tác, ít nhất là cho số lượng lớn các cam kết; cũng có thể dễ dàng hơn để đối phó với việc hợp nhất bằng git-filter-branch.

Đó là tất nhiên giả định rằng lịch sử là không được xuất bản (kho lưu trữ không được nhân bản).

4

Trong tương lai, tránh vấn đề này với các thiết lập core.autocrlf, tài liệu trong git config --help:

core.autocrlf

Nếu đúng, làm cho git chuyển đổi CRLF vào cuối các dòng trong tập tin văn bản đến LF khi đọc từ hệ thống tệp và chuyển đổi ngược lại khi ghi vào hệ thống tệp. Biến có thể được đặt thành input, trong trường hợp đó, chuyển đổi chỉ xảy ra khi đọc từ hệ thống tệp nhưng các tệp được ghi ra bằng LF ở cuối dòng. Tệp được coi là "văn bản" (tức là phải tuân theo cơ chế autocrlf) dựa trên thuộc tính crlf của tệp hoặc nếu không xác định crlf, dựa trên nội dung của tệp. Xem gitattributes.

+0

cảm ơn cho mẹo! chúng tôi đã sử dụng tính năng này nhưng đã hành xử thực sự lỗi sau đó (có lẽ những lỗi đã được sửa chữa ngay bây giờ) xem câu trả lời tiếp theo của tôi để xem cách chúng tôi tránh điều này. – keo

2

chúng tôi đang tránh vấn đề này trong thời gian tới với:

1) tất cả mọi người sử dụng một trình soạn thảo dùng để tách phần đuôi khoảng trắng, và chúng tôi lưu tất cả các file với LF.

2) nếu 1) không (nó có thể - ai đó vô tình lưu nó trong CRLF vì lý do gì), chúng tôi có một kịch bản pre-cam kết để kiểm tra cho chars 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 -- 

kịch bản này sử dụng GNU grep, và hoạt động trên Mac OS X, tuy nhiên cần phải kiểm tra trước khi sử dụng trên các nền tảng khác (chúng tôi gặp sự cố với Cygwin và BSD grep)

3) Trong trường hợp chúng tôi tìm thấy bất kỳ lỗi nào, chúng tôi sử dụng tập lệnh sau trên tệp sai:

#!/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; 
    } 
?> 
Các vấn đề liên quan