强制执行git分支机构政策 [英] Enforce git branch policies

查看:70
本文介绍了强制执行git分支机构政策的处理方法,对大家解决问题具有一定的参考价值,需要的朋友们下面随着小编来一起学习吧!

问题描述

我正在尝试执行以下假设的公司政策:

I'm trying to enforce a company policy, taking these assumptions:

  • 只有三个可用的上游分支:master,version/*和hotfix/*.
  • Master分支仅接受未转发的合并提交.
  • 版本分支和修补程序分支仅接受快速转发/基于基础的提交.
  • Master分支只能合并到Version或Hotfix分支.
  • 版本分支和Hotfix分支必须直接与Master分支分开.

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

So far this is what I come up with:

#!/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

一些问题:

  • 首先,我很高兴进行常规代码审查.我不是一个红宝石主义者,我只是在网上找到一些补丁.因此代码可能很糟糕.
  • 是否有一种更简便的方法来实施"Master分支仅接受未转发的合并提交."
  • sed grep 在git钩子上似乎不能很好地发挥作用,所以我基本上需要一个替代当前 $ ancestor_branch 命令的方法.尚未提出任何建议.
  • 第一次推送分支时, $ real_refname 不起作用-似乎不能正确缩写引用.
  • 我似乎找不到强制执行"Master分支只能从Version或Hotfix分支合并到"的方法.然而.有什么想法吗?
  • First, I'd be glad for a general code review. I'm not a rubyist whatsoever, and I just patched around things I found on the web. So the code is probably pretty bad.
  • Is there an easier way to enforce the "Master branch accepts only non-forwarded merge commits."?
  • sed and grep doesn't seem to play well with git hooks, so I basically need an alternative to the current $ancestor_branch command. Didn't come up with anything yet.
  • When first pushing a branch, $real_refname doesn't work - it can't seem to abbrev-ref properly.
  • I can't seem to find a way to enforce "Master branch must only be merged into from Version or Hotfix branches." yet. Any ideas?

经过一番修补后,我明白了:

After tinkering around a little bit I got to this:

#!/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/")的地方,但是它似乎不起作用.帮助吗?
  • 我找到了一种方法(聪明?太复杂了?去计算)找到分支是否偏离了它应该去的地方',但这会带来两个问题-我无法从钩子中获取所有裁判.--stdin标志似乎没有削减它.此外,排除标志(^ some_ref)在挂钩内不起作用,而在终端中则可以正常工作.有想法吗?
  • 假设我将此脚本移至 update 钩子,如何获得引用名?到目前为止,网络资源还不清楚...
  • Is there a way to serve the local variables without calling them in the method? Otherwise the script throws an error.
  • I managed to find a way to retrieve the short_refname, but it's not so elegant. I read somewhere I can use short_refname = refname.chomp("refs/heads/") but it doesn't seem to work. Help?
  • I found a way (clever? too complex? go figure) to find if a branch has diverged where it shouldn't have but this brings two issues - I can't get all the refs from the hook. --stdin flag doesn't seem to cut it. Further, the exclude flag (^some_ref) doesn't work inside the hook, whereas in the terminal it works fine. Ideas?
  • Assuming I move this script to update hook, how can I get the refnames? The web sources weren't so clear so far...

推荐答案

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

Lets first focus on the ruby part:

几乎没有理由在ruby中使用全局变量.而且在脚本中,它们始终处于全局"范围内=>摆脱变量名中的前面的 $

There is hardly ever a reason to use global variables in ruby. And in a script they are in a "global" scope anyway => get rid of the preceding $ in variable names

在此代码中:

$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

编写仅在为此目的而创建的全局对象上起作用的方法(或函数)是不好的风格.您不妨删除方法定义,因为它在这里不执行任何操作.这不是特别的红宝石风格",但通常适用于编程.更好的解决方案是只在方法内部创建对象并返回它.我也不喜欢这些冗长的可读行.因此,总的来说,它的结构可能更像这样:

It's bad style to write a method (or a function) which just works on a global object created only for this purpose. You might as well just remove the method definition, because it does nothing here. This is not particular "ruby style" thing but applies to programming in general. The better solution is to just create the object inside the method and return it. I also don't like these long unreadable lines. So in total would probably structure it more like this:

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}:前缀,如果它总是相同的话,它也可以出现在错误输出循环中.

Even though the messages may be less neatly aligned here, I think it's an improvement that one can better see the conditions which should hold in each case. Note that I used the ruby idoms << instead of .push and [] instead of .match. I also left the Branch #{real_refname}: prefix out, it can be just as well in your error output loop if its always the same.

当您拥有红宝石的力量时,几乎没有理由依赖 grep sed .

Also there is hardly a reason to rely on grep and sed when you have the power of ruby at hand.

至于git部分:

您尝试做的事情当然是可能的,但是我想需要一些尝试和错误.因此,我无法为您提供可行的解决方案.不过有一些说明:

What you're trying to do is certainly possible, but I guess some try and error is needed. So I can't give you a working solution out of the hand. Some remarks though:

  • 我认为在Ruby中获取简短的符号引用的一种更好的方法是

  • I think a better way to get a short symbolic ref in ruby is

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

甚至

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

您可以尝试是否比 git rev-parse --abbrev-ref 更可靠.此外,您的变量 real_refname 的命名错误.真实"参考名称听起来像它实际上是SHA1哈希.也许 short_refname 会更好.

you can try if that works more reliable than git rev-parse --abbrev-ref. Furthermore your variable real_refname is badly named. The 'real' ref name sounds like it would actually be the SHA1 hash. Probably short_refname would be better.

因为您正在从stdin中读取引用,所以我猜您使用了 pre-receive git hook?但是在这种情况下,您显然有一个错误,因为一次推送可能会更新多个分支.您应该遍历stdin或使用 update 钩子

Since you're reading the refs from stdin I guess that you use a pre-receive git hook? But in this case you've clearly a bug, because there might be several branches updated in one push. You should either iterate over stdin or use the update hook

git show-branch 是瓷器命令,即不应将其用于脚本编写,因为输出是针对用户的.我认为Junio在他的 pre-rebase.sample 中做了一些非常整洁的工作.也许您可以从那里获得一些如何使用管道命令进行操作的想法.

git show-branch is a porcelain command, i.e. it shouldn't be used for scripting because the output is meant for users. I think Junio did some pretty neat stuff in his pre-rebase.sample. Maybe you can get some ideas from there how to do it with plumbing commands.

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

I used to write even simple hooks in ruby, but I learned over the years that bash is also quite capable. So unless your hook gets really complex you might just start with bash.

这篇关于强制执行git分支机构政策的文章就介绍到这了,希望我们推荐的答案对大家有所帮助,也希望大家多多支持IT屋!

查看全文
登录 关闭
扫码关注1秒登录
发送“验证码”获取 | 15天全站免登陆