0b943bb3776d2616a1c6793a7a31b6b6949fc8c6
   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#
  22
  23use strict;
  24use warnings;
  25use IO::File;
  26
  27my $MAX_PACKET_CONTENT_SIZE = 65516;
  28my $log_file                = shift @ARGV;
  29my @capabilities            = @ARGV;
  30
  31open my $debug, ">>", $log_file or die "cannot open log file: $!";
  32
  33sub rot13 {
  34        my $str = shift;
  35        $str =~ y/A-Za-z/N-ZA-Mn-za-m/;
  36        return $str;
  37}
  38
  39sub packet_bin_read {
  40        my $buffer;
  41        my $bytes_read = read STDIN, $buffer, 4;
  42        if ( $bytes_read == 0 ) {
  43                # EOF - Git stopped talking to us!
  44                print $debug "STOP\n";
  45                exit();
  46        }
  47        elsif ( $bytes_read != 4 ) {
  48                die "invalid packet: '$buffer'";
  49        }
  50        my $pkt_size = hex($buffer);
  51        if ( $pkt_size == 0 ) {
  52                return ( 1, "" );
  53        }
  54        elsif ( $pkt_size > 4 ) {
  55                my $content_size = $pkt_size - 4;
  56                $bytes_read = read STDIN, $buffer, $content_size;
  57                if ( $bytes_read != $content_size ) {
  58                        die "invalid packet ($content_size bytes expected; $bytes_read bytes read)";
  59                }
  60                return ( 0, $buffer );
  61        }
  62        else {
  63                die "invalid packet size: $pkt_size";
  64        }
  65}
  66
  67sub packet_txt_read {
  68        my ( $res, $buf ) = packet_bin_read();
  69        unless ( $buf =~ s/\n$// ) {
  70                die "A non-binary line MUST be terminated by an LF.";
  71        }
  72        return ( $res, $buf );
  73}
  74
  75sub packet_bin_write {
  76        my $buf = shift;
  77        print STDOUT sprintf( "%04x", length($buf) + 4 );
  78        print STDOUT $buf;
  79        STDOUT->flush();
  80}
  81
  82sub packet_txt_write {
  83        packet_bin_write( $_[0] . "\n" );
  84}
  85
  86sub packet_flush {
  87        print STDOUT sprintf( "%04x", 0 );
  88        STDOUT->flush();
  89}
  90
  91print $debug "START\n";
  92$debug->flush();
  93
  94( packet_txt_read() eq ( 0, "git-filter-client" ) ) || die "bad initialize";
  95( packet_txt_read() eq ( 0, "version=2" ) )         || die "bad version";
  96( packet_bin_read() eq ( 1, "" ) )                  || die "bad version end";
  97
  98packet_txt_write("git-filter-server");
  99packet_txt_write("version=2");
 100packet_flush();
 101
 102( packet_txt_read() eq ( 0, "capability=clean" ) )  || die "bad capability";
 103( packet_txt_read() eq ( 0, "capability=smudge" ) ) || die "bad capability";
 104( packet_bin_read() eq ( 1, "" ) )                  || die "bad capability end";
 105
 106foreach (@capabilities) {
 107        packet_txt_write( "capability=" . $_ );
 108}
 109packet_flush();
 110print $debug "init handshake complete\n";
 111$debug->flush();
 112
 113while (1) {
 114        my ($command) = packet_txt_read() =~ /^command=(.+)$/;
 115        print $debug "IN: $command";
 116        $debug->flush();
 117
 118        my ($pathname) = packet_txt_read() =~ /^pathname=(.+)$/;
 119        print $debug " $pathname";
 120        $debug->flush();
 121
 122        if ( $pathname eq "" ) {
 123                die "bad pathname '$pathname'";
 124        }
 125
 126        # Flush
 127        packet_bin_read();
 128
 129        my $input = "";
 130        {
 131                binmode(STDIN);
 132                my $buffer;
 133                my $done = 0;
 134                while ( !$done ) {
 135                        ( $done, $buffer ) = packet_bin_read();
 136                        $input .= $buffer;
 137                }
 138                print $debug " " . length($input) . " [OK] -- ";
 139                $debug->flush();
 140        }
 141
 142        my $output;
 143        if ( $pathname eq "error.r" or $pathname eq "abort.r" ) {
 144                $output = "";
 145        }
 146        elsif ( $command eq "clean" and grep( /^clean$/, @capabilities ) ) {
 147                $output = rot13($input);
 148        }
 149        elsif ( $command eq "smudge" and grep( /^smudge$/, @capabilities ) ) {
 150                $output = rot13($input);
 151        }
 152        else {
 153                die "bad command '$command'";
 154        }
 155
 156        print $debug "OUT: " . length($output) . " ";
 157        $debug->flush();
 158
 159        if ( $pathname eq "error.r" ) {
 160                print $debug "[ERROR]\n";
 161                $debug->flush();
 162                packet_txt_write("status=error");
 163                packet_flush();
 164        }
 165        elsif ( $pathname eq "abort.r" ) {
 166                print $debug "[ABORT]\n";
 167                $debug->flush();
 168                packet_txt_write("status=abort");
 169                packet_flush();
 170        }
 171        else {
 172                packet_txt_write("status=success");
 173                packet_flush();
 174
 175                if ( $pathname eq "${command}-write-fail.r" ) {
 176                        print $debug "[WRITE FAIL]\n";
 177                        $debug->flush();
 178                        die "${command} write error";
 179                }
 180
 181                while ( length($output) > 0 ) {
 182                        my $packet = substr( $output, 0, $MAX_PACKET_CONTENT_SIZE );
 183                        packet_bin_write($packet);
 184                        # dots represent the number of packets
 185                        print $debug ".";
 186                        if ( length($output) > $MAX_PACKET_CONTENT_SIZE ) {
 187                                $output = substr( $output, $MAX_PACKET_CONTENT_SIZE );
 188                        }
 189                        else {
 190                                $output = "";
 191                        }
 192                }
 193                packet_flush();
 194                print $debug " [OK]\n";
 195                $debug->flush();
 196                packet_flush();
 197        }
 198}