web-dev-qa-db-ja.com

パターンに一致する行のみをgitaddするにはどうすればよいですか?

私はgitでいくつかの設定ファイルを追跡しています。私は通常インタラクティブなgit add -pしかし、パターンに一致するすべての新規/変更/削除された行を自動的に追加する方法を探しています。そうでなければ、すべてのインタラクティブな分割と追加を行うのに何年もかかるでしょう。 git addはファイル名に一致するパターンを持っていますが、コンテンツについて何も見つかりません。

31
Benoît

私はこの実験的でテストが不十分なプログラムを [〜#〜] txr [〜#〜] でクランクアウトしました:

サンプル実行:最初にリポジトリのどこにいますか:

$ git diff
diff --git a/lorem.txt b/lorem.txt
index d5d20a4..58609a7 100644
--- a/lorem.txt
+++ b/lorem.txt
@@ -2,10 +2,14 @@ Lorem ipsum dolor sit amet,
 consectetur adipiscing elit,
 sed do eiusmod tempor
 incididunt ut labore et dolore
-magna aliqua. Ut enim ad minim
+minim
+minim
 veniam, quis nostrud
 exercitation ullamco laboris
+maxim
+maxim
 nisi ut aliquip ex ea commodo
+minim
 consequat.  Duis aute irure
 dolor in reprehenderit in
 voluptate velit esse cillum

そして:

$ git diff --cached  # nothing staged in the index

目標は、minの一致を含む行をコミットすることです。

$ txr addmatch.txr min lorem.txt
patching file .merge_file_BilTfQ

さて、状態は何ですか?

$ git diff
diff --git a/lorem.txt b/lorem.txt
index 7e1b4cb..58609a7 100644
--- a/lorem.txt
+++ b/lorem.txt
@@ -6,6 +6,8 @@ minim
 minim
 veniam, quis nostrud
 exercitation ullamco laboris
+maxim
+maxim
 nisi ut aliquip ex ea commodo
 minim
 consequat.  Duis aute irure

そして:

$ git diff --cached
diff --git a/lorem.txt b/lorem.txt
index d5d20a4..7e1b4cb 100644
--- a/lorem.txt
+++ b/lorem.txt
@@ -2,10 +2,12 @@ Lorem ipsum dolor sit amet,
 consectetur adipiscing elit,
 sed do eiusmod tempor
 incididunt ut labore et dolore
-magna aliqua. Ut enim ad minim
+minim
+minim
 veniam, quis nostrud
 exercitation ullamco laboris
 nisi ut aliquip ex ea commodo
+minim
 consequat.  Duis aute irure
 dolor in reprehenderit in
 voluptate velit esse cillum

一致するものはインデックスにあり、一致しない+maxim行はまだステージングされていません。

addmatch.txrのコード:

@(next :args)
@(assert)
@pattern
@file
@(bind regex @(regex-compile pattern))
@(next (open-command `git diff @file`))
diff @diffjunk
index @indexjunk
--- a/@file
+++ b/@file
@(collect)
@@@@ -@bfline,@bflen +@afline,@aflen @@@@@(skip)
@  (bind (nminus nplus) (0 0))
@  (collect)
@    (cases)
 @line
@      (bind zerocol " ")
@    (or)
+@line
@      (bind zerocol "+")
@      (require (search-regex line regex))
@      (do (inc nplus))
@    (or)
-@line
@      (bind zerocol "-")
@      (require (search-regex line regex))
@      (do (inc nminus))
@    (or)
-@line
@;;    unmatched - line becomes context line
@      (bind zerocol " ")
@    (end)
@  (until)
@/[^+\- ]/@(skip)
@  (end)
@  (set (bfline bflen afline aflen)
        @[mapcar int-str (list bfline bflen afline aflen)])
@  (set aflen @(+ bflen nplus (- nminus)))
@(end)
@(output :into stripped-diff)
diff @diffjunk
index @indexjunk
--- a/@file
+++ b/@file
@  (repeat)
@@@@ -@bfline,@bflen +@afline,@aflen @@@@
@    (repeat)
@zerocol@line
@    (end)
@  (end)
@(end)
@(next (open-command `git checkout-index --temp @file`))
@tempname@\t@file
@(try)
@  (do
     (with-stream (patch-stream (open-command `patch -p1 @tempname` "w"))
       (put-lines stripped-diff patch-stream)))
@  (next (open-command `git hash-object -w @tempname`))
@newsha
@  (do (sh `git update-index --cacheinfo 100644 @newsha @file`))
@(catch)
@  (fail)
@(finally)
@  (do
     (ignerr [mapdo remove-path #`@tempname @tempname.orig @tempname.rej`]))
@(end)

基本的に戦略は次のとおりです。

  • git diff出力でパターンマッチングを実行して、ハンクを一致する行までフィルタリングします。ハンクヘッダーの「後」の行数を再計算し、コンテキスト行を保持する必要があります。

  • フィルタリングされた差分を変数に出力します。

  • git checkout-index --tempを使用して、インデックスからファイルの元のコピーを取得します。このコマンドは、生成した一時的な名前を出力し、それをキャプチャします。

  • 次に、フィルタリング/削減された差分をpatch -p1に送信し、インデックスからの元のコピーを保持しているこの一時ファイルをターゲットにします。これで、必要な変更だけが元のファイルに適用されました。

  • 次に、git hash-object -wを使用して、パッチを適用したファイルからGitオブジェクトを作成します。このコマンドが出力するハッシュをキャプチャします。

  • 最後に、git update-index --cacheinfo ...を使用して、この新しいオブジェクトを元のファイル名でインデックスに入力し、ファイルの変更を効果的にステージングします。

これがうまくいかない場合は、git resetを実行してインデックスを消去し、壊れたスクリプトを修正して、再試行できます。

+行と-行を盲目的に照合するだけでも明らかな問題があります。パターンがコンテンツではなく構成ファイルの変数名と一致する場合に機能するはずです。例えば。

置換:

-CONFIG_VAR=foo
+CONFIG_VAR=bar

ここで、CONFIG_VARで一致すると、両方の行が含まれます。右側のfooで一致すると、問題が発生します。最終的には、CONFIG_VAR=foo行を差し引くだけのパッチになります。

明らかに、これは、構成ファイルの構文とセマンティクスを考慮に入れて、巧妙にすることができます。

これを「実際に」解決する方法は、堅牢な構成ファイルパーサーとリジェネレーター(コメント、空白などすべてを保持する)を作成することです。次に、新しい元の元のファイルを解析してオブジェクトを構成し、一致する変更を1つのオブジェクトから別のオブジェクトに移行し、更新されたファイルを生成してインデックスに移動します。パッチをいじり回すことはありません。

3
Kaz

方法は次のとおりです。

  1. git diff > patchを使用して、現在の差分のパッチを作成します。

  2. gawkを使用して、パターンに一致する+/-行のみの2番目のパッチを作成します。パターンに一致しない削除された行から-を削除し、パターンに一致しない+行を削除します。ハンクヘッダーの行番号を変更し、変更された各ハンクを出力しますが、変更がなくなった変更済みハンクは出力しません。

  3. git stash saveapply patchadd -u、およびstash popを使用して、変更されたパッチを適用およびステージングし、残りの変更はステージングされないままにします。

これはいくつかのテストケースで機能し、diff全体(すべてのファイル)で一度に機能し、高速です。

#!/bin/sh

diff=`mktemp`
git diff > $diff
[ -s $diff ] || exit

patch=`mktemp`

gawk -v pat="$1" '
function hh(){
  if(keep && n > 0){
    for(i=0;i<n;i++){
      if(i==hrn){
        printf "@@ -%d,%d +%d,%d @@\n", har[1],har[2],har[3],har[4];
      }
      print out[i];
    }
  }
}
{
  if(/^diff --git a\/.* b\/.*/){
    hh();
    keep=0;
    dr=NR;
    n=0;
    out[n++]=$0
  }
  else if(NR == dr+1 && /^index [0-9a-f]+\.\.[0-9a-f]+ [0-9]+$/){
    ir=NR;
    out[n++]=$0
  }
  else if(NR == ir+1 && /^\-\-\- a\//){
    mr=NR;
    out[n++]=$0
  }
  else if(NR == mr+1 && /^\+\+\+ b\//){
    pr=NR;
    out[n++]=$0
  }
  else if(NR == pr+1 && match($0, /^@@ \-([0-9]+),?([0-9]+)? \+([0-9]+),?([0-9]+)? @@/, har)){
    hr=NR;
    hrn=n
  }
  else if(NR > hr){
    if(/^\-/ && $0 !~ pat){
      har[4]++;
      sub(/^\-/, " ", $0);
      out[n++] = $0
    }
    else if(/^\+/ && $0 !~ pat){
      har[4]--;
    }
    else{
      if(/^[+-]/){
        keep=1
      }
      out[n++] = $0
    }
  }
}
END{
  hh()
}' $diff > $patch

git stash save &&
  git apply $patch &&
  git add -u &&
  git stash pop

rm $diff
rm $patch

参照:

git diffapply

統一された差分形式

gawkmatchグループから配列

git add -u

6
webb

それは不可能だと思います。 git add -pは常に変更の塊を表示するため。ただし、そのハンクには、追加したい(そしてパターンに一致する)行と、追加したくない変更を含む行が含まれている場合があります。

2つの変更を行い、それらを別々にコミットしたいときに、同様の問題に直面することがあります。

  • 変数の名前を変更します
  • いくつかの機能を追加する

私が使用する回避策があります:

  • 変更を脇に置きます(git stashを使用するか、ファイルをコピーするだけです)
  • 変数の名前を変更します(変数の名前変更は通常IDEによって処理されるため、作業の簡単な部分をやり直します)
  • これらの変更をコミットする
  • 変更を再適用します(git stash popを使用するか、ファイルをコピーして戻します)
  • 残りの変更をコミットする
4
Chris Maes

もちろん、これはクレイジーです。しかし、ご存知のように、私の仕事には時々尋ねるクレイジーなワークフローがいくつかあり、通常、狂気にはいくつかの正当な理由があります。

わかりました。パターンは行ごとのパターンですか、それとも「チャンクに含まれている場合」のパターンですか。それが行ごとである場合、おそらくあなたはこのようなことをすることができます。これは正確には機能しませんが、それは始まりです

git diff <file> | egrep '^[^+]|<pattern' > file.patch
git stash
git apply file.patch

探しているパターンを持つチャンクを適用する必要がある場合は、差分を解析するために、より長く、よりステートフルなスクリプトが必要になります。実際、それはおそらくとにかく必要です。差分セクションの始まりを示す「@@」文字を探して、差分をクロールします。最後までそのセクションをバッファリングします。問題のパターンに遭遇した場合は、セクションを出力し、そうでない場合は、それを破棄します。次に、その新しい差分をパッチとして適用します。

git diff <file> | parse_diff_script.sh > file.patch
git stash
git apply file.patch
2
Mort