Gitlab 読み (3)

lib/hooks/post-receive から確認してみます。
このファイルですが導入ドキュメントに

## Setup GitLab Hooks

    sudo cp ./lib/hooks/post-receive /home/git/.gitolite/hooks/common/post-receive
    sudo chown git:git /home/git/.gitolite/hooks/common/post-receive

という記述があり、push 時に実行される hook という理解で良いのかな。
git の hook は引数を標準入力から受け取る模様。以下なコマンドを受け付けていますね (一部のみ)。

env -i redis-cli rpush "resque:gitlab:queue:post_receive"

ええと 'resque:gitlab' で grep したら config/initializers/4_sidekiq.rb にうんぬんと出力。ちなみに sidekiq ですが

RAILS_ENV=production bundle exec rake sidekiq:start

で開始されてますね。うーん。とりあえず app/workers/post_receive.rb と git な hook の postreceive を見比べてみるに perform の引数が以下な定義になってて

  def perform(repo_path, oldrev, newrev, ref, identifier)

queue に突っ込まれているソレが以下。

"{\"class\":\"PostReceive\",\"args\":[\"$repo_path\",\"$oldrev\",\"$newrev\",\"$ref\",\"$GL_USER\"]}"

引数が羅列されてて順番はそのまんま、ですね。repo_path は直前で

  repo_path=`pwd`

されてます。むむ。あるいは GL_USER は lib/gitlab/backend/grack_auth.rb にて

        ENV['GL_USER'] = user.email

みたいなことされてますが微妙。どちらにしても何となく push な hook 経由で PostReceive な perform が呼び出されるに違いない、と類推。
そこから

    project.trigger_post_receive(oldrev, newrev, ref, user)

が呼び出されていますがこれが本線なのかどうか。上記は project な model のメソドですね。

  # This method will be called after each post receive and only if the provided
  # user is present in GitLab.
  #
  # All callbacks for post receive should be placed here.
  def trigger_post_receive(oldrev, newrev, ref, user)

ちょっとこれが何をしているか、を確認してみる必要があるのかどうなのか。掘削開始にもの凄い気合いが必要な気がしますが構わず掘削開始。

始点は Project#trigger_post_receive

順に掘削。とりあえず以下から。

  def trigger_post_receive(oldrev, newrev, ref, user)
    data = post_receive_data(oldrev, newrev, ref, user)

post_receive_data メソドのコメントの一部が以下。

  # Produce a hash of post-receive data

基本的には

  • repository.commits_between で push に含まれる commit の固まり (配列?) 取得
  • commit 回数取得
  • 直近 20 個分取得
  • 辞書作る
  • 辞書の commits な配列に直近のソレを辞書にして格納

して戻してる模様。順に見ていきます。まず、commit の固まり取得が以下。

    push_commits = repository.commits_between(oldrev, newrev)

Repository#commits_between の定義が以下。

  def commits_between(from, to)
    Commit.commits_between(repo, from, to)
  end

ええと、Commit.commits_between は以下で良いのかな。

    def commits_between(repo, from, to)
      repo.commits_between(from, to).map { |c| Commit.new(c) }
    end

あら、Repository の repo という属性は何なのかな。以下な定義がありました。

  def repo
    @repo ||= Grit::Repo.new(path_to_repo)
  end

Grit て何でしょ。Gemfile 見たら gitlab 発の何か、なのかな。最終的に呼び出しているのは Grit::Repo#commits_between なのか。map してるので配列が戻る、って理解で良いかな。
https://github.com/gitlabhq/grit によれば以下とのこと。

Grit gives you object oriented read/write access to Git repositories via Ruby. The main goals are stability and performance. To this end, some of the interactions with Git repositories are done by shelling out to the system's git command, and other interactions are done with pure Ruby reimplementations of core Git functionality. This choice, however, is transparent to end users, and you need not know which method is being used.

で、trigger_post_receive に戻って次。

    # Create push event
    self.observe_push(data)

Project#observe_push の定義が以下。

  def observe_push(data)
    Event.create(
      project: self,
      action: Event::Pushed,
      data: data,
      author_id: data[:user_id]
    )
  end

むむ。ActiveRecord#create はオブジェクト生成して save まで、とありますね。ちなみに db/schema.rb 見てみると以下なカンジでした。

  create_table "events", :force => true do |t|
    t.string   "target_type"
    t.integer  "target_id"
    t.string   "title"
    t.text     "data"
    t.integer  "project_id"
    t.datetime "created_at",  :null => false
    t.datetime "updated_at",  :null => false
    t.integer  "action"
    t.integer  "author_id"
  end

Event::Pushed な Event のレコードを新規作成、したんですね。Event というクラスの用途については別途確認する必要あり。
次行きます。trigger_post_receive に戻って以下のあたり。

    if push_to_branch? ref, oldrev
      # Close merged MR
      self.update_merge_requests(oldrev, newrev, ref, user)

push_to_branch? の定義が以下。

  def push_to_branch? ref, oldrev
    ref_parts = ref.split('/')

    # Return if this is not a push to a branch (e.g. new commits)
    !(ref_parts[1] !~ /heads/ || oldrev == "00000000000000000000000000000000")
  end

う、ref って何だっけ。ref ではなくて refs か。ref が "refs/heads/" でなければ branch ではない、という事になる、のかどうか。
あら、もしかして merge request 関連だと違うナニを作るのかな。
workflow てきには remote branch を作って merge して欲しい branch を指定するのかどうか。これ、git-flow てきになんとなく良いカンジなのかな。git-flow だといきなり merge しちゃってますが branch を push して merge してくれ、ってカンジになるのかなぁ。
む、help/workflow 見てみたら以下とのこと。

  • Clone project
git clone git@example.com:project-name.git
  • Create branch with your feature
git checkout -b $feature_name
  • Write code. Commit changes
git commit -am "My feature is ready"
  • Push your branch to GitLab
git push origin $feature_name
  • Review your code on Commits page
  • Create a merge request
  • Your team lead will review code & merge it to main branch

つーことはこれができるのは Developer な権限持ってる人から、になるのか。あ、それで push したときに hook して云々、になってるのだな。成程。これベースで jenkins にナニをして、て workflow はありですね。

今日はこれで

一旦置きます。あまり掘れてる感が無いなぁorz