实施git分支策略

时间:2014-05-23 23:28:29

标签: ruby git githooks

我试图执行公司政策,采取以下假设:

  • 只有3个可用的上游分支:master,version / *和hotfix /*.
  • 主分支仅接受非转发的合并提交。
  • 版本和修补程序分支仅接受快进/重新提交的提交。
  • 主分支只能从版本或修补程序分支合并。
  • 版本和修补程序分支必须直接与Master分支不同。

到目前为止,这是我想出的:

#!/usr/bin/env ruby
# Encoding: utf-8

$oldrev, $newrev, $refname = STDIN.read.split(" ")
$real_refname = `git rev-parse --abbrev-ref #{$refname} 2> /dev/null`.strip
$merge_commits = `git rev-list --merges #{$oldrev}..#{$newrev} 2> /dev/null`.strip
$parent_commit = `git rev-parse #{$newrev}\^1`
$ancestor_branch = `git show-branch | grep '*' | grep -v '#{$real_refname}' | head -n1 | sed 's/.*\[\(.*\)\].*/\1/' | sed 's/[\^~].*//'`

puts "Enforcing Policies... \n(#{$real_refname}) (#{$oldrev[0,6]}) (#{$newrev[0,6]})"

$errors = []
def check_branch_policy()
  $errors.push "Branch #{$real_refname}: Only Version, Hotfix and Master branches are allowed to be pushed upstream." if !$real_refname.match(/^(version\/[1-9.]+|hotfix\/[1-9.]+|master)/)
  $errors.push "Branch #{$real_refname}: Master branch accepts only non-forwarded merge commits." if $real_refname.match('master') && (!$merge_commits.match($newrev) || !$parent_commit.match($oldrev))
  $errors.push "Branch #{$real_refname}: Version and Hotfix branches accept only fast-forward/rebased commits." if !$real_refname.match('master') && !$merge_commits.empty?
  $errors.push "Branch #{$real_refname}: Version and Hotfix branches must diverge from Master branch directly." if !$real_refname.match('master') && !$ancestor_branch[4,6].match('master')
  false
end
check_branch_policy

unless $errors.empty?
  puts '[POLICY] Invalid git branch rules.'
  $errors.each { |error| puts "#    #{error}" }
  exit 1
end

但有几个问题:

  • 首先,我很乐意进行一般性的代码审查。我不是一个rubyist,我只是修补了我在网上找到的东西。所以代码可能非常糟糕。
  • 是否有更简单的方法来强制执行"主分支仅接受非转发的合并提交。"?
  • sedgrep似乎不能很好地使用git hooks,所以我基本上需要替换当前的$ancestor_branch命令。还没有想出任何东西。
  • 首次推送分支时,$real_refname无法正常工作 - 它似乎无法正确缩写。
  • 我似乎无法找到强制执行的方法;主分支只能从版本或修补程序分支合并。"然而。有什么想法吗?

编辑#1 - 25.05.14

经过一点点的修补,我得到了这个:

#!/usr/bin/env ruby
# Encoding: utf-8

oldrev, newrev, refname = STDIN.read.split(" ")
short_refname = refname[11..-1]
merge_commits = `git rev-list --merges #{oldrev}..#{newrev}`.strip
unique_revs = `git rev-list --all --not $(git rev-list --all ^#{newrev})`
missed_revs = `git rev-list #{oldrev}..#{newrev}`

puts "Enforcing Policies... \n(#{short_refname}) (#{oldrev[0,6]}) (#{newrev[0,6]})"

def check_branch_policy(oldrev,newrev,short_refname,merge_commits,unique_revs,missed_revs)
  errors = []
  errors << "Only Version, Hotfix and Master branches are allowed to be pushed upstream." if
    !short_refname[/^(version\/[1-9.]+|hotfix\/[1-9.]+|master)/]
  if short_refname['master']
    # Master should have only one unique commit each time - the merge commit (newrev).
    errors << "Master branch accepts only non-forwarded merge commits, one at a time." if
      !merge_commits[newrev] && missed_revs.count > 2
  else
    # If not empty, it means there's a merge commit - whereas there shouldn't be.
    errors << "Version and Hotfix branches accept only fast-forward/rebased commits." if
      !merge_commits.empty?
    # If not equal, it means at least one commit is reachable from another ref - meaning it was diverged.
    errors << "Version and Hotfix branches must diverge from Master branch directly." if
      !unique_revs[missed_revs]
  end
  errors
end
errors = check_branch_policy(oldrev,newrev,short_refname,unique_revs,missed_revs)

