Make path-limiting be incremental when possible.
[gitweb.git] / git-mv.perl
index 990bec50343a337c90d269e3b860cc82fad414e3..75aa8feeb6201c54e55bfaca0cc178c53891e776 100755 (executable)
@@ -19,31 +19,26 @@ ()
        exit(1);
 }
 
-# Sanity checks:
-my $GIT_DIR = $ENV{'GIT_DIR'} || ".git";
-
-unless ( -d $GIT_DIR && -d $GIT_DIR . "/objects" && 
-       -d $GIT_DIR . "/objects/" && -d $GIT_DIR . "/refs") {
-    print "Error: git repository not found.";
-    exit(1);
-}
-
-
 our ($opt_n, $opt_f, $opt_h, $opt_k, $opt_v);
 getopts("hnfkv") || usage;
 usage() if $opt_h;
 @ARGV >= 1 or usage;
 
+my $GIT_DIR = `git rev-parse --git-dir`;
+exit 1 if $?; # rev-parse would have given "not a git dir" message.
+chomp($GIT_DIR);
+
 my (@srcArgs, @dstArgs, @srcs, @dsts);
 my ($src, $dst, $base, $dstDir);
 
+# remove any trailing slash in arguments
+for (@ARGV) { s/\/*$//; }
+
 my $argCount = scalar @ARGV;
 if (-d $ARGV[$argCount-1]) {
        $dstDir = $ARGV[$argCount-1];
-       # remove any trailing slash
-       $dstDir =~ s/\/$//;
        @srcArgs = @ARGV[0..$argCount-2];
-       
+
        foreach $src (@srcArgs) {
                $base = $src;
                $base =~ s/^.*\///;
@@ -52,10 +47,14 @@ ()
        }
 }
 else {
-    if ($argCount != 2) {
+    if ($argCount < 2) {
+       print "Error: need at least two arguments\n";
+       exit(1);
+    }
+    if ($argCount > 2) {
        print "Error: moving to directory '"
            . $ARGV[$argCount-1]
-           . "' not possible; not exisiting\n";
+           . "' not possible; not existing\n";
        exit(1);
     }
     @srcArgs = ($ARGV[0]);
@@ -63,12 +62,30 @@ ()
     $dstDir = "";
 }
 
+my $subdir_prefix = `git rev-parse --show-prefix`;
+chomp($subdir_prefix);
+
+# run in git base directory, so that git-ls-files lists all revisioned files
+chdir "$GIT_DIR/..";
+
+# normalize paths, needed to compare against versioned files and update-index
+# also, this is nicer to end-users by doing ".//a/./b/.//./c" ==> "a/b/c"
+for (@srcArgs, @dstArgs) {
+    # prepend git prefix as we run from base directory
+    $_ = $subdir_prefix.$_;
+    s|^\./||;
+    s|/\./|/| while (m|/\./|);
+    s|//+|/|g;
+    # Also "a/b/../c" ==> "a/c"
+    1 while (s,(^|/)[^/]+/\.\./,$1,);
+}
+
 my (@allfiles,@srcfiles,@dstfiles);
 my $safesrc;
 my (%overwritten, %srcForDst);
 
 $/ = "\0";
-open(F,"-|","git-ls-files","-z")
+open(F, 'git-ls-files -z |')
         or die "Failed to open pipe from git-ls-files: " . $!;
 
 @allfiles = map { chomp; $_; } <F>;
@@ -81,6 +98,15 @@ ()
     $dst = shift @dstArgs;
     $bad = "";
 
+    for ($src, $dst) {
+       # Be nicer to end-users by doing ".//a/./b/.//./c" ==> "a/b/c"
+       s|^\./||;
+       s|/\./|/| while (m|/\./|);
+       s|//+|/|g;
+       # Also "a/b/../c" ==> "a/c"
+       1 while (s,(^|/)[^/]+/\.\./,$1,);
+    }
+
     if ($opt_v) {
        print "Checking rename of '$src' to '$dst'\n";
     }
@@ -108,7 +134,7 @@ ()
        }
     }
     
-    if (($bad eq "") && ($src eq $dstDir)) {
+    if (($bad eq "") && ($dst =~ /^$safesrc\//)) {
        $bad = "can not move directory '$src' into itself";
     }
 
@@ -142,14 +168,22 @@ ()
 
 # Final pass: rename/move
 my (@deletedfiles,@addedfiles,@changedfiles);
+$bad = "";
 while(scalar @srcs > 0) {
     $src = shift @srcs;
     $dst = shift @dsts;
 
     if ($opt_n || $opt_v) { print "Renaming $src to $dst\n"; }
     if (!$opt_n) {
-       rename($src,$dst)
-           or die "rename failed: $!";
+       if (!rename($src,$dst)) {
+           $bad = "renaming '$src' failed: $!";
+           if ($opt_k) {
+               print "Warning: skipped: $bad\n";
+               $bad = "";
+               next;
+           }
+           last;
+       }
     }
 
     $safesrc = quotemeta($src);
@@ -209,3 +243,8 @@ ()
        close(H);
     }
 }
+
+if ($bad ne "") {
+    print "Error: $bad\n";
+    exit(1);
+}