git-mv.perlon commit git-mv: Allow -h without repo & fix error message (5e6f85f)
   1#!/usr/bin/perl
   2#
   3# Copyright 2005, Ryan Anderson <ryan@michonline.com>
   4#                 Josef Weidendorfer <Josef.Weidendorfer@gmx.de>
   5#
   6# This file is licensed under the GPL v2, or a later version
   7# at the discretion of Linus Torvalds.
   8
   9
  10use warnings;
  11use strict;
  12use Getopt::Std;
  13
  14sub usage() {
  15        print <<EOT;
  16$0 [-f] [-n] <source> <destination>
  17$0 [-f] [-n] [-k] <source> ... <destination directory>
  18EOT
  19        exit(1);
  20}
  21
  22our ($opt_n, $opt_f, $opt_h, $opt_k, $opt_v);
  23getopts("hnfkv") || usage;
  24usage() if $opt_h;
  25@ARGV >= 1 or usage;
  26
  27my $GIT_DIR = `git rev-parse --git-dir`;
  28exit 1 if $?; # rev-parse would have given "not a git dir" message.
  29chomp($GIT_DIR);
  30
  31my (@srcArgs, @dstArgs, @srcs, @dsts);
  32my ($src, $dst, $base, $dstDir);
  33
  34my $argCount = scalar @ARGV;
  35if (-d $ARGV[$argCount-1]) {
  36        $dstDir = $ARGV[$argCount-1];
  37        # remove any trailing slash
  38        $dstDir =~ s/\/$//;
  39        @srcArgs = @ARGV[0..$argCount-2];
  40        
  41        foreach $src (@srcArgs) {
  42                $base = $src;
  43                $base =~ s/^.*\///;
  44                $dst = "$dstDir/". $base;
  45                push @dstArgs, $dst;
  46        }
  47}
  48else {
  49    if ($argCount < 2) {
  50        print "Error: need at least two arguments\n";
  51        exit(1);
  52    }
  53    if ($argCount > 2) {
  54        print "Error: moving to directory '"
  55            . $ARGV[$argCount-1]
  56            . "' not possible; not existing\n";
  57        exit(1);
  58    }
  59    @srcArgs = ($ARGV[0]);
  60    @dstArgs = ($ARGV[1]);
  61    $dstDir = "";
  62}
  63
  64my (@allfiles,@srcfiles,@dstfiles);
  65my $safesrc;
  66my (%overwritten, %srcForDst);
  67
  68$/ = "\0";
  69open(F, 'git-ls-files -z |')
  70        or die "Failed to open pipe from git-ls-files: " . $!;
  71
  72@allfiles = map { chomp; $_; } <F>;
  73close(F);
  74
  75
  76my ($i, $bad);
  77while(scalar @srcArgs > 0) {
  78    $src = shift @srcArgs;
  79    $dst = shift @dstArgs;
  80    $bad = "";
  81
  82    for ($src, $dst) {
  83        # Be nicer to end-users by doing ".//a/./b/.//./c" ==> "a/b/c"
  84        s|^\./||;
  85        s|/\./|/| while (m|/\./|);
  86        s|//+|/|g;
  87        # Also "a/b/../c" ==> "a/c"
  88        1 while (s,(^|/)[^/]+/\.\./,$1,);
  89    }
  90
  91    if ($opt_v) {
  92        print "Checking rename of '$src' to '$dst'\n";
  93    }
  94
  95    unless (-f $src || -l $src || -d $src) {
  96        $bad = "bad source '$src'";
  97    }
  98
  99    $safesrc = quotemeta($src);
 100    @srcfiles = grep /^$safesrc(\/|$)/, @allfiles;
 101
 102    $overwritten{$dst} = 0;
 103    if (($bad eq "") && -e $dst) {
 104        $bad = "destination '$dst' already exists";
 105        if ($opt_f) {
 106            # only files can overwrite each other: check both source and destination
 107            if (-f $dst && (scalar @srcfiles == 1)) {
 108                print "Warning: $bad; will overwrite!\n";
 109                $bad = "";
 110                $overwritten{$dst} = 1;
 111            }
 112            else {
 113                $bad = "Can not overwrite '$src' with '$dst'";
 114            }
 115        }
 116    }
 117    
 118    if (($bad eq "") && ($dst =~ /^$safesrc\//)) {
 119        $bad = "can not move directory '$src' into itself";
 120    }
 121
 122    if ($bad eq "") {
 123        if (scalar @srcfiles == 0) {
 124            $bad = "'$src' not under version control";
 125        }
 126    }
 127
 128    if ($bad eq "") {
 129       if (defined $srcForDst{$dst}) {
 130           $bad = "can not move '$src' to '$dst'; already target of ";
 131           $bad .= "'".$srcForDst{$dst}."'";
 132       }
 133       else {
 134           $srcForDst{$dst} = $src;
 135       }
 136    }
 137
 138    if ($bad ne "") {
 139        if ($opt_k) {
 140            print "Warning: $bad; skipping\n";
 141            next;
 142        }
 143        print "Error: $bad\n";
 144        exit(1);
 145    }
 146    push @srcs, $src;
 147    push @dsts, $dst;
 148}
 149
 150# Final pass: rename/move
 151my (@deletedfiles,@addedfiles,@changedfiles);
 152$bad = "";
 153while(scalar @srcs > 0) {
 154    $src = shift @srcs;
 155    $dst = shift @dsts;
 156
 157    if ($opt_n || $opt_v) { print "Renaming $src to $dst\n"; }
 158    if (!$opt_n) {
 159        if (!rename($src,$dst)) {
 160            $bad = "renaming '$src' failed: $!";
 161            if ($opt_k) {
 162                print "Warning: skipped: $bad\n";
 163                $bad = "";
 164                next;
 165            }
 166            last;
 167        }
 168    }
 169
 170    $safesrc = quotemeta($src);
 171    @srcfiles = grep /^$safesrc(\/|$)/, @allfiles;
 172    @dstfiles = @srcfiles;
 173    s/^$safesrc(\/|$)/$dst$1/ for @dstfiles;
 174
 175    push @deletedfiles, @srcfiles;
 176    if (scalar @srcfiles == 1) {
 177        # $dst can be a directory with 1 file inside
 178        if ($overwritten{$dst} ==1) {
 179            push @changedfiles, $dstfiles[0];
 180
 181        } else {
 182            push @addedfiles, $dstfiles[0];
 183        }
 184    }
 185    else {
 186        push @addedfiles, @dstfiles;
 187    }
 188}
 189
 190if ($opt_n) {
 191    if (@changedfiles) {
 192        print "Changed  : ". join(", ", @changedfiles) ."\n";
 193    }
 194    if (@addedfiles) {
 195        print "Adding   : ". join(", ", @addedfiles) ."\n";
 196    }
 197    if (@deletedfiles) {
 198        print "Deleting : ". join(", ", @deletedfiles) ."\n";
 199    }
 200}
 201else {
 202    if (@changedfiles) {
 203        open(H, "| git-update-index -z --stdin")
 204                or die "git-update-index failed to update changed files with code $!\n";
 205        foreach my $fileName (@changedfiles) {
 206                print H "$fileName\0";
 207        }
 208        close(H);
 209    }
 210    if (@addedfiles) {
 211        open(H, "| git-update-index --add -z --stdin")
 212                or die "git-update-index failed to add new names with code $!\n";
 213        foreach my $fileName (@addedfiles) {
 214                print H "$fileName\0";
 215        }
 216        close(H);
 217    }
 218    if (@deletedfiles) {
 219        open(H, "| git-update-index --remove -z --stdin")
 220                or die "git-update-index failed to remove old names with code $!\n";
 221        foreach my $fileName (@deletedfiles) {
 222                print H "$fileName\0";
 223        }
 224        close(H);
 225    }
 226}
 227
 228if ($bad ne "") {
 229    print "Error: $bad\n";
 230    exit(1);
 231}