Fix memory leak in "git rev-list --objects"
[gitweb.git] / git-cvsserver.perl
index d641c037aac9dde755ee82ff26b9ab9398439933..5ccca4f99f31b9fa370baaf7e85795ed2bf49f13 100755 (executable)
 my $TEMP_DIR = tempdir( CLEANUP => 1 );
 $log->debug("Temporary directory is '$TEMP_DIR'");
 
+# if we are called with a pserver argument,
+# deal with the authentication cat before entering the
+# main loop
+if (@ARGV && $ARGV[0] eq 'pserver') {
+    my $line = <STDIN>; chomp $line;
+    unless( $line eq 'BEGIN AUTH REQUEST') {
+       die "E Do not understand $line - expecting BEGIN AUTH REQUEST\n";
+    }
+    $line = <STDIN>; chomp $line;
+    req_Root('root', $line) # reuse Root
+       or die "E Invalid root $line \n";
+    $line = <STDIN>; chomp $line;
+    unless ($line eq 'anonymous') {
+       print "E Only anonymous user allowed via pserver\n";
+       print "I HATE YOU\n";
+    }
+    $line = <STDIN>; chomp $line;    # validate the password?
+    $line = <STDIN>; chomp $line;
+    unless ($line eq 'END AUTH REQUEST') {
+       die "E Do not understand $line -- expecting END AUTH REQUEST\n";
+    }
+    print "I LOVE YOU\n";
+    # and now back to our regular programme...
+}
+
 # Keep going until the client closes the connection
 while (<STDIN>)
 {
     chomp;
 
-    # Check to see if we've seen this method, and call appropiate function.
+    # Check to see if we've seen this method, and call appropriate function.
     if ( /^([\w-]+)(?:\s+(.*))?$/ and defined($methods->{$1}) )
     {
         # use the $methods hash to call the appropriate sub for this command
@@ -139,8 +164,21 @@ sub req_Root
     $state->{CVSROOT} = $data;
 
     $ENV{GIT_DIR} = $state->{CVSROOT} . "/";
+    unless (-d $ENV{GIT_DIR} && -e $ENV{GIT_DIR}.'HEAD') {
+       print "E $ENV{GIT_DIR} does not seem to be a valid GIT repository\n";
+        print "E \n";
+        print "error 1 $ENV{GIT_DIR} is not a valid repository\n";
+       return 0;
+    }
 
-    foreach my $line ( `git-var -l` )
+    my @gitvars = `git-repo-config -l`;
+    if ($?) {
+       print "E problems executing git-repo-config on the server -- this is not a git repository or the PATH is not set correctly.\n";
+        print "E \n";
+        print "error 1 - problem executing git-repo-config\n";
+       return 0;
+    }
+    foreach my $line ( @gitvars )
     {
         next unless ( $line =~ /^(.*?)\.(.*?)=(.*)$/ );
         $cfg->{$1}{$2} = $3;
@@ -152,6 +190,7 @@ sub req_Root
         print "E the repo config file needs a [gitcvs] section added, and the parameter 'enabled' set to 1\n";
         print "E \n";
         print "error 1 GITCVS emulation disabled\n";
+        return 0;
     }
 
     if ( defined ( $cfg->{gitcvs}{logfile} ) )
@@ -160,6 +199,8 @@ sub req_Root
     } else {
         $log->nofile();
     }
+
+    return 1;
 }
 
 # Global_option option \n
@@ -173,8 +214,7 @@ sub req_Globaloption
 {
     my ( $cmd, $data ) = @_;
     $log->debug("req_Globaloption : $data");
-
-    # TODO : is this data useful ???
+    $state->{globaloptions}{$data} = 1;
 }
 
 # Valid-responses request-list \n
