ruby_git_hooks 0.0.31
Sign up to get free protection for your applications and to get access to all the features.
- 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
|