読者です 読者をやめる 読者になる 読者になる

kotas.tech

こたすの技術的なチラ裏 ( Twitter: @ksaito )

変態 git

git を変態的に使ったのでメモ。

どれもこれも、かなり特殊なシチュエーションなので参考になりません。多分。

SVN リポジトリを read-only で Git に取り込む

色々な都合により、コミットしてはいけない SVN リポジトリを git に取り込む必要があったので。(SVN サーバー側の hook 等は使えず)

普通に git-svn で取り込んだブランチを直接 git の中央リポジトリに push すると、他の人が clone して git svn dcommit とかできてしまうので、出来ないようにしたい。

そこで、git-svn で取り込んだ変更を、一度 Non-git-svn なブランチに merge し、そちらを push する運用にした。

レシピ

まず、空の git リポジトリを初期化。

$ mkdir hoge
$ cd hoge
$ git init
$ touch .gitignore
$ git add .gitignore
$ git commit -m 'initial commit'
$ git remote add origin REMOTE_GIT_REPOS
$ git push -u origin master

SVN リポジトリを取り込む。git svn clone ではなく、git svn init + fetch で一旦リモートブランチだけ作るのがミソ。

$ git svn init -R svn-hoge --prefix "svn-hoge/" SVN_REPOS_URL
$ git svn fetch -rHEAD svn-hoge
# でかい SVN リポジトリなので -rHEAD で最新のリビジョンだけ取り込んでます

作ったリモートの SVN ブランチ (svn-hoge/git-svn) をローカルブランチとして作成

$ git checkout -b svn-hoge svn-hoge/git-svn

この時点で、Non-git-svn な master と、git-svn な svn-hoge ブランチが出来る。

$ git branch -a
  master
* svn-hoge
  remotes/origin/master
  remotes/svn-hoge/git-svn

master と svn-hoge は全然関係ないブランチとなっているので、master では git svn rebase や dcommit は出来ない。

あとは、定期的に以下を行なって、master に SVN のブランチをマージ。

$ git checkout master
$ git merge --no-ff svn-hoge
$ git push origin master

git merge の際に --no-ff を付けておかないと、 master が git-svn 化してしまうので注意。

また、master を git 側で変更する場合、merge がコンフリクトする可能性があるけど、失敗した場合はアラート出して人間がマージします。

特定ワードをコミットメッセージに含むコミットだけ strategy を変えてマージ

色々な都合により、普通にマージすると特定のワードを含むコミットだけコンフリクトが起こるので、そいつらだけ --strategy=ours でマージしたかった。

結論から書くと、何回かにわけてマージする形で解決。

レシピ

git rev-list の --grep オプションを使うと、2つのブランチの差分のうち、特定のワードをコミットメッセージに含むコミットだけをリストアップできる。(git マジ変態。いい意味で)

# master 〜 hoge の差分の内、"特定のワード" を含むコミットをリストアップ (リビジョンのリストが返る)
$ git rev-list --grep="特定のワード" master..hoge

あとは、これで得られるリビジョンそれぞれについて git merge $rev^ + git merge -s ours $rev をする。($rev^ = $rev の1つ前のリビジョン)

$ git checkout master
$ for rev in `git rev-list --reverse --grep="特定のワード" master..hoge`; do
       git merge --no-ff "${rev}^" -m "early commit for merging hoge"
       git merge -s ours "${rev}"
   done
$ git merge --no-ff hoge -m "merged hoge"

とっても気持ち悪いですね^^

過去のコミット履歴から特定のワードを含むコミットを消し去る

git filter-branch を使うと、過去の履歴を改ざんできるのは git の 変態的 強みですが、それを使って特定のワードをコミットメッセージに含むコミットを無かったことにします。(もはや動機の説明に疲れたので割愛)

レシピ

git filter-branch --commit-filter でコミットを選択して無かった事にできます。

引数に渡したスクリプトが実行され、skip_commit "$@" するか、 git commit-tree "$@" するかでコミットするかどうか選択できるんですが、コミットメッセージは標準入力に渡されてくるので、普通に cat や grep しちゃうと、フィルター後のコミットのメッセージが空になってしまいます。

そこで、 /dev/stdin から読んで判定します。

$ git filter-branch --commit-filter '
  if [ `grep -c "特定ワード" /dev/stdin` -gt 0 ]; then
      skip_commit "$@";
  else
      git commit-tree "$@";
  fi' HEAD

これはそんなに変態じゃない、、、かな、、、?