| line |
stmt |
bran |
cond |
sub |
pod |
time |
code |
|
1
|
15
|
|
|
15
|
|
12063
|
use warnings; |
|
|
15
|
|
|
|
|
36
|
|
|
|
15
|
|
|
|
|
930
|
|
|
2
|
|
|
|
|
|
|
|
|
3
|
|
|
|
|
|
|
package Git::Repository::Plugin::GitHooks; |
|
4
|
|
|
|
|
|
|
# ABSTRACT: A Git::Repository plugin with some goodies for hook developers |
|
5
|
|
|
|
|
|
|
$Git::Repository::Plugin::GitHooks::VERSION = '3.4.0'; |
|
6
|
15
|
|
|
15
|
|
6795
|
use parent qw/Git::Repository::Plugin/; |
|
|
15
|
|
|
|
|
4628
|
|
|
|
15
|
|
|
|
|
93
|
|
|
7
|
|
|
|
|
|
|
|
|
8
|
15
|
|
|
15
|
|
15831
|
use v5.16.0; |
|
|
15
|
|
|
|
|
57
|
|
|
9
|
15
|
|
|
15
|
|
87
|
use utf8; |
|
|
15
|
|
|
|
|
38
|
|
|
|
15
|
|
|
|
|
230
|
|
|
10
|
15
|
|
|
15
|
|
338
|
use Carp; |
|
|
15
|
|
|
|
|
39
|
|
|
|
15
|
|
|
|
|
759
|
|
|
11
|
15
|
|
|
15
|
|
102
|
use Path::Tiny; |
|
|
15
|
|
|
|
|
31
|
|
|
|
15
|
|
|
|
|
626
|
|
|
12
|
15
|
|
|
15
|
|
7179
|
use IO::Interactive 'is_interactive'; |
|
|
15
|
|
|
|
|
14938
|
|
|
|
15
|
|
|
|
|
103
|
|
|
13
|
15
|
|
|
15
|
|
7685
|
use Log::Any '$log'; |
|
|
15
|
|
|
|
|
128911
|
|
|
|
15
|
|
|
|
|
78
|
|
|
14
|
|
|
|
|
|
|
|
|
15
|
|
|
|
|
|
|
sub _keywords { ## no critic (ProhibitUnusedPrivateSubroutines) |
|
16
|
|
|
|
|
|
|
|
|
17
|
|
|
|
|
|
|
return |
|
18
|
17
|
|
|
17
|
|
1470
|
qw/ |
|
19
|
|
|
|
|
|
|
prepare_hook load_plugins invoke_external_hooks |
|
20
|
|
|
|
|
|
|
|
|
21
|
|
|
|
|
|
|
post_hook post_hooks |
|
22
|
|
|
|
|
|
|
|
|
23
|
|
|
|
|
|
|
cache |
|
24
|
|
|
|
|
|
|
|
|
25
|
|
|
|
|
|
|
get_config get_config_boolean get_config_integer |
|
26
|
|
|
|
|
|
|
|
|
27
|
|
|
|
|
|
|
check_timeout |
|
28
|
|
|
|
|
|
|
|
|
29
|
|
|
|
|
|
|
fault get_faults fail_on_faults |
|
30
|
|
|
|
|
|
|
|
|
31
|
|
|
|
|
|
|
undef_commit empty_tree get_commit get_commits |
|
32
|
|
|
|
|
|
|
|
|
33
|
|
|
|
|
|
|
read_commit_msg_file write_commit_msg_file |
|
34
|
|
|
|
|
|
|
|
|
35
|
|
|
|
|
|
|
get_affected_refs get_affected_ref_range get_affected_ref_commits |
|
36
|
|
|
|
|
|
|
|
|
37
|
|
|
|
|
|
|
filter_name_status_in_index filter_name_status_in_range filter_name_status_in_commit |
|
38
|
|
|
|
|
|
|
|
|
39
|
|
|
|
|
|
|
filter_files_in_index filter_files_in_range filter_files_in_commit |
|
40
|
|
|
|
|
|
|
|
|
41
|
|
|
|
|
|
|
authenticated_user repository_name |
|
42
|
|
|
|
|
|
|
|
|
43
|
|
|
|
|
|
|
get_current_branch get_sha1 get_head_or_empty_tree |
|
44
|
|
|
|
|
|
|
|
|
45
|
|
|
|
|
|
|
blob file_size file_mode |
|
46
|
|
|
|
|
|
|
|
|
47
|
|
|
|
|
|
|
is_reference_enabled match_user im_admin grok_acls |
|
48
|
|
|
|
|
|
|
/; |
|
49
|
|
|
|
|
|
|
} |
|
50
|
|
|
|
|
|
|
|
|
51
|
|
|
|
|
|
|
# This package variable tells get_config which character encoding is used in |
|
52
|
|
|
|
|
|
|
# the output of the git-config command. Usually none, and decoding isn't |
|
53
|
|
|
|
|
|
|
# necessary. But sometimes it is... |
|
54
|
|
|
|
|
|
|
our $CONFIG_ENCODING = undef; |
|
55
|
|
|
|
|
|
|
|
|
56
|
|
|
|
|
|
|
############## |
|
57
|
|
|
|
|
|
|
# The following routines prepare the arguments for some hooks to make |
|
58
|
|
|
|
|
|
|
# it easier to deal with them later on. |
|
59
|
|
|
|
|
|
|
|
|
60
|
|
|
|
|
|
|
# Some hooks get information from STDIN as text lines with |
|
61
|
|
|
|
|
|
|
# space-separated fields. This routine reads up all of STDIN and tucks |
|
62
|
|
|
|
|
|
|
# that information in the Git::Repository object. |
|
63
|
|
|
|
|
|
|
|
|
64
|
|
|
|
|
|
|
sub _push_input_data { |
|
65
|
0
|
|
|
0
|
|
0
|
my ($git, $data) = @_; |
|
66
|
0
|
|
|
|
|
0
|
push @{$git->{_plugin_githooks}{input_data}}, $data; |
|
|
0
|
|
|
|
|
0
|
|
|
67
|
0
|
|
|
|
|
0
|
return; |
|
68
|
|
|
|
|
|
|
} |
|
69
|
|
|
|
|
|
|
|
|
70
|
|
|
|
|
|
|
sub _get_input_data { |
|
71
|
0
|
|
|
0
|
|
0
|
my ($git) = @_; |
|
72
|
0
|
|
0
|
|
|
0
|
return $git->{_plugin_githooks}{input_data} || []; |
|
73
|
|
|
|
|
|
|
} |
|
74
|
|
|
|
|
|
|
|
|
75
|
|
|
|
|
|
|
sub _prepare_input_data { |
|
76
|
0
|
|
|
0
|
|
0
|
my ($git) = @_; |
|
77
|
0
|
|
|
|
|
0
|
while () { ## no critic (InputOutput::ProhibitExplicitStdin) |
|
78
|
0
|
|
|
|
|
0
|
chomp; |
|
79
|
0
|
|
|
|
|
0
|
_push_input_data($git, [split]); |
|
80
|
|
|
|
|
|
|
} |
|
81
|
0
|
|
|
|
|
0
|
my $input_data = _get_input_data($git); |
|
82
|
0
|
|
|
|
|
0
|
$log->info(_prepare_input_data => {input_data => $input_data}); |
|
83
|
0
|
|
|
|
|
0
|
return $input_data; |
|
84
|
|
|
|
|
|
|
} |
|
85
|
|
|
|
|
|
|
|
|
86
|
|
|
|
|
|
|
# The pre-receive and post-receive hooks get the list of affected |
|
87
|
|
|
|
|
|
|
# commits via STDIN. This routine gets them all and set all affected |
|
88
|
|
|
|
|
|
|
# refs in the Git object. |
|
89
|
|
|
|
|
|
|
|
|
90
|
|
|
|
|
|
|
sub _prepare_receive { |
|
91
|
0
|
|
|
0
|
|
0
|
my ($git) = @_; |
|
92
|
0
|
|
|
|
|
0
|
foreach (@{_prepare_input_data($git)}) { |
|
|
0
|
|
|
|
|
0
|
|
|
93
|
0
|
|
|
|
|
0
|
my ($old_commit, $new_commit, $ref) = @$_; |
|
94
|
0
|
|
|
|
|
0
|
_set_affected_ref($git, $ref, $old_commit, $new_commit); |
|
95
|
|
|
|
|
|
|
} |
|
96
|
0
|
|
|
|
|
0
|
return; |
|
97
|
|
|
|
|
|
|
} |
|
98
|
|
|
|
|
|
|
|
|
99
|
|
|
|
|
|
|
# The update hook get three arguments telling which reference is being |
|
100
|
|
|
|
|
|
|
# updated, from which commit, to which commit. Here we use these |
|
101
|
|
|
|
|
|
|
# arguments to set the affected ref in the Git object. |
|
102
|
|
|
|
|
|
|
|
|
103
|
|
|
|
|
|
|
sub _prepare_update { |
|
104
|
0
|
|
|
0
|
|
0
|
my ($git, $args) = @_; |
|
105
|
0
|
|
|
|
|
0
|
_set_affected_ref($git, @$args); |
|
106
|
0
|
|
|
|
|
0
|
$log->debug(_prepare_update => {affected_refs => _get_affected_refs_hash($git)}); |
|
107
|
0
|
|
|
|
|
0
|
return; |
|
108
|
|
|
|
|
|
|
} |
|
109
|
|
|
|
|
|
|
|
|
110
|
|
|
|
|
|
|
# Gerrit hooks get a list of option/value pairs. Here we convert the list into a |
|
111
|
|
|
|
|
|
|
# hash and change the original argument list into a single hash-ref. We also |
|
112
|
|
|
|
|
|
|
# record information about the user performing the push. Based on: |
|
113
|
|
|
|
|
|
|
# https://gerrit.googlesource.com/plugins/hooks/+/refs/heads/master/src/main/resources/Documentation/hooks.md |
|
114
|
|
|
|
|
|
|
|
|
115
|
|
|
|
|
|
|
sub _prepare_gerrit_args { |
|
116
|
0
|
|
|
0
|
|
0
|
my ($git, $args) = @_; |
|
117
|
|
|
|
|
|
|
|
|
118
|
0
|
|
|
|
|
0
|
my %opt = @$args; |
|
119
|
|
|
|
|
|
|
|
|
120
|
|
|
|
|
|
|
# Each Gerrit hook receive the full name and email of the user |
|
121
|
|
|
|
|
|
|
# performing the hooked operation via a specific option in the |
|
122
|
|
|
|
|
|
|
# format "User Name (email@example.net)". Here we grok it. |
|
123
|
|
|
|
|
|
|
my $user = |
|
124
|
|
|
|
|
|
|
$opt{'--uploader'} || |
|
125
|
|
|
|
|
|
|
$opt{'--author'} || |
|
126
|
|
|
|
|
|
|
$opt{'--submitter'} || |
|
127
|
|
|
|
|
|
|
$opt{'--abandoner'} || |
|
128
|
|
|
|
|
|
|
$opt{'--restorer'} || |
|
129
|
0
|
|
0
|
|
|
0
|
$opt{'--reviewer'} || |
|
130
|
|
|
|
|
|
|
undef; |
|
131
|
|
|
|
|
|
|
|
|
132
|
|
|
|
|
|
|
# Here we make the name and email available in two environment variables |
|
133
|
|
|
|
|
|
|
# (GERRIT_USER_NAME and GERRIT_USER_EMAIL) so that |
|
134
|
|
|
|
|
|
|
# Git::Repository::Plugin::GitHooks::authenticated_user can more easily |
|
135
|
|
|
|
|
|
|
# grok the userid from them later. |
|
136
|
0
|
0
|
0
|
|
|
0
|
if ($user && $user =~ /([^\(]+)\s+\(([^\)]+)\)/) { |
|
137
|
0
|
|
|
|
|
0
|
$ENV{GERRIT_USER_NAME} = $1; ## no critic (Variables::RequireLocalizedPunctuationVars) |
|
138
|
0
|
|
|
|
|
0
|
$ENV{GERRIT_USER_EMAIL} = $2; ## no critic (Variables::RequireLocalizedPunctuationVars) |
|
139
|
|
|
|
|
|
|
} |
|
140
|
|
|
|
|
|
|
|
|
141
|
0
|
|
|
|
|
0
|
$log->debug(_prepare_gerrit_args => {opt => \%opt}); |
|
142
|
|
|
|
|
|
|
|
|
143
|
|
|
|
|
|
|
# Now we create a Gerrit::REST object connected to the Gerrit |
|
144
|
|
|
|
|
|
|
# server and tack it to the hook arguments so that Gerrit plugins |
|
145
|
|
|
|
|
|
|
# can interact with it. |
|
146
|
|
|
|
|
|
|
|
|
147
|
|
|
|
|
|
|
# We 'require' the module instead of 'use' it because it's only |
|
148
|
|
|
|
|
|
|
# used if one sets up Gerrit hooks, which may not be the most |
|
149
|
|
|
|
|
|
|
# common usage of Git::Hooks. |
|
150
|
0
|
0
|
|
|
|
0
|
eval {require Gerrit::REST} |
|
|
0
|
|
|
|
|
0
|
|
|
151
|
|
|
|
|
|
|
or croak __PACKAGE__, ": Please, install the Gerrit::REST module to use Gerrit hooks.\n"; |
|
152
|
|
|
|
|
|
|
|
|
153
|
0
|
|
|
|
|
0
|
$opt{gerrit} = do { |
|
154
|
0
|
|
|
|
|
0
|
my %info; |
|
155
|
0
|
|
|
|
|
0
|
foreach my $arg (qw/url username password/) { |
|
156
|
0
|
0
|
|
|
|
0
|
$info{$arg} = $git->get_config('githooks.gerrit' => $arg) |
|
157
|
|
|
|
|
|
|
or croak __PACKAGE__, ": Missing githooks.gerrit.$arg configuration variable.\n"; |
|
158
|
|
|
|
|
|
|
} |
|
159
|
|
|
|
|
|
|
|
|
160
|
0
|
|
|
|
|
0
|
Gerrit::REST->new(@info{qw/url username password/}); |
|
161
|
|
|
|
|
|
|
}; |
|
162
|
|
|
|
|
|
|
|
|
163
|
0
|
|
|
|
|
0
|
@$args = (\%opt); |
|
164
|
|
|
|
|
|
|
|
|
165
|
0
|
|
|
|
|
0
|
$git->{_plugin_githooks}{gerrit_args} = \%opt; |
|
166
|
|
|
|
|
|
|
|
|
167
|
0
|
|
|
|
|
0
|
return; |
|
168
|
|
|
|
|
|
|
} |
|
169
|
|
|
|
|
|
|
|
|
170
|
|
|
|
|
|
|
# The ref-update and the commit-received Gerrit hooks are invoked synchronously |
|
171
|
|
|
|
|
|
|
# when a user pushes commits to a branch. So, they act much like Git's standard |
|
172
|
|
|
|
|
|
|
# 'update' hook. This routine prepares the options as usual and sets the |
|
173
|
|
|
|
|
|
|
# affected ref accordingly. The documented arguments for the hook are these: |
|
174
|
|
|
|
|
|
|
|
|
175
|
|
|
|
|
|
|
# ref-update --project --refname --uploader |
|
176
|
|
|
|
|
|
|
# --uploader-username --oldrev --newrev |
|
177
|
|
|
|
|
|
|
|
|
178
|
|
|
|
|
|
|
# commit-received --project --refname --uploader |
|
179
|
|
|
|
|
|
|
# --uploader-username --oldrev --newrev |
|
180
|
|
|
|
|
|
|
# --cmdref |
|
181
|
|
|
|
|
|
|
|
|
182
|
|
|
|
|
|
|
sub _prepare_gerrit_ref_update { |
|
183
|
0
|
|
|
0
|
|
0
|
my ($git, $args) = @_; |
|
184
|
|
|
|
|
|
|
|
|
185
|
0
|
|
|
|
|
0
|
_prepare_gerrit_args($git, $args); |
|
186
|
|
|
|
|
|
|
|
|
187
|
|
|
|
|
|
|
# The --refname argument contains the branch short-name if it's in the |
|
188
|
|
|
|
|
|
|
# refs/heads/ namespace. But we need to always use the branch long-name, |
|
189
|
|
|
|
|
|
|
# so we change it here. |
|
190
|
0
|
|
|
|
|
0
|
my $refname = $args->[0]{'--refname'}; |
|
191
|
0
|
0
|
|
|
|
0
|
$refname = "refs/heads/$refname" |
|
192
|
|
|
|
|
|
|
unless $refname =~ m:^refs/:; |
|
193
|
|
|
|
|
|
|
|
|
194
|
0
|
|
|
|
|
0
|
_set_affected_ref($git, $refname, @{$args->[0]}{qw/--oldrev --newrev/}); |
|
|
0
|
|
|
|
|
0
|
|
|
195
|
0
|
|
|
|
|
0
|
$log->debug(_prepare_gerrit_ref_update => {affected_refs => _get_affected_refs_hash($git)}); |
|
196
|
0
|
|
|
|
|
0
|
return; |
|
197
|
|
|
|
|
|
|
} |
|
198
|
|
|
|
|
|
|
|
|
199
|
|
|
|
|
|
|
# The submit Gerrit hook is invoked synchronously when a user tries to submit a |
|
200
|
|
|
|
|
|
|
# change. So, it acts much like Git's standard 'update' hook. This routine |
|
201
|
|
|
|
|
|
|
# prepares the options as usual and sets the affected ref accordingly. The |
|
202
|
|
|
|
|
|
|
# documented arguments for the hook are these: |
|
203
|
|
|
|
|
|
|
|
|
204
|
|
|
|
|
|
|
# submit --project --branch --submitter |
|
205
|
|
|
|
|
|
|
# --patchset --commit |
|
206
|
|
|
|
|
|
|
|
|
207
|
|
|
|
|
|
|
sub _prepare_gerrit_submit { |
|
208
|
0
|
|
|
0
|
|
0
|
my ($git, $args) = @_; |
|
209
|
|
|
|
|
|
|
|
|
210
|
0
|
|
|
|
|
0
|
_prepare_gerrit_args($git, $args); |
|
211
|
|
|
|
|
|
|
|
|
212
|
|
|
|
|
|
|
# The --branch argument contains the branch short-name if it's in the |
|
213
|
|
|
|
|
|
|
# refs/heads/ namespace. But we need to always use the branch long-name, |
|
214
|
|
|
|
|
|
|
# so we change it here. |
|
215
|
0
|
|
|
|
|
0
|
my $refname = $args->[0]{'--branch'}; |
|
216
|
0
|
0
|
|
|
|
0
|
$refname = "refs/heads/$refname" |
|
217
|
|
|
|
|
|
|
unless $refname =~ m:^refs/:; |
|
218
|
|
|
|
|
|
|
|
|
219
|
0
|
|
|
|
|
0
|
my $parent = $git->get_sha1("$refname^"); |
|
220
|
|
|
|
|
|
|
|
|
221
|
0
|
|
|
|
|
0
|
_set_affected_ref($git, $refname, $parent, $args->[0]{'--commit'}); |
|
222
|
0
|
|
|
|
|
0
|
$log->debug(_prepare_gerrit_submit => {affected_refs => _get_affected_refs_hash($git)}); |
|
223
|
0
|
|
|
|
|
0
|
return; |
|
224
|
|
|
|
|
|
|
} |
|
225
|
|
|
|
|
|
|
|
|
226
|
|
|
|
|
|
|
# The following routine is the post_hook used by the Gerrit hooks |
|
227
|
|
|
|
|
|
|
# patchset-created and draft-published. It basically casts a vote on the |
|
228
|
|
|
|
|
|
|
# patchset based on the errors found during the hook processing. |
|
229
|
|
|
|
|
|
|
|
|
230
|
|
|
|
|
|
|
sub _gerrit_patchset_post_hook { |
|
231
|
0
|
|
|
0
|
|
0
|
my ($hook_name, $git, $args) = @_; |
|
232
|
|
|
|
|
|
|
|
|
233
|
0
|
|
|
|
|
0
|
for my $arg (qw/project branch change patchset/) { |
|
234
|
0
|
0
|
|
|
|
0
|
exists $args->{"--$arg"} |
|
235
|
|
|
|
|
|
|
or croak __PACKAGE__, ": Missing --$arg argument to Gerrit's $hook_name hook.\n"; |
|
236
|
|
|
|
|
|
|
} |
|
237
|
|
|
|
|
|
|
|
|
238
|
|
|
|
|
|
|
# We have to use the most complete form of Gerrit change ids because |
|
239
|
|
|
|
|
|
|
# it's the only unanbiguous one. Vide: |
|
240
|
|
|
|
|
|
|
# https://gerrit.cpqd.com.br/Documentation/rest-api-changes.html#change-id. |
|
241
|
|
|
|
|
|
|
|
|
242
|
|
|
|
|
|
|
# Up to Gerrit 2.12 the argument --change passed the change's Change-Id |
|
243
|
|
|
|
|
|
|
# code. So, we had to build the complete change id using the information |
|
244
|
|
|
|
|
|
|
# passed on the arguments --project and --branch. From Gerrit 2.13 on |
|
245
|
|
|
|
|
|
|
# the --change argument already contains the complete change id. So we |
|
246
|
|
|
|
|
|
|
# have to figure out if we need to build it or not. |
|
247
|
|
|
|
|
|
|
|
|
248
|
|
|
|
|
|
|
# Also, for the old Gerrit we have to url-escape the change-id because |
|
249
|
|
|
|
|
|
|
# the project name may contain slashes (and perhaps other reserved |
|
250
|
|
|
|
|
|
|
# characters). This is possibly not a complete solution. Vide: |
|
251
|
|
|
|
|
|
|
# http://mark.stosberg.com/blog/2010/12/percent-encoding-uris-in-perl.html. |
|
252
|
|
|
|
|
|
|
|
|
253
|
0
|
|
|
|
|
0
|
require URI::Escape; |
|
254
|
|
|
|
|
|
|
my $id = $args->{'--change'} =~ /~/ |
|
255
|
|
|
|
|
|
|
? $args->{'--change'} |
|
256
|
0
|
0
|
|
|
|
0
|
: URI::Escape::uri_escape(join('~', @{$args}{qw/--project --branch --change/})); |
|
|
0
|
|
|
|
|
0
|
|
|
257
|
|
|
|
|
|
|
|
|
258
|
0
|
|
|
|
|
0
|
my $patchset = $args->{'--patchset'}; |
|
259
|
|
|
|
|
|
|
|
|
260
|
|
|
|
|
|
|
# Grok all configuration options at once to make it easier to deal with them below. |
|
261
|
0
|
|
|
|
|
0
|
my %cfg = ( |
|
262
|
|
|
|
|
|
|
'votes-to-approve' => $git->get_config('githooks.gerrit' => 'votes-to-approve'), |
|
263
|
|
|
|
|
|
|
'votes-to-reject' => $git->get_config('githooks.gerrit' => 'votes-to-reject'), |
|
264
|
|
|
|
|
|
|
'comment-ok' => $git->get_config('githooks.gerrit' => 'comment-ok'), |
|
265
|
|
|
|
|
|
|
'auto-submit' => $git->get_config_boolean('githooks.gerrit' => 'auto-submit'), |
|
266
|
|
|
|
|
|
|
); |
|
267
|
|
|
|
|
|
|
|
|
268
|
|
|
|
|
|
|
# https://gerrit-review.googlesource.com/Documentation/rest-api-changes.html#set-review |
|
269
|
0
|
|
|
|
|
0
|
my %review_input = (tag => 'autogenerated:git-hooks'); |
|
270
|
0
|
|
|
|
|
0
|
my $auto_submit = 0; |
|
271
|
|
|
|
|
|
|
|
|
272
|
0
|
0
|
|
|
|
0
|
if (my $faults = $git->get_faults()) { |
|
273
|
0
|
|
0
|
|
|
0
|
$review_input{labels} = $cfg{'votes-to-reject'} || 'Code-Review-1'; |
|
274
|
|
|
|
|
|
|
|
|
275
|
|
|
|
|
|
|
# We have to truncate $faults down to a little less than 64kB because up |
|
276
|
|
|
|
|
|
|
# to at least Gerrit 2.14.4 messages are saved in a MySQL column of type |
|
277
|
|
|
|
|
|
|
# 'text', which has this limit. |
|
278
|
0
|
0
|
|
|
|
0
|
if (length $faults > 65000) { |
|
279
|
0
|
|
|
|
|
0
|
$faults = substr($faults, 0, 65000) . "...\n\n"; |
|
280
|
|
|
|
|
|
|
} |
|
281
|
0
|
|
|
|
|
0
|
$review_input{message} = $faults; |
|
282
|
|
|
|
|
|
|
} else { |
|
283
|
0
|
|
0
|
|
|
0
|
$review_input{labels} = $cfg{'votes-to-approve'} || 'Code-Review+1'; |
|
284
|
|
|
|
|
|
|
$review_input{message} = "[Git::Hooks] $cfg{'comment-ok'}" |
|
285
|
0
|
0
|
|
|
|
0
|
if $cfg{'comment-ok'}; |
|
286
|
0
|
0
|
|
|
|
0
|
$auto_submit = 1 if $cfg{'auto-submit'}; |
|
287
|
|
|
|
|
|
|
} |
|
288
|
|
|
|
|
|
|
|
|
289
|
|
|
|
|
|
|
# Convert, e.g., 'LabelA-1,LabelB+2' into { LabelA => '-1', LabelB => '+2' } |
|
290
|
0
|
|
|
|
|
0
|
$review_input{labels} = { map {/^([-\w]+)([-+]\d+)$/i} split(',', $review_input{labels}) }; |
|
|
0
|
|
|
|
|
0
|
|
|
291
|
|
|
|
|
|
|
|
|
292
|
0
|
0
|
|
|
|
0
|
if (my $notify = $git->get_config('githooks.gerrit' => 'notify')) { |
|
293
|
0
|
|
|
|
|
0
|
$review_input{notify} = $notify; |
|
294
|
|
|
|
|
|
|
} |
|
295
|
|
|
|
|
|
|
|
|
296
|
0
|
|
|
|
|
0
|
$log->debug(_gerrit_patchset_post_hook => { |
|
297
|
|
|
|
|
|
|
review_input => \%review_input, |
|
298
|
|
|
|
|
|
|
auto_submit => $auto_submit, |
|
299
|
|
|
|
|
|
|
}); |
|
300
|
|
|
|
|
|
|
|
|
301
|
|
|
|
|
|
|
# Cast review |
|
302
|
0
|
0
|
|
|
|
0
|
eval { $args->{gerrit}->POST("/changes/$id/revisions/$patchset/review", \%review_input) } |
|
|
0
|
|
|
|
|
0
|
|
|
303
|
|
|
|
|
|
|
or croak __PACKAGE__ . ": error in Gerrit::REST::POST(/changes/$id/revisions/$patchset/review): $@\n"; |
|
304
|
|
|
|
|
|
|
|
|
305
|
|
|
|
|
|
|
# Auto submit if requested and passed verification |
|
306
|
0
|
0
|
|
|
|
0
|
if ($auto_submit) { |
|
307
|
0
|
0
|
|
|
|
0
|
eval { $args->{gerrit}->POST("/changes/$id/submit", {wait_for_merge => 'true'}) } |
|
|
0
|
|
|
|
|
0
|
|
|
308
|
|
|
|
|
|
|
or croak __PACKAGE__ . ": I couldn't submit the change. Perhaps you have to rebase it manually to resolve a conflict. Please go to its web page to check it out. The error message follows: $@\n"; |
|
309
|
|
|
|
|
|
|
} |
|
310
|
|
|
|
|
|
|
|
|
311
|
0
|
|
|
|
|
0
|
return; |
|
312
|
|
|
|
|
|
|
} |
|
313
|
|
|
|
|
|
|
|
|
314
|
|
|
|
|
|
|
# Gerrit's patchset-created hook is invoked when a commit is pushed to a |
|
315
|
|
|
|
|
|
|
# refs/for/* branch for revision. It's invoked asynchronously, i.e., it |
|
316
|
|
|
|
|
|
|
# can't stop the push to happen. Instead, if it detects any problem, we must |
|
317
|
|
|
|
|
|
|
# reject the commit via Gerrit's own revision process. So, we prepare a post |
|
318
|
|
|
|
|
|
|
# hook action in which we see if there were errors that should be signaled |
|
319
|
|
|
|
|
|
|
# via a code review action. Note, however, that draft changes can only be |
|
320
|
|
|
|
|
|
|
# accessed by their respective owners and usually can't be voted on by the |
|
321
|
|
|
|
|
|
|
# hook. So, draft changes aren't voted on and we exit the hook prematurely. |
|
322
|
|
|
|
|
|
|
# The arguments for the hook are these: |
|
323
|
|
|
|
|
|
|
|
|
324
|
|
|
|
|
|
|
# patchset-created --change --is-draft \ |
|
325
|
|
|
|
|
|
|
# --kind --change-url \ |
|
326
|
|
|
|
|
|
|
# --change-owner --project \ |
|
327
|
|
|
|
|
|
|
# --branch --topic --uploader |
|
328
|
|
|
|
|
|
|
# --commit --patchset |
|
329
|
|
|
|
|
|
|
|
|
330
|
|
|
|
|
|
|
# Gerrit's draft-published hook is invoked when a draft change is |
|
331
|
|
|
|
|
|
|
# published. In this state they're are visible by the hook and can be voted |
|
332
|
|
|
|
|
|
|
# on. The arguments for the hook are these: |
|
333
|
|
|
|
|
|
|
|
|
334
|
|
|
|
|
|
|
# draft-published --change --change-url \ |
|
335
|
|
|
|
|
|
|
# --change-owner --project \ |
|
336
|
|
|
|
|
|
|
# --branch --topic --uploader \ |
|
337
|
|
|
|
|
|
|
# --commit --patchset |
|
338
|
|
|
|
|
|
|
|
|
339
|
|
|
|
|
|
|
sub _prepare_gerrit_patchset { |
|
340
|
0
|
|
|
0
|
|
0
|
my ($git, $args) = @_; |
|
341
|
|
|
|
|
|
|
|
|
342
|
0
|
|
|
|
|
0
|
_prepare_gerrit_args($git, $args); |
|
343
|
|
|
|
|
|
|
|
|
344
|
0
|
0
|
0
|
|
|
0
|
exit(0) if exists $args->[0]{'--is-draft'} and $args->[0]{'--is-draft'} eq 'true'; |
|
345
|
|
|
|
|
|
|
|
|
346
|
0
|
|
|
|
|
0
|
$git->post_hook(\&_gerrit_patchset_post_hook); |
|
347
|
|
|
|
|
|
|
|
|
348
|
0
|
|
|
|
|
0
|
return; |
|
349
|
|
|
|
|
|
|
} |
|
350
|
|
|
|
|
|
|
|
|
351
|
|
|
|
|
|
|
# The %prepare_hook hash maps hook names to the routine that must be |
|
352
|
|
|
|
|
|
|
# invoked in order to "prepare" their arguments. |
|
353
|
|
|
|
|
|
|
|
|
354
|
|
|
|
|
|
|
my %prepare_hook = ( |
|
355
|
|
|
|
|
|
|
'update' => \&_prepare_update, |
|
356
|
|
|
|
|
|
|
'pre-push' => \&_prepare_input_data, |
|
357
|
|
|
|
|
|
|
'post-rewrite' => \&_prepare_input_data, |
|
358
|
|
|
|
|
|
|
'pre-receive' => \&_prepare_receive, |
|
359
|
|
|
|
|
|
|
'post-receive' => \&_prepare_receive, |
|
360
|
|
|
|
|
|
|
'ref-update' => \&_prepare_gerrit_ref_update, |
|
361
|
|
|
|
|
|
|
'commit-received' => \&_prepare_gerrit_ref_update, |
|
362
|
|
|
|
|
|
|
'submit' => \&_prepare_gerrit_submit, |
|
363
|
|
|
|
|
|
|
'patchset-created' => \&_prepare_gerrit_patchset, |
|
364
|
|
|
|
|
|
|
'draft-published' => \&_prepare_gerrit_patchset, |
|
365
|
|
|
|
|
|
|
); |
|
366
|
|
|
|
|
|
|
|
|
367
|
|
|
|
|
|
|
sub prepare_hook { |
|
368
|
0
|
|
|
0
|
1
|
0
|
my ($git, $hook_name, $args) = @_; |
|
369
|
|
|
|
|
|
|
|
|
370
|
0
|
|
|
|
|
0
|
$git->{_plugin_githooks}{arguments} = $args; |
|
371
|
0
|
|
|
|
|
0
|
my $basename = path($hook_name)->basename; |
|
372
|
0
|
|
|
|
|
0
|
$git->{_plugin_githooks}{hookname} = $basename; |
|
373
|
|
|
|
|
|
|
|
|
374
|
|
|
|
|
|
|
# Some hooks need some argument munging before we invoke them |
|
375
|
0
|
0
|
|
|
|
0
|
if (my $prepare = $prepare_hook{$basename}) { |
|
376
|
0
|
|
|
|
|
0
|
$prepare->($git, $args); |
|
377
|
|
|
|
|
|
|
} |
|
378
|
|
|
|
|
|
|
|
|
379
|
0
|
|
|
|
|
0
|
return $basename; |
|
380
|
|
|
|
|
|
|
} |
|
381
|
|
|
|
|
|
|
|
|
382
|
|
|
|
|
|
|
sub load_plugins { |
|
383
|
0
|
|
|
0
|
1
|
0
|
my ($git) = @_; |
|
384
|
|
|
|
|
|
|
|
|
385
|
0
|
|
|
|
|
0
|
my %plugins; |
|
386
|
|
|
|
|
|
|
|
|
387
|
0
|
|
|
|
|
0
|
foreach my $plugin (map {split} $git->get_config(githooks => 'plugin')) { |
|
|
0
|
|
|
|
|
0
|
|
|
388
|
0
|
|
|
|
|
0
|
my ($negation, $prefix, $basename) = ($plugin =~ /^(\!?)((?:.+::)?)(.+)/); |
|
389
|
|
|
|
|
|
|
|
|
390
|
0
|
0
|
0
|
|
|
0
|
if (exists $ENV{$basename} && ! $ENV{$basename}) { |
|
|
|
0
|
|
|
|
|
|
|
391
|
0
|
|
|
|
|
0
|
delete @plugins{$basename, "$prefix$basename"}; |
|
392
|
|
|
|
|
|
|
} elsif ($negation) { |
|
393
|
0
|
|
|
|
|
0
|
delete $plugins{"$prefix$basename"}; |
|
394
|
|
|
|
|
|
|
} else { |
|
395
|
0
|
|
|
|
|
0
|
$plugins{"$prefix$basename"} = [$prefix, $basename]; |
|
396
|
|
|
|
|
|
|
} |
|
397
|
|
|
|
|
|
|
} |
|
398
|
|
|
|
|
|
|
|
|
399
|
0
|
0
|
|
|
|
0
|
return unless %plugins; # no one configured |
|
400
|
|
|
|
|
|
|
|
|
401
|
|
|
|
|
|
|
# Remove disabled plugins from the list of plugins |
|
402
|
0
|
|
|
|
|
0
|
my %disabled_plugins = map {($_ => undef)} map {split} $git->get_config(githooks => 'disable'); |
|
|
0
|
|
|
|
|
0
|
|
|
|
0
|
|
|
|
|
0
|
|
|
403
|
0
|
|
|
|
|
0
|
delete @plugins{grep {exists $disabled_plugins{$_}} keys %plugins}; |
|
|
0
|
|
|
|
|
0
|
|
|
404
|
|
|
|
|
|
|
|
|
405
|
|
|
|
|
|
|
# Define the list of directories where we'll look for the hook |
|
406
|
|
|
|
|
|
|
# plugins. First the local directory 'githooks' under the |
|
407
|
|
|
|
|
|
|
# repository path, then the optional list of directories |
|
408
|
|
|
|
|
|
|
# specified by the githooks.plugins config option, and, |
|
409
|
|
|
|
|
|
|
# finally, the Git::Hooks standard hooks directory. |
|
410
|
0
|
|
|
|
|
0
|
my @plugin_dirs = grep {-d} ( |
|
411
|
|
|
|
|
|
|
'githooks', |
|
412
|
|
|
|
|
|
|
$git->get_config(githooks => 'plugins'), |
|
413
|
0
|
|
|
|
|
0
|
path($INC{'Git/Hooks.pm'})->parent->child('Hooks'), |
|
414
|
|
|
|
|
|
|
); |
|
415
|
|
|
|
|
|
|
|
|
416
|
0
|
|
|
|
|
0
|
$log->debug(load_plugins => {plugins => \%plugins, plugin_dirs => \@plugin_dirs}); |
|
417
|
|
|
|
|
|
|
|
|
418
|
|
|
|
|
|
|
# Load remaining enabled plugins |
|
419
|
0
|
|
|
|
|
0
|
while (my ($key, $plugin) = each %plugins) { |
|
420
|
0
|
|
|
|
|
0
|
my ($prefix, $basename) = @$plugin; |
|
421
|
0
|
|
|
|
|
0
|
my $exit = do { |
|
422
|
0
|
0
|
|
|
|
0
|
if ($prefix) { |
|
423
|
|
|
|
|
|
|
# It must be a module name |
|
424
|
|
|
|
|
|
|
## no critic (ProhibitStringyEval, RequireCheckingReturnValueOfEval) |
|
425
|
0
|
|
|
|
|
0
|
eval "require $prefix$basename"; |
|
426
|
|
|
|
|
|
|
## use critic |
|
427
|
|
|
|
|
|
|
} else { |
|
428
|
|
|
|
|
|
|
# Otherwise, it's a basename we must look for in @plugin_dirs |
|
429
|
0
|
0
|
|
|
|
0
|
$basename .= '.pm' unless $basename =~ /\.p[lm]$/i; |
|
430
|
0
|
|
|
|
|
0
|
my @scripts = grep {!-d} map {path($_)->child($basename)} @plugin_dirs; |
|
|
0
|
|
|
|
|
0
|
|
|
|
0
|
|
|
|
|
0
|
|
|
431
|
0
|
0
|
|
|
|
0
|
$basename = shift @scripts |
|
432
|
|
|
|
|
|
|
or croak __PACKAGE__, ": can't find enabled hook $basename.\n"; |
|
433
|
0
|
|
|
|
|
0
|
do $basename; |
|
434
|
|
|
|
|
|
|
} |
|
435
|
|
|
|
|
|
|
}; |
|
436
|
0
|
0
|
|
|
|
0
|
unless ($exit) { |
|
437
|
0
|
0
|
|
|
|
0
|
croak __PACKAGE__, ": couldn't parse $basename: $@\n" if $@; |
|
438
|
0
|
0
|
|
|
|
0
|
croak __PACKAGE__, ": couldn't do $basename: $!\n" unless defined $exit; |
|
439
|
0
|
|
|
|
|
0
|
croak __PACKAGE__, ": couldn't run $basename\n"; |
|
440
|
|
|
|
|
|
|
} |
|
441
|
|
|
|
|
|
|
} |
|
442
|
|
|
|
|
|
|
|
|
443
|
0
|
|
|
|
|
0
|
return; |
|
444
|
|
|
|
|
|
|
} |
|
445
|
|
|
|
|
|
|
|
|
446
|
|
|
|
|
|
|
sub _invoke_external_hook { ## no critic (ProhibitExcessComplexity) |
|
447
|
0
|
|
|
0
|
|
0
|
my ($git, $file, $hook, @args) = @_; |
|
448
|
|
|
|
|
|
|
|
|
449
|
0
|
|
|
|
|
0
|
my $prefix = '[' . __PACKAGE__ . '(' . path($file)->basename . ')]'; |
|
450
|
|
|
|
|
|
|
|
|
451
|
0
|
|
|
|
|
0
|
my $tempfile = Path::Tiny->tempfile(UNLINK => 1); |
|
452
|
|
|
|
|
|
|
|
|
453
|
|
|
|
|
|
|
## no critic (RequireBriefOpen, RequireCarping) |
|
454
|
0
|
0
|
|
|
|
0
|
open(my $oldout, '>&', \*STDOUT) or croak "Can't dup STDOUT: $!"; |
|
455
|
0
|
0
|
|
|
|
0
|
open(STDOUT , '>' , $tempfile) or croak "Can't redirect STDOUT to \$tempfile: $!"; |
|
456
|
0
|
0
|
|
|
|
0
|
open(my $olderr, '>&', \*STDERR) or croak "Can't dup STDERR: $!"; |
|
457
|
0
|
0
|
|
|
|
0
|
open(STDERR , '>&', \*STDOUT) or croak "Can't dup STDOUT for STDERR: $!"; |
|
458
|
|
|
|
|
|
|
## use critic |
|
459
|
|
|
|
|
|
|
|
|
460
|
0
|
0
|
|
|
|
0
|
if ($hook =~ /^(?:pre-receive|post-receive|pre-push|post-rewrite)$/) { |
|
461
|
|
|
|
|
|
|
|
|
462
|
|
|
|
|
|
|
# These hooks receive information via STDIN that we read once |
|
463
|
|
|
|
|
|
|
# before invoking any hook. Now, we must regenerate the same |
|
464
|
|
|
|
|
|
|
# information and output it to the external hooks we invoke. |
|
465
|
|
|
|
|
|
|
|
|
466
|
0
|
|
|
|
|
0
|
my $pid = open my $pipe, '|-'; ## no critic (InputOutput::RequireBriefOpen) |
|
467
|
|
|
|
|
|
|
|
|
468
|
0
|
0
|
|
|
|
0
|
if (! defined $pid) { |
|
|
|
0
|
|
|
|
|
|
|
469
|
0
|
|
|
|
|
0
|
$git->fault("I can't fork: $!", {prefix => $prefix}); |
|
470
|
|
|
|
|
|
|
} elsif ($pid) { |
|
471
|
|
|
|
|
|
|
# parent |
|
472
|
0
|
|
|
|
|
0
|
$pipe->print(join("\n", map {join(' ', @$_)} @{_get_input_data($git)}) . "\n"); |
|
|
0
|
|
|
|
|
0
|
|
|
|
0
|
|
|
|
|
0
|
|
|
473
|
0
|
|
|
|
|
0
|
my $exit = $pipe->close; |
|
474
|
|
|
|
|
|
|
|
|
475
|
|
|
|
|
|
|
## no critic (RequireBriefOpen, RequireCarping) |
|
476
|
0
|
0
|
|
|
|
0
|
open(STDOUT, '>&', $oldout) or croak "Can't dup \$oldout: $!"; |
|
477
|
0
|
0
|
|
|
|
0
|
open(STDERR, '>&', $olderr) or croak "Can't dup \$olderr: $!"; |
|
478
|
|
|
|
|
|
|
## use critic |
|
479
|
|
|
|
|
|
|
|
|
480
|
0
|
|
|
|
|
0
|
my $output = $tempfile->slurp; |
|
481
|
0
|
0
|
|
|
|
0
|
if ($exit) { |
|
|
|
0
|
|
|
|
|
|
|
482
|
0
|
0
|
|
|
|
0
|
say STDERR $output if length $output; |
|
483
|
0
|
|
|
|
|
0
|
return 1; |
|
484
|
|
|
|
|
|
|
} elsif ($!) { |
|
485
|
0
|
|
|
|
|
0
|
$git->fault("Error closing pipe to external hook: $!", { |
|
486
|
|
|
|
|
|
|
prefix => $prefix, |
|
487
|
|
|
|
|
|
|
details => $output, |
|
488
|
|
|
|
|
|
|
}); |
|
489
|
|
|
|
|
|
|
} else { |
|
490
|
0
|
|
|
|
|
0
|
$git->fault("External hook exited with code $?", { |
|
491
|
|
|
|
|
|
|
prefix => $prefix, |
|
492
|
|
|
|
|
|
|
details => $output, |
|
493
|
|
|
|
|
|
|
}); |
|
494
|
|
|
|
|
|
|
} |
|
495
|
|
|
|
|
|
|
} else { |
|
496
|
|
|
|
|
|
|
# child |
|
497
|
0
|
|
|
|
|
0
|
{ exec {$file} ($hook, @args) } |
|
|
0
|
|
|
|
|
0
|
|
|
|
0
|
|
|
|
|
0
|
|
|
498
|
|
|
|
|
|
|
|
|
499
|
|
|
|
|
|
|
## no critic (RequireBriefOpen, RequireCarping) |
|
500
|
0
|
0
|
|
|
|
0
|
open(STDOUT, '>&', $oldout) or croak "Can't dup \$oldout: $!"; |
|
501
|
0
|
0
|
|
|
|
0
|
open(STDERR, '>&', $olderr) or croak "Can't dup \$olderr: $!"; |
|
502
|
|
|
|
|
|
|
## use critic |
|
503
|
|
|
|
|
|
|
|
|
504
|
0
|
|
|
|
|
0
|
croak "$prefix: can't exec: $!\n"; |
|
505
|
|
|
|
|
|
|
} |
|
506
|
|
|
|
|
|
|
|
|
507
|
|
|
|
|
|
|
} else { |
|
508
|
|
|
|
|
|
|
|
|
509
|
0
|
0
|
0
|
|
|
0
|
if (@args && ref $args[0]) { |
|
510
|
|
|
|
|
|
|
# This is a Gerrit hook and we need to expand its arguments |
|
511
|
0
|
|
|
|
|
0
|
@args = %{$args[0]}; |
|
|
0
|
|
|
|
|
0
|
|
|
512
|
|
|
|
|
|
|
} |
|
513
|
|
|
|
|
|
|
|
|
514
|
0
|
|
|
|
|
0
|
my $exit = system {$file} ($hook, @args); |
|
|
0
|
|
|
|
|
0
|
|
|
515
|
|
|
|
|
|
|
|
|
516
|
|
|
|
|
|
|
## no critic (RequireBriefOpen, RequireCarping) |
|
517
|
0
|
0
|
|
|
|
0
|
open(STDOUT, '>&', $oldout) or croak "Can't dup \$oldout: $!"; |
|
518
|
0
|
0
|
|
|
|
0
|
open(STDERR, '>&', $olderr) or croak "Can't dup \$olderr: $!"; |
|
519
|
|
|
|
|
|
|
## use critic |
|
520
|
|
|
|
|
|
|
|
|
521
|
0
|
|
|
|
|
0
|
my $output = $tempfile->slurp; |
|
522
|
|
|
|
|
|
|
|
|
523
|
0
|
0
|
|
|
|
0
|
if ($exit == 0) { |
|
524
|
0
|
0
|
|
|
|
0
|
say STDERR $output if length $output; |
|
525
|
0
|
|
|
|
|
0
|
return 1; |
|
526
|
|
|
|
|
|
|
} else { |
|
527
|
0
|
|
|
|
|
0
|
my $message = do { |
|
528
|
0
|
0
|
|
|
|
0
|
if ($exit == -1) { |
|
|
|
0
|
|
|
|
|
|
|
529
|
0
|
|
|
|
|
0
|
"failed to execute external hook: $!"; |
|
530
|
|
|
|
|
|
|
} elsif ($exit & 127) { |
|
531
|
0
|
0
|
|
|
|
0
|
sprintf("external hook died with signal %d, %s coredump", |
|
532
|
|
|
|
|
|
|
($exit & 127), ($exit & 128) ? 'with' : 'without'); |
|
533
|
|
|
|
|
|
|
} else { |
|
534
|
0
|
|
|
|
|
0
|
sprintf("'$file' exited abnormally with value %d", $exit >> 8); |
|
535
|
|
|
|
|
|
|
} |
|
536
|
|
|
|
|
|
|
}; |
|
537
|
0
|
|
|
|
|
0
|
$git->fault($message, {prefix => $prefix, details => $output}); |
|
538
|
|
|
|
|
|
|
} |
|
539
|
|
|
|
|
|
|
} |
|
540
|
|
|
|
|
|
|
|
|
541
|
0
|
|
|
|
|
0
|
return 0; |
|
542
|
|
|
|
|
|
|
} |
|
543
|
|
|
|
|
|
|
|
|
544
|
|
|
|
|
|
|
sub invoke_external_hooks { |
|
545
|
0
|
|
|
0
|
1
|
0
|
my ($git, @args) = @_; |
|
546
|
|
|
|
|
|
|
|
|
547
|
0
|
0
|
0
|
|
|
0
|
return if $^O eq 'MSWin32' || ! $git->get_config_boolean(githooks => 'externals'); |
|
548
|
|
|
|
|
|
|
|
|
549
|
0
|
|
|
|
|
0
|
my $hookname = $git->{_plugin_githooks}{hookname}; |
|
550
|
|
|
|
|
|
|
|
|
551
|
0
|
|
|
|
|
0
|
foreach my $dir ( |
|
552
|
0
|
|
|
|
|
0
|
grep {-e} |
|
553
|
0
|
|
|
|
|
0
|
map {path($_)->child($hookname)} |
|
554
|
|
|
|
|
|
|
($git->get_config(githooks => 'hooks'), path($git->git_dir())->child('hooks.d')) |
|
555
|
|
|
|
|
|
|
) { |
|
556
|
0
|
0
|
0
|
|
|
0
|
opendir my $dh, $dir |
|
557
|
|
|
|
|
|
|
or $git->fault("I cannot opendir '$dir'", {details => $!}) |
|
558
|
|
|
|
|
|
|
and next; |
|
559
|
0
|
0
|
|
|
|
0
|
foreach my $file (grep {!-d && -x} map {path($dir)->child($_)} readdir $dh) { |
|
|
0
|
|
|
|
|
0
|
|
|
|
0
|
|
|
|
|
0
|
|
|
560
|
0
|
0
|
|
|
|
0
|
_invoke_external_hook($git, $file, $hookname, @args) |
|
561
|
|
|
|
|
|
|
or $git->fault(": error in external hook '$file'"); |
|
562
|
|
|
|
|
|
|
} |
|
563
|
|
|
|
|
|
|
} continue { |
|
564
|
0
|
|
|
|
|
0
|
$git->check_timeout(); |
|
565
|
|
|
|
|
|
|
} |
|
566
|
|
|
|
|
|
|
|
|
567
|
0
|
|
|
|
|
0
|
return; |
|
568
|
|
|
|
|
|
|
} |
|
569
|
|
|
|
|
|
|
|
|
570
|
|
|
|
|
|
|
############## |
|
571
|
|
|
|
|
|
|
# The following routines are invoked after all hooks have been |
|
572
|
|
|
|
|
|
|
# processed. Some hooks may need to take a global action depending on |
|
573
|
|
|
|
|
|
|
# the overall result of all hooks. |
|
574
|
|
|
|
|
|
|
|
|
575
|
|
|
|
|
|
|
sub post_hook { |
|
576
|
0
|
|
|
0
|
1
|
0
|
my ($git, $sub) = @_; |
|
577
|
0
|
|
|
|
|
0
|
push @{$git->{_plugin_githooks}{post_hooks}}, $sub; |
|
|
0
|
|
|
|
|
0
|
|
|
578
|
0
|
|
|
|
|
0
|
return; |
|
579
|
|
|
|
|
|
|
} |
|
580
|
|
|
|
|
|
|
|
|
581
|
|
|
|
|
|
|
sub post_hooks { |
|
582
|
0
|
|
|
0
|
1
|
0
|
my ($git) = @_; |
|
583
|
0
|
0
|
|
|
|
0
|
if ($git->{_plugin_githooks}{post_hooks}) { |
|
584
|
0
|
|
|
|
|
0
|
return @{$git->{_plugin_githooks}{post_hooks}} |
|
|
0
|
|
|
|
|
0
|
|
|
585
|
|
|
|
|
|
|
} else { |
|
586
|
0
|
|
|
|
|
0
|
return; |
|
587
|
|
|
|
|
|
|
} |
|
588
|
|
|
|
|
|
|
} |
|
589
|
|
|
|
|
|
|
|
|
590
|
|
|
|
|
|
|
sub cache { |
|
591
|
12
|
|
|
12
|
1
|
56
|
my ($git, $section) = @_; |
|
592
|
|
|
|
|
|
|
|
|
593
|
12
|
100
|
|
|
|
115
|
unless (exists $git->{_plugin_githooks}{cache}{$section}) { |
|
594
|
2
|
|
|
|
|
15
|
$git->{_plugin_githooks}{cache}{$section} = {}; |
|
595
|
|
|
|
|
|
|
} |
|
596
|
|
|
|
|
|
|
|
|
597
|
12
|
|
|
|
|
60
|
return $git->{_plugin_githooks}{cache}{$section}; |
|
598
|
|
|
|
|
|
|
} |
|
599
|
|
|
|
|
|
|
|
|
600
|
|
|
|
|
|
|
sub get_config { |
|
601
|
21
|
|
|
21
|
1
|
380
|
my ($git, $section, $var) = @_; |
|
602
|
|
|
|
|
|
|
|
|
603
|
21
|
100
|
|
|
|
213
|
unless (exists $git->{_plugin_githooks}{config}) { |
|
604
|
3
|
|
|
|
|
22
|
my %config; |
|
605
|
|
|
|
|
|
|
|
|
606
|
3
|
|
|
|
|
16
|
my $config = do { |
|
607
|
3
|
|
|
|
|
58
|
local $/ = "\c@"; |
|
608
|
3
|
|
|
|
|
49
|
$git->run(qw/config --null --list/); |
|
609
|
|
|
|
|
|
|
}; |
|
610
|
|
|
|
|
|
|
|
|
611
|
3
|
50
|
|
|
|
61398
|
if (defined $CONFIG_ENCODING) { |
|
612
|
0
|
|
|
|
|
0
|
require Encode; |
|
613
|
0
|
|
|
|
|
0
|
$config = Encode::decode($CONFIG_ENCODING, $config); |
|
614
|
|
|
|
|
|
|
} |
|
615
|
|
|
|
|
|
|
|
|
616
|
3
|
50
|
|
|
|
75
|
if (defined $config) { |
|
617
|
|
|
|
|
|
|
# The --null option to git-log makes it output a null character |
|
618
|
|
|
|
|
|
|
# after each option/value. The option and value are separated by a |
|
619
|
|
|
|
|
|
|
# newline, unless there is no value, in which case, there is no |
|
620
|
|
|
|
|
|
|
# newline. |
|
621
|
3
|
|
|
|
|
105
|
while ($config =~ /([^\cJ]+)(\cJ[^\c@]*|)\c@/sg) { |
|
622
|
29
|
|
|
|
|
161
|
my ($option, $value) = ($1, $2); |
|
623
|
29
|
50
|
|
|
|
144
|
if ($option =~ /(.+)\.(.+)/) { |
|
624
|
29
|
|
|
|
|
108
|
my ($osection, $okey) = (lc $1, lc $2); |
|
625
|
29
|
50
|
|
|
|
134
|
if ($value =~ s/^\cJ//) { |
|
626
|
29
|
|
|
|
|
51
|
push @{$config{$osection}{$okey}}, $value; |
|
|
29
|
|
|
|
|
356
|
|
|
627
|
|
|
|
|
|
|
} else { |
|
628
|
|
|
|
|
|
|
# An option without a value is considered a boolean |
|
629
|
|
|
|
|
|
|
# true. We mark it explicitly so instead of leaving it |
|
630
|
|
|
|
|
|
|
# undefined because Perl would consider it false. |
|
631
|
0
|
|
|
|
|
0
|
push @{$config{$osection}{$okey}}, 'true'; |
|
|
0
|
|
|
|
|
0
|
|
|
632
|
|
|
|
|
|
|
} |
|
633
|
|
|
|
|
|
|
} else { |
|
634
|
0
|
|
|
|
|
0
|
croak __PACKAGE__, ": Cannot grok config variable name '$option'.\n"; |
|
635
|
|
|
|
|
|
|
} |
|
636
|
|
|
|
|
|
|
} |
|
637
|
|
|
|
|
|
|
} |
|
638
|
|
|
|
|
|
|
|
|
639
|
|
|
|
|
|
|
# Set default values for undefined ones. |
|
640
|
3
|
|
50
|
|
|
95
|
$config{githooks}{externals} //= ['true']; |
|
641
|
3
|
|
50
|
|
|
75
|
$config{githooks}{gerrit}{enabled} //= ['true']; |
|
642
|
3
|
|
50
|
|
|
59
|
$config{githooks}{'abort-commit'} //= ['true']; |
|
643
|
|
|
|
|
|
|
|
|
644
|
3
|
|
|
|
|
37
|
$git->{_plugin_githooks}{config} = \%config; |
|
645
|
|
|
|
|
|
|
} |
|
646
|
|
|
|
|
|
|
|
|
647
|
21
|
|
|
|
|
157
|
my $config = $git->{_plugin_githooks}{config}; |
|
648
|
|
|
|
|
|
|
|
|
649
|
21
|
50
|
|
|
|
165
|
$section = lc $section if defined $section; |
|
650
|
21
|
50
|
|
|
|
249
|
$var = lc $var if defined $var; |
|
651
|
|
|
|
|
|
|
|
|
652
|
21
|
50
|
|
|
|
164
|
if (! defined $section) { |
|
|
|
50
|
|
|
|
|
|
|
|
|
50
|
|
|
|
|
|
|
653
|
0
|
|
|
|
|
0
|
return $config; |
|
654
|
|
|
|
|
|
|
} elsif (! defined $var) { |
|
655
|
0
|
0
|
|
|
|
0
|
$config->{$section} = {} unless exists $config->{$section}; |
|
656
|
0
|
|
|
|
|
0
|
return $config->{$section}; |
|
657
|
|
|
|
|
|
|
} elsif (exists $config->{$section}{$var}) { |
|
658
|
0
|
0
|
|
|
|
0
|
if (wantarray) { |
|
659
|
|
|
|
|
|
|
$log->trace(get_config => { |
|
660
|
|
|
|
|
|
|
wantarray => 1, |
|
661
|
|
|
|
|
|
|
section => $section, |
|
662
|
|
|
|
|
|
|
var => $var, |
|
663
|
0
|
|
|
|
|
0
|
result => $config->{$section}{$var}, |
|
664
|
|
|
|
|
|
|
}); |
|
665
|
0
|
|
|
|
|
0
|
return @{$config->{$section}{$var}}; |
|
|
0
|
|
|
|
|
0
|
|
|
666
|
|
|
|
|
|
|
} else { |
|
667
|
|
|
|
|
|
|
$log->trace(get_config => { |
|
668
|
|
|
|
|
|
|
wantarray => 0, |
|
669
|
|
|
|
|
|
|
section => $section, |
|
670
|
|
|
|
|
|
|
var => $var, |
|
671
|
0
|
|
|
|
|
0
|
result => $config->{$section}{$var}[-1], |
|
672
|
|
|
|
|
|
|
}); |
|
673
|
0
|
|
|
|
|
0
|
return $config->{$section}{$var}[-1]; |
|
674
|
|
|
|
|
|
|
} |
|
675
|
|
|
|
|
|
|
} else { |
|
676
|
21
|
|
|
|
|
930
|
$log->trace(get_config => { |
|
677
|
|
|
|
|
|
|
wantarray => wantarray, |
|
678
|
|
|
|
|
|
|
section => $section, |
|
679
|
|
|
|
|
|
|
var => $var, |
|
680
|
|
|
|
|
|
|
result => [], |
|
681
|
|
|
|
|
|
|
}); |
|
682
|
21
|
|
|
|
|
522
|
return; |
|
683
|
|
|
|
|
|
|
} |
|
684
|
|
|
|
|
|
|
} |
|
685
|
|
|
|
|
|
|
|
|
686
|
|
|
|
|
|
|
sub get_config_boolean { |
|
687
|
0
|
|
|
0
|
1
|
0
|
my ($git, $section, $var) = @_; |
|
688
|
|
|
|
|
|
|
|
|
689
|
0
|
|
|
|
|
0
|
my $bool = $git->get_config($section, $var); |
|
690
|
|
|
|
|
|
|
|
|
691
|
0
|
0
|
|
|
|
0
|
if (! defined $bool) { |
|
|
|
0
|
|
|
|
|
|
|
|
|
0
|
|
|
|
|
|
|
|
|
0
|
|
|
|
|
|
|
692
|
0
|
|
|
|
|
0
|
return; |
|
693
|
|
|
|
|
|
|
} elsif (ref $bool) { |
|
694
|
0
|
|
|
|
|
0
|
croak __PACKAGE__, ": get_bool_config method requires two arguments\n"; |
|
695
|
|
|
|
|
|
|
} elsif ($bool =~ /^(?:yes|on|true|1)$/i) { |
|
696
|
0
|
|
|
|
|
0
|
return 1; |
|
697
|
|
|
|
|
|
|
} elsif ($bool =~ /^(?:no|off|false|0|)$/i) { |
|
698
|
0
|
|
|
|
|
0
|
return 0; |
|
699
|
|
|
|
|
|
|
} else { |
|
700
|
0
|
|
|
|
|
0
|
croak __PACKAGE__, ": get_config_boolean($section, $var) not a valid boolean: '$bool'\n"; |
|
701
|
|
|
|
|
|
|
} |
|
702
|
|
|
|
|
|
|
} |
|
703
|
|
|
|
|
|
|
|
|
704
|
|
|
|
|
|
|
sub get_config_integer { |
|
705
|
0
|
|
|
0
|
1
|
0
|
my ($git, $section, $var) = @_; |
|
706
|
|
|
|
|
|
|
|
|
707
|
0
|
|
|
|
|
0
|
my $int = $git->get_config($section, $var); |
|
708
|
|
|
|
|
|
|
|
|
709
|
0
|
0
|
|
|
|
0
|
if (! defined $int) { |
|
|
|
0
|
|
|
|
|
|
|
|
|
0
|
|
|
|
|
|
|
710
|
0
|
|
|
|
|
0
|
return; |
|
711
|
|
|
|
|
|
|
} elsif (ref $int) { |
|
712
|
0
|
|
|
|
|
0
|
croak __PACKAGE__, ": get_config_integer() requires two arguments\n"; |
|
713
|
|
|
|
|
|
|
} elsif ($int =~ /^([+-]?)([0-9]+)([kmg]?)$/i) { |
|
714
|
0
|
|
|
|
|
0
|
my ($signal, $num, $unit) = ($1, $2, lc $3); |
|
715
|
0
|
0
|
|
|
|
0
|
if ($unit) { |
|
716
|
0
|
0
|
|
|
|
0
|
if ($unit eq 'k') { |
|
|
|
0
|
|
|
|
|
|
|
|
|
0
|
|
|
|
|
|
|
717
|
0
|
|
|
|
|
0
|
$num *= 1024; |
|
718
|
|
|
|
|
|
|
} elsif ($unit eq 'm') { |
|
719
|
0
|
|
|
|
|
0
|
$num *= 1024*1024; |
|
720
|
|
|
|
|
|
|
} elsif ($unit eq 'g') { |
|
721
|
0
|
|
|
|
|
0
|
$num *= 1024*1024*1024; |
|
722
|
|
|
|
|
|
|
} |
|
723
|
|
|
|
|
|
|
} |
|
724
|
0
|
0
|
|
|
|
0
|
if ($signal eq '-') { |
|
725
|
0
|
|
|
|
|
0
|
$num *= -1; |
|
726
|
|
|
|
|
|
|
} |
|
727
|
0
|
|
|
|
|
0
|
return $num; |
|
728
|
|
|
|
|
|
|
} else { |
|
729
|
0
|
|
|
|
|
0
|
croak __PACKAGE__, ": get_config_integer($section, $var) not a valid integer: '$int'\n"; |
|
730
|
|
|
|
|
|
|
} |
|
731
|
|
|
|
|
|
|
} |
|
732
|
|
|
|
|
|
|
|
|
733
|
|
|
|
|
|
|
sub _githooks_colors { |
|
734
|
1
|
|
|
1
|
|
3
|
my ($git) = @_; |
|
735
|
|
|
|
|
|
|
|
|
736
|
1
|
|
|
|
|
12
|
my $cache = $git->cache('colors'); |
|
737
|
|
|
|
|
|
|
|
|
738
|
1
|
50
|
|
|
|
7
|
unless (exists $cache->{reset}) { |
|
739
|
|
|
|
|
|
|
# Check if we want to colorize the output, and if so, return a hash |
|
740
|
|
|
|
|
|
|
# containing the default colors. Otherwise, return a hash containing no |
|
741
|
|
|
|
|
|
|
# color codes at all. |
|
742
|
|
|
|
|
|
|
|
|
743
|
|
|
|
|
|
|
# NOTE: We have to pass the TERM environment variable explicitly because |
|
744
|
|
|
|
|
|
|
# Git::Repository's constructor deletes it by default. (Se discussion in |
|
745
|
|
|
|
|
|
|
# https://rt.cpan.org/Ticket/Display.html?id=124711.) |
|
746
|
|
|
|
|
|
|
|
|
747
|
1
|
50
|
|
|
|
16
|
my $stdout_is_tty = is_interactive() ? 'true' : 'false'; |
|
748
|
|
|
|
|
|
|
my $githooks_color = $git->run(qw/config --get-colorbool githooks.color/, $stdout_is_tty, |
|
749
|
1
|
|
|
|
|
35
|
{env => {TERM => $ENV{TERM}}}); |
|
750
|
1
|
50
|
|
|
|
17541
|
if ($githooks_color eq 'true') { |
|
751
|
0
|
|
|
|
|
0
|
$cache->{header} = $git->run(qw/config --get-color githooks.color.header/, 'green'); |
|
752
|
0
|
|
|
|
|
0
|
$cache->{footer} = $git->run(qw/config --get-color githooks.color.footer/, 'green'); |
|
753
|
0
|
|
|
|
|
0
|
$cache->{context} = $git->run(qw/config --get-color githooks.color.context/, 'red bold'); |
|
754
|
0
|
|
|
|
|
0
|
$cache->{message} = $git->run(qw/config --get-color githooks.color.message/, 'yellow'); |
|
755
|
0
|
|
|
|
|
0
|
$cache->{details} = ''; |
|
756
|
0
|
|
|
|
|
0
|
$cache->{reset} = $git->run(qw/config --get-color/, '', 'reset'); |
|
757
|
|
|
|
|
|
|
} else { |
|
758
|
1
|
|
|
|
|
13
|
$cache->{header} = ''; |
|
759
|
1
|
|
|
|
|
12
|
$cache->{footer} = ''; |
|
760
|
1
|
|
|
|
|
10
|
$cache->{context} = ''; |
|
761
|
1
|
|
|
|
|
8
|
$cache->{message} = ''; |
|
762
|
1
|
|
|
|
|
7
|
$cache->{details} = ''; |
|
763
|
1
|
|
|
|
|
12
|
$cache->{reset} = ''; |
|
764
|
|
|
|
|
|
|
} |
|
765
|
|
|
|
|
|
|
} |
|
766
|
|
|
|
|
|
|
|
|
767
|
1
|
|
|
|
|
18
|
return $cache; |
|
768
|
|
|
|
|
|
|
} |
|
769
|
|
|
|
|
|
|
|
|
770
|
|
|
|
|
|
|
sub check_timeout { |
|
771
|
0
|
|
|
0
|
1
|
0
|
my ($git) = @_; |
|
772
|
|
|
|
|
|
|
|
|
773
|
0
|
|
|
|
|
0
|
my $cache = $git->cache('timeout'); |
|
774
|
|
|
|
|
|
|
|
|
775
|
0
|
|
|
|
|
0
|
$cache->{timeout} = $git->get_config_integer(githooks => 'timeout'); |
|
776
|
|
|
|
|
|
|
|
|
777
|
0
|
0
|
|
|
|
0
|
return unless $cache->{timeout}; |
|
778
|
|
|
|
|
|
|
|
|
779
|
0
|
|
|
|
|
0
|
$cache->{start_time} = time; |
|
780
|
|
|
|
|
|
|
|
|
781
|
0
|
|
|
|
|
0
|
my $now = time; |
|
782
|
|
|
|
|
|
|
|
|
783
|
0
|
0
|
|
|
|
0
|
if (($now - $cache->{start_time}) >= $cache->{timeout}) { |
|
784
|
0
|
|
|
|
|
0
|
$git->fault("Hook timeout"); |
|
785
|
0
|
|
|
|
|
0
|
$git->fail_on_faults(); |
|
786
|
|
|
|
|
|
|
} |
|
787
|
|
|
|
|
|
|
|
|
788
|
0
|
|
|
|
|
0
|
return; |
|
789
|
|
|
|
|
|
|
} |
|
790
|
|
|
|
|
|
|
|
|
791
|
|
|
|
|
|
|
sub fault { |
|
792
|
1
|
|
|
1
|
1
|
6
|
my ($git, $message, $info) = @_; |
|
793
|
1
|
|
50
|
|
|
4
|
$info //= {}; |
|
794
|
|
|
|
|
|
|
|
|
795
|
1
|
|
|
|
|
4
|
my $colors = _githooks_colors($git); |
|
796
|
|
|
|
|
|
|
|
|
797
|
1
|
|
|
|
|
3
|
my $msg; |
|
798
|
|
|
|
|
|
|
|
|
799
|
|
|
|
|
|
|
{ |
|
800
|
1
|
|
33
|
|
|
2
|
my $prefix = $info->{prefix} || caller; |
|
|
1
|
|
|
|
|
20
|
|
|
801
|
1
|
|
|
|
|
4
|
my @context; |
|
802
|
1
|
50
|
|
|
|
10
|
if (my $commit = $info->{commit}) { |
|
803
|
0
|
0
|
|
|
|
0
|
$commit = $commit->commit |
|
804
|
|
|
|
|
|
|
if ref $commit; # It's a Git::Repository::Log object |
|
805
|
0
|
0
|
|
|
|
0
|
$commit = $git->run('rev-parse', '--short', $commit) |
|
806
|
|
|
|
|
|
|
if $commit =~ /^[0-9a-f]{40}$/; # It can be '' or ':0' sometimes |
|
807
|
0
|
|
|
|
|
0
|
push @context, "commit $commit"; |
|
808
|
|
|
|
|
|
|
} |
|
809
|
1
|
50
|
|
|
|
12
|
if (my $ref = $info->{ref}) { |
|
810
|
0
|
|
|
|
|
0
|
push @context, "on ref $ref"; |
|
811
|
|
|
|
|
|
|
} |
|
812
|
1
|
50
|
|
|
|
11
|
if (my $option = $info->{option}) { |
|
813
|
1
|
|
|
|
|
7
|
push @context, "violates option '$option'"; |
|
814
|
|
|
|
|
|
|
} |
|
815
|
1
|
|
|
|
|
5
|
$msg = "$colors->{context}\[$prefix"; |
|
816
|
1
|
50
|
|
|
|
11
|
$msg .= ': ' . join(' ', @context) if @context; |
|
817
|
1
|
|
|
|
|
10
|
$msg .= "]$colors->{reset}\n"; |
|
818
|
|
|
|
|
|
|
} |
|
819
|
|
|
|
|
|
|
|
|
820
|
1
|
|
|
|
|
13
|
chomp $message; # strip trailing newlines |
|
821
|
1
|
|
|
|
|
7
|
$msg .= "\n$colors->{message}$message$colors->{reset}\n"; |
|
822
|
|
|
|
|
|
|
|
|
823
|
1
|
50
|
|
|
|
5
|
if (my $details = $info->{details}) { |
|
824
|
1
|
|
|
|
|
54
|
$details =~ s/\n*$//s; # strip trailing newlines |
|
825
|
1
|
|
|
|
|
16
|
$details =~ s/^/ /gm; # prefix each line with two spaces |
|
826
|
1
|
|
|
|
|
7
|
$msg .= "\n$colors->{details}$details$colors->{reset}\n\n"; |
|
827
|
|
|
|
|
|
|
} |
|
828
|
|
|
|
|
|
|
|
|
829
|
1
|
|
|
|
|
3
|
push @{$git->{_plugin_githooks}{faults}}, $msg; |
|
|
1
|
|
|
|
|
6
|
|
|
830
|
|
|
|
|
|
|
|
|
831
|
|
|
|
|
|
|
# Return true to allow for the idiom: or $git->fault(...) and ; |
|
832
|
1
|
|
|
|
|
12
|
return 1; |
|
833
|
|
|
|
|
|
|
} |
|
834
|
|
|
|
|
|
|
|
|
835
|
|
|
|
|
|
|
sub get_faults { |
|
836
|
0
|
|
|
0
|
1
|
0
|
my ($git) = @_; |
|
837
|
|
|
|
|
|
|
|
|
838
|
0
|
0
|
|
|
|
0
|
return unless exists $git->{_plugin_githooks}{faults}; |
|
839
|
|
|
|
|
|
|
|
|
840
|
0
|
|
|
|
|
0
|
my $colors = _githooks_colors($git); |
|
841
|
|
|
|
|
|
|
|
|
842
|
0
|
|
|
|
|
0
|
my $faults = ''; |
|
843
|
|
|
|
|
|
|
|
|
844
|
0
|
0
|
|
|
|
0
|
if (my $header = $git->get_config(githooks => 'error-header')) { |
|
845
|
0
|
|
|
|
|
0
|
$faults .= $colors->{header} . qx{$header} . "$colors->{reset}\n"; ## no critic (ProhibitBacktickOperators) |
|
846
|
|
|
|
|
|
|
} |
|
847
|
|
|
|
|
|
|
|
|
848
|
0
|
|
|
|
|
0
|
$faults .= join("\n\n", @{$git->{_plugin_githooks}{faults}}); |
|
|
0
|
|
|
|
|
0
|
|
|
849
|
|
|
|
|
|
|
|
|
850
|
0
|
0
|
0
|
|
|
0
|
if ($git->{_plugin_githooks}{hookname} =~ /^commit-msg|pre-commit$/ |
|
851
|
|
|
|
|
|
|
&& ! $git->get_config_boolean(githooks => 'abort-commit')) { |
|
852
|
0
|
|
|
|
|
0
|
$faults .= <<'EOS'; |
|
853
|
|
|
|
|
|
|
|
|
854
|
|
|
|
|
|
|
ATTENTION: To fix the problems in this commit, please consider amending it: |
|
855
|
|
|
|
|
|
|
|
|
856
|
|
|
|
|
|
|
git commit --amend |
|
857
|
|
|
|
|
|
|
EOS |
|
858
|
|
|
|
|
|
|
} |
|
859
|
|
|
|
|
|
|
|
|
860
|
0
|
0
|
|
|
|
0
|
if (my $footer = $git->get_config(githooks => 'error-footer')) { |
|
861
|
0
|
|
|
|
|
0
|
$faults .= "\n$colors->{footer}" . qx{$footer} . "$colors->{reset}\n"; ## no critic (ProhibitBacktickOperators) |
|
862
|
|
|
|
|
|
|
} |
|
863
|
|
|
|
|
|
|
|
|
864
|
0
|
0
|
|
|
|
0
|
if (my $prefix = $git->get_config(githooks => 'error-prefix')) { |
|
865
|
0
|
|
|
|
|
0
|
$faults =~ s/^/$prefix/gm; |
|
866
|
|
|
|
|
|
|
} |
|
867
|
|
|
|
|
|
|
|
|
868
|
0
|
0
|
|
|
|
0
|
if (my $limit = $git->get_config_integer(githooks => 'error-length-limit')) { |
|
869
|
0
|
0
|
0
|
|
|
0
|
if ($limit > 0 && $limit < length($faults)) { |
|
870
|
0
|
|
|
|
|
0
|
my $mark = "\n\n[MESSAGE TRUNCATED at githooks.error-length-limit]\n"; |
|
871
|
0
|
|
|
|
|
0
|
substr($faults, $limit - length($mark) - 1, length($faults), $mark); |
|
872
|
|
|
|
|
|
|
} |
|
873
|
|
|
|
|
|
|
} |
|
874
|
|
|
|
|
|
|
|
|
875
|
0
|
|
|
|
|
0
|
return $faults; |
|
876
|
|
|
|
|
|
|
} |
|
877
|
|
|
|
|
|
|
|
|
878
|
|
|
|
|
|
|
sub fail_on_faults { |
|
879
|
0
|
|
|
0
|
1
|
0
|
my ($git, $warn_only) = @_; |
|
880
|
|
|
|
|
|
|
|
|
881
|
0
|
0
|
|
|
|
0
|
if (my $faults = $git->get_faults()) { |
|
882
|
0
|
|
|
|
|
0
|
$log->debug(Environment => {ENV => \%ENV}); |
|
883
|
0
|
0
|
|
|
|
0
|
$faults .= "\n" unless $faults =~ /\n$/; |
|
884
|
0
|
0
|
|
|
|
0
|
if ($warn_only) { |
|
885
|
0
|
|
|
|
|
0
|
$log->warning(Warning => {faults => $faults}); |
|
886
|
0
|
|
|
|
|
0
|
carp $faults; |
|
887
|
|
|
|
|
|
|
} else { |
|
888
|
0
|
|
|
|
|
0
|
$log->error(Error => {faults => $faults}); |
|
889
|
0
|
|
|
|
|
0
|
croak $faults; |
|
890
|
|
|
|
|
|
|
} |
|
891
|
|
|
|
|
|
|
} |
|
892
|
|
|
|
|
|
|
|
|
893
|
0
|
|
|
|
|
0
|
return; |
|
894
|
|
|
|
|
|
|
} |
|
895
|
|
|
|
|
|
|
|
|
896
|
|
|
|
|
|
|
sub undef_commit { |
|
897
|
0
|
|
|
0
|
1
|
0
|
return '0000000000000000000000000000000000000000'; |
|
898
|
|
|
|
|
|
|
} |
|
899
|
|
|
|
|
|
|
|
|
900
|
|
|
|
|
|
|
sub empty_tree { |
|
901
|
0
|
|
|
0
|
1
|
0
|
return '4b825dc642cb6eb9a060e54bf8d69288fbee4904'; |
|
902
|
|
|
|
|
|
|
} |
|
903
|
|
|
|
|
|
|
|
|
904
|
|
|
|
|
|
|
sub get_commit { |
|
905
|
11
|
|
|
11
|
1
|
565
|
my ($git, $commit) = @_; |
|
906
|
|
|
|
|
|
|
|
|
907
|
11
|
|
|
|
|
208
|
my $cache = $git->cache('commits'); |
|
908
|
|
|
|
|
|
|
|
|
909
|
|
|
|
|
|
|
# $commit may be a symbolic reference, but we only want to cache commits |
|
910
|
|
|
|
|
|
|
# by their SHA1 ids, since the symbolic references may change. |
|
911
|
11
|
50
|
33
|
|
|
74
|
unless ($commit =~ /^[0-9A-F]{40}$/ && exists $cache->{$commit}) { |
|
912
|
11
|
|
|
|
|
247
|
my @commits = $git->log('-1', $commit); |
|
913
|
11
|
|
|
|
|
391681
|
$commit = $commits[0]->{commit}; |
|
914
|
11
|
|
|
|
|
109
|
$cache->{$commit} = $commits[0]; |
|
915
|
|
|
|
|
|
|
} |
|
916
|
|
|
|
|
|
|
|
|
917
|
11
|
|
|
|
|
188
|
return $cache->{$commit}; |
|
918
|
|
|
|
|
|
|
} |
|
919
|
|
|
|
|
|
|
|
|
920
|
|
|
|
|
|
|
sub get_commits { |
|
921
|
0
|
|
|
0
|
1
|
0
|
my ($git, $old_commit, $new_commit, $options, $paths) = @_; |
|
922
|
|
|
|
|
|
|
|
|
923
|
0
|
|
|
|
|
0
|
my $cache = $git->cache('ranges'); |
|
924
|
|
|
|
|
|
|
|
|
925
|
0
|
0
|
|
|
|
0
|
my $range = join( |
|
|
|
0
|
|
|
|
|
|
|
926
|
|
|
|
|
|
|
':', |
|
927
|
|
|
|
|
|
|
$old_commit, |
|
928
|
|
|
|
|
|
|
$new_commit, |
|
929
|
|
|
|
|
|
|
defined $options ? join('', @$options) : '', |
|
930
|
|
|
|
|
|
|
defined $paths ? join('', @$paths) : '', |
|
931
|
|
|
|
|
|
|
); |
|
932
|
|
|
|
|
|
|
|
|
933
|
0
|
0
|
|
|
|
0
|
unless (exists $cache->{$range}) { |
|
934
|
|
|
|
|
|
|
# We're interested in all commits reachable from $new_commit but |
|
935
|
|
|
|
|
|
|
# neither reachable from $old_commit nor from any other existing |
|
936
|
|
|
|
|
|
|
# reference. |
|
937
|
|
|
|
|
|
|
|
|
938
|
|
|
|
|
|
|
# We're going to use the "git rev-list" command for that. As you can |
|
939
|
|
|
|
|
|
|
# read on its documentation, the syntax to specify this set of |
|
940
|
|
|
|
|
|
|
# commits is this: |
|
941
|
|
|
|
|
|
|
# "--not --branches --tags --not $new_commit ^$old_commit". |
|
942
|
|
|
|
|
|
|
|
|
943
|
|
|
|
|
|
|
# However, there are some special cases... |
|
944
|
|
|
|
|
|
|
|
|
945
|
|
|
|
|
|
|
# When an old branch is deleted $new_commit is null (i.e., |
|
946
|
|
|
|
|
|
|
# '0'x40). In this case previous commits are being forgotten and the |
|
947
|
|
|
|
|
|
|
# hooks usually don't need to check them. So, in this situation we |
|
948
|
|
|
|
|
|
|
# simply return an empty list of commits. |
|
949
|
|
|
|
|
|
|
|
|
950
|
0
|
0
|
|
|
|
0
|
return if $new_commit eq $git->undef_commit; |
|
951
|
|
|
|
|
|
|
|
|
952
|
|
|
|
|
|
|
# The @excludes list will contain the arguments to git-log necessary to |
|
953
|
|
|
|
|
|
|
# exclude from $new_commit history all commits already reachable by any |
|
954
|
|
|
|
|
|
|
# other reference. |
|
955
|
0
|
|
|
|
|
0
|
my @excludes; |
|
956
|
|
|
|
|
|
|
|
|
957
|
0
|
0
|
|
|
|
0
|
if ($git->{_plugin_githooks}{hookname} !~ /^post-/) { |
|
958
|
|
|
|
|
|
|
# In pre-* hooks (e.g., pre-receive, update) we can use the '--not |
|
959
|
|
|
|
|
|
|
# --branches --tags' arguments. |
|
960
|
0
|
|
|
|
|
0
|
@excludes = qw/--not --branches --tags --not/; |
|
961
|
|
|
|
|
|
|
} else { |
|
962
|
|
|
|
|
|
|
# When we're called in a post-receive or post-update hook, the |
|
963
|
|
|
|
|
|
|
# pushed references already point to $new_commit. So, in these cases |
|
964
|
|
|
|
|
|
|
# the "--not --branches --tags" options would exclude from the |
|
965
|
|
|
|
|
|
|
# results all commits reachable from $new_commit, which is exactly |
|
966
|
|
|
|
|
|
|
# what we don't want... In order to avoid that we can't use these |
|
967
|
|
|
|
|
|
|
# options directly with git-log. Instead, we use the git-rev-parse |
|
968
|
|
|
|
|
|
|
# command to get a list of all commits directly reachable by |
|
969
|
|
|
|
|
|
|
# existing references. Then we'll see if we have to remove any |
|
970
|
|
|
|
|
|
|
# commit from that list. |
|
971
|
|
|
|
|
|
|
|
|
972
|
0
|
|
|
|
|
0
|
@excludes = $git->run(qw/rev-parse --not --branches --tags/); |
|
973
|
|
|
|
|
|
|
|
|
974
|
|
|
|
|
|
|
# But we can't simply remove $new_commit from @excludes because it |
|
975
|
|
|
|
|
|
|
# can be reachable by other references. This can happen, for |
|
976
|
|
|
|
|
|
|
# instance, when one creates a new branch and pushes it before |
|
977
|
|
|
|
|
|
|
# making any commits to it or when one pushes a branch after a |
|
978
|
|
|
|
|
|
|
# fast-forward merge. So, we only remove it if it's reachable by a |
|
979
|
|
|
|
|
|
|
# single reference, which must be the reference being pushed. |
|
980
|
|
|
|
|
|
|
|
|
981
|
0
|
0
|
|
|
|
0
|
if ($git->version_ge('2.7.0')) { |
|
982
|
|
|
|
|
|
|
# The --points-at option was implemented in this version of Git |
|
983
|
0
|
|
|
|
|
0
|
my @new_commit_refs = $git->run( |
|
984
|
|
|
|
|
|
|
qw/for-each-ref --format %(refname) --count 2 --points-at/, $new_commit, |
|
985
|
|
|
|
|
|
|
); |
|
986
|
0
|
0
|
|
|
|
0
|
if (@new_commit_refs == 1) { |
|
987
|
0
|
|
|
|
|
0
|
@excludes = grep {$_ ne "^$new_commit"} @excludes; |
|
|
0
|
|
|
|
|
0
|
|
|
988
|
|
|
|
|
|
|
} |
|
989
|
|
|
|
|
|
|
} else { |
|
990
|
|
|
|
|
|
|
# KLUDGE: I couldn't find a direct way to see how many refs |
|
991
|
|
|
|
|
|
|
# point to $new_commit in older Gits. So, I use the porcelain |
|
992
|
|
|
|
|
|
|
# git-log command with a format that shows the decoration for a |
|
993
|
|
|
|
|
|
|
# single commit, which returns something like: (HEAD -> next, |
|
994
|
|
|
|
|
|
|
# tag: v2.2.0, origin/next) |
|
995
|
0
|
|
|
|
|
0
|
my $decoration = $git->run(qw/log -n1 --format=%d/, $new_commit); |
|
996
|
0
|
|
|
|
|
0
|
$decoration =~ s/HEAD,\s*//; |
|
997
|
|
|
|
|
|
|
|
|
998
|
|
|
|
|
|
|
# If there are commas in $decoration it means that there are |
|
999
|
|
|
|
|
|
|
# more than one reference. |
|
1000
|
0
|
0
|
|
|
|
0
|
if ($decoration !~ /,/) { |
|
1001
|
0
|
|
|
|
|
0
|
@excludes = grep {$_ ne "^$new_commit"} @excludes; |
|
|
0
|
|
|
|
|
0
|
|
|
1002
|
|
|
|
|
|
|
} |
|
1003
|
|
|
|
|
|
|
} |
|
1004
|
|
|
|
|
|
|
|
|
1005
|
|
|
|
|
|
|
# And we have to make sure $old_commit is on the list, as --not |
|
1006
|
|
|
|
|
|
|
# --branches --tags wouldn't bring it when we're being called in a |
|
1007
|
|
|
|
|
|
|
# post-receive or post-update hook. |
|
1008
|
|
|
|
|
|
|
|
|
1009
|
0
|
0
|
|
|
|
0
|
push @excludes, "^$old_commit" unless $old_commit eq $git->undef_commit; |
|
1010
|
|
|
|
|
|
|
} |
|
1011
|
|
|
|
|
|
|
|
|
1012
|
0
|
|
|
|
|
0
|
my @arguments; |
|
1013
|
|
|
|
|
|
|
|
|
1014
|
0
|
0
|
|
|
|
0
|
push @arguments, @$options if defined $options; |
|
1015
|
0
|
|
|
|
|
0
|
push @arguments, @excludes, $new_commit; |
|
1016
|
0
|
0
|
|
|
|
0
|
push @arguments, '--', @$paths if defined $paths; |
|
1017
|
|
|
|
|
|
|
|
|
1018
|
0
|
|
|
|
|
0
|
$cache->{$range} = [$git->log(@arguments)]; |
|
1019
|
|
|
|
|
|
|
} |
|
1020
|
|
|
|
|
|
|
|
|
1021
|
0
|
|
|
|
|
0
|
return @{$cache->{$range}}; |
|
|
0
|
|
|
|
|
0
|
|
|
1022
|
|
|
|
|
|
|
} |
|
1023
|
|
|
|
|
|
|
|
|
1024
|
|
|
|
|
|
|
sub read_commit_msg_file { |
|
1025
|
17
|
|
|
17
|
1
|
81
|
my ($git, $msgfile) = @_; |
|
1026
|
|
|
|
|
|
|
|
|
1027
|
17
|
|
50
|
|
|
315
|
my $encoding = $git->get_config(i18n => 'commitEncoding') || 'utf-8'; |
|
1028
|
|
|
|
|
|
|
|
|
1029
|
17
|
|
|
|
|
210
|
my $msg = path($msgfile)->slurp({binmode => ":encoding($encoding)"}); |
|
1030
|
|
|
|
|
|
|
|
|
1031
|
|
|
|
|
|
|
# Truncate the message just before the diff, if any. |
|
1032
|
17
|
|
|
|
|
27757
|
$msg =~ s:\ndiff --git .*::s; |
|
1033
|
|
|
|
|
|
|
|
|
1034
|
|
|
|
|
|
|
# The comments in the following lines were taken from the "git |
|
1035
|
|
|
|
|
|
|
# help stripspace" documentation to guide the |
|
1036
|
|
|
|
|
|
|
# implementation. Previously we invoked the "git stripspace -s" |
|
1037
|
|
|
|
|
|
|
# external command via Git::command_bidi_pipe to do the cleaning |
|
1038
|
|
|
|
|
|
|
# but it seems that it doesn't work on FreeBSD. So, we reimplement |
|
1039
|
|
|
|
|
|
|
# its functionality here. |
|
1040
|
|
|
|
|
|
|
|
|
1041
|
17
|
|
|
|
|
79
|
for ($msg) { |
|
1042
|
|
|
|
|
|
|
# Skip and remove all lines starting with comment character |
|
1043
|
|
|
|
|
|
|
# (default #). |
|
1044
|
17
|
|
|
|
|
77
|
s/^#.*//gm; |
|
1045
|
|
|
|
|
|
|
|
|
1046
|
|
|
|
|
|
|
# remove trailing whitespace from all lines |
|
1047
|
17
|
|
|
|
|
254
|
s/[ \t\f]+$//gm; |
|
1048
|
|
|
|
|
|
|
|
|
1049
|
|
|
|
|
|
|
# collapse multiple consecutive empty lines into one empty line |
|
1050
|
17
|
|
|
|
|
164
|
s/\n{3,}/\n\n/gs; |
|
1051
|
|
|
|
|
|
|
|
|
1052
|
|
|
|
|
|
|
# remove empty lines from the beginning and end of the input |
|
1053
|
|
|
|
|
|
|
# add a missing \n to the last line if necessary. |
|
1054
|
17
|
|
|
|
|
69
|
s/^\n+//s; |
|
1055
|
17
|
|
|
|
|
438
|
s/\n*$/\n/s; |
|
1056
|
|
|
|
|
|
|
|
|
1057
|
|
|
|
|
|
|
# In the case where the input consists entirely of whitespace |
|
1058
|
|
|
|
|
|
|
# characters, no output will be produced. |
|
1059
|
17
|
|
|
|
|
105
|
s/^\s+$//s; |
|
1060
|
|
|
|
|
|
|
} |
|
1061
|
|
|
|
|
|
|
|
|
1062
|
17
|
|
|
|
|
101
|
return $msg; |
|
1063
|
|
|
|
|
|
|
} |
|
1064
|
|
|
|
|
|
|
|
|
1065
|
|
|
|
|
|
|
sub write_commit_msg_file { |
|
1066
|
2
|
|
|
2
|
1
|
30
|
my ($git, $msgfile, @msg) = @_; |
|
1067
|
|
|
|
|
|
|
|
|
1068
|
2
|
|
50
|
|
|
36
|
my $encoding = $git->get_config(i18n => 'commitEncoding') || 'utf-8'; |
|
1069
|
|
|
|
|
|
|
|
|
1070
|
2
|
|
|
|
|
41
|
path($msgfile)->spew({binmode => ":encoding($encoding)"}, @msg); |
|
1071
|
|
|
|
|
|
|
|
|
1072
|
2
|
|
|
|
|
2710
|
return; |
|
1073
|
|
|
|
|
|
|
} |
|
1074
|
|
|
|
|
|
|
|
|
1075
|
|
|
|
|
|
|
# Internal funtion to set the affected references in an update or |
|
1076
|
|
|
|
|
|
|
# pre-receive hook. |
|
1077
|
|
|
|
|
|
|
|
|
1078
|
|
|
|
|
|
|
sub _set_affected_ref { |
|
1079
|
0
|
|
|
0
|
|
0
|
my ($git, $ref, $old_commit, $new_commit) = @_; |
|
1080
|
0
|
|
|
|
|
0
|
$git->{_plugin_githooks}{affected_refs}{$ref}{range} = [$old_commit, $new_commit]; |
|
1081
|
0
|
|
|
|
|
0
|
return; |
|
1082
|
|
|
|
|
|
|
} |
|
1083
|
|
|
|
|
|
|
|
|
1084
|
|
|
|
|
|
|
# internal method |
|
1085
|
|
|
|
|
|
|
sub _get_affected_refs_hash { |
|
1086
|
0
|
|
|
0
|
|
0
|
my ($git) = @_; |
|
1087
|
|
|
|
|
|
|
|
|
1088
|
0
|
|
0
|
|
|
0
|
return $git->{_plugin_githooks}{affected_refs} || {}; |
|
1089
|
|
|
|
|
|
|
} |
|
1090
|
|
|
|
|
|
|
|
|
1091
|
|
|
|
|
|
|
sub get_affected_refs { |
|
1092
|
0
|
|
|
0
|
1
|
0
|
my ($git) = @_; |
|
1093
|
|
|
|
|
|
|
|
|
1094
|
0
|
|
|
|
|
0
|
return keys %{_get_affected_refs_hash($git)}; |
|
|
0
|
|
|
|
|
0
|
|
|
1095
|
|
|
|
|
|
|
} |
|
1096
|
|
|
|
|
|
|
|
|
1097
|
|
|
|
|
|
|
sub get_affected_ref_range { |
|
1098
|
0
|
|
|
0
|
1
|
0
|
my ($git, $ref) = @_; |
|
1099
|
|
|
|
|
|
|
|
|
1100
|
0
|
|
|
|
|
0
|
my $affected = _get_affected_refs_hash($git); |
|
1101
|
|
|
|
|
|
|
|
|
1102
|
|
|
|
|
|
|
exists $affected->{$ref}{range} |
|
1103
|
0
|
0
|
|
|
|
0
|
or croak __PACKAGE__, ": get_affected_ref_range($ref): no such affected ref\n"; |
|
1104
|
|
|
|
|
|
|
|
|
1105
|
0
|
|
|
|
|
0
|
return @{$affected->{$ref}{range}}; |
|
|
0
|
|
|
|
|
0
|
|
|
1106
|
|
|
|
|
|
|
} |
|
1107
|
|
|
|
|
|
|
|
|
1108
|
|
|
|
|
|
|
sub get_affected_ref_commits { |
|
1109
|
0
|
|
|
0
|
1
|
0
|
my ($git, $ref, $options, $paths) = @_; |
|
1110
|
|
|
|
|
|
|
|
|
1111
|
0
|
|
|
|
|
0
|
return $git->get_commits($git->get_affected_ref_range($ref), $options, $paths); |
|
1112
|
|
|
|
|
|
|
} |
|
1113
|
|
|
|
|
|
|
|
|
1114
|
|
|
|
|
|
|
sub filter_name_status_in_index { |
|
1115
|
0
|
|
|
0
|
1
|
0
|
my ($git, $filter) = @_; |
|
1116
|
|
|
|
|
|
|
|
|
1117
|
0
|
|
|
|
|
0
|
my %actions; |
|
1118
|
|
|
|
|
|
|
|
|
1119
|
0
|
|
|
|
|
0
|
my $output = $git->run( |
|
1120
|
|
|
|
|
|
|
qw/diff-index --name-status --ignore-submodules --no-commit-id --cached -r -z/, |
|
1121
|
|
|
|
|
|
|
"--diff-filter=$filter", |
|
1122
|
|
|
|
|
|
|
$git->get_head_or_empty_tree(), |
|
1123
|
|
|
|
|
|
|
); |
|
1124
|
|
|
|
|
|
|
|
|
1125
|
0
|
|
|
|
|
0
|
my @output = split /\0/, $output; |
|
1126
|
0
|
|
|
|
|
0
|
while (@output >= 2) { |
|
1127
|
0
|
|
|
|
|
0
|
my ($action, $file) = splice @output, 0, 2; |
|
1128
|
0
|
|
|
|
|
0
|
$actions{$file} = $action; |
|
1129
|
|
|
|
|
|
|
} |
|
1130
|
|
|
|
|
|
|
|
|
1131
|
0
|
|
|
|
|
0
|
return \%actions; |
|
1132
|
|
|
|
|
|
|
} |
|
1133
|
|
|
|
|
|
|
|
|
1134
|
|
|
|
|
|
|
sub filter_name_status_in_range { |
|
1135
|
0
|
|
|
0
|
1
|
0
|
my ($git, $filter, $from, $to, $options, $paths) = @_; |
|
1136
|
|
|
|
|
|
|
|
|
1137
|
|
|
|
|
|
|
# If $to is the undefined commit this means that a branch or tag is being |
|
1138
|
|
|
|
|
|
|
# removed. In this situation we return the empty list, bacause no file |
|
1139
|
|
|
|
|
|
|
# has been affected. |
|
1140
|
0
|
0
|
|
|
|
0
|
return {} if $to eq $git->undef_commit; |
|
1141
|
|
|
|
|
|
|
|
|
1142
|
0
|
0
|
|
|
|
0
|
if ($from eq $git->undef_commit) { |
|
1143
|
|
|
|
|
|
|
# If $from is the undefined commit we get the list of commits |
|
1144
|
|
|
|
|
|
|
# reachable from $to and not reachable from $from and all other |
|
1145
|
|
|
|
|
|
|
# references. This list is in chronological order. We want to grok |
|
1146
|
|
|
|
|
|
|
# the files changed from the list's first commit's PARENT commit to |
|
1147
|
|
|
|
|
|
|
# the list's last commit. |
|
1148
|
|
|
|
|
|
|
|
|
1149
|
0
|
0
|
|
|
|
0
|
if (my @commits = $git->get_commits($from, $to, $options, $paths)) { |
|
1150
|
0
|
0
|
|
|
|
0
|
if (my @parents = $commits[0]->parent()) { |
|
1151
|
0
|
|
|
|
|
0
|
$from = $parents[0]; |
|
1152
|
|
|
|
|
|
|
} else { |
|
1153
|
|
|
|
|
|
|
# If the list's first commit has no parent (i.e., it's a root |
|
1154
|
|
|
|
|
|
|
# commit) then we return the empty hash because git-diff-tree |
|
1155
|
|
|
|
|
|
|
# cannot compare the undefined commit with a commit. |
|
1156
|
0
|
|
|
|
|
0
|
return {}; |
|
1157
|
|
|
|
|
|
|
} |
|
1158
|
|
|
|
|
|
|
} else { |
|
1159
|
|
|
|
|
|
|
# If @commits is empty we return an empty hash because no new commit |
|
1160
|
|
|
|
|
|
|
# was pushed. |
|
1161
|
0
|
|
|
|
|
0
|
return {}; |
|
1162
|
|
|
|
|
|
|
} |
|
1163
|
|
|
|
|
|
|
} |
|
1164
|
|
|
|
|
|
|
|
|
1165
|
0
|
|
|
|
|
0
|
my %actions; |
|
1166
|
|
|
|
|
|
|
|
|
1167
|
0
|
|
|
|
|
0
|
my $output = $git->run( |
|
1168
|
|
|
|
|
|
|
qw/diff-tree --name-status --ignore-submodules --no-commit-id -r -z/, |
|
1169
|
|
|
|
|
|
|
"--diff-filter=$filter", |
|
1170
|
|
|
|
|
|
|
$from, $to, '--', |
|
1171
|
|
|
|
|
|
|
); |
|
1172
|
|
|
|
|
|
|
|
|
1173
|
0
|
|
|
|
|
0
|
my @output = split /\0/, $output; |
|
1174
|
0
|
|
|
|
|
0
|
while (@output >= 2) { |
|
1175
|
0
|
|
|
|
|
0
|
my ($action, $file) = splice @output, 0, 2; |
|
1176
|
0
|
|
|
|
|
0
|
$actions{$file} = $action; |
|
1177
|
|
|
|
|
|
|
} |
|
1178
|
|
|
|
|
|
|
|
|
1179
|
0
|
|
|
|
|
0
|
return \%actions; |
|
1180
|
|
|
|
|
|
|
} |
|
1181
|
|
|
|
|
|
|
|
|
1182
|
|
|
|
|
|
|
sub filter_name_status_in_commit { |
|
1183
|
0
|
|
|
0
|
1
|
0
|
my ($git, $filter, $commit) = @_; |
|
1184
|
|
|
|
|
|
|
|
|
1185
|
0
|
|
|
|
|
0
|
my $output = $git->run( |
|
1186
|
|
|
|
|
|
|
qw/diff-tree --name-status --ignore-submodules -m -r -z/, |
|
1187
|
|
|
|
|
|
|
"--diff-filter=$filter", |
|
1188
|
|
|
|
|
|
|
$commit, |
|
1189
|
|
|
|
|
|
|
); |
|
1190
|
|
|
|
|
|
|
|
|
1191
|
0
|
|
|
|
|
0
|
my @output = split /\0/, $output; |
|
1192
|
|
|
|
|
|
|
|
|
1193
|
|
|
|
|
|
|
# @output is a sequence of commits, actions, and files, with the following |
|
1194
|
|
|
|
|
|
|
# general pattern: { COMMIT { ACTION FILE }* }+, |
|
1195
|
|
|
|
|
|
|
|
|
1196
|
|
|
|
|
|
|
# COMMIT is the parent commit of $commit. There can be more than one if |
|
1197
|
|
|
|
|
|
|
# $commit is a merge commit. |
|
1198
|
|
|
|
|
|
|
|
|
1199
|
|
|
|
|
|
|
# Below we parse the sequence, tucking all the information in %actions. |
|
1200
|
|
|
|
|
|
|
|
|
1201
|
0
|
|
|
|
|
0
|
my %actions; |
|
1202
|
|
|
|
|
|
|
|
|
1203
|
|
|
|
|
|
|
my $sha1; |
|
1204
|
0
|
|
|
|
|
0
|
my $action; |
|
1205
|
0
|
|
|
|
|
0
|
my $parents = 0; |
|
1206
|
0
|
|
|
|
|
0
|
my $expect = 'sha1'; |
|
1207
|
|
|
|
|
|
|
|
|
1208
|
|
|
|
|
|
|
# PARSE @output |
|
1209
|
0
|
|
|
|
|
0
|
while (@output) { |
|
1210
|
0
|
0
|
|
|
|
0
|
if ($expect eq 'sha1') { |
|
|
|
0
|
|
|
|
|
|
|
|
|
0
|
|
|
|
|
|
|
1211
|
0
|
0
|
|
|
|
0
|
if ($output[0] =~ /^[0-9a-f]{40}$/) { |
|
1212
|
0
|
|
|
|
|
0
|
$sha1 = shift @output; |
|
1213
|
0
|
|
|
|
|
0
|
++$parents; |
|
1214
|
0
|
|
|
|
|
0
|
$expect = 'sha1 or action'; |
|
1215
|
|
|
|
|
|
|
} else { |
|
1216
|
0
|
|
|
|
|
0
|
croak; |
|
1217
|
|
|
|
|
|
|
} |
|
1218
|
|
|
|
|
|
|
} elsif ($expect eq 'sha1 or action') { |
|
1219
|
0
|
0
|
|
|
|
0
|
if ($output[0] =~ /^[0-9a-f]{40}$/) { |
|
|
|
0
|
|
|
|
|
|
|
1220
|
0
|
|
|
|
|
0
|
$sha1 = shift @output; |
|
1221
|
0
|
|
|
|
|
0
|
++$parents; |
|
1222
|
|
|
|
|
|
|
} elsif ($output[0] =~ /^[A-Z]$/) { |
|
1223
|
0
|
|
|
|
|
0
|
$action = shift @output; |
|
1224
|
0
|
|
|
|
|
0
|
$expect = 'file'; |
|
1225
|
|
|
|
|
|
|
} else { |
|
1226
|
0
|
|
|
|
|
0
|
croak; |
|
1227
|
|
|
|
|
|
|
} |
|
1228
|
|
|
|
|
|
|
} elsif ($expect eq 'file') { |
|
1229
|
0
|
|
|
|
|
0
|
$actions{shift @output}{$sha1} = $action; |
|
1230
|
0
|
|
|
|
|
0
|
$expect = 'sha1 or action'; |
|
1231
|
|
|
|
|
|
|
} else { |
|
1232
|
0
|
|
|
|
|
0
|
croak; |
|
1233
|
|
|
|
|
|
|
} |
|
1234
|
|
|
|
|
|
|
} |
|
1235
|
|
|
|
|
|
|
|
|
1236
|
|
|
|
|
|
|
# %actions is a multi-level hash: $actions{$file}{$sha1} = $action. Next |
|
1237
|
|
|
|
|
|
|
# we remove the $commit level, joining all $actions together under $file. |
|
1238
|
|
|
|
|
|
|
|
|
1239
|
0
|
|
|
|
|
0
|
foreach my $file (keys %actions) { |
|
1240
|
0
|
0
|
|
|
|
0
|
if (keys(%{$actions{$file}}) == $parents) { |
|
|
0
|
|
|
|
|
0
|
|
|
1241
|
|
|
|
|
|
|
# For merge commits we're interested only in files that were |
|
1242
|
|
|
|
|
|
|
# affected in all parent commits. For files affected in all parents |
|
1243
|
|
|
|
|
|
|
# we join their actions together. Non-merge commits ($parents == 1) |
|
1244
|
|
|
|
|
|
|
# reduce to the general case of merge commits. |
|
1245
|
0
|
|
|
|
|
0
|
$actions{$file} = join('', values %{$actions{$file}}); |
|
|
0
|
|
|
|
|
0
|
|
|
1246
|
|
|
|
|
|
|
} else { |
|
1247
|
|
|
|
|
|
|
# Files not affected in all parents we don't care about. |
|
1248
|
0
|
|
|
|
|
0
|
delete $actions{$file}; |
|
1249
|
|
|
|
|
|
|
} |
|
1250
|
|
|
|
|
|
|
} |
|
1251
|
|
|
|
|
|
|
|
|
1252
|
0
|
|
|
|
|
0
|
return \%actions; |
|
1253
|
|
|
|
|
|
|
} |
|
1254
|
|
|
|
|
|
|
|
|
1255
|
|
|
|
|
|
|
sub filter_files_in_index { |
|
1256
|
0
|
|
|
0
|
1
|
0
|
my ($git, $filter) = @_; |
|
1257
|
0
|
|
|
|
|
0
|
my @files = sort keys %{$git->filter_name_status_in_index($filter)}; |
|
|
0
|
|
|
|
|
0
|
|
|
1258
|
0
|
|
|
|
|
0
|
return @files; |
|
1259
|
|
|
|
|
|
|
} |
|
1260
|
|
|
|
|
|
|
|
|
1261
|
|
|
|
|
|
|
sub filter_files_in_range { |
|
1262
|
0
|
|
|
0
|
1
|
0
|
my ($git, @args) = @_; |
|
1263
|
0
|
|
|
|
|
0
|
my @files = sort keys %{$git->filter_name_status_in_range(@args)}; |
|
|
0
|
|
|
|
|
0
|
|
|
1264
|
0
|
|
|
|
|
0
|
return @files; |
|
1265
|
|
|
|
|
|
|
} |
|
1266
|
|
|
|
|
|
|
|
|
1267
|
|
|
|
|
|
|
sub filter_files_in_commit { |
|
1268
|
0
|
|
|
0
|
1
|
0
|
my ($git, $commit) = @_; |
|
1269
|
0
|
|
|
|
|
0
|
my @files = sort keys %{$git->filter_name_status_in_commit($commit)}; |
|
|
0
|
|
|
|
|
0
|
|
|
1270
|
0
|
|
|
|
|
0
|
return @files; |
|
1271
|
|
|
|
|
|
|
} |
|
1272
|
|
|
|
|
|
|
|
|
1273
|
|
|
|
|
|
|
sub authenticated_user { |
|
1274
|
1
|
|
|
1
|
1
|
21478
|
my ($git) = @_; |
|
1275
|
|
|
|
|
|
|
|
|
1276
|
1
|
50
|
|
|
|
54
|
unless (exists $git->{_plugin_githooks}{authenticated_user}) { |
|
1277
|
1
|
50
|
|
|
|
32
|
if (my $userenv = $git->get_config(githooks => 'userenv')) { |
|
1278
|
0
|
0
|
|
|
|
0
|
if ($userenv =~ /^eval:(.*)/) { |
|
|
|
0
|
|
|
|
|
|
|
1279
|
0
|
|
|
|
|
0
|
$git->{_plugin_githooks}{authenticated_user} = eval $1; ## no critic (BuiltinFunctions::ProhibitStringyEval) |
|
1280
|
0
|
0
|
|
|
|
0
|
croak __PACKAGE__, ": error evaluating userenv value ($userenv): $@\n" |
|
1281
|
|
|
|
|
|
|
if $@; |
|
1282
|
|
|
|
|
|
|
} elsif (exists $ENV{$userenv}) { |
|
1283
|
0
|
|
|
|
|
0
|
$git->{_plugin_githooks}{authenticated_user} = $ENV{$userenv}; |
|
1284
|
|
|
|
|
|
|
} else { |
|
1285
|
0
|
|
|
|
|
0
|
croak __PACKAGE__, ": option userenv environment variable ($userenv) is not defined.\n"; |
|
1286
|
|
|
|
|
|
|
} |
|
1287
|
|
|
|
|
|
|
} else { |
|
1288
|
1
|
|
50
|
|
|
59
|
$git->{_plugin_githooks}{authenticated_user} = $ENV{GERRIT_USER_EMAIL} || $ENV{BB_USER_NAME} || $ENV{GL_USERNAME} || $ENV{USER} || undef; |
|
1289
|
|
|
|
|
|
|
} |
|
1290
|
|
|
|
|
|
|
} |
|
1291
|
|
|
|
|
|
|
|
|
1292
|
1
|
|
|
|
|
54
|
return $git->{_plugin_githooks}{authenticated_user}; |
|
1293
|
|
|
|
|
|
|
} |
|
1294
|
|
|
|
|
|
|
|
|
1295
|
|
|
|
|
|
|
sub repository_name { |
|
1296
|
0
|
|
|
0
|
1
|
0
|
my ($git) = @_; |
|
1297
|
|
|
|
|
|
|
|
|
1298
|
0
|
0
|
|
|
|
0
|
unless (exists $git->{_plugin_githooks}{repository_name}) { |
|
1299
|
0
|
0
|
|
|
|
0
|
if (my $gerrit_args = $git->{_plugin_githooks}{gerrit_args}) { |
|
|
|
0
|
|
|
|
|
|
|
|
|
0
|
|
|
|
|
|
|
1300
|
|
|
|
|
|
|
# Gerrit |
|
1301
|
0
|
|
|
|
|
0
|
$git->{_plugin_githooks}{repository_name} = $gerrit_args->{'--project'}; |
|
1302
|
|
|
|
|
|
|
} elsif (exists $ENV{BB_REPO_SLUG}) { |
|
1303
|
|
|
|
|
|
|
# Bitbucket Server environment variables available for hooks: |
|
1304
|
|
|
|
|
|
|
# https://developer.atlassian.com/server/bitbucket/how-tos/write-hook-scripts/ |
|
1305
|
0
|
|
|
|
|
0
|
$git->{_plugin_githooks}{repository_name} = "$ENV{BB_PROJECT_KEY}/$ENV{BB_REPO_SLUG}"; |
|
1306
|
|
|
|
|
|
|
} elsif (exists $ENV{GL_PROJECT_PATH}) { |
|
1307
|
|
|
|
|
|
|
# GitLab environment variables available for hooks: |
|
1308
|
|
|
|
|
|
|
# https://docs.gitlab.com/ee/administration/server_hooks.html |
|
1309
|
0
|
|
|
|
|
0
|
$git->{_plugin_githooks}{repository_name} = "$ENV{GL_PROJECT_PATH}"; |
|
1310
|
|
|
|
|
|
|
} else { |
|
1311
|
|
|
|
|
|
|
# As a last resort, return GIT_DIR's basename |
|
1312
|
0
|
|
|
|
|
0
|
my $gitdir = path($git->git_dir()); |
|
1313
|
0
|
|
|
|
|
0
|
my $basename = $gitdir->basename; |
|
1314
|
0
|
0
|
|
|
|
0
|
if ($basename eq '.git') { |
|
1315
|
0
|
|
|
|
|
0
|
$basename = $gitdir->parent->basename; |
|
1316
|
|
|
|
|
|
|
} |
|
1317
|
0
|
|
|
|
|
0
|
$git->{_plugin_githooks}{repository_name} = $basename; |
|
1318
|
|
|
|
|
|
|
} |
|
1319
|
|
|
|
|
|
|
} |
|
1320
|
|
|
|
|
|
|
|
|
1321
|
0
|
|
|
|
|
0
|
return $git->{_plugin_githooks}{repository_name}; |
|
1322
|
|
|
|
|
|
|
} |
|
1323
|
|
|
|
|
|
|
|
|
1324
|
|
|
|
|
|
|
sub get_current_branch { |
|
1325
|
3
|
|
|
3
|
1
|
1885609
|
my ($git) = @_; |
|
1326
|
3
|
|
|
|
|
72
|
my $branch = $git->run({fatal => [-129, -128], quiet => 1}, qw/symbolic-ref HEAD/); |
|
1327
|
|
|
|
|
|
|
|
|
1328
|
|
|
|
|
|
|
# Return undef if we're in detached head state |
|
1329
|
3
|
50
|
|
|
|
54208
|
return $? == 0 ? $branch : undef; |
|
1330
|
|
|
|
|
|
|
} |
|
1331
|
|
|
|
|
|
|
|
|
1332
|
|
|
|
|
|
|
sub get_sha1 { |
|
1333
|
0
|
|
|
0
|
1
|
|
my ($git, $rev) = @_; |
|
1334
|
|
|
|
|
|
|
|
|
1335
|
0
|
|
|
|
|
|
return $git->run(qw/rev-parse --verify/, $rev); |
|
1336
|
|
|
|
|
|
|
} |
|
1337
|
|
|
|
|
|
|
|
|
1338
|
|
|
|
|
|
|
sub get_head_or_empty_tree { |
|
1339
|
0
|
|
|
0
|
1
|
|
my ($git) = @_; |
|
1340
|
|
|
|
|
|
|
|
|
1341
|
0
|
|
|
|
|
|
my $head = $git->run({fatal => [-129, -128], quiet => 1}, qw/rev-parse --verify HEAD/); |
|
1342
|
|
|
|
|
|
|
|
|
1343
|
|
|
|
|
|
|
# Return the empty tree object if in the initial commit |
|
1344
|
0
|
0
|
|
|
|
|
return $? == 0 ? $head : $git->empty_tree; |
|
1345
|
|
|
|
|
|
|
} |
|
1346
|
|
|
|
|
|
|
|
|
1347
|
|
|
|
|
|
|
sub blob { |
|
1348
|
0
|
|
|
0
|
1
|
|
my ($git, $rev, $file, @args) = @_; |
|
1349
|
|
|
|
|
|
|
|
|
1350
|
0
|
|
|
|
|
|
my $cache = $git->cache('blob'); |
|
1351
|
|
|
|
|
|
|
|
|
1352
|
0
|
|
|
|
|
|
my $blob = "$rev:$file"; |
|
1353
|
|
|
|
|
|
|
|
|
1354
|
0
|
0
|
|
|
|
|
unless (exists $cache->{$blob}) { |
|
1355
|
0
|
|
0
|
|
|
|
$cache->{tmpdir} //= Path::Tiny->tempdir(@args); |
|
1356
|
|
|
|
|
|
|
|
|
1357
|
0
|
|
|
|
|
|
my $path = path($file); |
|
1358
|
|
|
|
|
|
|
|
|
1359
|
|
|
|
|
|
|
# Calculate temporary file path |
|
1360
|
0
|
|
|
|
|
|
my $revdir = $rev =~ s/^://r; # remove ':' from ':0' because Windows don't like ':' in filenames |
|
1361
|
0
|
|
|
|
|
|
my $filepath = $cache->{tmpdir}->child($revdir, $path); |
|
1362
|
|
|
|
|
|
|
|
|
1363
|
|
|
|
|
|
|
# Create directory path for the temporary file. |
|
1364
|
0
|
|
|
|
|
|
$filepath->parent->mkpath; |
|
1365
|
|
|
|
|
|
|
|
|
1366
|
|
|
|
|
|
|
# Create temporary file and copy contents to it |
|
1367
|
0
|
0
|
|
|
|
|
open my $tmp, '>:', $filepath ## no critic (RequireBriefOpen) |
|
1368
|
|
|
|
|
|
|
or croak "Internal error: can't create file '$filepath': $!"; |
|
1369
|
|
|
|
|
|
|
|
|
1370
|
0
|
|
|
|
|
|
my $cmd = $git->command(qw/cat-file blob/, $blob); |
|
1371
|
0
|
|
|
|
|
|
my $stdout = $cmd->stdout; |
|
1372
|
0
|
|
|
|
|
|
my $read; |
|
1373
|
0
|
|
|
|
|
|
while ($read = sysread $stdout, my $buffer, 64 * 1024) { |
|
1374
|
0
|
|
|
|
|
|
my $length = length $buffer; |
|
1375
|
0
|
|
|
|
|
|
my $offset = 0; |
|
1376
|
0
|
|
|
|
|
|
while ($length) { |
|
1377
|
0
|
|
|
|
|
|
my $written = syswrite $tmp, $buffer, $length, $offset; |
|
1378
|
0
|
0
|
|
|
|
|
defined $written |
|
1379
|
|
|
|
|
|
|
or croak "Internal error: can't write to '$filepath': $!"; |
|
1380
|
0
|
|
|
|
|
|
$length -= $written; |
|
1381
|
0
|
|
|
|
|
|
$offset += $written; |
|
1382
|
|
|
|
|
|
|
} |
|
1383
|
|
|
|
|
|
|
} |
|
1384
|
0
|
0
|
|
|
|
|
defined $read |
|
1385
|
|
|
|
|
|
|
or croak "Internal error: can't read from git cat-file pipe: $!"; |
|
1386
|
0
|
|
|
|
|
|
$cmd->close; |
|
1387
|
|
|
|
|
|
|
|
|
1388
|
0
|
|
|
|
|
|
$tmp->close; |
|
1389
|
|
|
|
|
|
|
|
|
1390
|
0
|
0
|
|
|
|
|
if (my $exit = $cmd->exit) { |
|
1391
|
0
|
|
|
|
|
|
croak "Command 'git cat-file blob $blob' exited with code $exit\n"; |
|
1392
|
|
|
|
|
|
|
} |
|
1393
|
|
|
|
|
|
|
|
|
1394
|
0
|
|
|
|
|
|
$cache->{$blob} = $filepath; |
|
1395
|
|
|
|
|
|
|
} |
|
1396
|
|
|
|
|
|
|
|
|
1397
|
0
|
|
|
|
|
|
return $cache->{$blob}->stringify; |
|
1398
|
|
|
|
|
|
|
} |
|
1399
|
|
|
|
|
|
|
|
|
1400
|
|
|
|
|
|
|
sub file_size { |
|
1401
|
0
|
|
|
0
|
1
|
|
my ($git, $rev, $file) = @_; |
|
1402
|
|
|
|
|
|
|
|
|
1403
|
0
|
|
|
|
|
|
return $git->run(qw/cat-file -s/, "$rev:$file"); |
|
1404
|
|
|
|
|
|
|
} |
|
1405
|
|
|
|
|
|
|
|
|
1406
|
|
|
|
|
|
|
sub file_mode { |
|
1407
|
0
|
|
|
0
|
1
|
|
my ($git, $rev, $file) = @_; |
|
1408
|
|
|
|
|
|
|
|
|
1409
|
0
|
0
|
|
|
|
|
if ($rev eq ':0') { |
|
1410
|
0
|
|
|
|
|
|
my @diff_index = $git->run(qw/diff-index --cached --raw --no-color HEAD/, $file); |
|
1411
|
|
|
|
|
|
|
|
|
1412
|
0
|
0
|
|
|
|
|
if (@diff_index == 1) { |
|
1413
|
0
|
0
|
|
|
|
|
if (my ($src_mode, $dst_mode, $rest) = $diff_index[0] =~ /^:(\d+) (\d+) (.*)/) { |
|
1414
|
0
|
|
|
|
|
|
return oct $dst_mode; |
|
1415
|
|
|
|
|
|
|
} else { |
|
1416
|
0
|
|
|
|
|
|
croak "Internal error: cannot parse output of git-diff-idex:\n\n $diff_index[0]"; |
|
1417
|
|
|
|
|
|
|
} |
|
1418
|
|
|
|
|
|
|
} else { |
|
1419
|
0
|
|
|
|
|
|
croak "Internal error: git-diff-index should return a single line"; |
|
1420
|
|
|
|
|
|
|
} |
|
1421
|
|
|
|
|
|
|
} else { |
|
1422
|
0
|
|
|
|
|
|
my @ls_tree = $git->run('ls-tree', "$rev:", $file ); |
|
1423
|
|
|
|
|
|
|
|
|
1424
|
0
|
0
|
|
|
|
|
if (@ls_tree == 1) { |
|
1425
|
0
|
0
|
|
|
|
|
if (my ($mode, $type, $object, $filename) = |
|
1426
|
|
|
|
|
|
|
$ls_tree[0] =~ /^(\d+) ([a-z]+) ([a-z0-9]{40})\t(.+)/) { |
|
1427
|
0
|
|
|
|
|
|
return oct $mode; |
|
1428
|
|
|
|
|
|
|
} else { |
|
1429
|
0
|
|
|
|
|
|
croak "Internal error: cannot parse output of git-ls-tree:\n\n $ls_tree[0]"; |
|
1430
|
|
|
|
|
|
|
} |
|
1431
|
|
|
|
|
|
|
} else { |
|
1432
|
0
|
|
|
|
|
|
croak "Internal error: $rev:$file should be a blob"; |
|
1433
|
|
|
|
|
|
|
} |
|
1434
|
|
|
|
|
|
|
} |
|
1435
|
|
|
|
|
|
|
|
|
1436
|
0
|
|
|
|
|
|
croak "Can't happen!"; |
|
1437
|
|
|
|
|
|
|
} |
|
1438
|
|
|
|
|
|
|
|
|
1439
|
|
|
|
|
|
|
sub is_reference_enabled { |
|
1440
|
0
|
|
|
0
|
1
|
|
my ($git, $reference) = @_; |
|
1441
|
|
|
|
|
|
|
|
|
1442
|
0
|
0
|
|
|
|
|
return 1 unless defined $reference; |
|
1443
|
|
|
|
|
|
|
|
|
1444
|
0
|
|
|
|
|
|
my $cache = $git->cache('is_reference_enabled'); |
|
1445
|
|
|
|
|
|
|
|
|
1446
|
0
|
0
|
|
|
|
|
unless (exists $cache->{$reference}) { |
|
1447
|
|
|
|
|
|
|
my $check_reference = sub { |
|
1448
|
0
|
|
|
0
|
|
|
foreach ($git->get_config(githooks => 'ref')) { |
|
1449
|
0
|
0
|
|
|
|
|
if (/^\^/) { |
|
1450
|
0
|
0
|
|
|
|
|
return 1 if $reference =~ qr/$_/; |
|
1451
|
|
|
|
|
|
|
} else { |
|
1452
|
0
|
0
|
|
|
|
|
return 1 if $reference eq $_; |
|
1453
|
|
|
|
|
|
|
} |
|
1454
|
|
|
|
|
|
|
} |
|
1455
|
|
|
|
|
|
|
|
|
1456
|
0
|
|
|
|
|
|
foreach ($git->get_config(githooks => 'noref')) { |
|
1457
|
0
|
0
|
|
|
|
|
if (/^\^/) { |
|
1458
|
0
|
0
|
|
|
|
|
return 0 if $reference =~ qr/$_/; |
|
1459
|
|
|
|
|
|
|
} else { |
|
1460
|
0
|
0
|
|
|
|
|
return 0 if $reference eq $_; |
|
1461
|
|
|
|
|
|
|
} |
|
1462
|
|
|
|
|
|
|
} |
|
1463
|
|
|
|
|
|
|
|
|
1464
|
0
|
|
|
|
|
|
return 1; |
|
1465
|
0
|
|
|
|
|
|
}; |
|
1466
|
|
|
|
|
|
|
|
|
1467
|
0
|
|
|
|
|
|
$cache->{$reference} = $check_reference->(); |
|
1468
|
|
|
|
|
|
|
} |
|
1469
|
|
|
|
|
|
|
|
|
1470
|
0
|
|
|
|
|
|
return $cache->{$reference}; |
|
1471
|
|
|
|
|
|
|
} |
|
1472
|
|
|
|
|
|
|
|
|
1473
|
|
|
|
|
|
|
sub _grok_groups_spec { |
|
1474
|
0
|
|
|
0
|
|
|
my ($groups, $specs, $source) = @_; |
|
1475
|
0
|
|
|
|
|
|
foreach (@$specs) { |
|
1476
|
0
|
|
|
|
|
|
s/\#.*//; # strip comments |
|
1477
|
0
|
0
|
|
|
|
|
next unless /\S/; # skip blank lines |
|
1478
|
0
|
0
|
|
|
|
|
/^\s*([\w-]+)\s*=\s*(.+?)\s*$/ |
|
1479
|
|
|
|
|
|
|
or croak __PACKAGE__, ": invalid line in '$source': $_\n"; |
|
1480
|
0
|
|
|
|
|
|
my ($groupname, $members) = ($1, $2); |
|
1481
|
0
|
0
|
|
|
|
|
exists $groups->{"\@$groupname"} |
|
1482
|
|
|
|
|
|
|
and croak __PACKAGE__, ": redefinition of group ($groupname) in '$source': $_\n"; |
|
1483
|
0
|
|
|
|
|
|
foreach my $member (split ' ', $members) { |
|
1484
|
0
|
0
|
|
|
|
|
if ($member =~ /^\@/) { |
|
1485
|
|
|
|
|
|
|
# group member |
|
1486
|
0
|
0
|
|
|
|
|
$groups->{"\@$groupname"}{$member} = $groups->{$member} |
|
1487
|
|
|
|
|
|
|
or croak __PACKAGE__, ": unknown group ($member) cited in '$source': $_\n"; |
|
1488
|
|
|
|
|
|
|
} else { |
|
1489
|
|
|
|
|
|
|
# user member |
|
1490
|
0
|
|
|
|
|
|
$groups->{"\@$groupname"}{$member} = undef; |
|
1491
|
|
|
|
|
|
|
} |
|
1492
|
|
|
|
|
|
|
} |
|
1493
|
|
|
|
|
|
|
} |
|
1494
|
0
|
|
|
|
|
|
return; |
|
1495
|
|
|
|
|
|
|
} |
|
1496
|
|
|
|
|
|
|
|
|
1497
|
|
|
|
|
|
|
sub _grok_groups { |
|
1498
|
0
|
|
|
0
|
|
|
my ($git) = @_; |
|
1499
|
|
|
|
|
|
|
|
|
1500
|
0
|
|
|
|
|
|
my $cache = $git->cache('githooks'); |
|
1501
|
|
|
|
|
|
|
|
|
1502
|
0
|
0
|
|
|
|
|
unless (exists $cache->{groups}) { |
|
1503
|
0
|
0
|
|
|
|
|
my @groups = $git->get_config(githooks => 'groups') |
|
1504
|
|
|
|
|
|
|
or croak __PACKAGE__, ": you have to define the githooks.groups option to use groups.\n"; |
|
1505
|
|
|
|
|
|
|
|
|
1506
|
0
|
|
|
|
|
|
my $groups = {}; |
|
1507
|
0
|
|
|
|
|
|
foreach my $spec (@groups) { |
|
1508
|
0
|
0
|
|
|
|
|
if (my ($groupfile) = ($spec =~ /^file:(.*)/)) { |
|
1509
|
0
|
|
|
|
|
|
my @groupspecs = path($groupfile)->lines; |
|
1510
|
0
|
0
|
|
|
|
|
defined $groupspecs[0] |
|
1511
|
|
|
|
|
|
|
or croak __PACKAGE__, ": can't open groups file ($groupfile): $!\n"; |
|
1512
|
0
|
|
|
|
|
|
_grok_groups_spec($groups, \@groupspecs, $groupfile); |
|
1513
|
|
|
|
|
|
|
} else { |
|
1514
|
0
|
|
|
|
|
|
my @groupspecs = split /\n/, $spec; |
|
1515
|
0
|
|
|
|
|
|
_grok_groups_spec($groups, \@groupspecs, "githooks.groups"); |
|
1516
|
|
|
|
|
|
|
} |
|
1517
|
|
|
|
|
|
|
} |
|
1518
|
0
|
|
|
|
|
|
$cache->{groups} = $groups; |
|
1519
|
|
|
|
|
|
|
} |
|
1520
|
|
|
|
|
|
|
|
|
1521
|
0
|
|
|
|
|
|
return $cache->{groups}; |
|
1522
|
|
|
|
|
|
|
} |
|
1523
|
|
|
|
|
|
|
|
|
1524
|
|
|
|
|
|
|
sub _im_memberof { |
|
1525
|
0
|
|
|
0
|
|
|
my ($git, $myself, $groupname) = @_; |
|
1526
|
|
|
|
|
|
|
|
|
1527
|
0
|
|
|
|
|
|
my $groups = _grok_groups($git); |
|
1528
|
|
|
|
|
|
|
|
|
1529
|
0
|
0
|
|
|
|
|
exists $groups->{$groupname} |
|
1530
|
|
|
|
|
|
|
or croak __PACKAGE__, ": group $groupname is not defined.\n"; |
|
1531
|
|
|
|
|
|
|
|
|
1532
|
0
|
|
|
|
|
|
my $group = $groups->{$groupname}; |
|
1533
|
0
|
0
|
|
|
|
|
return 1 if exists $group->{$myself}; |
|
1534
|
0
|
|
|
|
|
|
while (my ($member, $subgroup) = each %$group) { |
|
1535
|
0
|
0
|
|
|
|
|
next unless defined $subgroup; |
|
1536
|
0
|
0
|
|
|
|
|
return 1 if _im_memberof($git, $myself, $member); |
|
1537
|
|
|
|
|
|
|
} |
|
1538
|
0
|
|
|
|
|
|
return 0; |
|
1539
|
|
|
|
|
|
|
} |
|
1540
|
|
|
|
|
|
|
|
|
1541
|
|
|
|
|
|
|
sub match_user { |
|
1542
|
0
|
|
|
0
|
1
|
|
my ($git, $spec) = @_; |
|
1543
|
|
|
|
|
|
|
|
|
1544
|
0
|
0
|
|
|
|
|
if (my $myself = $git->authenticated_user()) { |
|
1545
|
0
|
0
|
|
|
|
|
if ($spec =~ /^\^/) { |
|
|
|
0
|
|
|
|
|
|
|
1546
|
0
|
0
|
|
|
|
|
return 1 if $myself =~ $spec; |
|
1547
|
|
|
|
|
|
|
} elsif ($spec =~ /^@/) { |
|
1548
|
0
|
0
|
|
|
|
|
return 1 if _im_memberof($git, $myself, $spec); |
|
1549
|
|
|
|
|
|
|
} else { |
|
1550
|
0
|
0
|
|
|
|
|
return 1 if $myself eq $spec; |
|
1551
|
|
|
|
|
|
|
} |
|
1552
|
|
|
|
|
|
|
} |
|
1553
|
|
|
|
|
|
|
|
|
1554
|
0
|
|
|
|
|
|
return 0; |
|
1555
|
|
|
|
|
|
|
} |
|
1556
|
|
|
|
|
|
|
|
|
1557
|
|
|
|
|
|
|
sub im_admin { |
|
1558
|
0
|
|
|
0
|
1
|
|
my ($git) = @_; |
|
1559
|
0
|
|
|
|
|
|
foreach my $spec ($git->get_config(githooks => 'admin')) { |
|
1560
|
0
|
0
|
|
|
|
|
return 1 if match_user($git, $spec); |
|
1561
|
|
|
|
|
|
|
} |
|
1562
|
0
|
|
|
|
|
|
return 0; |
|
1563
|
|
|
|
|
|
|
} |
|
1564
|
|
|
|
|
|
|
|
|
1565
|
|
|
|
|
|
|
sub grok_acls { |
|
1566
|
0
|
|
|
0
|
1
|
|
my ($git, $cfg, $actions) = @_; |
|
1567
|
|
|
|
|
|
|
|
|
1568
|
0
|
|
|
|
|
|
my @acls; |
|
1569
|
|
|
|
|
|
|
|
|
1570
|
|
|
|
|
|
|
ACL: |
|
1571
|
0
|
|
|
|
|
|
foreach ($git->get_config($cfg => 'acl')) { |
|
1572
|
0
|
|
|
|
|
|
my %acl; |
|
1573
|
0
|
0
|
|
|
|
|
if (/^\s*(allow|deny)\s+([$actions]+)\s+(\S+)/) { |
|
1574
|
0
|
|
|
|
|
|
$acl{acl} = $_; |
|
1575
|
0
|
|
|
|
|
|
$acl{allow} = $1 eq 'allow'; |
|
1576
|
0
|
|
|
|
|
|
$acl{action} = $2; |
|
1577
|
0
|
|
|
|
|
|
my $spec = $3; |
|
1578
|
|
|
|
|
|
|
|
|
1579
|
|
|
|
|
|
|
# Interpolate environment variables embedded as "{VAR}". |
|
1580
|
0
|
|
|
|
|
|
$spec =~ s/{(\w+)}/$ENV{$1}/ige; |
|
|
0
|
|
|
|
|
|
|
|
1581
|
|
|
|
|
|
|
# Pre-compile regex |
|
1582
|
0
|
0
|
|
|
|
|
$acl{spec} = substr($spec, 0, 1) eq '^' ? qr/$spec/ : $spec; |
|
1583
|
|
|
|
|
|
|
} else { |
|
1584
|
0
|
|
|
|
|
|
croak "invalid acl syntax for actions '$actions': $_\n"; |
|
1585
|
|
|
|
|
|
|
} |
|
1586
|
|
|
|
|
|
|
|
|
1587
|
0
|
0
|
|
|
|
|
if (substr($_, $+[0]) =~ /^\s*by\s+(\S+)\s*$/) { |
|
|
|
0
|
|
|
|
|
|
|
1588
|
0
|
|
|
|
|
|
$acl{who} = $1; |
|
1589
|
|
|
|
|
|
|
# Discard this ACL if it doesn't match the user |
|
1590
|
0
|
0
|
|
|
|
|
next ACL unless $git->match_user($acl{who}); |
|
1591
|
|
|
|
|
|
|
} elsif (substr($_, $+[0]) !~ /^\s*$/) { |
|
1592
|
0
|
|
|
|
|
|
croak "invalid acl syntax for actions '$actions: $_\n"; |
|
1593
|
|
|
|
|
|
|
} |
|
1594
|
|
|
|
|
|
|
|
|
1595
|
0
|
|
|
|
|
|
unshift @acls, \%acl; |
|
1596
|
|
|
|
|
|
|
} |
|
1597
|
|
|
|
|
|
|
|
|
1598
|
0
|
|
|
|
|
|
return @acls; |
|
1599
|
|
|
|
|
|
|
} |
|
1600
|
|
|
|
|
|
|
|
|
1601
|
|
|
|
|
|
|
|
|
1602
|
|
|
|
|
|
|
1; # End of Git::Repository::Plugin::GitHooks |
|
1603
|
|
|
|
|
|
|
|
|
1604
|
|
|
|
|
|
|
__END__ |