$ 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 blame
はgit blame COMMIT^ -- <filename>
ではなく、名前の変更に従います
しかし、これは、ファイル名の変更を大量の名前変更と大量の履歴を通じて手動で追跡するのが困難です。この動作は、git blame COMMIT^ -- <filename>
の名前変更を暗黙のうちに追跡するように修正する必要があると思います。または、少なくとも--follow
を実装する必要があるため、次のことができます:git blame --follow COMMIT^ -- <filename>
PDATE2:それは不可能です。以下をお読みください。
メールリストからの回答 Junio C Hamano
git blame
はgit 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がなかった場合、唯一賢明なことは推測する代わりに終了します(ユーザーに推測方法を知らせずに)。
git blame
does名前の変更に従います(git log
を指定した場合は--follow
と同様)。問題はwayにあります。名前の変更に従いますが、これは完全なハックではありません。一度に1つのコミットを(各子から各親に)戻すので、差分が作成されます—手動で作成できるのと同じ種類のdiff:
git diff -M SHA1^ SHA1
-そして、このdiffが名前変更を検出したかどうかを確認します。1
それはそれで十分ですが、git blame
が名前の変更を検出するには、(a)git diff -M
がableである必要があります(幸い、ケースはここにあります)そして—ここに問題の原因があります—それは名前を変更するステップでなければなりません。
たとえば、コミットグラフが次のようになっているとします。
A <-- B <-- ... Q <-- R <-- S <-- T
各大文字はコミットを表します。さらに、R
からR
までのコミットではT
の名前が付けられ、newname
からA
までのコミットではQ
の名前が付けられるように、ファイルがコミットoldname
で名前が変更されたとします。
git blame -- newname
を実行すると、シーケンスはT
から始まり、S
とT
を比較し、R
とS
を比較し、Q
とR
を比較します。 WhenはQ
とR
を比較し、git blame
は名前の変更を検出し、コミットoldname
以前でQ
の検索を開始するため、P
とQ
を比較すると、ファイルoldname
とoldname
を比較しますこれら2つのコミット。
一方、git blame R^ -- newname
(またはgit blame Q -- newname
)を実行して、シーケンスがコミットQ
で始まる場合、そのコミットにはファイルnewname
がなく、P
とQ
を比較するときに名前が変更されません。 、および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 oldsha1
mayは、HEAD
からoldsha1
に変換するには、ファイルをR
enameする必要があると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つの問題があります:
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
)を使用して、最初に最も古い履歴を要求しないでください。
最新の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
変更する前にファイルを隠しておく必要があります