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