複数のgitのレポジトリを巡回するスクリプト
ここ最近perl界隈の人たちもgithubに移行してきてて、watchしてるプロジェクトが大幅に増えてくると、githubのtimelineを全部みることもできないし、リポジトリの更新をサボりがちだったのですが、各プロジェクトを巡回してupdateだけ取ってくるスクリプトを書いてみてここ数日試してる感じだとなかなか快適なので、久しぶりにBlogのネタにでもしてみる。
あるディレクトリ以下に、gitのレポジトリを集めまくっている状況で、
ruby git-update.rb
とかいうように実行すると、各リポジトリに対して、git remote update(あるいはgit svn fetch)してくれるというだけのスクリプトです。
git pullとかしないのは、自分の変更とかが途中になってたとか、トピックブランチをチェックアウトしてあった状態でうっかりmergeされるのが嫌なためです。変更があったやつのうち、気になったリポジトリへcdして、
git whatchanged origin/master@{1}..origin/master --reverse --stat
みたいな感じで前回の更新からの変更ログをみて、さらに知りたければ、
git whatchanged origin/master@{1}..origin/master --reverse --stat -p
でコードを読んで、特に問題なければ、git rebase origin/masterしたりしています。(git whatchanged oigin/master@{1}..origin/master --reverse --stat をwtというaliasにしてます。これのorigin/masterのところを任意にできるaliasの定義方法知ってる人がいたら知りたいかも。)
bzrとか、hgとかにも対応しても便利そうですが、今のところあまり巡回してるのはないので個人的にはこれで事足りています。
gitのレポジトリはほとんどgithubなのでアクセスが集中してしまうのがどうかなと思いつつ、いちおうsleep1くらいはするようにしています。
ソースはこちらから。ruby1.9でしか動かしてませんが、ruby1.8.6以降であれば動くと思います。
require 'pathname' require 'logger' require 'thread' require 'open3' class App def initialize args @logger = args[:logger] or raise "no logger" @ignore = args[:ignore] || [] end def run base targets = Pathname.glob("#{base}/**/.git").sort threads = [] targets.each do |dir| next if @ignore.any? {|i| i =~ dir.to_s } threads << Thread.new do |t| @logger.debug("start update #{dir}") git_update dir.parent.realpath @logger.debug("end update #{dir}") end sleep 1 # stop too many access to github. # for avoiding fork bomb if threads.length >= 10 while th = threads.pop th.join end end end threads.each {|th| th.join } end private def git_update dir git_cmd = "git --git-dir=#{dir}/.git" Open3.popen3 "#{git_cmd} remote update && #{git_cmd} config --get-regexp svn-remote > /dev/null && #{git_cmd} svn fetch" do |stdin, stdout, stderr| stdout.each_line do |line| if line =~ /^Updating / # 必ず出るので特別扱い。 @logger.debug("#{dir}: #{line}") else @logger.info("#{dir}: #{line}") end end stderr.each_line do |line| @logger.error("#{dir}: #{line}") end end end end def main logger = Logger.new($stderr) logger.level = Logger::INFO app = App.new(:logger => logger, :ignore => [ %r{ruby/(pathobserver|rubycocoa)} ]) app.run Pathname(__FILE__).parent.realpath end main()
補足
sshのパスフレーズをなるべく何度も設定していなくてもよい設定にするのが継続してチェックするポイントかなと思います。
なので自分は、cloneするurlは、gitプロトコルを利用するもの、pushするurlはsshを利用するものに設定してます。
git configだとこんな感じ。
[remote "origin"] url = git://github.com/nekokak/p5-dbix-skinny.git pushurl = git@github.com:nekokak/p5-dbix-skinny.git fetch = +refs/heads/*:refs/remotes/origin/*
入門Gitで紹介されてて気づきました。RelNotesによると、git 1.6.4とのことなので結構新しめのを入れてないとこの機能はないかもしれません。
他の方法としては、自分はあまり利用したことないのですが、SSHKeychainなどを使うと入力が何度もしなくてもよいようです。(git-svnでsshでの書き込みしか公開されていないsvnリポジトリではこの方法しかないかも)
追記
ちょっと、接続先のサーバーの応答が悪いと、thread & systemの組合せで、fork bombっぽくなってしまうので、ちょっと対策入れてみました。もし使ってる人がいたらupdateをおすすめします。