37cecd865495f89f7e84d468393564f46ea68523
1#
2# Example implementation for the Git filter protocol version 2
3# See Documentation/gitattributes.txt, section "Filter Protocol"
4#
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).
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.
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.
31#
32
33use strict;
34use warnings;
35use IO::File;
36
37my $MAX_PACKET_CONTENT_SIZE = 65516;
38my $log_file = shift @ARGV;
39my @capabilities = @ARGV;
40
41open my $debug, ">>", $log_file or die "cannot open log file: $!";
42
43my %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
52sub rot13 {
53 my $str = shift;
54 $str =~ y/A-Za-z/N-ZA-Mn-za-m/;
55 return $str;
56}
57
58sub 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
72sub 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!
77 return ( -1, "" );
78 } elsif ( $bytes_read != 4 ) {
79 die "invalid packet: '$buffer'";
80 }
81 my $pkt_size = hex($buffer);
82 if ( $pkt_size == 0 ) {
83 return ( 1, "" );
84 } elsif ( $pkt_size > 4 ) {
85 my $content_size = $pkt_size - 4;
86 $bytes_read = read STDIN, $buffer, $content_size;
87 if ( $bytes_read != $content_size ) {
88 die "invalid packet ($content_size bytes expected; $bytes_read bytes read)";
89 }
90 return ( 0, $buffer );
91 } else {
92 die "invalid packet size: $pkt_size";
93 }
94}
95
96sub packet_txt_read {
97 my ( $res, $buf ) = packet_bin_read();
98 unless ( $res == -1 or $buf eq '' or $buf =~ s/\n$// ) {
99 die "A non-binary line MUST be terminated by an LF.";
100 }
101 return ( $res, $buf );
102}
103
104sub packet_required_key_val_read {
105 my ( $key ) = @_;
106 my ( $res, $buf ) = packet_txt_read();
107 unless ( $res == -1 or ( $buf =~ s/^$key=// and $buf ne '' ) ) {
108 die "bad $key: '$buf'";
109 }
110 return ( $res, $buf );
111}
112
113sub packet_bin_write {
114 my $buf = shift;
115 print STDOUT sprintf( "%04x", length($buf) + 4 );
116 print STDOUT $buf;
117 STDOUT->flush();
118}
119
120sub packet_txt_write {
121 packet_bin_write( $_[0] . "\n" );
122}
123
124sub packet_flush {
125 print STDOUT sprintf( "%04x", 0 );
126 STDOUT->flush();
127}
128
129print $debug "START\n";
130$debug->flush();
131
132packet_compare_lists([0, "git-filter-client"], packet_txt_read()) ||
133 die "bad initialize";
134packet_compare_lists([0, "version=2"], packet_txt_read()) ||
135 die "bad version";
136packet_compare_lists([1, ""], packet_bin_read()) ||
137 die "bad version end";
138
139packet_txt_write("git-filter-server");
140packet_txt_write("version=2");
141packet_flush();
142
143packet_compare_lists([0, "capability=clean"], packet_txt_read()) ||
144 die "bad capability";
145packet_compare_lists([0, "capability=smudge"], packet_txt_read()) ||
146 die "bad capability";
147packet_compare_lists([0, "capability=delay"], packet_txt_read()) ||
148 die "bad capability";
149packet_compare_lists([1, ""], packet_bin_read()) ||
150 die "bad capability end";
151
152foreach (@capabilities) {
153 packet_txt_write( "capability=" . $_ );
154}
155packet_flush();
156print $debug "init handshake complete\n";
157$debug->flush();
158
159while (1) {
160 my ( $res, $command ) = packet_required_key_val_read("command");
161 if ( $res == -1 ) {
162 print $debug "STOP\n";
163 exit();
164 }
165 print $debug "IN: $command";
166 $debug->flush();
167
168 if ( $command eq "list_available_blobs" ) {
169 # Flush
170 packet_compare_lists([1, ""], packet_bin_read()) ||
171 die "bad list_available_blobs end";
172
173 foreach my $pathname ( sort keys %DELAY ) {
174 if ( $DELAY{$pathname}{"requested"} >= 1 ) {
175 $DELAY{$pathname}{"count"} = $DELAY{$pathname}{"count"} - 1;
176 if ( $pathname eq "invalid-delay.a" ) {
177 # Send Git a pathname that was not delayed earlier
178 packet_txt_write("pathname=unfiltered");
179 }
180 if ( $pathname eq "missing-delay.a" ) {
181 # Do not signal Git that this file is available
182 } elsif ( $DELAY{$pathname}{"count"} == 0 ) {
183 print $debug " $pathname";
184 packet_txt_write("pathname=$pathname");
185 }
186 }
187 }
188
189 packet_flush();
190
191 print $debug " [OK]\n";
192 $debug->flush();
193 packet_txt_write("status=success");
194 packet_flush();
195 } else {
196 my ( $res, $pathname ) = packet_required_key_val_read("pathname");
197 if ( $res == -1 ) {
198 die "unexpected EOF while expecting pathname";
199 }
200 print $debug " $pathname";
201 $debug->flush();
202
203 # Read until flush
204 my ( $done, $buffer ) = packet_txt_read();
205 while ( $buffer ne '' ) {
206 if ( $buffer eq "can-delay=1" ) {
207 if ( exists $DELAY{$pathname} and $DELAY{$pathname}{"requested"} == 0 ) {
208 $DELAY{$pathname}{"requested"} = 1;
209 }
210 } else {
211 die "Unknown message '$buffer'";
212 }
213
214 ( $done, $buffer ) = packet_txt_read();
215 }
216 if ( $done == -1 ) {
217 die "unexpected EOF after pathname '$pathname'";
218 }
219
220 my $input = "";
221 {
222 binmode(STDIN);
223 my $buffer;
224 my $done = 0;
225 while ( !$done ) {
226 ( $done, $buffer ) = packet_bin_read();
227 $input .= $buffer;
228 }
229 if ( $done == -1 ) {
230 die "unexpected EOF while reading input for '$pathname'";
231 }
232 print $debug " " . length($input) . " [OK] -- ";
233 $debug->flush();
234 }
235
236 my $output;
237 if ( exists $DELAY{$pathname} and exists $DELAY{$pathname}{"output"} ) {
238 $output = $DELAY{$pathname}{"output"}
239 } elsif ( $pathname eq "error.r" or $pathname eq "abort.r" ) {
240 $output = "";
241 } elsif ( $command eq "clean" and grep( /^clean$/, @capabilities ) ) {
242 $output = rot13($input);
243 } elsif ( $command eq "smudge" and grep( /^smudge$/, @capabilities ) ) {
244 $output = rot13($input);
245 } else {
246 die "bad command '$command'";
247 }
248
249 if ( $pathname eq "error.r" ) {
250 print $debug "[ERROR]\n";
251 $debug->flush();
252 packet_txt_write("status=error");
253 packet_flush();
254 } elsif ( $pathname eq "abort.r" ) {
255 print $debug "[ABORT]\n";
256 $debug->flush();
257 packet_txt_write("status=abort");
258 packet_flush();
259 } elsif ( $command eq "smudge" and
260 exists $DELAY{$pathname} and
261 $DELAY{$pathname}{"requested"} == 1 ) {
262 print $debug "[DELAYED]\n";
263 $debug->flush();
264 packet_txt_write("status=delayed");
265 packet_flush();
266 $DELAY{$pathname}{"requested"} = 2;
267 $DELAY{$pathname}{"output"} = $output;
268 } else {
269 packet_txt_write("status=success");
270 packet_flush();
271
272 if ( $pathname eq "${command}-write-fail.r" ) {
273 print $debug "[WRITE FAIL]\n";
274 $debug->flush();
275 die "${command} write error";
276 }
277
278 print $debug "OUT: " . length($output) . " ";
279 $debug->flush();
280
281 while ( length($output) > 0 ) {
282 my $packet = substr( $output, 0, $MAX_PACKET_CONTENT_SIZE );
283 packet_bin_write($packet);
284 # dots represent the number of packets
285 print $debug ".";
286 if ( length($output) > $MAX_PACKET_CONTENT_SIZE ) {
287 $output = substr( $output, $MAX_PACKET_CONTENT_SIZE );
288 } else {
289 $output = "";
290 }
291 }
292 packet_flush();
293 print $debug " [OK]\n";
294 $debug->flush();
295 packet_flush();
296 }
297 }
298}