#!/usr/bin/perl

use FileHandle;
use IPC::Open2;
use Fcntl ':mode';

sub hash {
  my($type, $str, $file) = @_;
  if ($file) {
    open Reader, "git-hash-object -t $type \"$str\" |";
  } else {
    open2(*Reader, *Writer, "git-hash-object -t $type --stdin");
    print Writer $str;
    close Writer;
  }
  my $result = <Reader>;
  close Reader;
  chomp $result;
  return $result;
}

sub mktree {
  my($dir, $format) = @_;
  my $result = "";
  opendir DIR, $dir;
  my @files = sort {
    my($len1, $len2) = (length($a), length($b));
    my $len = $len1 < $len2 ? $len1 : $len2;
    my $cmp = (substr($a,0,$len) cmp substr($b,0,$len));
    return $cmp if ($cmp);
    my($c1, $c2) = (substr($a,$len,1), substr($b,$len,1));
    $c1 = '/' if (! $c1 && (-d "$dir/$a"));
    $c2 = '/' if (! $c2 && (-d "$dir/$b"));
    return ($c1 cmp $c2);
  } grep !/^(\.{1,2}|\.git)$/, readdir DIR; # Sort function ported from base_name_compare in read-cache.c
  closedir DIR;
  foreach my $file (@files) {
    my $mode = (lstat("$dir/$file"))[2];
    $mode &= S_IFMT unless (S_ISREG $mode);
    my $sha1;
    if (S_ISLNK $mode) {
      $sha1 = hash "blob", readlink("$dir/$file");
    } elsif (S_ISDIR $mode) {
      $sha1 = hash "tree", mktree("$dir/$file");
    } else {
      $sha1 = hash "blob", "$dir/$file", 1;
    }
    $result .= sprintf "%o %s\0%s", $mode, $file, pack("H[40]", $sha1);
  }
  return $result;
}

($#ARGV != 0) && die "missing argument";

my $dir = $ARGV[0];

(! -e $dir) && die "$dir doesn't exist";
(! -d $dir) && die "$dir is not a directory";

print hash("tree", mktree($dir)),"\n";
