liquid_feedback_frontend

changeset 1219:30523f31b186

Added lf4rcs module and example configuration
author bsw
date Mon Nov 30 19:40:38 2015 +0100 (2015-11-30)
parents bb10f001443e
children af42478acf74
files config/example.lua env/lf4rcs/_init.lua env/lf4rcs/commit.lua env/lf4rcs/exec.lua env/lf4rcs/get_config.lua env/lf4rcs/init.lua env/lf4rcs/notification_handler.lua env/lf4rcs/render_draft_reference.lua env/lf4rcs/render_initiative_reference.lua env/lf4rcs/update_references.lua
line diff
     1.1 --- a/config/example.lua	Mon Jul 27 23:33:26 2015 +0200
     1.2 +++ b/config/example.lua	Mon Nov 30 19:40:38 2015 +0100
     1.3 @@ -255,6 +255,118 @@
     1.4  }
     1.5  
     1.6  
     1.7 +-- Configuration of lf4rcs
     1.8 +-- ------------------------------------------------------------------------
     1.9 +config.lf4rc = {}
    1.10 +
    1.11 +-- Example configuration for controlling a Git repository
    1.12 +config.lf4rcs.git = {
    1.13 +  
    1.14 +  render_draft_reference = function(url, draft)
    1.15 +    if not draft.external_reference then return end
    1.16 +    ui.tag{ content = _"Changeset:" }
    1.17 +    slot.put(" ")
    1.18 +    ui.link{
    1.19 +      text = draft.external_reference,
    1.20 +      external = url .. ";a=commit;h=" .. draft.external_reference
    1.21 +    }
    1.22 +  end,
    1.23 +  
    1.24 +  get_remote_user = function()
    1.25 +    return os.getenv("REMOTE_USER")
    1.26 +  end,
    1.27 +  
    1.28 +  get_branches = function(path, exec)
    1.29 +    local branches = {}
    1.30 +    for line in io.lines() do
    1.31 +      local oldrev, newrev, branch = string.match(line, "([^ ]+) ([^ ]+) refs/heads/(.+)")
    1.32 +      if not branch then
    1.33 +        return nil, "unexpected format from git hook environment"
    1.34 +      end
    1.35 +      branches[branch] = { newrev }
    1.36 +    end
    1.37 +    return branches
    1.38 +  end,
    1.39 +  
    1.40 +  commit = function(path, exec, branch, target_node_id, close_message, merge_message)
    1.41 +    if merge_message then
    1.42 +      -- TODO reset on error
    1.43 +      exec("git", "-C", path, "checkout", "master")
    1.44 +      exec("git", "-C", path, "merge", target_node_id, "-m", merge_message)
    1.45 +      exec("git", "-C", path, "push", "origin", "master")
    1.46 +    end
    1.47 +  end
    1.48 +
    1.49 +}
    1.50 +
    1.51 +-- Example configuration for controlling a Mercurial repository
    1.52 +config.lf4rcs.hg = {
    1.53 +
    1.54 +  working_branch_name = "work",
    1.55 +
    1.56 +  render_draft_reference = function(url, draft)
    1.57 +    if not draft.external_reference then return end
    1.58 +    ui.tag{ content = _"Changeset graph:" }
    1.59 +    slot.put(" ")
    1.60 +    ui.link{
    1.61 +      text = draft.external_reference,
    1.62 +      external = url .. "/graph/" .. draft.external_reference
    1.63 +    }
    1.64 +  end,
    1.65 +  
    1.66 +  get_remote_user = function()
    1.67 +    return os.getenv("REMOTE_USER")
    1.68 +  end,
    1.69 +  
    1.70 +  get_branches = function(path, exec)
    1.71 +    local first_node_id = os.getenv("HG_NODE")
    1.72 +    if not first_node_id then
    1.73 +      return nil, "internal error, no first node ID available"
    1.74 +    end
    1.75 +    local hg_log = exec(
    1.76 +      "hg", "log", "-R", path, "-r", first_node_id .. ":", "--template", "{branches}\n"
    1.77 +    )
    1.78 +    local branches = {}
    1.79 +    for branch in hg_log:gmatch("(.-)\n") do
    1.80 +      if branch == "" then branch = "default" end
    1.81 +      if not branches[branch] then
    1.82 +        branches[branch] = {}
    1.83 +        local head_lines = exec(
    1.84 +          "hg", "heads", "-R", path, "--template", "{node}\n", branch
    1.85 +        )
    1.86 +        for node_id in string.gmatch(head_lines, "[^\n]+") do
    1.87 +          table.insert(branches[branch], node_id)
    1.88 +        end
    1.89 +      end
    1.90 +    end
    1.91 +    return branches
    1.92 +  end,
    1.93 +
    1.94 +  extra_checks = function(path, exec)
    1.95 +    local result = exec("hg", "heads", "-t", "-c")
    1.96 +    for branch in string.gmatch(result, "[^\n]+") do
    1.97 +      if branch == lf4rcs.config.hg.working_branch_name then
    1.98 +        return nil, "open head found for branch " .. lf4rcs.config.hg.working_branch_name
    1.99 +      end
   1.100 +    end
   1.101 +    return true
   1.102 +  end,
   1.103 +
   1.104 +  commit = function(path, exec, branch, target_node_id, close_message, merge_message)
   1.105 +    exec("hg", "up", "-R", path, "-C", "-r", target_node_id)
   1.106 +    exec("hg", "commit", "-R", path, "--close-branch", "-m", close_message)
   1.107 +    if merge_message then
   1.108 +      exec("hg", "up", "-R", path, "-C", "-r", "default")
   1.109 +      exec("hg", "merge", "-R", path, "-r", "tip")
   1.110 +      exec("hg", "commit", "-R", path, "-m", merge_message)
   1.111 +    end
   1.112 +  end
   1.113 +  
   1.114 +}
   1.115 +
   1.116 +lf4rcs.init()
   1.117 +
   1.118 +
   1.119  -- External references
   1.120  -- ------------------------------------------------------------------------
   1.121  -- Rendering of external references
     2.1 --- /dev/null	Thu Jan 01 00:00:00 1970 +0000
     2.2 +++ b/env/lf4rcs/_init.lua	Mon Nov 30 19:40:38 2015 +0100
     2.3 @@ -0,0 +1,1 @@
     2.4 +lf4rcs.log_prefix = "[lf4rcs] "
     3.1 --- /dev/null	Thu Jan 01 00:00:00 1970 +0000
     3.2 +++ b/env/lf4rcs/commit.lua	Mon Nov 30 19:40:38 2015 +0100
     3.3 @@ -0,0 +1,40 @@
     3.4 +function lf4rcs.commit(issue)
     3.5 +  local repository, path, url = lf4rcs.get_config(issue.area.unit)
     3.6 +  if not (config.lf4rcs[repository] and config.lf4rcs[repository].commit) then
     3.7 +    error("Unsupported repository type")
     3.8 +  end
     3.9 +  local initiatives = Initiative:new_selector()
    3.10 +    :add_where{ "issue_id = ?", issue.id }
    3.11 +    :exec()
    3.12 +  for i, initiative in ipairs(initiatives) do
    3.13 +    local function exec(...)
    3.14 +      local command, output, err_message, exit_code = lf4rcs.exec(...)
    3.15 +      local log = "Executed: " .. command .. "\n"
    3.16 +      if output then
    3.17 +        log = log .. output .. "\n"
    3.18 +      end
    3.19 +      if err_message and #err_message > 0 then
    3.20 +        log = log .. "ERROR: " .. err_message .. "\n"
    3.21 +      end
    3.22 +      if exit_code and exit_code ~= 0 then
    3.23 +        log = log .. "Exit code: " .. tostring(exit_code) .. "\n"
    3.24 +      end
    3.25 +      issue.admin_notice = (issue.admin_notice or "") .. log
    3.26 +      issue:save()
    3.27 +    end
    3.28 +    local close_message, merge_message
    3.29 +    if initiative.winner then
    3.30 +      close_message = "Initiative i" .. initiative.id
    3.31 +                      .. " accepted as winner. Closing branch."
    3.32 +      merge_message = "Initiative i" .. initiative.id 
    3.33 +                      .. " accepted as winner. Applying branch changesets to upstream."
    3.34 +    else
    3.35 +      close_message = "Initiative i" .. initiative.id .. " rejected. Closing branch."
    3.36 +    end
    3.37 +    local target_node_id = initiative.current_draft.external_reference
    3.38 +    if target_node_id then
    3.39 +      local branch = "i" .. initiative.id
    3.40 +      lf4rcs[repository].commit(path, exec, branch, target_node_id, close_message, merge_message)
    3.41 +    end
    3.42 +  end
    3.43 +end
     4.1 --- /dev/null	Thu Jan 01 00:00:00 1970 +0000
     4.2 +++ b/env/lf4rcs/exec.lua	Mon Nov 30 19:40:38 2015 +0100
     4.3 @@ -0,0 +1,12 @@
     4.4 +function lf4rcs.exec(...)
     4.5 +  local output, err_message, exit_code = extos.pfilter(nil, ...)
     4.6 +  local command_parts = {...}
     4.7 +  for i, part in ipairs(command_parts) do
     4.8 +    if string.match(part, " ") then
     4.9 +      command_parts[i] = '"' .. part .. '"'
    4.10 +    end
    4.11 +  end
    4.12 +  local command = table.concat(command_parts, " ")
    4.13 +  return command, output, err_message, exit_code
    4.14 +end
    4.15 +
     5.1 --- /dev/null	Thu Jan 01 00:00:00 1970 +0000
     5.2 +++ b/env/lf4rcs/get_config.lua	Mon Nov 30 19:40:38 2015 +0100
     5.3 @@ -0,0 +1,8 @@
     5.4 +function lf4rcs.get_config(unit)
     5.5 +  if not unit.external_reference then
     5.6 +    error("Unit is not configured for lf4rcs")
     5.7 +  end
     5.8 +  local repository, path, url = string.match(unit.external_reference, "([^ ]+) ([^ ]+) (.*)")
     5.9 +  return repository, path, url
    5.10 +end
    5.11 +
     6.1 --- /dev/null	Thu Jan 01 00:00:00 1970 +0000
     6.2 +++ b/env/lf4rcs/init.lua	Mon Nov 30 19:40:38 2015 +0100
     6.3 @@ -0,0 +1,12 @@
     6.4 +function lf4rcs.init()
     6.5 +  local super_handler = config.notification_handler_func
     6.6 +  config.notification_handler_func = function(event)
     6.7 +    if super_handler then super_handler(event) end
     6.8 +    lf4rcs.notification_handler(event)
     6.9 +  end
    6.10 +  config.render_external_reference = {
    6.11 +    draft = lf4rcs.render_draft_reference,
    6.12 +    initiative = lf4rcs.render_initiative_reference
    6.13 +  }
    6.14 +end
    6.15 +
     7.1 --- /dev/null	Thu Jan 01 00:00:00 1970 +0000
     7.2 +++ b/env/lf4rcs/notification_handler.lua	Mon Nov 30 19:40:38 2015 +0100
     7.3 @@ -0,0 +1,11 @@
     7.4 +function lf4rcs.notification_handler(event)
     7.5 +  if event.event == "issue_state_changed" and (
     7.6 +      event.state ~= "admission" and 
     7.7 +      event.state ~= "discussion" and 
     7.8 +      event.state ~= "verification" and
     7.9 +      event.state ~= "voting"
    7.10 +  ) then
    7.11 +    lf4rcs.commit(event.issue)
    7.12 +  end
    7.13 +end
    7.14 +  
     8.1 --- /dev/null	Thu Jan 01 00:00:00 1970 +0000
     8.2 +++ b/env/lf4rcs/render_draft_reference.lua	Mon Nov 30 19:40:38 2015 +0100
     8.3 @@ -0,0 +1,12 @@
     8.4 +function lf4rcs.render_draft_reference(draft, wrapper)
     8.5 +  local repository, path, url = lf4rcs.get_config(draft.initiative.issue.area.unit)
     8.6 +  if not (config.lf4rcs[repository] and config.lf4rcs[repository].render_draft_reference) then
     8.7 +    error("Unsupported repository type")
     8.8 +  end
     8.9 +  if draft.external_reference then
    8.10 +    wrapper(function()
    8.11 +      config.lf4rcs[repository].render_draft_reference(url, draft)
    8.12 +    end)
    8.13 +  end
    8.14 +end
    8.15 +
     9.1 --- /dev/null	Thu Jan 01 00:00:00 1970 +0000
     9.2 +++ b/env/lf4rcs/render_initiative_reference.lua	Mon Nov 30 19:40:38 2015 +0100
     9.3 @@ -0,0 +1,6 @@
     9.4 +function lf4rcs.render_initiative_reference(initiative, wrapper)
     9.5 +  if initiative.current_draft.external_reference then
     9.6 +    config.render_external_reference.draft(initiative.current_draft, wrapper)
     9.7 +  end
     9.8 +end
     9.9 +
    10.1 --- /dev/null	Thu Jan 01 00:00:00 1970 +0000
    10.2 +++ b/env/lf4rcs/update_references.lua	Mon Nov 30 19:40:38 2015 +0100
    10.3 @@ -0,0 +1,100 @@
    10.4 +function lf4rcs.update_references(repository, path, unit_id)
    10.5 +  local function log(message)
    10.6 +    print(lf4rcs.log_prefix .. message)
    10.7 +  end
    10.8 +  if not config.lf4rcs[repository]
    10.9 +      or not config.lf4rcs[repository].get_remote_user
   10.10 +      or not config.lf4rcs[repository].get_branches
   10.11 +  then
   10.12 +    log("Unsupported repository type")
   10.13 +    os.exit(1)
   10.14 +  end
   10.15 +  log("inspecting changesets")
   10.16 +  local remote_user = config.lf4rcs[repository].get_remote_user()
   10.17 +  local function abort(message)
   10.18 +    log("TEST FAILED: " .. message)
   10.19 +    log("ABORTING and ROLLBACK due to failed test.")
   10.20 +    db:query("ROLLBACK")
   10.21 +    os.exit(1)
   10.22 +  end
   10.23 +  db:query("BEGIN")
   10.24 +  local member = Member:new_selector()
   10.25 +    :add_where{ "login = ?", remote_user }
   10.26 +    :optional_object_mode()
   10.27 +    :exec()
   10.28 +  if not member then
   10.29 +    abort(
   10.30 +      "internal error, member '" 
   10.31 +      .. remote_user .. "' not found in database"
   10.32 +    )
   10.33 +  end
   10.34 +  local function exec(...)
   10.35 +    local command, output, err_message, exit_code = lf4rcs.exec(...)
   10.36 +    if not output then
   10.37 +      log("Could not execute: " .. command)
   10.38 +      abort(err_message)
   10.39 +    end
   10.40 +    if exit_code ~= 0 then
   10.41 +      log("Could not execute: " .. command)
   10.42 +      abort("Exit code: " .. tostring(exit_code))
   10.43 +    end
   10.44 +    return output
   10.45 +  end
   10.46 +  if config.lf4rcs[repository].extra_checks then
   10.47 +    local success, err_message = config.lf4rcs[repository].extra_checks(path, exec)
   10.48 +    if not success then
   10.49 +      abort(err_message)
   10.50 +    end
   10.51 +  end
   10.52 +  local branches, err = config.lf4rcs[repository].get_branches(path, exec)
   10.53 +  if not branches then abort(err) end
   10.54 +  for branch, head_node_ids in pairs(branches) do
   10.55 +    log('checking branch ' .. branch)
   10.56 +    if branch ~= config.lf4rcs[repository].working_branch_name then
   10.57 +      local initiative_id = string.match(branch, "^i([0-9]+)$")
   10.58 +      if not initiative_id 
   10.59 +          or initiative_id ~= tostring(tonumber(initiative_id))
   10.60 +      then
   10.61 +        abort("this branch name is not allowed")
   10.62 +      end
   10.63 +      initiative_id = tonumber(initiative_id)
   10.64 +      if #head_node_ids > 1 then
   10.65 +        abort("number of heads found for branch is greater than 1: " .. #head_node_ids)
   10.66 +      end
   10.67 +      local initiative = Initiative:by_id(initiative_id)
   10.68 +      if not initiative then
   10.69 +        abort("initiative i" .. initiative_id .. " not found" )
   10.70 +      end
   10.71 +      if initiative.issue.area.unit_id ~= tonumber(unit_id) then
   10.72 +        abort("initiative belongs to another unit (unit ID " .. initiative.issue.area.unit_id .. ")")
   10.73 +      end
   10.74 +      if initiative.issue.state ~= "admission" and initiative.issue.state ~= "discussion" then
   10.75 +        abort("issue is already frozen or closed (" .. initiative.issue.state .. ")")
   10.76 +      end
   10.77 +      if initiative.revoked then
   10.78 +        abort("initiative has been revoked")
   10.79 +      end
   10.80 +      local initiator = Initiator:by_pk(initiative.id, member.id)
   10.81 +      if not initiator then
   10.82 +        abort("member is not initiator of initiative i" .. initiative_id)
   10.83 +      end
   10.84 +      if not initiator.accepted then
   10.85 +        abort(
   10.86 +          "member has not accepted invitation to become initiator of initiative i" 
   10.87 +          .. initiative_id
   10.88 +        )
   10.89 +      end
   10.90 +      local node_id = head_node_ids[1] or false
   10.91 +      if node_id then
   10.92 +        log("adding node " .. node_id .. " to initiative i" .. initiative_id)
   10.93 +      else
   10.94 +        log("removing node reference from initiative i" .. initiative_id)
   10.95 +      end
   10.96 +      Draft:update_content(member.id, initiative_id, nil, nil, node_id)
   10.97 +    end
   10.98 +  end
   10.99 +  log("changes cleared. continue committing.")
  10.100 +  db:query("COMMIT")
  10.101 +  os.exit(0)
  10.102 +end
  10.103 +

Impressum / About Us