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_bin_read { 59my$buffer; 60my$bytes_read=read STDIN,$buffer,4; 61if($bytes_read==0) { 62# EOF - Git stopped talking to us! 63print$debug"STOP\n"; 64exit(); 65} 66elsif($bytes_read!=4) { 67die"invalid packet: '$buffer'"; 68} 69my$pkt_size=hex($buffer); 70if($pkt_size==0) { 71return(1,""); 72} 73elsif($pkt_size>4) { 74my$content_size=$pkt_size-4; 75$bytes_read=read STDIN,$buffer,$content_size; 76if($bytes_read!=$content_size) { 77die"invalid packet ($content_sizebytes expected;$bytes_readbytes read)"; 78} 79return(0,$buffer); 80} 81else{ 82die"invalid packet size:$pkt_size"; 83} 84} 85 86sub packet_txt_read { 87my($res,$buf) = packet_bin_read(); 88unless($bufeq''or$buf=~s/\n$//) { 89die"A non-binary line MUST be terminated by an LF."; 90} 91return($res,$buf); 92} 93 94sub packet_bin_write { 95my$buf=shift; 96print STDOUT sprintf("%04x",length($buf) +4); 97print STDOUT $buf; 98 STDOUT->flush(); 99} 100 101sub packet_txt_write { 102 packet_bin_write($_[0] ."\n"); 103} 104 105sub packet_flush { 106print STDOUT sprintf("%04x",0); 107 STDOUT->flush(); 108} 109 110print$debug"START\n"; 111$debug->flush(); 112 113( packet_txt_read()eq(0,"git-filter-client") ) ||die"bad initialize"; 114( packet_txt_read()eq(0,"version=2") ) ||die"bad version"; 115( packet_bin_read()eq(1,"") ) ||die"bad version end"; 116 117packet_txt_write("git-filter-server"); 118packet_txt_write("version=2"); 119packet_flush(); 120 121( packet_txt_read()eq(0,"capability=clean") ) ||die"bad capability"; 122( packet_txt_read()eq(0,"capability=smudge") ) ||die"bad capability"; 123( packet_txt_read()eq(0,"capability=delay") ) ||die"bad capability"; 124( packet_bin_read()eq(1,"") ) ||die"bad capability end"; 125 126foreach(@capabilities) { 127 packet_txt_write("capability=".$_); 128} 129packet_flush(); 130print$debug"init handshake complete\n"; 131$debug->flush(); 132 133while(1) { 134my($command) = packet_txt_read() =~/^command=(.+)$/; 135print$debug"IN:$command"; 136$debug->flush(); 137 138if($commandeq"list_available_blobs") { 139# Flush 140 packet_bin_read(); 141 142foreachmy$pathname(sort keys%DELAY) { 143if($DELAY{$pathname}{"requested"} >=1) { 144$DELAY{$pathname}{"count"} =$DELAY{$pathname}{"count"} -1; 145if($pathnameeq"invalid-delay.a") { 146# Send Git a pathname that was not delayed earlier 147 packet_txt_write("pathname=unfiltered"); 148} 149if($pathnameeq"missing-delay.a") { 150# Do not signal Git that this file is available 151}elsif($DELAY{$pathname}{"count"} ==0) { 152print$debug"$pathname"; 153 packet_txt_write("pathname=$pathname"); 154} 155} 156} 157 158 packet_flush(); 159 160print$debug" [OK]\n"; 161$debug->flush(); 162 packet_txt_write("status=success"); 163 packet_flush(); 164} 165else{ 166my($pathname) = packet_txt_read() =~/^pathname=(.+)$/; 167print$debug"$pathname"; 168$debug->flush(); 169 170if($pathnameeq"") { 171die"bad pathname '$pathname'"; 172} 173 174# Read until flush 175my($done,$buffer) = packet_txt_read(); 176while($bufferne'') { 177if($buffereq"can-delay=1") { 178if(exists$DELAY{$pathname}and$DELAY{$pathname}{"requested"} ==0) { 179$DELAY{$pathname}{"requested"} =1; 180} 181}else{ 182die"Unknown message '$buffer'"; 183} 184 185($done,$buffer) = packet_txt_read(); 186} 187 188my$input=""; 189{ 190binmode(STDIN); 191my$buffer; 192my$done=0; 193while( !$done) { 194($done,$buffer) = packet_bin_read(); 195$input.=$buffer; 196} 197print$debug" ".length($input) ." [OK] -- "; 198$debug->flush(); 199} 200 201my$output; 202if(exists$DELAY{$pathname}and exists$DELAY{$pathname}{"output"} ) { 203$output=$DELAY{$pathname}{"output"} 204} 205elsif($pathnameeq"error.r"or$pathnameeq"abort.r") { 206$output=""; 207} 208elsif($commandeq"clean"and grep(/^clean$/,@capabilities) ) { 209$output= rot13($input); 210} 211elsif($commandeq"smudge"and grep(/^smudge$/,@capabilities) ) { 212$output= rot13($input); 213} 214else{ 215die"bad command '$command'"; 216} 217 218if($pathnameeq"error.r") { 219print$debug"[ERROR]\n"; 220$debug->flush(); 221 packet_txt_write("status=error"); 222 packet_flush(); 223} 224elsif($pathnameeq"abort.r") { 225print$debug"[ABORT]\n"; 226$debug->flush(); 227 packet_txt_write("status=abort"); 228 packet_flush(); 229} 230elsif($commandeq"smudge"and 231exists$DELAY{$pathname}and 232$DELAY{$pathname}{"requested"} ==1 233) { 234print$debug"[DELAYED]\n"; 235$debug->flush(); 236 packet_txt_write("status=delayed"); 237 packet_flush(); 238$DELAY{$pathname}{"requested"} =2; 239$DELAY{$pathname}{"output"} =$output; 240} 241else{ 242 packet_txt_write("status=success"); 243 packet_flush(); 244 245if($pathnameeq"${command}-write-fail.r") { 246print$debug"[WRITE FAIL]\n"; 247$debug->flush(); 248die"${command} write error"; 249} 250 251print$debug"OUT: ".length($output) ." "; 252$debug->flush(); 253 254while(length($output) >0) { 255my$packet=substr($output,0,$MAX_PACKET_CONTENT_SIZE); 256 packet_bin_write($packet); 257# dots represent the number of packets 258print$debug"."; 259if(length($output) >$MAX_PACKET_CONTENT_SIZE) { 260$output=substr($output,$MAX_PACKET_CONTENT_SIZE); 261} 262else{ 263$output=""; 264} 265} 266 packet_flush(); 267print$debug" [OK]\n"; 268$debug->flush(); 269 packet_flush(); 270} 271} 272}