git push -u <myRemote> HEAD
という具体的使用例が出てくるわけじゃあない。if exist A\ echo A is a directory.
でディレクトリが判別できるとあって、確かにそうだったのだけど、例外が見つかった。■テストした A が zip ファイルだったから書庫フォルダの特別扱いかと思ったがただのテキストファイルでも同じだった。ファイルを上位階層に移動しもってテストしていったら、ジャンクションを境に判別ができなくなっていた。■Y:\dirA\dirB というパスに T: を割り当てていると考えてほしい。dirB より下の階層ではフォルダ判別ができずに、ファイルパスの末尾に \ が付いていようが存在を確認できてしまった。■やめてくれよ……。気がついても考慮したくないよ……。cmd /C
を頭に付けて必要なら引数を二重引用符でくくってって面倒くさい。|a.bat
とは書けなかったけど call|a.bat
とか echo>nul|a.bat
なら呼び出し元に影響を与えない新しい環境でバッチを呼び出せた。普通に何も出力しない NOOP コマンドがあれば意図が明確なんだけどなあ。残念、rem|a.bat
は無理だった。■あとバッチファイル衝撃の事実。入力を受け取って処理して出力するフィルタを書こうとしたら、標準入力に対して繰り返すコマンドが存在しなかった。for にそういう専用のオプションがあってしかるべきだと思う。これってバッチの用途のど真ん中にあると思うんだ。他のコマンドをパイプで繋ぐのはお手のものなのに、バッチファイルがそのコマンドの一翼を担うことができないのはなんで?最終更新: 2019-04-12T22:46+0900
タイムスタンプを保持してくれないために更新日時がチェックアウト日時になってしまう Git のために。
文法が手探りなので何時間もかかった。とりあえず覚えるべきは help *
、|gm
、.GetType()
。セルフドキュメンテッドである。map は foreach {$_}
、filter は where {$_}
。式やコマンドをくくるのは $(...)
。演算子は -band
だったりするからリファレンスを見るしかない。リファレンスが手元にない。
いやあった。Windows SDK > Win32 and COM Development > Administration and Management > Windows PowerShell もしくは、スタートメニュー > すべてのプログラム > アクセサリ > Windows PowerShell > Windows PowerShell ISE を起動して F1。インストールはしたけど興味がないだけだった。
git ls-files | foreach { @{ File = "$(pwd)\$_" -as [System.IO.FileInfo] Time = $(git log --pretty=format:%ci -n 1 -- "$_") -as [DateTime] } } | where { $_.File } | foreach { try { $_.File.LastWriteTime = $_.Time } catch { Write-Error $_ } }
明らかに git log をパスでフィルタリングして最新の時刻を取り出す処理が遅い。SQL ではないけど構造的には N+1 問題。
ls-files の段階でコミットオブジェクトを取得してそれの時刻を取り出せないかと考えたが、--stage オプションで取り出せた ID を git show した結果はファイルの内容だけだった。
post-checkout フックに仕込むのは無理だなあ。
N+1 問題を抱えてるのは日本語で公開されているスクリプトであって、Perl と英語で書かれた「ExampleScripts - Git SCM Wiki」はたぶんそうではない。Perl は読めないけどたぶん。
このバージョンだとログの長さに比例した時間がかかるものの、サクラエディタのリポジトリ(ログ数3600)で "Roughly" バージョンのたかだか3倍の時間(6秒)で完了する。
<# .SYNOPSIS Git ワーキングツリーに含まれるファイルの更新日時に最終コミット日時を設定します。 .DESCRIPTION Git でチェックアウトしたファイルの更新日時はチェックアウト日時になります。これはファイルのタイムスタンプを比較する場合に不都合なことがあり、代わりに最終コミット日時をタイムスタンプに設定します。 Git リポジトリ内で実行します。すべての階層のファイルが対象になります。 #> $TopDir = (git rev-parse --show-toplevel) $waiting = @{} (git ls-files --full-name "$TopDir") | foreach { $waiting[$_] = $NULL } (git log --format=format:?%ci --name-only) | foreach { if ($_ -eq "") { } elseif ($_[0] -eq "?") { $t = $_.Substring(1) } else { $f = [System.IO.FileInfo] "$TopDir\$_" # run once a file. $c = $waiting.Count $waiting.Remove($_) if ($waiting.Count -ne $c -and $f.Exists) { try { $t = [System.DateTime] $t $f.LastWriteTime = $t } catch { Write-Debug $_ } } } }
git ls-files
を使っている。git subtree
によって追加されて以後変更されていないファイルのコミット日時が取れない。「Subtrees allow subprojects to be included within a subdirectory of the main project, optionally including the subproject’s entire history.」
たぶん git log
に -m --first-parent
を付けるのが一番期待するものになる。
最終更新: 2019-04-12T22:46+0900
RestoreCommitterDate.ps1 が実行に1分から1分半かかるとしたら、この RestoreCommitterDateRoughly.ps1 は2、3秒で終わる。
"Roughly" が意味するところは、1か月分のログだけを遡ってワーキングツリーにあるファイルのタイムスタンプを設定するのであって、それ以上前に変更されたファイルは一律で1か月前のタイムスタンプが設定されること。
このスクリプトの目的はキャッシュしたビルド産物を再利用することで再コンパイルを省略することにあって、そのためにはファイルのタイムスタンプがチェックアウト日時であっては困る。Git はそうだ。AppVeyor は必ず git clone、git checkout からビルドを始めるから、チェックアウトしたソースファイルが必ず前回のビルド産物より新しくなってしまう。
変更がなくても1か月経てば再コンパイルされてしまうけど、まあ、リーズナブルなスパンでしょう。
<# .SYNOPSIS Git ワーキングツリーに含まれるファイルの更新日時に最終コミット日時を設定します。 .DESCRIPTION Git でチェックアウトしたファイルの更新日時はチェックアウト日時になります。これはファイルのタイムスタンプを比較する場合に不都合な場合があり、代わりに最終コミット日時をタイムスタンプに設定します。 Git リポジトリ内で実行します。すべての階層のファイルが対象になります。 制限: ログを遡りすべてのファイルの最終コミット日時を取得することは1分以上かかることがあるため、このスクリプトでは1か月を上限としてログを遡ります。そのため最も古いタイムスタンプは実行日時を基準として前月同日の00:00:00になります。 #> $TopDir = (git rev-parse --show-toplevel) $Oldest = [DateTime]::Today.AddMonths(-1) # Of course, this is incorrect for the oldest timestamp. That's what "Roughly" means. $waiting = @{} (git ls-files --full-name "$TopDir") | foreach { $waiting[$_] = $NULL } @( (git log --format=format:?%ci --name-only --since="$($Oldest.ToString("yyyy-MM-dd HH:mm:ss"))") , "?$($Oldest.ToString("yyyy-MM-dd HH:mm:ss"))" , (git ls-files --full-name "$TopDir") ) | foreach { $_ | foreach { if ($_ -eq "") { } elseif ($_[0] -eq "?") { $t = $_.Substring(1) } else { $f = [System.IO.FileInfo] "$TopDir\$_" # run once a file. $c = $waiting.Count $waiting.Remove($_) if ($waiting.Count -ne $c -and $f.Exists) { try { $t = [System.DateTime] $t $f.LastWriteTime = $t } catch { Write-Debug $_ } } } } }
たぶん git log
に -m --first-parent
を付けるのが一番期待するものになる。
何もなしもありっちゃあり。-m のみ付けるのはぴったりはまる状況がわからない。
-m オプションのみの場合に、双方のブランチから見た修正内容が2つのコミットとして出力されています。一方が長大なリストを出力しているのはブランチの側で master を追いかけて pull/merge/rebase した結果だと見られます。
というのはたぶん間違い。むしろ rebase しなかった結果、分岐から合流までのあいだに master が進んだその差分が現れている。
この間違いで論旨(「明らかに無視すべきものです。」)は変わらない。
最終更新: 2019-04-12T22:46+0900
コミット構成を変えるために複数のブランチを作成したりマージしたりなんだりしてから作業用のブランチを整理したら、再構成後のブランチまで一緒に削除してしまっていた! 1時間の作業成果!
.git/objects/*/* から一番新しいコミットオブジェクトをサルベージしてなんとかなった。サルベージっていうのは git checkout -b MyAnHour xxx(全部で40桁)xxx
すること。
コミットの再構成は git rebase でもできるらしい。削除・入れ替え・併合は当然。edit オプションを使えばコミットの分割もできるとか。
そういえばまだ reflog って知らない。
最終更新: 2021-05-07T19:12+0900
「6.3 GitHub - プロジェクトのメンテナンス - プルリクエストの参照」
GitHub のリポジトリを git clone --mirror
してると refs/pull/xxx/head
, refs/pull/xxx/merge
という見慣れない refs がダウンロードされてくる。
これはそのまま refs/pull/xxx/head
という名前でコミットオブジェクトが参照でき、git fetch
や git pull
の引数として利用できる。
リンク先で書かれていることに従って .git/config の <remote> 名のセクションで fetch = +refs/pull/*/head:refs/remotes/<remote>/pull/*
というマッピングルールを付け加えると、<remote>/pull/xxx
というブランチ名が利用できるようになる。git branch --remotes
が大量の PR ブランチで押し流されることと、コミットオブジェクトの大群がダウンロードされてくることが気にならないなら、便利。
PR を出した人のリポジトリを URL なり登録したリモート名なりで参照することなく、git checkout -b PRxxx <remote>/pull/xxx
するだけでプルリクエストが試せる。
PR は誰でも出せるものであるからして、問題のあるコードを簡単にダウンロードできてしまう方法だという自覚は必要。
git fetch <remote>
したら新規 PR が [new ref] として降ってくるし、更新があった PR の番号もわかる。そこで git show <remote>/pull/xxx
すると最新のコミットの内容から PR をチラ見できる。
gitk
するとあれやこれやのマージコミットの横に remotes/<remote>/pull/xxx
というラベルが付いてどの PR 由来のコミットかがわかる。数が多すぎてちょっとうるさいけど。
いやあ便利。
大量の PR ブランチに押し流されるのが嫌なら、最初に書いた fetch におけるマッピングルールを工夫して、<remote> におけるコミットオブジェクトへの参照名(refs/pull/xxx/head)をそのままローカルで利用する参照名としてダウンロードできると思う。ローカルの名前なら fetch, pull に限らない幅広いサブコマンドで利用できるでしょう。
やってみた。同じ場所に同じように fetch = +refs/pull/*/head:refs/pull/<remote>/*
と書いたら同じように [new ref] が降ってきた。refs/pull/xxx/head
は GitHub にあるリポジトリで利用できる名前だけど、refs/pull/<remote>/xxx
は同じコミットオブジェクトを指すローカルの名前。git show refs/pull/<remote>/xxx
でコミットの内容が見られるのはさっきと同じだけど、git checkout -b PRxxx refs/pull/<remote>/xxx
するまではブランチとしては存在しない。gitk
するとこれまで見たことのない背景色で pull/<remote>/xxx
というラベルが付いていた。コマンドの引数で refs/
は省略できるみたい。
refs/heads, refs/remotes, refs/tags の基盤となる refs という機能が Git にはあって、挙げた3つは Git が標準的に利用している。refs/pull は GitHub が私的に利用している。gitk はすべての refs をラベルとして表示することができ、既知の種類のラベルに特別の色分けを施していただけなのだろう。
あまり区別せずにブランチって書いてきたけど、最初の fetch ルールで作成するのはローカルのリポジトリで定義したリモート名の下にあるとするリモートブランチ。ローカルのブランチではないし、リモートにそのままの形で実在するブランチでもない(fetch 後に削除されたかもしれないし、fetch のマッピングルールによって名前を変えたのは自分だ)。実際のところ refs と何が違うのかわからない。git branch -r
でリストできるかどうかの違いしか今のところわからない。git push
の既定の動作に違いが現れるのかもしれないけど、そういう自動化は無効にしてるから本当に違いがない。
たぶん今日ここに書いたことは、わかる人はすでにわかってる、わからない人には何の参考にもならない、そんな内容だと思う。こち亀で「OS」だの「インストール」だのといった専門用語(※そういう時代!)が飛び交っていて、柱をびっしり埋める脚注を読んでもさっぱりわからなかった回のように。