web-dev-qa-db-ja.com

なぜgit blameが名前の変更に従わないのですか?

$ pwd
/data/mdi2/classes

$ git blame -L22,+1 -- utils.js
99b7a802 mdi2/utils.js (user 2015-03-26 21:54:57 +0200 22)  #comment

$ git blame -L22,+1 99b7a802^ -- utils.js
fatal: no such path mdi2/classes/utils.js in 99b7a802^

お気づきのとおり、ファイルはそのコミットの別のディレクトリにありました

$ git blame -L22,+1 99b7a802^ -- ../utils.js
c5105267 (user 2007-04-10 08:00:20 +0000 22)    #comment 2

Docにもかかわらず

The Origin of lines is automatically followed across whole-file renames (currently there is no option to turn
       the rename-following off)

非難は名前変更に従いません。どうして?

更新:短い答え

git blamegit blame COMMIT^ -- <filename>ではなく、名前の変更に従います

しかし、これは、ファイル名の変更を大量の名前変更と大量の履歴を通じて手動で追跡するのが困難です。この動作は、git blame COMMIT^ -- <filename>の名前変更を暗黙のうちに追跡するように修正する必要があると思います。または、少なくとも--followを実装する必要があるため、次のことができます:git blame --follow COMMIT^ -- <filename>

PDATE2:それは不可能です。以下をお読みください。

メールリストからの回答 Junio C Hamano

git blamegit blame COMMIT^ -- <filename>ではなく、名前の変更に従います

バージョンv1.0にファイルAとファイルBがあるとします。

6か月後、コードは大幅にリファクタリングされたので、これら2つのファイルの内容を個別に用意する必要はありません。 AとBを削除しました。それらの多くはファイルCにあります。それが現在の状態です。

git blame -C HEAD -- C

両方からのコンテンツを順調に追跡できますが、あなたがした場合

git blame v1.0 -- C

それはどういう意味ですか? Cはv1.0にはまったく存在しませんでした。当時のAの内容、またはBの内容をフォローするように求めていますか?このコマンドでCと言ったときに、BではなくAを意味しているとどうして言いましたか?

「git blame」はコンテンツの動きに従い、「名前の変更」を特別な方法で処理することはありません。名前の変更がなんらかの特別なものであると考えるのは愚かなことです;-)

コマンドラインからコマンドに掘り始めるコンテンツを伝える方法は、開始点をコミットすることです(デフォルトはHEADですが、例としてCOMMIT ^を指定できます)。その出発点。CをGitに伝えて、魔法のように、ある場合にはAを意味し、他の場合にはBを意味するようにしても意味がありません。v1.0にCがなかった場合、唯一賢明なことは推測する代わりに終了します(ユーザーに推測方法を知らせずに)。

41
Eugen Konkov

git blamedoes名前の変更に従います(git logを指定した場合は--followと同様)。問題はwayにあります。名前の変更に従いますが、これは完全なハックではありません。一度に1つのコミットを(各子から各親に)戻すので、差分が作成されます—手動で作成できるのと同じ種類のdiff:

git diff -M SHA1^ SHA1

-そして、このdiffが名前変更を検出したかどうかを確認します。1

それはそれで十分ですが、git blameが名前の変更を検出するには、(a)git diff -Mableである必要があります(幸い、ケースはここにあります)そして—ここに問題の原因があります—それは名前を変更するステップでなければなりません。

たとえば、コミットグラフが次のようになっているとします。

A <-- B <-- ... Q <-- R <-- S <-- T

各大文字はコミットを表します。さらに、RからRまでのコミットではTの名前が付けられ、newnameからAまでのコミットではQの名前が付けられるように、ファイルがコミットoldnameで名前が変更されたとします。

git blame -- newnameを実行すると、シーケンスはTから始まり、STを比較し、RSを比較し、QRを比較します。 WhenQRを比較し、git blameは名前の変更を検出し、コミットoldname以前でQの検索を開始するため、PQを比較すると、ファイルoldnameoldnameを比較しますこれら2つのコミット。

一方、git blame R^ -- newname(またはgit blame Q -- newname)を実行して、シーケンスがコミットQで始まる場合、そのコミットにはファイルnewnameがなく、PQを比較するときに名前が変更されません。 、およびgit blameは単にあきらめます。

トリックは、ファイルに以前の名前が付いているコミットから開始する場合、gitに古い名前を付ける必要があるということです。

git blame R^ -- oldname

そして、それはすべて再び機能します。


1git diffのドキュメント では、how-Mが名前の変更を検出することを制御するgit diffオプションがあることがわかります。 blameコードはこれを少し変更し(実際には2つのパスを実行します。1つは-Mをオフにし、もう1つは-Mをオンにします)、独自の(異なる)-Mオプションを使用します。目的は多少異なりますが、最終的には同じコードを使用しています。