@@ -183,7 +223,7 @@ sub req_Globaloption
 sub req_Validresponses
 {
     my ( $cmd, $data ) = @_;
-    $log->debug("req_Validrepsonses : $data");
+    $log->debug("req_Validresponses : $data");
 
     # TODO : re-enable this, currently it's not particularly useful
     #$state->{validresponses} = [ split /\s+/, $data ];
@@ -226,12 +266,32 @@ sub req_Directory
 
     $state->{localdir} = $data;
     $state->{repository} = $repository;
-    $state->{directory} = $repository;
-    $state->{directory} =~ s/^$state->{CVSROOT}\///;
-    $state->{module} = $1 if ($state->{directory} =~ s/^(.*?)(\/|$)//);
+    $state->{path} = $repository;
+    $state->{path} =~ s/^$state->{CVSROOT}\///;
+    $state->{module} = $1 if ($state->{path} =~ s/^(.*?)(\/|$)//);
+    $state->{path} .= "/" if ( $state->{path} =~ /\S/ );
+
+    $state->{directory} = $state->{localdir};
+    $state->{directory} = "" if ( $state->{directory} eq "." );
     $state->{directory} .= "/" if ( $state->{directory} =~ /\S/ );
 
-    $log->debug("req_Directory : localdir=$data repository=$repository directory=$state->{directory} module=$state->{module}");
+    if ( not defined($state->{prependdir}) and $state->{localdir} eq "." and $state->{path} =~ /\S/ )
+    {
+        $log->info("Setting prepend to '$state->{path}'");
+        $state->{prependdir} = $state->{path};
+        foreach my $entry ( keys %{$state->{entries}} )
+        {
+            $state->{entries}{$state->{prependdir} . $entry} = $state->{entries}{$entry};
+            delete $state->{entries}{$entry};
+        }
+    }
+
+    if ( defined ( $state->{prependdir} ) )
+    {
+        $log->debug("Prepending '$state->{prependdir}' to state|directory");
+        $state->{directory} = $state->{prependdir} . $state->{directory}
+    }
+    $log->debug("req_Directory : localdir=$data repository=$repository path=$state->{path} directory=$state->{directory} module=$state->{module}");
 }
 
 # Entry entry-line \n
@@ -249,7 +309,7 @@ sub req_Entry
 {
     my ( $cmd, $data ) = @_;
 
-    $log->debug("req_Entry : $data");
+    #$log->debug("req_Entry : $data");
 
     my @data = split(/\//, $data);
 
@@ -259,6 +319,22 @@ sub req_Entry
         options     => $data[4],
         tag_or_date => $data[5],
     };
+
+    $log->info("Received entry line '$data' => '" . $state->{directory} . $data[1] . "'");
+}
+
+# Questionable filename \n
+#     Response expected: no. Additional data: no. Tell the server to check
+#     whether filename should be ignored, and if not, next time the server
+#     sends responses, send (in a M response) `?' followed by the directory and
+#     filename. filename must not contain `/'; it needs to be a file in the
+#     directory named by the most recent Directory request.
+sub req_Questionable
+{
+    my ( $cmd, $data ) = @_;
+
+    $log->debug("req_Questionable : $data");
+    $state->{entries}{$state->{directory}.$data}{questionable} = 1;
 }
 
 # add \n
@@ -291,8 +367,7 @@ sub req_add
             next;
         }
 
-
-        my ( $filepart, $dirpart ) = filenamesplit($filename);
+        my ( $filepart, $dirpart ) = filenamesplit($filename, 1);
 
         print "E cvs add: scheduling file `$filename' for addition\n";
 
@@ -373,7 +448,7 @@ sub req_remove
         }
 
 
-        my ( $filepart, $dirpart ) = filenamesplit($filename);
+        my ( $filepart, $dirpart ) = filenamesplit($filename, 1);
 
         print "E cvs remove: scheduling `$filename' for removal\n";
 
@@ -461,22 +536,6 @@ sub req_Unchanged
     #$log->debug("req_Unchanged : $data");
 }
 
