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_fileor 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 { 53my$str=shift; 54$str=~y/A-Za-z/N-ZA-Mn-za-m/; 55return$str; 56} 57 58sub packet_compare_lists { 59my($expect,@result) =@_; 60my$ix; 61if(scalar@$expect!=scalar@result) { 62returnundef; 63} 64for($ix=0;$ix<$#result;$ix++) { 65if($expect->[$ix]ne$result[$ix]) { 66returnundef; 67} 68} 69return1; 70} 71 72sub packet_bin_read { 73my$buffer; 74my$bytes_read=read STDIN,$buffer,4; 75if($bytes_read==0) { 76# EOF - Git stopped talking to us! 77return( -1,""); 78}elsif($bytes_read!=4) { 79die"invalid packet: '$buffer'"; 80} 81my$pkt_size=hex($buffer); 82if($pkt_size==0) { 83return(1,""); 84}elsif($pkt_size>4) { 85my$content_size=$pkt_size-4; 86$bytes_read=read STDIN,$buffer,$content_size; 87if($bytes_read!=$content_size) { 88die"invalid packet ($content_sizebytes expected;$bytes_readbytes read)"; 89} 90return(0,$buffer); 91}else{ 92die"invalid packet size:$pkt_size"; 93} 94} 95 96sub packet_txt_read { 97my($res,$buf) = packet_bin_read(); 98unless($res== -1or$bufeq''or$buf=~s/\n$//) { 99die"A non-binary line MUST be terminated by an LF."; 100} 101return($res,$buf); 102} 103 104sub packet_required_key_val_read { 105my($key) =@_; 106my($res,$buf) = packet_txt_read(); 107unless($res== -1or($buf=~s/^$key=//and$bufne'') ) { 108die"bad$key: '$buf'"; 109} 110return($res,$buf); 111} 112 113sub packet_bin_write { 114my$buf=shift; 115print STDOUT sprintf("%04x",length($buf) +4); 116print STDOUT $buf; 117 STDOUT->flush(); 118} 119 120sub packet_txt_write { 121 packet_bin_write($_[0] ."\n"); 122} 123 124sub packet_flush { 125print 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()) || 133die"bad initialize"; 134packet_compare_lists([0,"version=2"], packet_txt_read()) || 135die"bad version"; 136packet_compare_lists([1,""], packet_bin_read()) || 137die"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()) || 144die"bad capability"; 145packet_compare_lists([0,"capability=smudge"], packet_txt_read()) || 146die"bad capability"; 147packet_compare_lists([0,"capability=delay"], packet_txt_read()) || 148die"bad capability"; 149packet_compare_lists([1,""], packet_bin_read()) || 150die"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) { 160my($res,$command) = packet_required_key_val_read("command"); 161if($res== -1) { 162print$debug"STOP\n"; 163exit(); 164} 165print$debug"IN:$command"; 166$debug->flush(); 167 168if($commandeq"list_available_blobs") { 169# Flush 170 packet_compare_lists([1,""], packet_bin_read()) || 171die"bad list_available_blobs end"; 172 173foreachmy$pathname(sort keys%DELAY) { 174if($DELAY{$pathname}{"requested"} >=1) { 175$DELAY{$pathname}{"count"} =$DELAY{$pathname}{"count"} -1; 176if($pathnameeq"invalid-delay.a") { 177# Send Git a pathname that was not delayed earlier 178 packet_txt_write("pathname=unfiltered"); 179} 180if($pathnameeq"missing-delay.a") { 181# Do not signal Git that this file is available 182}elsif($DELAY{$pathname}{"count"} ==0) { 183print$debug"$pathname"; 184 packet_txt_write("pathname=$pathname"); 185} 186} 187} 188 189 packet_flush(); 190 191print$debug" [OK]\n"; 192$debug->flush(); 193 packet_txt_write("status=success"); 194 packet_flush(); 195}else{ 196my($res,$pathname) = packet_required_key_val_read("pathname"); 197if($res== -1) { 198die"unexpected EOF while expecting pathname"; 199} 200print$debug"$pathname"; 201$debug->flush(); 202 203# Read until flush 204my($done,$buffer) = packet_txt_read(); 205while($bufferne'') { 206if($buffereq"can-delay=1") { 207if(exists$DELAY{$pathname}and$DELAY{$pathname}{"requested"} ==0) { 208$DELAY{$pathname}{"requested"} =1; 209} 210}else{ 211die"Unknown message '$buffer'"; 212} 213 214($done,$buffer) = packet_txt_read(); 215} 216if($done== -1) { 217die"unexpected EOF after pathname '$pathname'"; 218} 219 220my$input=""; 221{ 222binmode(STDIN); 223my$buffer; 224my$done=0; 225while( !$done) { 226($done,$buffer) = packet_bin_read(); 227$input.=$buffer; 228} 229if($done== -1) { 230die"unexpected EOF while reading input for '$pathname'"; 231} 232print$debug" ".length($input) ." [OK] -- "; 233$debug->flush(); 234} 235 236my$output; 237if(exists$DELAY{$pathname}and exists$DELAY{$pathname}{"output"} ) { 238$output=$DELAY{$pathname}{"output"} 239}elsif($pathnameeq"error.r"or$pathnameeq"abort.r") { 240$output=""; 241}elsif($commandeq"clean"and grep(/^clean$/,@capabilities) ) { 242$output= rot13($input); 243}elsif($commandeq"smudge"and grep(/^smudge$/,@capabilities) ) { 244$output= rot13($input); 245}else{ 246die"bad command '$command'"; 247} 248 249if($pathnameeq"error.r") { 250print$debug"[ERROR]\n"; 251$debug->flush(); 252 packet_txt_write("status=error"); 253 packet_flush(); 254}elsif($pathnameeq"abort.r") { 255print$debug"[ABORT]\n"; 256$debug->flush(); 257 packet_txt_write("status=abort"); 258 packet_flush(); 259}elsif($commandeq"smudge"and 260exists$DELAY{$pathname}and 261$DELAY{$pathname}{"requested"} ==1) { 262print$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 272if($pathnameeq"${command}-write-fail.r") { 273print$debug"[WRITE FAIL]\n"; 274$debug->flush(); 275die"${command} write error"; 276} 277 278print$debug"OUT: ".length($output) ." "; 279$debug->flush(); 280 281while(length($output) >0) { 282my$packet=substr($output,0,$MAX_PACKET_CONTENT_SIZE); 283 packet_bin_write($packet); 284# dots represent the number of packets 285print$debug"."; 286if(length($output) >$MAX_PACKET_CONTENT_SIZE) { 287$output=substr($output,$MAX_PACKET_CONTENT_SIZE); 288}else{ 289$output=""; 290} 291} 292 packet_flush(); 293print$debug" [OK]\n"; 294$debug->flush(); 295 packet_flush(); 296} 297} 298}