ruby - 执行 git 分支策略

标签 ruby git githooks

我正在尝试执行公司政策,并采用以下假设:

  • 只有 3 个可用的上游分支:master、version/* 和 hotfix/*。
  • 主分支只接受非转发的 merge 提交。
  • Version 和 Hotfix 分支只接受 fast-forward/rebased 提交。
  • Master 分支只能从 Version 或 Hotfix 分支 merge 。
  • 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

不过有几个问题:

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

编辑 #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但这带来了两个问题——我无法从钩子(Hook)中获取所有引用。 --stdin 标志似乎没有削减它。此外,排除标志 (^some_ref) 在钩子(Hook)内部不起作用,而在终端中它工作正常。想法?
  • 假设我将此脚本移动到 update Hook ,我怎样才能获得 refnames?到目前为止,网络资源还不是很清楚...

最佳答案

让我们首先关注 ruby 部分:

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

在这段代码中:

$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

编写仅适用于为此目的而创建的全局对象的方法(或函数)是一种糟糕的风格。您还不如删除方法定义,因为它在这里什么都不做。这不是特别的“ ruby 风格”的东西,但适用于一般的编程。更好的解决方案是只在方法内部创建对象并返回它。我也不喜欢这些冗长的不可读的行。所以总的来说,它的结构可能更像这样:

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当您拥有 ruby 的力量时。

至于git部分:

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

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

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

    甚至

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

    你可以尝试一下是否比 git rev-parse --abbrev-ref 更可靠.此外,您的变量 real_refname名字不好。 “真实”引用名称听起来像是实际上是 SHA1 哈希。大概 short_refname会更好。

  • 由于您正在从标准输入读取引用,我猜您使用了 pre-receive混帐钩子(Hook)?但是在这种情况下,您显然有一个错误,因为一次推送可能会更新多个分支。您应该遍历标准输入或使用 update Hook

  • git show-branch是瓷器命令,即它不应该用于脚本,因为输出是为用户准备的。我认为 Junio 在他的 pre-rebase.sample 中做了一些非常巧妙的事情.也许您可以从那里获得一些关于如何使用管道命令执行此操作的想法。

  • 我以前甚至用 ruby​​ 编写简单的钩子(Hook),但多年来我了解到 bash 也很强大。因此,除非您的钩子(Hook)变得非常复杂,否则您可能只是从 bash 开始。

关于ruby - 执行 git 分支策略,我们在Stack Overflow上找到一个类似的问题: https://stackoverflow.com/questions/23839718/

相关文章:

svn - git-svn 如何知道要提交到哪个分支?

ruby-on-rails - 如何根据连接表按频率获取 ActiveRecord 对象列表

Cygwin 下的 Git Clone 挂了

ruby - ruby 字符串中的单个反斜杠

git - 如何从 HomeBrew 中删除浅克隆警告

git hook 来测试文件是否包含正确的数据

git - 如何配置SonarQube、Sonar Runner、GitLab和Jenkins获得持续集成

python - 查找项目中所有python包

ruby-on-rails - 无法使用 rvm install rails 5 gem

ruby - 上传/发布 stringIO 作为文件