ruby_git_hooks 0.0.31
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- data/.gitignore +19 -0
- data/.yardopts +1 -0
- data/Gemfile +4 -0
- data/LICENSE.txt +22 -0
- data/README.md +327 -0
- data/Rakefile +7 -0
- data/TODO +67 -0
- data/bin/git-add-hooks +111 -0
- data/bin/git-clone +57 -0
- data/bin/git-hclone +57 -0
- data/lib/ruby_git_hooks/all.rb +7 -0
- data/lib/ruby_git_hooks/case_clash.rb +30 -0
- data/lib/ruby_git_hooks/copyright_check.rb +181 -0
- data/lib/ruby_git_hooks/email_notify.rb +50 -0
- data/lib/ruby_git_hooks/git_ops.rb +93 -0
- data/lib/ruby_git_hooks/jira_add_comment.rb +354 -0
- data/lib/ruby_git_hooks/jira_ref_check.rb +32 -0
- data/lib/ruby_git_hooks/max_file_size.rb +32 -0
- data/lib/ruby_git_hooks/non_ascii.rb +33 -0
- data/lib/ruby_git_hooks/ruby_debug.rb +26 -0
- data/lib/ruby_git_hooks/version.rb +5 -0
- data/lib/ruby_git_hooks/watermark.rb +27 -0
- data/lib/ruby_git_hooks.rb +343 -0
- data/ruby_git_hooks.gemspec +37 -0
- data/test/basic_hook_test.rb +143 -0
- data/test/case_clash_hook_test.rb +58 -0
- data/test/copyright_check_test.rb +162 -0
- data/test/fake_curl +12 -0
- data/test/fake_mailer +5 -0
- data/test/jira_add_comment_test.rb +151 -0
- data/test/jira_ref_check_test.rb +37 -0
- data/test/max_file_size_hook_test.rb +52 -0
- data/test/multi_hook_test.rb +77 -0
- data/test/non_ascii_hook_test.rb +47 -0
- data/test/repos/.keep +0 -0
- data/test/test_helper.rb +17 -0
- data/test/watermark_test.rb +39 -0
- metadata +226 -0
@@ -0,0 +1,30 @@
|
|
1
|
+
# Copyright (C) 2013 OL2, Inc. See LICENSE.txt for details.
|
2
|
+
|
3
|
+
require "ruby_git_hooks"
|
4
|
+
|
5
|
+
# This hook checks whether a similar file exists with the same name
|
6
|
+
# except for uppercase/lowercase. It's useful when Mac OS and Unix
|
7
|
+
# people need to coexist in a single Git repository. You can be sure
|
8
|
+
# that the Linux people can't check in files that the Mac people can
|
9
|
+
# neither see nor delete.
|
10
|
+
|
11
|
+
class CaseClashHook < RubyGitHooks::Hook
|
12
|
+
def check
|
13
|
+
downcase_hash = {}
|
14
|
+
|
15
|
+
ls_files.map(&:strip).each do |filename|
|
16
|
+
downcase_hash[filename.downcase] ||= []
|
17
|
+
downcase_hash[filename.downcase].push filename
|
18
|
+
end
|
19
|
+
|
20
|
+
okay = true
|
21
|
+
downcase_hash.each do |_, filenames|
|
22
|
+
if filenames.length > 1
|
23
|
+
okay = false
|
24
|
+
STDERR.puts "Duplicate-except-case files detected: #{filenames.inspect}"
|
25
|
+
end
|
26
|
+
end
|
27
|
+
|
28
|
+
okay
|
29
|
+
end
|
30
|
+
end
|
@@ -0,0 +1,181 @@
|
|
1
|
+
# Copyright (C) 2013 OL2, Inc. See LICENSE.txt for details.
|
2
|
+
|
3
|
+
require "ruby_git_hooks"
|
4
|
+
|
5
|
+
# This hook checks whether a proper copyright notice exists at the top
|
6
|
+
# of each of your files. Right now it hardcodes most of the Copyright
|
7
|
+
# notice format, though you can customize the company name. If you'd
|
8
|
+
# prefer a more flexible format, we accept pull requests!
|
9
|
+
|
10
|
+
class CopyrightCheckHook < RubyGitHooks::Hook
|
11
|
+
COPYRIGHT_REGEXP = /Copyright\s+\(C\)\s*(?<pre_year>.*)-?(?<cur_year>\d{4})\s+(?<company>.+)/i
|
12
|
+
|
13
|
+
# Only check files with known checkable extensions
|
14
|
+
EXTENSIONS = [
|
15
|
+
"c", "cpp", "cc", "cp",
|
16
|
+
"h", "hp", "hpp",
|
17
|
+
"m", "mm",
|
18
|
+
"java",
|
19
|
+
"bat",
|
20
|
+
"sh",
|
21
|
+
"ps1",
|
22
|
+
"rb",
|
23
|
+
]
|
24
|
+
|
25
|
+
OPTIONS = [ "domain", "from", "subject", "via", "via_options", "intro",
|
26
|
+
"no_send", "company_check", "exclude_files" ]
|
27
|
+
|
28
|
+
Hook = RubyGitHooks::Hook
|
29
|
+
|
30
|
+
attr_accessor :cur_year
|
31
|
+
|
32
|
+
def initialize(options = {})
|
33
|
+
bad_options = options.keys - OPTIONS
|
34
|
+
raise "CopyrightCheckHook created with unrecognized options: " +
|
35
|
+
"#{bad_options.inspect}!" if bad_options.size > 0
|
36
|
+
|
37
|
+
@options = options
|
38
|
+
@options["domain"] ||= "mydomain.com"
|
39
|
+
@options["from"] ||= "Copyright Cop <noreply@#{@options["domain"]}>"
|
40
|
+
@options["subject"] ||= "Copyright Your Files, Please!"
|
41
|
+
@options["via"] ||= "no_send"
|
42
|
+
@options["via_options"] ||= {}
|
43
|
+
@options["exclude_files"] ||= [] # for generated files, etc.
|
44
|
+
@cur_year = Time.now.strftime("%Y")
|
45
|
+
end
|
46
|
+
|
47
|
+
def check
|
48
|
+
no_notice = []
|
49
|
+
outdated_notice = []
|
50
|
+
outdated_company = []
|
51
|
+
|
52
|
+
cur_year = Time.now.strftime("%Y")
|
53
|
+
|
54
|
+
files_changed.each do |filename|
|
55
|
+
extension = (filename.split(".") || [])[-1]
|
56
|
+
next unless EXTENSIONS.include?(extension)
|
57
|
+
next if file_contents[filename] == "" # for now this is how we recognize a deleted file.
|
58
|
+
next if @options["exclude_files"].include? filename
|
59
|
+
if file_contents[filename] =~ COPYRIGHT_REGEXP
|
60
|
+
parsed_cur_year = $~["cur_year"]
|
61
|
+
parsed_company = $~["company"]
|
62
|
+
|
63
|
+
unless parsed_cur_year == cur_year
|
64
|
+
outdated_notice << filename
|
65
|
+
end
|
66
|
+
|
67
|
+
# If there is a "company_check" option, either a string
|
68
|
+
# or regexp, make sure that the detected company name
|
69
|
+
# matches it.
|
70
|
+
if @options["company_check"] &&
|
71
|
+
!(parsed_company[@options["company_check"]])
|
72
|
+
outdated_company << filename
|
73
|
+
end
|
74
|
+
else
|
75
|
+
no_notice << filename
|
76
|
+
end
|
77
|
+
end
|
78
|
+
|
79
|
+
bad_num = no_notice.size + outdated_notice.size + outdated_company.size
|
80
|
+
return true if bad_num < 1
|
81
|
+
|
82
|
+
desc = build_description(no_notice, outdated_notice, outdated_company)
|
83
|
+
|
84
|
+
recipients = {}
|
85
|
+
self.commits.each do |commit|
|
86
|
+
author = Hook.shell!("git log -n 1 --pretty=format:\"%aE %aN\" #{commit}")
|
87
|
+
email, name = author.chomp.split(" ", 2)
|
88
|
+
recipients[name] = email
|
89
|
+
end
|
90
|
+
|
91
|
+
STDERR.puts "Warnings for commit from Copyright Check:\n--#{desc}\n--"
|
92
|
+
|
93
|
+
unless @options["no_send"] || @options["via"] == "no_send"
|
94
|
+
require "pony" # wait until we need it
|
95
|
+
# NOTE: Pony breaks on Windows so don't use this option in Windows.
|
96
|
+
recipients.each do |name, email|
|
97
|
+
email_str = "#{name} <#{email}>"
|
98
|
+
STDERR.puts "Sending warning email to #{email_str}"
|
99
|
+
ret = Pony.mail :to => email_str,
|
100
|
+
:from => @options["from"],
|
101
|
+
:subject => @options["subject"],
|
102
|
+
:body => desc,
|
103
|
+
:via => @options["via"],
|
104
|
+
:via_options => @options["via_options"]
|
105
|
+
end
|
106
|
+
end
|
107
|
+
# Block commit if installed as a pre-commit or pre-receive hook
|
108
|
+
false
|
109
|
+
end
|
110
|
+
|
111
|
+
protected
|
112
|
+
|
113
|
+
def commit_list
|
114
|
+
# return the list of commits to display. We don't want to show them all
|
115
|
+
# (it looks scary when there's a lot)
|
116
|
+
|
117
|
+
# return a list of the shortened SHAH1s for readability
|
118
|
+
# tried listing commit.last..commit.first but that didn't seem right either
|
119
|
+
# problem is really when there's dozens of commits.
|
120
|
+
self.commits.map {|c| c[0..6]}.join(", ")
|
121
|
+
end
|
122
|
+
|
123
|
+
|
124
|
+
def current_repo
|
125
|
+
# which repository are these commits in
|
126
|
+
# Since we are running as a hook, we may get back the .git directory
|
127
|
+
|
128
|
+
full_path = Hook.shell!("git rev-parse --show-toplevel").chomp
|
129
|
+
# like "/path/to/repos/test-repo" or maybe "/path/to/repos/test-repo/.git"
|
130
|
+
# return "test-repo"
|
131
|
+
File.basename full_path.sub(/.git\z/, "")
|
132
|
+
end
|
133
|
+
#
|
134
|
+
# Return an appropriate email based on the set of files with
|
135
|
+
# problems. If you need a different format, please inherit from
|
136
|
+
# CopyrightCheckHook and override this method.
|
137
|
+
#
|
138
|
+
def build_description(no_notice, outdated_notice, outdated_company)
|
139
|
+
|
140
|
+
description = @options["intro"] || ""
|
141
|
+
description.concat <<DESCRIPTION
|
142
|
+
|
143
|
+
In repository: #{current_repo}
|
144
|
+
commit(s): #{commit_list}
|
145
|
+
|
146
|
+
You have outdated, inaccurate or missing copyright notices.
|
147
|
+
|
148
|
+
Specifically:
|
149
|
+
=============
|
150
|
+
DESCRIPTION
|
151
|
+
|
152
|
+
if outdated_notice.size > 0
|
153
|
+
description.concat <<DESCRIPTION
|
154
|
+
The following files do not list #{cur_year} as the most recent copyright year:
|
155
|
+
|
156
|
+
#{outdated_notice.join("\n ")}
|
157
|
+
-----
|
158
|
+
DESCRIPTION
|
159
|
+
end
|
160
|
+
|
161
|
+
if outdated_company.size > 0
|
162
|
+
description.concat <<DESCRIPTION
|
163
|
+
The following files do not properly list your company as the holder of copyright:
|
164
|
+
|
165
|
+
#{outdated_company.join("\n ")}
|
166
|
+
|
167
|
+
DESCRIPTION
|
168
|
+
end
|
169
|
+
|
170
|
+
if no_notice.size > 0
|
171
|
+
description.concat <<DESCRIPTION
|
172
|
+
The following files have no notice or a notice I didn't recognize:
|
173
|
+
|
174
|
+
#{no_notice.join("\n ")}
|
175
|
+
|
176
|
+
DESCRIPTION
|
177
|
+
end
|
178
|
+
|
179
|
+
description
|
180
|
+
end
|
181
|
+
end
|
@@ -0,0 +1,50 @@
|
|
1
|
+
# Copyright (C) 2013 OL2, Inc. See LICENSE.txt for details.
|
2
|
+
|
3
|
+
require "ruby_git_hooks"
|
4
|
+
|
5
|
+
# This hook sends out email notification of commits to a configurable
|
6
|
+
# list of recipients via the Pony gem, which uses the Ruby Mail gem.
|
7
|
+
|
8
|
+
class EmailNotifyHook < RubyGitHooks::Hook
|
9
|
+
LEGAL_OPTIONS = [ "no_send", "via", "via_options", "max_lines", "recipients" ]
|
10
|
+
|
11
|
+
def initialize(options = {})
|
12
|
+
bad_opts = options.keys - LEGAL_OPTIONS
|
13
|
+
unless bad_opts.empty?
|
14
|
+
STDERR.puts "Called EmailNotifyHook with bad options: #{bad_opts.inspect}!"
|
15
|
+
exit 1
|
16
|
+
end
|
17
|
+
|
18
|
+
@options = options
|
19
|
+
@options["max_lines"] ||= 300
|
20
|
+
|
21
|
+
unless @options["recipients"]
|
22
|
+
raise "Must specify at least one recipient to EmailNotifyHook!"
|
23
|
+
end
|
24
|
+
end
|
25
|
+
|
26
|
+
def check
|
27
|
+
content = file_diffs.flat_map { |path, diff| ["", path, diff] }.join("\n")
|
28
|
+
if content.split("\n").size > options["max_lines"]
|
29
|
+
content = "Diffs are too big. Skipping them.\nFiles:\n" +
|
30
|
+
file_diffs.keys.join("\n")
|
31
|
+
end
|
32
|
+
|
33
|
+
recipients = @options.recipients.split /,|;/
|
34
|
+
|
35
|
+
unless @options["no_send"] || @options["via"] == "no_send"
|
36
|
+
require "pony"
|
37
|
+
|
38
|
+
recipients.each do |name, email|
|
39
|
+
ret = Pony.mail :to => email,
|
40
|
+
:from => @options["from"],
|
41
|
+
:subject => @options["subject"],
|
42
|
+
:body => desc,
|
43
|
+
:via => @options["via"],
|
44
|
+
:via_options => @options["via_options"]
|
45
|
+
end
|
46
|
+
end
|
47
|
+
|
48
|
+
true
|
49
|
+
end
|
50
|
+
end
|
@@ -0,0 +1,93 @@
|
|
1
|
+
# Copyright (C) 2013 OL2, Inc. See LICENSE.txt for details.
|
2
|
+
|
3
|
+
# This is a set of Git operations, run via shell. It permits much
|
4
|
+
# cleaner unit tests. Initially it was written in this way because it
|
5
|
+
# was very small and simple. Now it should be converted to Grit or a
|
6
|
+
# similar tool or library.
|
7
|
+
|
8
|
+
module RubyGitHooks; end
|
9
|
+
|
10
|
+
module RubyGitHooks::GitOps
|
11
|
+
extend self
|
12
|
+
|
13
|
+
Hook = RubyGitHooks::Hook
|
14
|
+
|
15
|
+
def new_bare_repo(name = "parent_repo.git")
|
16
|
+
Hook.shell! "mkdir #{name} && cd #{name} && git init --bare"
|
17
|
+
end
|
18
|
+
|
19
|
+
def clone_repo(parent_name = "parent_repo.git", child_name = "child_repo")
|
20
|
+
Hook.shell! "git clone #{parent_name} #{child_name}"
|
21
|
+
end
|
22
|
+
|
23
|
+
def add_hook(repo_name, hook_name, contents)
|
24
|
+
# We're adding to either a normal or bare directory.
|
25
|
+
# Check for hooks directory.
|
26
|
+
if File.exist? "#{repo_name}/.git/hooks"
|
27
|
+
hooks_dir = "#{repo_name}/.git/hooks"
|
28
|
+
elsif File.exist? "#{repo_name}/hooks"
|
29
|
+
hooks_dir = "#{repo_name}/hooks"
|
30
|
+
else
|
31
|
+
raise "Can't locate hooks directory under #{repo_name.inspect}!"
|
32
|
+
end
|
33
|
+
|
34
|
+
filename = File.join(hooks_dir, hook_name)
|
35
|
+
File.open(filename, "w") do |f|
|
36
|
+
f.write(contents)
|
37
|
+
end
|
38
|
+
Hook.shell!("chmod +x #{filename}")
|
39
|
+
end
|
40
|
+
|
41
|
+
def new_commit(repo_name, filename, contents = "Contents", commit_message = "Single-file commit of #{filename}")
|
42
|
+
if !contents.nil?
|
43
|
+
File.open(File.join(repo_name, filename), "w") do |f|
|
44
|
+
f.write(contents)
|
45
|
+
end
|
46
|
+
end
|
47
|
+
|
48
|
+
Hook.shell! "cd #{repo_name} && git add #{filename} && git commit -m \"#{commit_message}\""
|
49
|
+
end
|
50
|
+
|
51
|
+
def new_single_file_commit(repo_name = "child_repo", contents = "Single-file commit")
|
52
|
+
@single_file_counter ||= 1
|
53
|
+
|
54
|
+
filename = "test_file_#{@single_file_counter}"
|
55
|
+
|
56
|
+
new_commit(repo_name, filename, contents)
|
57
|
+
|
58
|
+
@single_file_counter += 1
|
59
|
+
end
|
60
|
+
system("git mv child_repo/file_to_rename child_repo/renamed_file")
|
61
|
+
|
62
|
+
def git_delete(repo_name="child_repo", filename="file_to_delete")
|
63
|
+
# filename is a file that has already been added and commited to the repo
|
64
|
+
Hook.shell! "cd #{repo_name} && git rm #{filename}"
|
65
|
+
end
|
66
|
+
|
67
|
+
def git_rename(repo_name="child_repo", filename="file_to_rename", new_filename)
|
68
|
+
# filename is a file that has already been added and commited to the repo
|
69
|
+
Hook.shell! "cd #{repo_name} && git mv #{filename} #{new_filename}"
|
70
|
+
end
|
71
|
+
|
72
|
+
|
73
|
+
def last_commit_sha(repo_name = "child_repo")
|
74
|
+
Hook.shell!("cd #{repo_name} && git log -n 1 --format=%H").chomp
|
75
|
+
end
|
76
|
+
|
77
|
+
def git_commit(repo_name = "child_repo", commit_message = "Generic commit message")
|
78
|
+
Hook.shell! "cd #{repo_name} && git commit -m \"#{commit_message}\""
|
79
|
+
end
|
80
|
+
|
81
|
+
def git_push(repo_name = "child_repo", remote = "origin", branch = "master")
|
82
|
+
Hook.shell! "cd #{repo_name} && git push #{remote} #{branch}"
|
83
|
+
end
|
84
|
+
|
85
|
+
def rewind_one_commit(repo_name = "child_repo")
|
86
|
+
Hook.shell! "cd #{repo_name} && git reset --hard HEAD~"
|
87
|
+
end
|
88
|
+
|
89
|
+
def last_commit_message(repo_name = "child_repo")
|
90
|
+
Hook.shell!("cd #{repo_name} && git log -1").chomp
|
91
|
+
end
|
92
|
+
|
93
|
+
end
|