Merge branch '19092-upload-crunchstat_summary-to-pypi'
[arvados-dev.git] / git / hooks / check-copyright-headers.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_COPYRIGHT_HEADER_CHECK')
21   puts "Skipping copyright header check..."
22   exit 0
23 end
24
25 puts "Enforcing copyright headers..."
26 puts "(#{$refname}) (#{$oldrev[0,6]}) (#{$newrev[0,6]})"
27
28 def load_licenseignore
29   $licenseignore = `git show #{$newrev}:.licenseignore 2>/dev/null`.gsub(/\./,'\\.').gsub(/\*/,'.*').gsub(/\?/,'.').split("\n")
30 end
31
32 def check_file(filename, header, broken)
33   ignore = false
34   $licenseignore.each do |li|
35     if filename =~ /#{li}/
36       ignore = true
37     end
38   end
39   return broken if ignore
40
41   if header !~ /SPDX-License-Identifier:/
42     if not broken
43       puts "\nERROR\n"
44     end
45     puts "missing or invalid copyright header in file #{filename}"
46     broken = true
47   end
48   return broken
49 end
50
51 # enforce copyright headers
52 def check_copyright_headers
53   if ($newrev[0,6] ==  '000000')
54     # A branch is being deleted. Do not check old commits for DCO signoff!
55     all_objects = []
56     commits = []
57   elsif ($oldrev[0,6] ==  '000000')
58     if $refname != 'refs/heads/main'
59       # A new branch was pushed. Check all new commits in this branch.
60       puts "git rev-list --objects main..#{$newrev} | git cat-file --batch-check='%(objecttype) %(objectname) %(objectsize) %(rest)'| sed -n 's/^blob //p'"
61       blob_objects  = `git rev-list --objects main..#{$newrev} | git cat-file --follow-symlinks --batch-check='%(objecttype) %(objectname) %(objectsize) %(rest)'| sed -n 's/^blob //p'`.split("\n")
62       commit_objects  = `git rev-list --objects main..#{$newrev} | git cat-file --batch-check='%(objecttype) %(objectname) %(objectsize) %(rest)'| sed -n 's/^commit //p'`.split("\n")
63       all_objects = blob_objects + commit_objects
64       commits = `git rev-list main..#{$newrev}`.split("\n")
65     else
66       # First push to an empty repository
67       puts "git rev-list --objects #{$newrev} | git cat-file --batch-check='%(objecttype) %(objectname) %(objectsize) %(rest)'| sed -n 's/^blob //p'"
68       blob_objects  = `git rev-list --objects #{$newrev} | git cat-file --follow-symlinks --batch-check='%(objecttype) %(objectname) %(objectsize) %(rest)'| sed -n 's/^blob //p'`.split("\n")
69       commit_objects  = `git rev-list --objects #{$newrev} | git cat-file --batch-check='%(objecttype) %(objectname) %(objectsize) %(rest)'| sed -n 's/^commit //p'`.split("\n")
70       all_objects = blob_objects + commit_objects
71       commits = `git rev-list #{$newrev}`.split("\n")
72     end
73   else
74     blob_objects = `git rev-list --objects #{$oldrev}..#{$newrev} --not --branches='*' | git cat-file --follow-symlinks --batch-check='%(objecttype) %(objectname) %(objectsize) %(rest)'| sed -n 's/^blob //p'`.split("\n")
75     commit_objects  = `git rev-list --objects #{$oldrev}..#{$newrev} | git cat-file --batch-check='%(objecttype) %(objectname) %(objectsize) %(rest)'| sed -n 's/^commit //p'`.split("\n")
76     all_objects = blob_objects + commit_objects
77     commits = `git rev-list #{$oldrev}..#{$newrev}`.split("\n")
78   end
79
80   broken = false
81
82   all_objects.each do |rev|
83     ignore = false
84     tmp = rev.split(' ')
85     if tmp[2].nil?
86       # git object of type 'commit'
87        # This could be a new file that was added in this commit
88       # If this wasn't a bare repo, we could run the following to get the list of new files in this commit:
89       #    new_files = `git show #{tmp[0]} --name-only --diff-filter=A --pretty=""`.split("\n")
90       # Instead, we just look at all the files touched in the commit and check the diff to see
91       # see if it is a new file. This could prove brittle...
92       files = `git show #{tmp[0]} --name-only --pretty=""`.split("\n")
93       files.each do |f|
94         filename = f
95         commit = `git show #{tmp[0]} -- #{f}`
96         # Only consider files, not symlinks (mode 120000)
97         if commit =~ /^new file mode (100644|10755)\nindex 000000/
98           headerCount = 0
99           lineCount = 0
100           header = ""
101           previousLine = ""
102           commit.each_line do |line|
103             if ((headerCount == 0) and (line =~ /Copyright.*All rights reserved./))
104               header = previousLine
105               header += line
106               headerCount = 1
107             elsif ((headerCount > 0) and (headerCount < 3))
108               header += line
109               headerCount += 1
110             elsif (headerCount == 3)
111               break
112             end
113             previousLine = line
114             lineCount += 1
115             if lineCount > 50
116               break
117             end
118           end
119           broken = check_file(filename, header, broken)
120         end
121       end
122     else
123       # git object of type 'blob'
124       filename = tmp[2]
125       # test if this is a symlink.
126       # Get the tree for each revision we are considering, find the blob hash in there, check the mode at start of line.
127       # Stop looking at revisions once we have a match.
128       symlink = false
129       commits.each do |r|
130         tree = `git cat-file -p #{r}^{tree}`
131         if tree =~ /#{tmp[0]}/
132            if tree =~ /^120000.blob.#{tmp[0]}/
133             symlink = true
134           end
135           break
136         end
137       end
138       if symlink == false
139         header = `git show #{tmp[0]} | head -n20 | egrep -A3 -B1 'Copyright.*All rights reserved.'`
140         broken = check_file(filename, header, broken)
141       else
142         #puts "#{filename} is a symbolic link, skipping"
143       end
144     end
145   end
146
147   if broken
148     puts
149     puts "[POLICY] all files must contain copyright headers, for more information see"
150     puts
151     puts "      https://dev.arvados.org/projects/arvados/wiki/Coding_Standards#Copyright-headers"
152     puts
153     puts "Enforcing copyright headers: FAIL"
154     exit 1
155
156   end
157   puts "Enforcing copyright headers: PASS"
158 end
159
160 load_licenseignore
161 check_copyright_headers