-# Questionable filename \n
-#     Response expected: no. Additional data: no.
-#     Tell the server to check whether filename should be ignored,
-#     and if not, next time the server sends responses, send (in
-#     a M response) `?' followed by the directory and filename.
-#     filename must not contain `/'; it needs to be a file in the
-#     directory named by the most recent Directory request.
-sub req_Questionable
-{
-    my ( $cmd, $data ) = @_;
-
-    $state->{entries}{$state->{directory}.$data}{questionable} = 1;
-
-    #$log->debug("req_Questionable : $data");
-}
-
 # Argument text \n
 #     Response expected: no. Save argument for use in a subsequent command.
 #     Arguments accumulate until an argument-using command is given, at which
@@ -576,14 +635,57 @@ sub req_co
     # Eclipse seems to need the Clear-sticky command
     # to prepare the 'Entries' file for the new directory.
     print "Clear-sticky $checkout_path/\n";
-    print $state->{CVSROOT} . "/$checkout_path/\n";
+    print $state->{CVSROOT} . "/$module/\n";
     print "Clear-static-directory $checkout_path/\n";
-    print $state->{CVSROOT} . "/$checkout_path/\n";
+    print $state->{CVSROOT} . "/$module/\n";
+    print "Clear-sticky $checkout_path/\n"; # yes, twice
+    print $state->{CVSROOT} . "/$module/\n";
+    print "Template $checkout_path/\n";
+    print $state->{CVSROOT} . "/$module/\n";
+    print "0\n";
 
     # instruct the client that we're checking out to $checkout_path
     print "E cvs checkout: Updating $checkout_path\n";
 
     my %seendirs = ();
+    my $lastdir ='';
+
+    # recursive
+    sub prepdir {
+       my ($dir, $repodir, $remotedir, $seendirs) = @_;
+       my $parent = dirname($dir);
+       $dir       =~ s|/+$||;
+       $repodir   =~ s|/+$||;
+       $remotedir =~ s|/+$||;
+       $parent    =~ s|/+$||;
+       $log->debug("announcedir $dir, $repodir, $remotedir" );
+
+       if ($parent eq '.' || $parent eq './') {
+           $parent = '';
+       }
+       # recurse to announce unseen parents first
+       if (length($parent) && !exists($seendirs->{$parent})) {
+           prepdir($parent, $repodir, $remotedir, $seendirs);
+       }
+       # Announce that we are going to modify at the parent level
+       if ($parent) {
+           print "E cvs checkout: Updating $remotedir/$parent\n";
+       } else {
+           print "E cvs checkout: Updating $remotedir\n";
+       }
+       print "Clear-sticky $remotedir/$parent/\n";
+       print "$repodir/$parent/\n";
+
+       print "Clear-static-directory $remotedir/$dir/\n";
+       print "$repodir/$dir/\n";
+       print "Clear-sticky $remotedir/$parent/\n"; # yes, twice
+       print "$repodir/$parent/\n";
+       print "Template $remotedir/$dir/\n";
+       print "$repodir/$dir/\n";
+       print "0\n";
+
+       $seendirs->{$dir} = 1;
+    }
 
     foreach my $git ( @{$updater->gethead} )
     {
@@ -592,6 +694,17 @@ sub req_co
 
         ( $git->{name}, $git->{dir} ) = filenamesplit($git->{name});
 
+       if (length($git->{dir}) && $git->{dir} ne './'
+           && $git->{dir} ne $lastdir ) {
+           unless (exists($seendirs{$git->{dir}})) {
+               prepdir($git->{dir}, $state->{CVSROOT} . "/$module/",
+                       $checkout_path, \%seendirs);
+               $lastdir = $git->{dir};
+               $seendirs{$git->{dir}} = 1;
+           }
+           print "E cvs checkout: Updating /$checkout_path/$git->{dir}\n";
+       }
+
         # modification time of this file
         print "Mod-time $git->{modified}\n";
 
@@ -603,22 +716,10 @@ sub req_co
             print "M U $checkout_path/$git->{name}\n";
         }
 