unless errors.empty?
  puts '[POLICY] Invalid git branch rules.'
  errors.each { |error| puts "#    Branch #{short_refname}: #{error}" }
  exit 1
end

虽然出现了更多问题:

  • 有没有办法在不调用方法的情况下提供局部变量?否则脚本会抛出错误。
  • 我设法找到了一种检索short_refname的方法,但它并不那么优雅。我读过某个地方我可以使用short_refname = refname.chomp("refs/heads/"),但它似乎没有用。帮助
  • 我找到了一种方法(聪明?太复杂了?去图)到find if a branch has diverged where it shouldn't have,但这带来了两个问题 - 我无法从钩子中获取所有引用。 --stdin flag似乎没有削减它。此外,排除标志(^ some_ref)在钩子内部不起作用,而在终端中它工作正常。想法?
  • 假设我将此脚本移至update挂钩,我该如何获取重新命名?到目前为止,网络资源并不是那么清楚......

1 个答案:

答案 0 :(得分:1)

让我们首先关注红宝石部分:

几乎没有理由在ruby中使用全局变量。并且在脚本中他们无论如何都处于“全局”范围内=&gt;摆脱变量名中的前面$

在此代码中:

$errors = []
def check_branch_policy()
  $errors.push "Branch #{$real_refname}: Only Version, Hotfix and Master branches are allowed to be pushed upstream." if !$real_refname.match(/^(version\/[1-9.]+|hotfix\/[1-9.]+|master)/)
  $errors.push "Branch #{$real_refname}: Master branch accepts only non-forwarded merge commits." if $real_refname.match('master') && (!$merge_commits.match($newrev) || !$parent_commit.match($oldrev))
  $errors.push "Branch #{$real_refname}: Version and Hotfix branches accept only fast-forward/rebased commits." if !$real_refname.match('master') && !$merge_commits.empty?
  $errors.push "Branch #{$real_refname}: Version and Hotfix branches must diverge from Master branch directly." if !$real_refname.match('master') && !$ancestor_branch[4,6].match('master')
  false
end
check_branch_policy

编写一个方法(或函数)的风格很糟糕,该方法只适用于仅为此目的创建的全局对象。您也可以删除方法定义,因为它在此处不执行任何操作。这不是特别的“红宝石风格”的东西,但一般适用于编程。更好的解决方案是在方法内创建对象并返回它。我也不喜欢这些长期难以理解的线条。所以总的来说可能会更像这样:

def check_branch_policy
  errors = []
  errors << "Only Version, Hotfix and Master branches are allowed to be pushed upstream." if 
    !real_refname[/^(version\/[1-9.]+|hotfix\/[1-9.]+|master)/]
  if real_refname['master']
    errors << "Master branch accepts only non-forwarded merge commits." if
      !merge_commits[newrev] || !parent_commit[oldrev]
  else
    errors << "Version and Hotfix branches accept only fast-forward/rebased commits." if
      merge_commits.empty?
    errors << "Version and Hotfix branches must diverge from Master branch directly." if
      !ancestor_branch[4, 6]['master']
  end
  errors
end

尽管这里的消息可能不那么整齐,但我认为这是一种改进,人们可以更好地看到每种情况应该保持的条件。请注意,我使用了ruby idoms <<而不是.push[]而不是.match。我也将Branch #{real_refname}:前缀保留了,如果它始终相同,它在错误输出循环中也可以。

当你掌握红宝石的力量时,几乎没有理由依赖grepsed

至于git部分:

你想要做的当然是可能的,但我想有些尝试和错误是必要的。所以我不能给你一个可行的解决方案。但有些评论:

  • 我认为在ruby中获得简短符号引用的更好方法是

    `git symbolic-ref #{refname}\`[/[^\/]*$/].chomp
    

    甚至

    `git symbolic-ref --short #{refname}`
    

    如果它比git rev-parse --abbrev-ref更可靠,你可以试试。此外,您的变量real_refname名称错误。 '真正的'引用名称听起来像实际上是SHA1哈希。可能short_refname会更好。

  • 既然你正在从stdin读取引用,我猜你使用了pre-receive git hook?但在这种情况下,你显然是一个错误,因为可能会在一次推送中更新几个分支。您应该迭代stdin或使用update hook

  • git show-branch是一个瓷器命令,即它不应该用于编写脚本,因为输出是针对用户的。我认为Junio在他的pre-rebase.sample中做了一些非常好的东西。也许你可以从那里得到一些想法如何使用管道命令。

  • 我曾经在Ruby中写过简单的钩子,但多年来我学会了bash也很有能力。因此,除非您的钩子变得非常复杂,否则您可能只是从bash开始。