Commits

Alex Efros committed 1e31bde

vcprompt-hgst: speedup up to 50%
added tests for hg (one fail, not sure why)

Comments (0)

Files changed (5)

 	cd tests && ./setup-git
 
 clean:
-	rm -f $(objects) vcprompt
+	rm -f $(objects) vcprompt $(hgrepo) $(gitrepo)
 
 DESTDIR =
 PREFIX = /usr/local
 
     if (context->options->show_modified || context->options->show_unknown) {
 	int status = system("vcprompt-hgst");
-	if (WEXITSTATUS(status) & 1)
-		result->modified = 1;
-	if (WEXITSTATUS(status) & 2)
-		result->unknown = 1;
+	if (WEXITSTATUS(status) <= 3) {
+		if (WEXITSTATUS(status) & 1<<0)
+			result->modified = 1;
+		if (WEXITSTATUS(status) & 1<<1)
+			result->unknown = 1;
+	}
+	/* any other outcome (including failure to fork/exec,
+	   failure to run git, or diff error): assume no
+	   modifications */
     }
 
     return result;
 vcprompt=""
 tmpdir=""
 
+LANG=
+PATH=$testdir/..:$PATH
+
 die()
 {
     msg=$1
 find_hgrepo()
 {
     hgrepo="$testdir/hg-repo.tar"
-    if [ ! -f $hgrepo ]; then
-	echo "$hgrepo not found" >&2
-	exit 1
-    fi
+    [ -f $hgrepo ] || die "$hgrepo not found"
 }
 
 pretest()
     hg -q update stable
     assert_vcprompt "show branch 2" "stable" "%b"
 
-    # not implemented yet
-    #echo foo >> b
-    #echo junk > junk
-    #assert_vcprompt "show modified" "+" "%m"
-    #assert_vcprompt "show unknown" "?" "%u"
+#     hg st
+#     assert_vcprompt "show no modified" "" "%m"
+    assert_vcprompt "show no unknown" "" "%u"
+    echo foo >> b
+    echo junk > junk
+    assert_vcprompt "show modified" "+" "%m"
+    assert_vcprompt "show unknown" "?" "%u"
 
     posttest
 }
 # use warnings;
 # use strict;
 
-our $VERSION = 1.00;
+our $VERSION = 2.00;
 
-my $hgignore = qr/\A\z/xms;
-if (open my $i, '<', '.hgignore') {
-    $hgignore = join q{|}, map {chomp; length $_ ? qr/$_/ : ()} <$i>;
-    close $i or exit 255;
+my $found_modified = 0;
+my $found_unknown  = 0;
+
+
+my $hgignore = qr/\A\z/;
+if (open my $f, '<', '.hgignore') {
+    # TODO Implement real .hgignore syntax:
+    # http://www.selenic.com/mercurial/hgignore.5.html
+    # FIXME current implementation may lead to security issue - .hgignore
+    #       may contain perl code inside regex and it will be executed
+    $hgignore = join q{|}, map {chomp; length $_ ? qr/$_/ : ()} <$f>;
 }
 
 open my $f, '<', '.hg/dirstate'     or exit 255;
-my @dirstate = unpack '@40 (a N l> N N/a)*', join q{}, <$f>;
-close $f                            or exit 255;
+my @dirstate = unpack '@40 (a N l> l> N/a)*', join q{}, <$f>;
 
-my %stat;
-my ($status, $mode, $size, $mtime, $filename);
-for (my $i = 0; $i < $#dirstate; $i += 5) {
-    ($status, $mode, $size, $mtime, $filename) = @dirstate[$i .. $i+4];
+my %seen;
+for my $i (0 .. $#dirstate/5) {
+    my ($status, $mode, $size, $mtime, $filename) = @dirstate[$i*5 .. $i*5+4];
+    # n (normal) - usual files, may be modified
+    # a (added) - will have unset mode/size/mtime, and thus detected as modified
+    # r (removed) - must be ignored, to detect at unknown if this file exists
+    # m (3-way merged) - TODO I've no idea what this mean
     next if $status eq 'r';
-    "./$filename" =~ m{\A(?:([^\0]*)/)?([^/\0]+)}xms    or exit 255;
-    my ($dir, $name) = ($1, $2);
-    $stat{$dir}{$name} = [ $size, $mtime ];
+    $filename =~ s/\0.*//s;
+
+    $filename =~ m{(.*/)?(.+)}s    or exit 255;
+    $seen{$1}{$2} = 1;
+
+    if (!$found_modified) {
+        my @stat = stat $filename;
+        if (@stat && !($stat[2]==$mode && $stat[7]==$size && $stat[9]==$mtime)) {
+            $found_modified = 1;
+        }
+    }
 }
 
-my $modified = 0;
-my $unknown  = 0;
-checkdir('.');
-exit $modified + $unknown;
+my @dirs = (q{});   # dirs here must end with /, except root dir (empty string)
+DIR: while (@dirs) {
+    my $dir = shift @dirs;
+    next if $dir eq '.hg/';
 
-sub checkdir {
-    my ($dir) = @_;
+    my $known = $seen{$dir} || {};
+    $known->{'.'} = $known->{'..'} = 1;
 
-    return if $modified && $unknown;
-
-    opendir my $d, $dir     or exit 255;
-    my @names = readdir $d;
-    closedir $d             or exit 255;
-
-    my $stat = $stat{$dir} || {};
-    for my $name (@names) {
-        my $path = "$dir/$name";
-        if ($name eq '.' || $name eq '..' || $path eq './.hg' || substr($path,2) =~ /$hgignore/o) {
-            next;
-        }
-        if (-d $path) {
-            checkdir($path);
-        }
-        elsif (!$stat->{$name}) {
-            $unknown = 2;
-        }
-        elsif (!$modified) {
-            my ($size, $mtime) = (stat $path)[7,9];
-            if ($size  != $stat->{$name}[0] ||
-                $mtime != $stat->{$name}[1]) {
-                $modified = 1;
+    opendir my $d, $dir || './'     or exit 255;
+    for my $name (readdir $d) {
+        if (!$known->{$name} && "$dir$name" !~ /$hgignore/o) {
+            if (-d "$dir$name") {
+                push @dirs, "$dir$name/";
+            }
+            else {
+                $found_unknown = 1;
+                last DIR;
             }
         }
-        last if $modified && $unknown;
     }
-
-    return;
 }
 
+exit(($found_modified << 0) | ($found_unknown << 1));