#!/usr/bin/perl
#
# Converts a git repository from hex to base32
#

use Compress::Zlib;
use Digest::SHA1 qw(sha1 sha1_hex);
use bytes;

$flat = 1;			# Set to 1 to use flat base64 representation

sub sha1_base32($)
{
    my($data) = @_;
    my(@hash) = unpack("C*", sha1($data));
    my($i, $c, $b, $v, $n);
    my($out) = '';

    die unless ( scalar(@hash) == 20 );

    $b = 0;
    $v = 0;
    foreach $c ( @hash ) {
	$v = ($v << 8) + $c;
	$b += 8;
	
	while ( $b >= 5 ) {
	    $b -= 5;
	    $n = ($v >> $b) & 0x1f;
	    $out .= substr("abcdefghijklmnopqrstuvwxyz234567", $n, 1);
	}
    }

    die unless ( $b == 0 && length($out) == 32 );

    return $out;
}

sub get_hex_file($$)
{
    my($root, $hex) = @_;
    my($h1) = substr($hex,0,2);
    my($h2) = substr($hex,2);
    my($data, @st, $sig);

    open(HX, '<', "$root/$h1/$h2") or die "$0: $root/$h1/$h2: $!\n";
    @st = stat(HX);
    if ( read(HX, $data, $st[7]) != $st[7] ) {
	die "$0: $root/$h1/$h2: read error: $!\n";
    }

    $sig = sha1_hex($data);

    close(HX);
    
    if ( $sig ne $hex ) {
	die "$0: $root/$h1/$h2 doesn\'t match its own signature: $sig\n";
    }
    
    return $data;
}

sub write_file($$)
{
    my($root, $data) = @_;

    my($name) = sha1_base32($data);
    $name =~ tr/\//_/;		# Hybrid of regular and filesystem safe base64

    my($pf);

    if ( $flat ) {
	$pf = $name;
    } else {
	my($px) = substr($name,0,2);
	$pf = $px.'/'.substr($name,2);
	mkdir("$root/$px", 0777);
    }
    open(WF, '>', "$root/$pf") or die "$0: $root/$pf: $!\n";
    if ( !print WF $data ) {
	die "$0: $root/$pf: write error: $!\n";
    }
    close(WF);

    return $name;
}

sub handle_file($$$)
{
    my($root, $newroot, $hex) = @_;
    my($cdata, $data, $tdata, $odata, $object_type, $he);

    $cdata = get_hex_file($root, $hex);
    $data = uncompress($cdata);
    if ( !defined($data) ) {
	die "$0: $hex: failed to uncompress\n";
    }
    $data =~ /^((\S+) ([0-9]+)\0)/s;
    $tdata = $1;
    $odata = "$'";
    $object_type = $2;

    # Did we already do this file?
    $he = $hash_equiv{$hex};
    return $he if ( defined($he) );

    if ( $object_type eq 'commit' ) {
	while ( $odata =~ /^(\S+ )([0-9a-f]{40})$/m ) {
	    my($h) = $2;
	    my($pre) = $`.$1;
	    my($suf) = "$'";
	    # Make sure we process precedents
	    $he = handle_file($root, $newroot, $h);
	    if ( !defined($he) ) {
		die "$0: unhandled hash: $h\n";
	    }
	    $odata = $pre.$he.$suf;
	}
	$cdata = compress($tdata.$odata, Z_BEST_COMPRESSION);
	if ( !defined($data) ) {
	    die "$0: $hex: failed to compress\n";
	}
    }
    
    $he = write_file($newroot, $cdata);
    $hash_equiv{$hex} = $he;

    return $he;
}

sub recurse($$)
{
    my($root, $newroot) = @_;
    my($i,$x,$e);

    %hash_equiv = ();

    for ( $i = 0 ; $i < 256 ; $i++ ) {
	$x = sprintf("%02x", $i);
	next unless ( opendir(RDIR, "$root/$x") );
	while ( $e = readdir(RDIR) ) {
	    if ( $e =~ /^[0-9a-f]{38}$/ ) {
		handle_file($root, $newroot, "$x$e");
	    }
	}
	closedir(RDIR);
    }
}

($old_root, $new_root) = @ARGV;
mkdir($new_root, 0777) or die "$0: cannot mkdir $new_root: $!\n";
mkdir("$new_root/objects", 0777) or die "$0: cannot mkdir $new_root/objects: $!\n";

recurse("$old_root/objects", "$new_root/objects");

open(HEAD, '<', "$old_root/HEAD") or exit 0;
$head = <HEAD>;
chomp $head;
if ( defined($hash_equiv{$head}) ) {
    $head = $hash_equiv{$head};
} else {
    print STDERR "Warning: HEAD not found: $head\n";
    exit 1;
}
close(HEAD);
open(NEWHEAD, '>', "$new_root/HEAD") or die "$0: cannot write new HEAD file\n";
print NEWHEAD $head, "\n";
close(NEWHEAD);