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__ |