-       if (length($git->{dir}) && $git->{dir} ne './' && !exists($seendirs{$git->{dir}})) {
-
-           # Eclipse seems to need the Clear-sticky command
-           # to prepare the 'Entries' file for the new directory.
-           print "Clear-sticky $module/$git->{dir}\n";
-           print $state->{CVSROOT} . "/$module/$git->{dir}\n";
-           print "Clear-static-directory $module/$git->{dir}\n";
-           print $state->{CVSROOT} . "/$module/$git->{dir}\n";
-           print "E cvs checkout: Updating /$module/$git->{dir}\n";
-           $seendirs{$git->{dir}} = 1;
-       }
-
-        # instruct client we're sending a file to put in this path
-        print "Created $checkout_path/" . ( defined ( $git->{dir} ) and $git->{dir} ne "./" ? $git->{dir} . "/" : "" ) . "\n";
+       # instruct client we're sending a file to put in this path
+       print "Created $checkout_path/" . ( defined ( $git->{dir} ) and $git->{dir} ne "./" ? $git->{dir} . "/" : "" ) . "\n";
 
-        print $state->{CVSROOT} . "/$module/" . ( defined ( $git->{dir} ) and $git->{dir} ne "./" ? $git->{dir} . "/" : "" ) . "$git->{name}\n";
+       print $state->{CVSROOT} . "/$module/" . ( defined ( $git->{dir} ) and $git->{dir} ne "./" ? $git->{dir} . "/" : "" ) . "$git->{name}\n";
 
         # this is an "entries" line
         print "/$git->{name}/1.$git->{revision}///\n";
@@ -650,7 +751,7 @@ sub req_update
     argsplit("update");
 
     #
-    # It may just be a client exploring the available heads/modukles
+    # It may just be a client exploring the available heads/modules
     # in that case, list them as top level directories and leave it
     # at that. Eclipse uses this technique to offer you a list of
     # projects (heads in this case) to checkout.
@@ -674,8 +775,7 @@ sub req_update
 
     $updater->update();
 
-    # if no files were specified, we need to work out what files we should be providing status on ...
-    argsfromdir($updater) if ( scalar ( @{$state->{args}} ) == 0 );
+    argsfromdir($updater);
 
     #$log->debug("update state : " . Dumper($state));
 
