2015-05-20 9 views
2

Я установил «side_status.rb» на стороне сервера на Assembla. Хотя это именно то, что я ищу (теоретически), оно не отображается, пока разработчик не попытается нажать на сервер. Если перед тем, как нажать, они совершили несколько коммитов, становится невероятно неприятно возвращаться к истории и редактировать любые недействительные сообщения фиксации.Нужна помощь при создании крючка «commit-msg» на стороне клиента Git

Я ищу, чтобы создать крючок на стороне клиента, который отклонит фиксацию разработчика, если открытый код в Assembla не упоминается в сообщении фиксации. Я предполагаю, что, поскольку он является клиентским, он не сможет проверить, открыт ли билет в пространстве проекта Assembla. Однако, если крючок мог хотя бы проверить, что «#n» был включен в сообщение фиксации (где 0 < n < 10 000), он должен поймать большинство недействительных сообщений фиксации.

GitHub предоставил образец кода для клиентской стороны «commit-msg». Я хотел бы помощи в модификации коды ниже, чтобы вместо искать номер билета (#n) в сообщении фиксации (или открытый билет в пространстве проекта Assembla, если это возможно):

#!/bin/sh 
# 
# An example hook script to check the commit log message. 
# Called by "git commit" with one argument, the name of the file 
# that has the commit message. The hook should exit with non-zero 
# status after issuing an appropriate message if it wants to stop the 
# commit. The hook is allowed to edit the commit message file. 
# 
# To enable this hook, rename this file to "commit-msg". 

# Uncomment the below to add a Signed-off-by line to the message. 
# Doing this in a hook is a bad idea in general, but the prepare-commit-msg 
# hook is more suited to it. 
# 
# SOB=$(git var GIT_AUTHOR_IDENT | sed -n 's/^\(.*>\).*$/Signed-off-by: \1/p') 
# grep -qs "^$SOB" "$1" || echo "$SOB" >> "$1" 

# This example catches duplicate Signed-off-by lines. 

test "" = "$(grep '^Signed-off-by: ' "$1" | 
    sort | uniq -c | sed -e '/^[ ]*1[ ]/d')" || { 
    echo >&2 Duplicate Signed-off-by lines. 
    exit 1 
} 

Я также при условии, исходный код на стороне сервера крюка, который отклоняет фиксацию, если она не содержит действительный номер открытого билета в сообщении фиксации (ticket_status.rb):

#!/usr/bin/env ruby 
# -*- encoding : utf-8 -*- 

# 
# Reject a push to a branch if it has commits that do refer a ticket in open state 
# 

# ref = ARGV[0] 
sha_start = ARGV[1] 
sha_end = ARGV[2] 

# HOOK PARAMS 
space = 'space-wiki-name' 
api_key = 'user-api-key' 
api_secret = 'user-api-secret' 
# HOOK START, end of params block 

require "net/https" 
require "uri" 
begin 
    require "json" 
rescue LoadError 
    require 'rubygems' 
    require 'json' 
end 

