Merge branch '21521-aptly-flock'
[arvados-dev.git] / git / hooks / coding-standards.rb
1 #!/usr/bin/env ruby
2
3 # Copyright (C) The Arvados Authors. All rights reserved.
4 #
5 # SPDX-License-Identifier: AGPL-3.0
6
7 # This script can be installed as a git update hook.
8
9 # It can also be installed as a gitolite 'hooklet' in the
10 # hooks/common/update.secondary.d/ directory.
11
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.
14
15 $refname = ARGV[0]
16 $oldrev  = ARGV[1]
17 $newrev  = ARGV[2]
18 $user    = ENV['USER']
19
20 if ENV.has_key?('GL_OPTION_SKIP_CODING_STANDARDS_CHECK')
21   puts "Skipping coding standards check..."
22   exit 0
23 end
24
25 def blacklist bl
26   if ($newrev[0,6] ==  '000000')
27     # A branch is being deleted. Do not check old commits for DCO signoff!
28     all_revs    = []
29   elsif ($oldrev[0,6] ==  '000000')
30     if $refname != 'refs/heads/main'
31       # A new branch was pushed. Check all new commits in this branch.
32       all_revs  = `git log --pretty=format:%H main..#{$newrev}`.split("\n")
33     else
34       # When does this happen?
35       all_revs  = [$newrev]
36     end
37   else
38     all_revs    = `git rev-list #{$oldrev}..#{$newrev}`.split("\n")
39   end
40
41   all_revs.each do |rev|
42     bl.each do |b|
43       if rev == b
44         puts "Revision #{b} is blacklisted, you must remove it from your branch (possibly using git rebase) before you can push."
45         exit 1
46       end
47     end
48   end
49 end
50
51 blacklist ['26d74dc0524c87c5dcc0c76040ce413a4848b57a']
52
53 # Only enforce policy on the main branch
54 exit 0 if $refname != 'refs/heads/main'
55
56 puts "Enforcing Policies..."
57 puts "(#{$refname}) (#{$oldrev[0,6]}) (#{$newrev[0,6]})"
58
59 $regex = /\[ref: (\d+)\]/
60
61 $broken_commit_message = /Please enter a commit message to explain why this merge is necessary/
62 $wrong_way_merge_main = /Merge( remote-tracking)? branch '([^\/]+\/)?main' into/
63 $merge_main = /Merge branch '[^']+'((?! into)| into main)/
64 $pull_merge = /Merge branch 'main' of /
65 $refs_or_closes_or_no_issue = /(refs #|closes #|fixes #|resolves #|no issue #)/i
66
67 # enforced custom commit message format
68 def check_message_format
69   all_revs    = `git rev-list --first-parent #{$oldrev}..#{$newrev}`.split("\n")
70   merge_revs  = `git rev-list --first-parent --min-parents=2 #{$oldrev}..#{$newrev}`.split("\n")
71   # single_revs = `git rev-list --first-parent --max-parents=1 #{$oldrev}..#{$newrev}`.split("\n")
72   broken = false
73   no_ff = false
74
75   merge_revs.each do |rev|
76     message = `git cat-file commit #{rev} | sed '1,/^$/d'`
77     if $wrong_way_merge_main.match(message)
78       puts "\n[POLICY] Only non-fast-forward merges into main are allowed. Please"
79       puts "reset your main branch:"
80       puts "  git reset --hard origin/main"
81       puts "and then merge your branch with the --no-ff option:"
82       puts "  git merge your-branch --no-ff\n"
83       puts "Remember to add a reference to an issue number in the merge commit!\n"
84       puts "\n******************************************************************\n"
85       puts "\nOffending commit: #{rev}\n"
86       puts "\nOffending commit message:\n"
87       puts message
88       puts "\n******************************************************************\n"
89       puts "\n\n"
90       broken = true
91       no_ff = true
92     elsif $pull_merge.match(message)
93       puts "\n[POLICY] This appears to be a git pull merge of remote main into local"
94       puts "main.  In order to maintain a linear first-parent history of main,"
95       puts "please reset your branch and remerge or rebase using the latest main.\n"
96       puts "\n******************************************************************\n"
97       puts "\nOffending commit: #{rev}\n"
98       puts "\nOffending commit message:\n\n"
99       puts message
100       puts "\n******************************************************************\n"
101       puts "\n\n"
102       broken = true
103     elsif not $merge_main.match(message) and not
104       puts "\n[POLICY] This does not appear to be a merge of a feature"
105       puts "branch into main.  Merges must follow the format"
106       puts "\"Merge branch 'feature-branch'\".\n"
107       puts "\n******************************************************************\n"
108       puts "\nOffending commit: #{rev}\n"
109       puts "\nOffending commit message:\n\n"
110       puts message
111       puts "\n******************************************************************\n"
112       puts "\n\n"
113       broken = true
114     end
115   end
116
117   all_revs.each do |rev|
118     message = `git cat-file commit #{rev} | sed '1,/^$/d'`
119     if $broken_commit_message.match(message)
120       puts "\n[POLICY] Rejected broken commit message for including boilerplate"
121       puts "instruction text.\n"
122       puts "\n******************************************************************\n"
123       puts "\nOffending commit: #{rev}\n"
124       puts "\nOffending commit message:\n\n"
125       puts message
126       puts "\n******************************************************************\n"
127       puts "\n\n"
128       broken = true
129     end
130
131     # Do not test when the commit is a no_ff merge (which will be rejected), because
132     # this test will complain about *every* commit in the merge otherwise, obscuring
133     # the real reason for the rejection (the no_ff merge)
134     if not no_ff and not $refs_or_closes_or_no_issue.match(message)
135       puts "\n[POLICY] All commits to main must include an issue using \"refs #\","
136       puts "\"fixes #\", \"closes #\", or \"resolves #\", or specify \"no issue #\"\n"
137       puts "\n******************************************************************\n"
138       puts "\nOffending commit: #{rev}\n"
139       puts "\nOffending commit message:\n\n"
140       puts message
141       puts "\n******************************************************************\n"
142       puts "\n\n"
143       broken = true
144     end
145   end
146
147   if broken
148     puts "Enforcing Policies: FAIL"
149     exit 1
150   end
151   puts "Enforcing Policies: PASS"
152 end
153
154 check_message_format