@@ -684,6 +784,8 @@ sub req_update
     {
         $filename = filecleanup($filename);
 
+        $log->debug("Processing file $filename");
+
         # if we have a -C we should pretend we never saw modified stuff
         if ( exists ( $state->{opt}{C} ) )
         {
@@ -714,20 +816,43 @@ sub req_update
 
         #$log->debug("Target revision is $meta->{revision}, current working revision is $wrev");
 
-        # Files are up to date if the working copy and repo copy have the same revision, and the working copy is unmodified _and_ the user hasn't specified -C
-        next if ( defined ( $wrev ) and defined($meta->{revision}) and $wrev == $meta->{revision} and $state->{entries}{$filename}{unchanged} and not exists ( $state->{opt}{C} ) );
+        # Files are up to date if the working copy and repo copy have the same revision,
+        # and the working copy is unmodified _and_ the user hasn't specified -C
+        next if ( defined ( $wrev )
+                  and defined($meta->{revision})
+                  and $wrev == $meta->{revision}
+                  and $state->{entries}{$filename}{unchanged}
+                  and not exists ( $state->{opt}{C} ) );
+
+        # If the working copy and repo copy have the same revision,
+        # but the working copy is modified, tell the client it's modified
+        if ( defined ( $wrev )
+             and defined($meta->{revision})
+             and $wrev == $meta->{revision}
+             and not exists ( $state->{opt}{C} ) )
+        {
+            $log->info("Tell the client the file is modified");
+            print "MT text U\n";
+            print "MT fname $filename\n";
+            print "MT newline\n";
+            next;
+        }
 
         if ( $meta->{filehash} eq "deleted" )
         {
-            my ( $filepart, $dirpart ) = filenamesplit($filename);
+            my ( $filepart, $dirpart ) = filenamesplit($filename,1);
 
             $log->info("Removing '$filename' from working copy (no longer in the repo)");
 
             print "E cvs update: `$filename' is no longer in the repository\n";
-            print "Removed $dirpart\n";
-            print "$filepart\n";
+            # Don't want to actually _DO_ the update if -n specified
+            unless ( $state->{globaloptions}{-n} ) {
+               print "Removed $dirpart\n";
+               print "$filepart\n";
+           }
         }
-        elsif ( not defined ( $state->{entries}{$filename}{modified_hash} ) or $state->{entries}{$filename}{modified_hash} eq $oldmeta->{filehash} )
+        elsif ( not defined ( $state->{entries}{$filename}{modified_hash} )
+               or $state->{entries}{$filename}{modified_hash} eq $oldmeta->{filehash} )
         {
             $log->info("Updating '$filename'");
             # normal update, just send the new revision (either U=Update, or A=Add, or R=Remove)
@@ -737,33 +862,42 @@ sub req_update
             print "MT newline\n";
             print "MT -updated\n";
 
-            my ( $filepart, $dirpart ) = filenamesplit($filename);
-            $dirpart =~ s/^$state->{directory}//;
-
-            if ( defined ( $wrev ) )
-            {
-                # instruct client we're sending a file to put in this path as a replacement
-                print "Update-existing $dirpart\n";
-                $log->debug("Updating existing file 'Update-existing $dirpart'");
-            } else {
-                # instruct client we're sending a file to put in this path as a new file
-                print "Created $dirpart\n";
-                $log->debug("Creating new file 'Created $dirpart'");
-            }
-            print $state->{CVSROOT} . "/$state->{module}/$filename\n";
-
-            # this is an "entries" line
-            $log->debug("/$filepart/1.$meta->{revision}///");
-            print "/$filepart/1.$meta->{revision}///\n";
-
-            # permissions
-            $log->debug("SEND : u=$meta->{mode},g=$meta->{mode},o=$meta->{mode}");
-            print "u=$meta->{mode},g=$meta->{mode},o=$meta->{mode}\n";
-
-            # transmit file
-            transmitfile($meta->{filehash});
+            my ( $filepart, $dirpart ) = filenamesplit($filename,1);
+
+           # Don't want to actually _DO_ the update if -n specified
+           unless ( $state->{globaloptions}{-n} )
+           {
+               if ( defined ( $wrev ) )
+               {
+                   # instruct client we're sending a file to put in this path as a replacement
+                   print "Update-existing $dirpart\n";
+                   $log->debug("Updating existing file 'Update-existing $dirpart'");
+               } else {
+                   # instruct client we're sending a file to put in this path as a new file
+                   print "Clear-static-directory $dirpart\n";
+                   print $state->{CVSROOT} . "/$state->{module}/$dirpart\n";
+                   print "Clear-sticky $dirpart\n";
+                   print $state->{CVSROOT} . "/$state->{module}/$dirpart\n";
+
+                   $log->debug("Creating new file 'Created $dirpart'");
+                   print "Created $dirpart\n";
+               }
+               print $state->{CVSROOT} . "/$state->{module}/$filename\n";
+
+               # this is an "entries" line
+               $log->debug("/$filepart/1.$meta->{revision}///");
+               print "/$filepart/1.$meta->{revision}///\n";
+
+               # permissions
+               $log->debug("SEND : u=$meta->{mode},g=$meta->{mode},o=$meta->{mode}");
+               print "u=$meta->{mode},g=$meta->{mode},o=$meta->{mode}\n";
+
+               # transmit file
+               transmitfile($meta->{filehash});
+           }
         } else {
-            my ( $filepart, $dirpart ) = filenamesplit($meta->{name});
+            $log->info("Updating '$filename'");
+            my ( $filepart, $dirpart ) = filenamesplit($meta->{name},1);
 
             my $dir = tempdir( DIR => $TEMP_DIR, CLEANUP => 1 ) . "/";
 
@@ -788,19 +922,29 @@ sub req_update
                 $log->info("Merged successfully");
                 print "M M $filename\n";
                 $log->debug("Update-existing $dirpart");
-                print "Update-existing $dirpart\n";
-                $log->debug($state->{CVSROOT} . "/$state->{module}/$filename");
-                print $state->{CVSROOT} . "/$state->{module}/$filename\n";
-                $log->debug("/$filepart/1.$meta->{revision}///");
-                print "/$filepart/1.$meta->{revision}///\n";
+
+                # Don't want to actually _DO_ the update if -n specified
+                unless ( $state->{globaloptions}{-n} )
+                {
+                    print "Update-existing $dirpart\n";
+                    $log->debug($state->{CVSROOT} . "/$state->{module}/$filename");
+                    print $state->{CVSROOT} . "/$state->{module}/$filename\n";
+                    $log->debug("/$filepart/1.$meta->{revision}///");
+                    print "/$filepart/1.$meta->{revision}///\n";
+                }
             }
             elsif ( $return == 1 )
             {
                 $log->info("Merged with conflicts");
                 print "M C $filename\n";
-                print "Update-existing $dirpart\n";
-                print $state->{CVSROOT} . "/$state->{module}/$filename\n";
-                print "/$filepart/1.$meta->{revision}/+//\n";
+
+                # Don't want to actually _DO_ the update if -n specified
+                unless ( $state->{globaloptions}{-n} )
+                {
+                    print "Update-existing $dirpart\n";
+                    print $state->{CVSROOT} . "/$state->{module}/$filename\n";
+                    print "/$filepart/1.$meta->{revision}/+//\n";
+                }
             }
             else
             {
@@ -808,17 +952,21 @@ sub req_update
                 next;
             }
 
-            # permissions
-            $log->debug("SEND : u=$meta->{mode},g=$meta->{mode},o=$meta->{mode}");
-            print "u=$meta->{mode},g=$meta->{mode},o=$meta->{mode}\n";
-
-            # transmit file, format is single integer on a line by itself (file
-            # size) followed by the file contents
-            # TODO : we should copy files in blocks
-            my $data = `cat $file_local`;
-            $log->debug("File size : " . length($data));
-            print length($data) . "\n";
-            print $data;
+            # Don't want to actually _DO_ the update if -n specified
+            unless ( $state->{globaloptions}{-n} )
+            {
+                # permissions
+                $log->debug("SEND : u=$meta->{mode},g=$meta->{mode},o=$meta->{mode}");
+                print "u=$meta->{mode},g=$meta->{mode},o=$meta->{mode}\n";
+
+                # transmit file, format is single integer on a line by itself (file
+                # size) followed by the file contents
+                # TODO : we should copy files in blocks
+                my $data = `cat $file_local`;
+                $log->debug("File size : " . length($data));
+                print length($data) . "\n";
+                print $data;
+            }
 
             chdir "/";
         }
@@ -838,8 +986,15 @@ sub req_ci
 
     $log->info("req_ci : " . ( defined($data) ? $data : "[NULL]" ));
 
+    if ( @ARGV && $ARGV[0] eq 'pserver')
+    {
+        print "error 1 pserver access cannot commit\n";
+        exit;
+    }
+
     if ( -e $state->{CVSROOT} . "/index" )
     {
+        $log->warn("file 'index' already exists in the git repository");
         print "error 1 Index already exists in git repo\n";
         exit;
     }
@@ -847,6 +1002,7 @@ sub req_ci
     my $lockfile = "$state->{CVSROOT}/refs/heads/$state->{module}.lock";
     unless ( sysopen(LOCKFILE,$lockfile,O_EXCL|O_CREAT|O_WRONLY) )
     {
+        $log->warn("lockfile '$lockfile' already exists, please try again");
         print "error 1 Lock file '$lockfile' already exists, please try again\n";
         exit;
     }
@@ -878,6 +1034,7 @@ sub req_ci
     # foreach file specified on the commandline ...
     foreach my $filename ( @{$state->{args}} )
     {
+        my $committedfile = $filename;
         $filename = filecleanup($filename);
 
         next unless ( exists $state->{entries}{$filename}{modified_filename} or not $state->{entries}{$filename}{unchanged} );
@@ -912,7 +1069,7 @@ sub req_ci
             exit;
         }
 
-        push @committedfiles, $filename;
+        push @committedfiles, $committedfile;
         $log->info("Committing $filename");
 
         system("mkdir","-p",$dirpart) unless ( -d $dirpart );
@@ -995,7 +1152,7 @@ sub req_ci
 
         my $meta = $updater->getmeta($filename);
 
-        my ( $filepart, $dirpart ) = filenamesplit($filename);
+        my ( $filepart, $dirpart ) = filenamesplit($filename, 1);
 
         $log->debug("Checked-in $dirpart : $filename");
 
@@ -1031,7 +1188,7 @@ sub req_status
     $updater->update();
 
     # if no files were specified, we need to work out what files we should be providing status on ...
-    argsfromdir($updater) if ( scalar ( @{$state->{args}} ) == 0 );
+    argsfromdir($updater);
 
     # foreach file specified on the commandline ...
     foreach my $filename ( @{$state->{args}} )
@@ -1132,7 +1289,7 @@ sub req_diff
     $updater->update();
 
     # if no files were specified, we need to work out what files we should be providing status on ...
-    argsfromdir($updater) if ( scalar ( @{$state->{args}} ) == 0 );
+    argsfromdir($updater);
 
     # foreach file specified on the commandline ...
     foreach my $filename ( @{$state->{args}} )
@@ -1274,7 +1431,7 @@ sub req_log
     $updater->update();
 
     # if no files were specified, we need to work out what files we should be providing status on ...
-    argsfromdir($updater) if ( scalar ( @{$state->{args}} ) == 0 );
+    argsfromdir($updater);
 
     # foreach file specified on the commandline ...
     foreach my $filename ( @{$state->{args}} )
@@ -1350,7 +1507,7 @@ sub req_annotate
     $updater->update();
 
     # if no files were specified, we need to work out what files we should be providing annotate on ...
-    argsfromdir($updater) if ( scalar ( @{$state->{args}} ) == 0 );
+    argsfromdir($updater);
 
     # we'll need a temporary checkout dir
     my $tmpdir = tempdir ( DIR => $TEMP_DIR );
@@ -1545,13 +1702,36 @@ sub argsfromdir
 {
     my $updater = shift;
 
-    $state->{args} = [];
+    $state->{args} = [] if ( scalar(@{$state->{args}}) == 1 and $state->{args}[0] eq "." );
 
-    foreach my $file ( @{$updater->gethead} )
+    return if ( scalar ( @{$state->{args}} ) > 1 );
+
+    if ( scalar(@{$state->{args}}) == 1 )
     {
-        next if ( $file->{filehash} eq "deleted" and not defined ( $state->{entries}{$file->{name}} ) );
-        next unless ( $file->{name} =~ s/^$state->{directory}// );
-        push @{$state->{args}}, $file->{name};
+        my $arg = $state->{args}[0];
+        $arg .= $state->{prependdir} if ( defined ( $state->{prependdir} ) );
+
+        $log->info("Only one arg specified, checking for directory expansion on '$arg'");
+
+        foreach my $file ( @{$updater->gethead} )
+        {
+            next if ( $file->{filehash} eq "deleted" and not defined ( $state->{entries}{$file->{name}} ) );
+            next unless ( $file->{name} =~ /^$arg\// or $file->{name} eq $arg  );
+            push @{$state->{args}}, $file->{name};
+        }
+
+        shift @{$state->{args}} if ( scalar(@{$state->{args}}) > 1 );
+    } else {
+        $log->info("Only one arg specified, populating file list automatically");
+
+        $state->{args} = [];
+
+        foreach my $file ( @{$updater->gethead} )
+        {
+            next if ( $file->{filehash} eq "deleted" and not defined ( $state->{entries}{$file->{name}} ) );
+            next unless ( $file->{name} =~ s/^$state->{prependdir}// );
+            push @{$state->{args}}, $file->{name};
+        }
     }
 }
 
@@ -1621,16 +1801,22 @@ sub transmitfile
 }
 
 # This method takes a file name, and returns ( $dirpart, $filepart ) which
-# refers to the directory porition and the file portion of the filename
+# refers to the directory portion and the file portion of the filename
 # respectively
 sub filenamesplit
 {
     my $filename = shift;
+    my $fixforlocaldir = shift;
 
     my ( $filepart, $dirpart ) = ( $filename, "." );
     ( $filepart, $dirpart ) = ( $2, $1 ) if ( $filename =~ /(.*)\/(.*)/ );
     $dirpart .= "/";
 
+    if ( $fixforlocaldir )
+    {
+        $dirpart =~ s/^$state->{prependdir}//;
+    }
+
     return ( $filepart, $dirpart );
 }
 
@@ -1646,8 +1832,7 @@ sub filecleanup
     }
 
     $filename =~ s/^\.\///g;
-    $filename = $state->{directory} . $filename;
-
+    $filename = $state->{prependdir} . $filename;
     return $filename;
 }
 
@@ -1680,7 +1865,7 @@ =head1 METHODS
 =head2 new
 
 Creates a new log object, optionally you can specify a filename here to
-indicate the file to log to. If no log file is specified, you can specifiy one
+indicate the file to log to. If no log file is specified, you can specify one
 later with method setfile, or indicate you no longer want logging with method
 nofile.
 
@@ -1966,14 +2151,15 @@ sub update
     # TODO: log processing is memory bound
     # if we can parse into a 2nd file that is in reverse order
     # we can probably do something really efficient
-    my @git_log_params = ('--parents', '--topo-order');
+    my @git_log_params = ('--pretty', '--parents', '--topo-order');
 
     if (defined $lastcommit) {
         push @git_log_params, "$lastcommit..$self->{module}";
     } else {
         push @git_log_params, $self->{module};
     }
-    open(GITLOG, '-|', 'git-log', @git_log_params) or die "Cannot call git-log: $!";
+    # git-rev-list is the backend / plumbing version of git-log
+    open(GITLOG, '-|', 'git-rev-list', @git_log_params) or die "Cannot call git-rev-list: $!";
 
     my @commits;
 
@@ -2328,7 +2514,7 @@ sub gethead
 
     return $self->{gethead_cache} if ( defined ( $self->{gethead_cache} ) );
 
-    my $db_query = $self->{dbh}->prepare_cached("SELECT name, filehash, mode, revision, modified, commithash, author FROM head",{},1);
+    my $db_query = $self->{dbh}->prepare_cached("SELECT name, filehash, mode, revision, modified, commithash, author FROM head ORDER BY name ASC",{},1);
     $db_query->execute();
 
     my $tree = [];
@@ -2485,7 +2671,7 @@ sub in_array
 
 =head2 safe_pipe_capture
 
-an alterative to `command` that allows input to be passed as an array
+an alternative to `command` that allows input to be passed as an array
 to work around shell problems with weird characters in arguments
 
 =cut