# Check referred tickets that are in open stage 
class TicketValidator 
    API_URL = "https://api.assembla.com" 

    attr_accessor :space, :api_key, :api_secret 

    def initialize() 
    @ticket_statuses = [] 
    @tickets = {} 
    end 

    def init 
    init_http 
    load_statuses 
    end 

    def check(sha, comment) 
    comment.to_s.scan(/#\d+/).each do |t| 
     ticket = t.tr('#', '') 
     # Do not check it twice 
     next if @tickets[ticket] 
     ticket_js = api_call "/v1/spaces/#{space}/tickets/#{ticket}.json" 

     error = nil 

     if ticket_js['error'].nil? 
     unless @ticket_statuses.include? ticket_js['status'].downcase 
      error = "Ticket #{t} is not open!" 
     end 
     else 
     error = ticket_js['error'] 
     end 

     if error 
     @tickets[ticket] = {:error => error, :sha => sha} 
     else 
     @tickets[ticket] = :ok 
     end 
    end 
    end 

    def load_statuses 
    statuses = api_call "/v1/spaces/#{space}/tickets/statuses.json" 
    statuses.each do |status| 
     if status["state"] == 1 # open 
     @ticket_statuses << status["name"].downcase 
     end 
    end 
    end 

    def api_call(uri) 
    request = Net::HTTP::Get.new(uri, 
           {'Content-Type' => 'application/json', 
            'X-Api-Key' => api_key, 
            'X-Api-Secret' => api_secret}) 
    result = @http.request(request) 
    JSON.parse(result.body) 
    end 

    def init_http 
    uri = URI.parse(API_URL) 
    @http = Net::HTTP.new(uri.host, uri.port) 
    @http.use_ssl = true 
    @http.verify_mode = OpenSSL::SSL::VERIFY_NONE 
    end 

    def show_decision! 
    @tickets.reject! {|_, value| value == :ok } 

    unless @tickets.empty? 
     puts "You have references to tickets in closed state" 

     @tickets.each do |ticket, details| 
     puts "\t#{details[:sha]} - ##{ticket} #{details[:error]}" 
     end 

     puts "Valid statuses: #{@ticket_statuses.join(', ')}" 
     exit 1 
    end 
    end 
end 

class Parser 
    def initialize(text, validator) 
    @text = text 
    @validator = validator 
    end 

    def parse 
    commit = nil 
    comment = nil 

    @validator.init 

    @text.to_s.split("\n").each do |line| 
     if line =~ /^commit: ([a-z0-9]+)$/i 
     new_commit = $1 

     if comment 
      @validator.check(commit, comment) 
      comment = nil 
     end 

     commit = new_commit 
     else 
     comment = comment.to_s + line + "\n" 
     end 
    end 

    # Check last commit 
    @validator.check(commit, comment) if comment 
    end 
end 

text = `git log --pretty='format:commit: %h%n%B' #{sha_start}..#{sha_end}` 

@validator = TicketValidator.new 
@validator.space = space 
@validator.api_key = api_key 
@validator.api_secret = api_secret 

Parser.new(text, @validator).parse 
@validator.show_decision! 

Любой помощь очень ценится. Спасибо

ответ

0

Вы можете попробовать этот commit-msg валидатор. Это не рубин, но вы можете легко настроить его для своих нужд, и вы даже можете написать your own Assembla reference, чтобы проверить номера билетов на свой API. Подробнее см. README в репо.

Вот отправная точка для вашей специальной справки и связанного с ней тестового файла. Я не тестировал его полностью, но это должно быть довольно легко изменить, как вы хотите, поскольку это в основном JavaScript.

Библиотека/ссылки/assembla.js

'use strict'; 

var exec = require('child_process').exec; 
var https = require('https'); 
var util = require('util'); 

// HOOK PARAMS 
var space = 'space-wiki-name'; 
var apiKey = 'user-api-key'; 
var apiSecret = 'user-api-secret'; 

function Ticket(ticket, match) { 
    this.allowInSubject = true; 
    this.match = match; 

    this._ticket = ticket; 
} 

Ticket.prototype.toString = function() { 
    return '#' + this._ticket; 
} 

Ticket.prototype.isValid = function(cb) { 

    var options = { 
     hostname: 'api.assembla.com', 
     path: util.format('/v1/spaces/%s/tickets/%s.json', space, this._ticket), 
     headers: { 
      'Content-Type' : 'application/json', 
      'X-Api-Key'  : apiKey, 
      'X-Api-Secret' : apiSecret 
     } 
    }; 
    https.get(options, function(res) { 
     if (res.statusCode === 404) { 
      return cb(null, false); // invalid 
     } 

     var body = ''; 
     res.on('data', function(chunk) { 
      body += chunk.toString(); 
     }); 

     res.on('end', function() { 
      var response = body ? JSON.parse(body) : false; 

      if (res.statusCode < 300 && response) { 
       return cb(null, true); // valid? 
      } 

      console.error('warning: Reference check failed with status code %d', 
       res.statusCode, 
       response && response.message ? ('; reason: ' + response.message) : ''); 

      cb(null, false); // request errored out? 
     }); 
    }); 
} 

// Fake class that requires the existence of a ticket # in every commit 
function TicketRequired() { 
    Ticket.call(this); 
    this.error = new Error('Commit should include an Assembla ticket #'); 
} 

util.inherits(TicketRequired, Ticket); 

TicketRequired.prototype.isValid = function(cb) { 
    cb(null, false); 
} 

Ticket.parse = function(text) { 
    var instances = []; 
    var cb = function(match, ticket) { 
     instances.push(new Ticket(ticket, match)); 
    }; 
    text.replace(/#(-?\d+)\b/gi, cb); 
    if (!instances.length) { 
     // maybe should skip merge commits here 
     instances.push(new TicketRequired()); 
    } 
    return instances; 
} 

module.exports = Ticket; 

тест/ссылки/assembla.js

'use strict'; 

var assert = require('assert'); 
var Ticket = require('../../lib/references/assembla'); 

describe('references/assembla', function() { 

    it('should validate correctly using the API', function(done) { 
     this.timeout(5000); // allow enough time 

     var tickets = Ticket.parse('Change functionality\n\nFixes #13 and #9999 (invalid)'); 

     var ct = 0; 
     var checkDone = function() { 
      if (++ct == tickets.length) done(); 
     }; 
     var valid = [true, false]; 

     valid.forEach(function(val, idx) { 
      tickets[idx].isValid(function(err, valid) { 
       assert.equal(valid, val, tickets[idx].toString()); 
       checkDone(); 
      }); 
     }); 
    }); 

    it('should require a ticket #', function() { 
     var tickets = Ticket.parse('Commit message without any ticket ref #'); 

     assert.equal(tickets.length, 1); 
     assert.equal(tickets[0].error.message, 'Commit should include an Assembla ticket #'); 
    }); 
});