3 # Copyright (C) The Arvados Authors. All rights reserved.
5 # SPDX-License-Identifier: AGPL-3.0
7 # This script can be installed as a git update hook.
9 # It can also be installed as a gitolite 'hooklet' in the
10 # hooks/common/update.secondary.d/ directory.
12 # NOTE: this script runs under the same assumptions as the 'update' hook, so
13 # the starting directory must be maintained and arguments must be passed on.
21 if ($newrev[0,6] == '000000')
22 # A branch is being deleted. Do not check old commits for DCO signoff!
24 elsif ($oldrev[0,6] == '000000')
25 if $refname != 'refs/heads/main'
26 # A new branch was pushed. Check all new commits in this branch.
27 all_revs = `git log --pretty=format:%H main..#{$newrev}`.split("\n")
29 # When does this happen?
33 all_revs = `git rev-list #{$oldrev}..#{$newrev}`.split("\n")
36 all_revs.each do |rev|
39 puts "Revision #{b} is blacklisted, you must remove it from your branch (possibly using git rebase) before you can push."
46 blacklist ['26d74dc0524c87c5dcc0c76040ce413a4848b57a']
48 # Only enforce policy on the main branch
49 exit 0 if $refname != 'refs/heads/main'
51 puts "Enforcing Policies..."
52 puts "(#{$refname}) (#{$oldrev[0,6]}) (#{$newrev[0,6]})"
54 $regex = /\[ref: (\d+)\]/
56 $broken_commit_message = /Please enter a commit message to explain why this merge is necessary/
57 $wrong_way_merge_main = /Merge( remote-tracking)? branch '([^\/]+\/)?main' into/
58 $merge_main = /Merge branch '[^']+'((?! into)| into main)/
59 $pull_merge = /Merge branch 'main' of /
60 $refs_or_closes_or_no_issue = /(refs #|closes #|fixes #|no issue #)/i
62 # enforced custom commit message format
63 def check_message_format
64 all_revs = `git rev-list --first-parent #{$oldrev}..#{$newrev}`.split("\n")
65 merge_revs = `git rev-list --first-parent --min-parents=2 #{$oldrev}..#{$newrev}`.split("\n")
66 # single_revs = `git rev-list --first-parent --max-parents=1 #{$oldrev}..#{$newrev}`.split("\n")
70 merge_revs.each do |rev|
71 message = `git cat-file commit #{rev} | sed '1,/^$/d'`
72 if $wrong_way_merge_main.match(message)
73 puts "\n[POLICY] Only non-fast-forward merges into main are allowed. Please"
74 puts "reset your main branch:"
75 puts " git reset --hard origin/main"
76 puts "and then merge your branch with the --no-ff option:"
77 puts " git merge your-branch --no-ff\n"
78 puts "Remember to add a reference to an issue number in the merge commit!\n"
79 puts "\n******************************************************************\n"
80 puts "\nOffending commit: #{rev}\n"
81 puts "\nOffending commit message:\n"
83 puts "\n******************************************************************\n"
87 elsif $pull_merge.match(message)
88 puts "\n[POLICY] This appears to be a git pull merge of remote main into local"
89 puts "main. In order to maintain a linear first-parent history of main,"
90 puts "please reset your branch and remerge or rebase using the latest main.\n"
91 puts "\n******************************************************************\n"
92 puts "\nOffending commit: #{rev}\n"
93 puts "\nOffending commit message:\n\n"
95 puts "\n******************************************************************\n"
98 elsif not $merge_main.match(message) and not
99 puts "\n[POLICY] This does not appear to be a merge of a feature"
100 puts "branch into main. Merges must follow the format"
101 puts "\"Merge branch 'feature-branch'\".\n"
102 puts "\n******************************************************************\n"
103 puts "\nOffending commit: #{rev}\n"
104 puts "\nOffending commit message:\n\n"
106 puts "\n******************************************************************\n"
112 all_revs.each do |rev|
113 message = `git cat-file commit #{rev} | sed '1,/^$/d'`
114 if $broken_commit_message.match(message)
115 puts "\n[POLICY] Rejected broken commit message for including boilerplate"
116 puts "instruction text.\n"
117 puts "\n******************************************************************\n"
118 puts "\nOffending commit: #{rev}\n"
119 puts "\nOffending commit message:\n\n"
121 puts "\n******************************************************************\n"
126 # Do not test when the commit is a no_ff merge (which will be rejected), because
127 # this test will complain about *every* commit in the merge otherwise, obscuring
128 # the real reason for the rejection (the no_ff merge)
129 if not no_ff and not $refs_or_closes_or_no_issue.match(message)
130 puts "\n[POLICY] All commits to main must include an issue using \"refs #\" or"
131 puts "\"closes #\", or specify \"no issue #\"\n"
132 puts "\n******************************************************************\n"
133 puts "\nOffending commit: #{rev}\n"
134 puts "\nOffending commit message:\n\n"
136 puts "\n******************************************************************\n"
143 puts "Enforcing Policies: FAIL"
146 puts "Enforcing Policies: PASS"