]>
Commit | Line | Data |
---|---|---|
edcc8581 LS |
1 | # |
2 | # Example implementation for the Git filter protocol version 2 | |
3 | # See Documentation/gitattributes.txt, section "Filter Protocol" | |
4 | # | |
e1ec4721 LS |
5 | # The first argument defines a debug log file that the script write to. |
6 | # All remaining arguments define a list of supported protocol | |
7 | # capabilities ("clean", "smudge", etc). | |
edcc8581 LS |
8 | # |
9 | # This implementation supports special test cases: | |
10 | # (1) If data with the pathname "clean-write-fail.r" is processed with | |
11 | # a "clean" operation then the write operation will die. | |
12 | # (2) If data with the pathname "smudge-write-fail.r" is processed with | |
13 | # a "smudge" operation then the write operation will die. | |
14 | # (3) If data with the pathname "error.r" is processed with any | |
15 | # operation then the filter signals that it cannot or does not want | |
16 | # to process the file. | |
17 | # (4) If data with the pathname "abort.r" is processed with any | |
18 | # operation then the filter signals that it cannot or does not want | |
19 | # to process the file and any file after that is processed with the | |
20 | # same command. | |
2841e8f8 LS |
21 | # (5) If data with a pathname that is a key in the DELAY hash is |
22 | # requested (e.g. "test-delay10.a") then the filter responds with | |
23 | # a "delay" status and sets the "requested" field in the DELAY hash. | |
24 | # The filter will signal the availability of this object after | |
25 | # "count" (field in DELAY hash) "list_available_blobs" commands. | |
26 | # (6) If data with the pathname "missing-delay.a" is processed that the | |
27 | # filter will drop the path from the "list_available_blobs" response. | |
28 | # (7) If data with the pathname "invalid-delay.a" is processed that the | |
29 | # filter will add the path "unfiltered" which was not delayed before | |
30 | # to the "list_available_blobs" response. | |
edcc8581 LS |
31 | # |
32 | ||
33 | use strict; | |
34 | use warnings; | |
4821494e | 35 | use IO::File; |
edcc8581 LS |
36 | |
37 | my $MAX_PACKET_CONTENT_SIZE = 65516; | |
e1ec4721 | 38 | my $log_file = shift @ARGV; |
edcc8581 LS |
39 | my @capabilities = @ARGV; |
40 | ||
e1ec4721 | 41 | open my $debug, ">>", $log_file or die "cannot open log file: $!"; |
edcc8581 | 42 | |
2841e8f8 LS |
43 | my %DELAY = ( |
44 | 'test-delay10.a' => { "requested" => 0, "count" => 1 }, | |
45 | 'test-delay11.a' => { "requested" => 0, "count" => 1 }, | |
46 | 'test-delay20.a' => { "requested" => 0, "count" => 2 }, | |
47 | 'test-delay10.b' => { "requested" => 0, "count" => 1 }, | |
48 | 'missing-delay.a' => { "requested" => 0, "count" => 1 }, | |
49 | 'invalid-delay.a' => { "requested" => 0, "count" => 1 }, | |
50 | ); | |
51 | ||
edcc8581 LS |
52 | sub rot13 { |
53 | my $str = shift; | |
54 | $str =~ y/A-Za-z/N-ZA-Mn-za-m/; | |
55 | return $str; | |
56 | } | |
57 | ||
0a268826 CC |
58 | sub packet_compare_lists { |
59 | my ($expect, @result) = @_; | |
60 | my $ix; | |
61 | if (scalar @$expect != scalar @result) { | |
62 | return undef; | |
63 | } | |
64 | for ($ix = 0; $ix < $#result; $ix++) { | |
65 | if ($expect->[$ix] ne $result[$ix]) { | |
66 | return undef; | |
67 | } | |
68 | } | |
69 | return 1; | |
70 | } | |
71 | ||
edcc8581 LS |
72 | sub packet_bin_read { |
73 | my $buffer; | |
74 | my $bytes_read = read STDIN, $buffer, 4; | |
75 | if ( $bytes_read == 0 ) { | |
76 | # EOF - Git stopped talking to us! | |
2c9ea595 | 77 | return ( -1, "" ); |
edcc8581 LS |
78 | } |
79 | elsif ( $bytes_read != 4 ) { | |
80 | die "invalid packet: '$buffer'"; | |
81 | } | |
82 | my $pkt_size = hex($buffer); | |
83 | if ( $pkt_size == 0 ) { | |
84 | return ( 1, "" ); | |
85 | } | |
86 | elsif ( $pkt_size > 4 ) { | |
87 | my $content_size = $pkt_size - 4; | |
88 | $bytes_read = read STDIN, $buffer, $content_size; | |
89 | if ( $bytes_read != $content_size ) { | |
90 | die "invalid packet ($content_size bytes expected; $bytes_read bytes read)"; | |
91 | } | |
92 | return ( 0, $buffer ); | |
93 | } | |
94 | else { | |
95 | die "invalid packet size: $pkt_size"; | |
96 | } | |
97 | } | |
98 | ||
99 | sub packet_txt_read { | |
100 | my ( $res, $buf ) = packet_bin_read(); | |
2c9ea595 | 101 | unless ( $res == -1 or $buf eq '' or $buf =~ s/\n$// ) { |
edcc8581 LS |
102 | die "A non-binary line MUST be terminated by an LF."; |
103 | } | |
104 | return ( $res, $buf ); | |
105 | } | |
106 | ||
2c9ea595 CC |
107 | sub packet_required_key_val_read { |
108 | my ( $key ) = @_; | |
109 | my ( $res, $buf ) = packet_txt_read(); | |
110 | unless ( $res == -1 or ( $buf =~ s/^$key=// and $buf ne '' ) ) { | |
111 | die "bad $key: '$buf'"; | |
112 | } | |
113 | return ( $res, $buf ); | |
114 | } | |
115 | ||
edcc8581 LS |
116 | sub packet_bin_write { |
117 | my $buf = shift; | |
118 | print STDOUT sprintf( "%04x", length($buf) + 4 ); | |
119 | print STDOUT $buf; | |
120 | STDOUT->flush(); | |
121 | } | |
122 | ||
123 | sub packet_txt_write { | |
124 | packet_bin_write( $_[0] . "\n" ); | |
125 | } | |
126 | ||
127 | sub packet_flush { | |
128 | print STDOUT sprintf( "%04x", 0 ); | |
129 | STDOUT->flush(); | |
130 | } | |
131 | ||
132 | print $debug "START\n"; | |
133 | $debug->flush(); | |
134 | ||
0a268826 CC |
135 | packet_compare_lists([0, "git-filter-client"], packet_txt_read()) || |
136 | die "bad initialize"; | |
137 | packet_compare_lists([0, "version=2"], packet_txt_read()) || | |
138 | die "bad version"; | |
139 | packet_compare_lists([1, ""], packet_bin_read()) || | |
140 | die "bad version end"; | |
edcc8581 LS |
141 | |
142 | packet_txt_write("git-filter-server"); | |
143 | packet_txt_write("version=2"); | |
144 | packet_flush(); | |
145 | ||
0a268826 CC |
146 | packet_compare_lists([0, "capability=clean"], packet_txt_read()) || |
147 | die "bad capability"; | |
148 | packet_compare_lists([0, "capability=smudge"], packet_txt_read()) || | |
149 | die "bad capability"; | |
150 | packet_compare_lists([0, "capability=delay"], packet_txt_read()) || | |
151 | die "bad capability"; | |
152 | packet_compare_lists([1, ""], packet_bin_read()) || | |
153 | die "bad capability end"; | |
edcc8581 LS |
154 | |
155 | foreach (@capabilities) { | |
156 | packet_txt_write( "capability=" . $_ ); | |
157 | } | |
158 | packet_flush(); | |
159 | print $debug "init handshake complete\n"; | |
160 | $debug->flush(); | |
161 | ||
162 | while (1) { | |
2c9ea595 CC |
163 | my ( $res, $command ) = packet_required_key_val_read("command"); |
164 | if ( $res == -1 ) { | |
165 | print $debug "STOP\n"; | |
166 | exit(); | |
167 | } | |
edcc8581 LS |
168 | print $debug "IN: $command"; |
169 | $debug->flush(); | |
170 | ||
2841e8f8 LS |
171 | if ( $command eq "list_available_blobs" ) { |
172 | # Flush | |
2c9ea595 CC |
173 | packet_compare_lists([1, ""], packet_bin_read()) || |
174 | die "bad list_available_blobs end"; | |
c6b0831c | 175 | |
2841e8f8 LS |
176 | foreach my $pathname ( sort keys %DELAY ) { |
177 | if ( $DELAY{$pathname}{"requested"} >= 1 ) { | |
178 | $DELAY{$pathname}{"count"} = $DELAY{$pathname}{"count"} - 1; | |
179 | if ( $pathname eq "invalid-delay.a" ) { | |
180 | # Send Git a pathname that was not delayed earlier | |
181 | packet_txt_write("pathname=unfiltered"); | |
182 | } | |
183 | if ( $pathname eq "missing-delay.a" ) { | |
184 | # Do not signal Git that this file is available | |
185 | } elsif ( $DELAY{$pathname}{"count"} == 0 ) { | |
186 | print $debug " $pathname"; | |
187 | packet_txt_write("pathname=$pathname"); | |
188 | } | |
189 | } | |
edcc8581 | 190 | } |
edcc8581 | 191 | |
edcc8581 | 192 | packet_flush(); |
2841e8f8 LS |
193 | |
194 | print $debug " [OK]\n"; | |
edcc8581 | 195 | $debug->flush(); |
2841e8f8 | 196 | packet_txt_write("status=success"); |
edcc8581 LS |
197 | packet_flush(); |
198 | } | |
199 | else { | |
2c9ea595 CC |
200 | my ( $res, $pathname ) = packet_required_key_val_read("pathname"); |
201 | if ( $res == -1 ) { | |
202 | die "unexpected EOF while expecting pathname"; | |
203 | } | |
2841e8f8 LS |
204 | print $debug " $pathname"; |
205 | $debug->flush(); | |
206 | ||
2841e8f8 LS |
207 | # Read until flush |
208 | my ( $done, $buffer ) = packet_txt_read(); | |
209 | while ( $buffer ne '' ) { | |
210 | if ( $buffer eq "can-delay=1" ) { | |
211 | if ( exists $DELAY{$pathname} and $DELAY{$pathname}{"requested"} == 0 ) { | |
212 | $DELAY{$pathname}{"requested"} = 1; | |
213 | } | |
214 | } else { | |
215 | die "Unknown message '$buffer'"; | |
216 | } | |
edcc8581 | 217 | |
2841e8f8 LS |
218 | ( $done, $buffer ) = packet_txt_read(); |
219 | } | |
2c9ea595 CC |
220 | if ( $done == -1 ) { |
221 | die "unexpected EOF after pathname '$pathname'"; | |
222 | } | |
2841e8f8 LS |
223 | |
224 | my $input = ""; | |
225 | { | |
226 | binmode(STDIN); | |
227 | my $buffer; | |
228 | my $done = 0; | |
229 | while ( !$done ) { | |
230 | ( $done, $buffer ) = packet_bin_read(); | |
231 | $input .= $buffer; | |
232 | } | |
2c9ea595 CC |
233 | if ( $done == -1 ) { |
234 | die "unexpected EOF while reading input for '$pathname'"; | |
235 | } | |
2841e8f8 | 236 | print $debug " " . length($input) . " [OK] -- "; |
edcc8581 | 237 | $debug->flush(); |
edcc8581 LS |
238 | } |
239 | ||
2841e8f8 LS |
240 | my $output; |
241 | if ( exists $DELAY{$pathname} and exists $DELAY{$pathname}{"output"} ) { | |
242 | $output = $DELAY{$pathname}{"output"} | |
243 | } | |
244 | elsif ( $pathname eq "error.r" or $pathname eq "abort.r" ) { | |
245 | $output = ""; | |
246 | } | |
247 | elsif ( $command eq "clean" and grep( /^clean$/, @capabilities ) ) { | |
248 | $output = rot13($input); | |
249 | } | |
250 | elsif ( $command eq "smudge" and grep( /^smudge$/, @capabilities ) ) { | |
251 | $output = rot13($input); | |
252 | } | |
253 | else { | |
254 | die "bad command '$command'"; | |
255 | } | |
256 | ||
257 | if ( $pathname eq "error.r" ) { | |
258 | print $debug "[ERROR]\n"; | |
259 | $debug->flush(); | |
260 | packet_txt_write("status=error"); | |
261 | packet_flush(); | |
262 | } | |
263 | elsif ( $pathname eq "abort.r" ) { | |
264 | print $debug "[ABORT]\n"; | |
265 | $debug->flush(); | |
266 | packet_txt_write("status=abort"); | |
267 | packet_flush(); | |
268 | } | |
269 | elsif ( $command eq "smudge" and | |
270 | exists $DELAY{$pathname} and | |
271 | $DELAY{$pathname}{"requested"} == 1 | |
272 | ) { | |
273 | print $debug "[DELAYED]\n"; | |
274 | $debug->flush(); | |
275 | packet_txt_write("status=delayed"); | |
276 | packet_flush(); | |
277 | $DELAY{$pathname}{"requested"} = 2; | |
278 | $DELAY{$pathname}{"output"} = $output; | |
279 | } | |
280 | else { | |
281 | packet_txt_write("status=success"); | |
282 | packet_flush(); | |
17573334 | 283 | |
2841e8f8 LS |
284 | if ( $pathname eq "${command}-write-fail.r" ) { |
285 | print $debug "[WRITE FAIL]\n"; | |
286 | $debug->flush(); | |
287 | die "${command} write error"; | |
edcc8581 | 288 | } |
2841e8f8 LS |
289 | |
290 | print $debug "OUT: " . length($output) . " "; | |
291 | $debug->flush(); | |
292 | ||
293 | while ( length($output) > 0 ) { | |
294 | my $packet = substr( $output, 0, $MAX_PACKET_CONTENT_SIZE ); | |
295 | packet_bin_write($packet); | |
296 | # dots represent the number of packets | |
297 | print $debug "."; | |
298 | if ( length($output) > $MAX_PACKET_CONTENT_SIZE ) { | |
299 | $output = substr( $output, $MAX_PACKET_CONTENT_SIZE ); | |
300 | } | |
301 | else { | |
302 | $output = ""; | |
303 | } | |
edcc8581 | 304 | } |
2841e8f8 LS |
305 | packet_flush(); |
306 | print $debug " [OK]\n"; | |
307 | $debug->flush(); | |
308 | packet_flush(); | |
edcc8581 | 309 | } |
edcc8581 LS |
310 | } |
311 | } |