[編集コメントへの返信を追加します(コメント自体に収まりませんでした)]:

Git renames <filename> SHA date oldname-> newnameのようなファイル名の変更を表示できるツールはありますか

正確ではありませんが、git diff -Mが近くなり、十分に近い可能性があります。

ここで「SHA日付」の意味がわかりませんが、git diff -Mでは、2つのSHA-1を指定して、左と右を比較できます。 --name-statusを追加して、ファイル名と処理のみを取得します。したがって、git diff -M --name-status HEAD oldsha1mayは、HEADからoldsha1に変換するには、ファイルをRenameする必要があるとgitが報告し、古い名前を「新しい」名前として報告します。たとえば、gitリポジトリ自体には、現在Documentation/giteveryday.txtという名前のファイルがあり、以前は少し異なる名前でした。

$ git diff -M --name-status HEAD 992cb206
M       .gitignore
M       .mailmap
[...snip...]
M       Documentation/diff-options.txt
R097    Documentation/giteveryday.txt   Documentation/everyday.txt
D       Documentation/everyday.txto
[...]

それがあなたが気にかけているファイルであれば、あなたは大丈夫です。ここに2つの問題があります:

  • sHA1を見つける:992cb206はどこから来たのですか?すでにSHA-1をお持ちの場合は簡単です。そうでない場合、git rev-listはSHA1検索ツールです。そのドキュメントを読んでください。
  • そして、git blameが行うように、一度に1つのコミットで各コミットを介して一連の名前変更を行うと、かなり前のコミット(HEAD)と以前のコミット(992cb206など。この場合、同じ結果になりますが、ここでの「類似性インデックス」は100のうち97です。いくつかの中間ステップでさらに変更された場合、その類似性インデックスは50%を下回る可能性があります...それでも、992cb206から992cb206の後にlittleを付けてリビジョンを比較すると(git blameと同様)、おそらく2つのファイル間の類似性インデックスは高くなる。

必要な(そして欠落している)のは、git rev-list自体が--followを実装して、内部でgit rev-listを使用するすべてのコマンド(つまり、複数のリビジョンで機能するほとんどのコマンド)が実行できるようにすることです。トリック。途中で、それが他の方向で動作した場合はいいでしょう(現在--followは新旧のみです。つまり、git blameで問題なく動作し、git logで問題なく動作します。 --reverse)を使用して、最初に最も古い履歴を要求しないでください。

32
torek

最新のgitには興味深いコマンドがあります。設定の横に追加:

[alias]
    follow= "!sh -c 'git log --topo-order -u -L $2,${3:-$2}:"$1"'" -

今することができます:

$git follow <filename> <linefrom> [<lineto>]

そして、<filename>の指定された行を変更する各コミットが表示されます。

また、--followコマンドのgit logオプションに興味がある場合もあります。

名前の変更を超えてファイルの履歴をリストし続けます(単一のファイルでのみ機能します)。

コピー検出に関心がある場合は、-Cを使用してください。

コピーと名前の変更を検出します。 --find-copies-harderも参照してください。 nを指定すると、-Mと同じ意味になります。

-Cは、同じコミットで異なるファイルを検索します。このコミットで変更されなかった別のファイルからコードが取得されたことを検出したい場合。次に、--find-copies-harderオプションを指定する必要があります。

パフォーマンス上の理由から、デフォルトでは、-Cオプションはコピーの元のファイルが同じチェンジセットで変更された場合にのみコピーを見つけます。このフラグにより​​、コマンドは変更されていないファイルをコピー元の候補として検査します。これは大規模なプロジェクトでは非常にコストのかかる操作であるため、注意して使用してください。複数の-Cオプションを指定しても同じ効果があります。

[〜#〜]更新[〜#〜]
このエイリアスを改善します:

[alias]
    follow = "!bash -c '                                                 \
        if [[ $1 == \"/\"* ]]; then                                      \
            FILE=$1;                                                     \
        else                                                             \
            FILE=${GIT_PREFIX}$1;                                        \
        fi;                                                              \
        echo \"git log --topo-order -u -L $2,${3:-$2}:\\\"$FILE\\\"\";   \
        git log --topo-order -u -L $2,${3:-$2}:\"$FILE\";                \
    ' --"

これで、指定した範囲の行がどのように変更されるかを追跡できます。

git follow file_name.c 30 35

注意:残念ながら、gitは作業ディレクトリの変更を考慮していません。したがって、ファイルをローカルで変更する場合は、follow変更する前にファイルを隠しておく必要があります

1
Eugen Konkov