git-mv.perlon commit Merge branch 'maint' (ac5f7c6)
   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
  22my $GIT_DIR = `git rev-parse --git-dir`;
  23exit 1 if $?; # rev-parse would have given "not a git dir" message.
  24chomp($GIT_DIR);
  25
  26our ($opt_n, $opt_f, $opt_h, $opt_k, $opt_v);
  27getopts("hnfkv") || usage;
  28usage() if $opt_h;
  29@ARGV >= 1 or usage;
  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: moving to directory '"
  51            . $ARGV[$argCount-1]
  52            . "' not possible; not exisiting\n";
  53        exit(1);
  54    }
  55    @srcArgs = ($ARGV[0]);
  56    @dstArgs = ($ARGV[1]);
  57    $dstDir = "";
  58}
  59
  60my (@allfiles,@srcfiles,@dstfiles);
  61my $safesrc;
  62my (%overwritten, %srcForDst);
  63
  64$/ = "\0";
  65open(F, 'git-ls-files -z |')
  66        or die "Failed to open pipe from git-ls-files: " . $!;
  67
  68@allfiles = map { chomp; $_; } <F>;
  69close(F);
  70
  71
  72my ($i, $bad);
  73while(scalar @srcArgs > 0) {
  74    $src = shift @srcArgs;
  75    $dst = shift @dstArgs;
  76    $bad = "";
  77
  78    for ($src, $dst) {
  79        # Be nicer to end-users by doing ".//a/./b/.//./c" ==> "a/b/c"
  80        s|^\./||;
  81        s|/\./|/| while (m|/\./|);
  82        s|//+|/|g;
  83        # Also "a/b/../c" ==> "a/c"
  84        1 while (s,(^|/)[^/]+/\.\./,$1,);
  85    }
  86
  87    if ($opt_v) {
  88        print "Checking rename of '$src' to '$dst'\n";
  89    }
  90
  91    unless (-f $src || -l $src || -d $src) {
  92        $bad = "bad source '$src'";
  93    }
  94
  95    $safesrc = quotemeta($src);
  96    @srcfiles = grep /^$safesrc(\/|$)/, @allfiles;
  97
  98    $overwritten{$dst} = 0;
  99    if (($bad eq "") && -e $dst) {
 100        $bad = "destination '$dst' already exists";
 101        if ($opt_f) {
 102            # only files can overwrite each other: check both source and destination
 103            if (-f $dst && (scalar @srcfiles == 1)) {
 104                print "Warning: $bad; will overwrite!\n";
 105                $bad = "";
 106                $overwritten{$dst} = 1;
 107            }
 108            else {
 109                $bad = "Can not overwrite '$src' with '$dst'";
 110            }
 111        }
 112    }
 113    
 114    if (($bad eq "") && ($dst =~ /^$safesrc\//)) {
 115        $bad = "can not move directory '$src' into itself";
 116    }
 117
 118    if ($bad eq "") {
 119        if (scalar @srcfiles == 0) {
 120            $bad = "'$src' not under version control";
 121        }
 122    }
 123
 124    if ($bad eq "") {
 125       if (defined $srcForDst{$dst}) {
 126           $bad = "can not move '$src' to '$dst'; already target of ";
 127           $bad .= "'".$srcForDst{$dst}."'";
 128       }
 129       else {
 130           $srcForDst{$dst} = $src;
 131       }
 132    }
 133
 134    if ($bad ne "") {
 135        if ($opt_k) {
 136            print "Warning: $bad; skipping\n";
 137            next;
 138        }
 139        print "Error: $bad\n";
 140        exit(1);
 141    }
 142    push @srcs, $src;
 143    push @dsts, $dst;
 144}
 145
 146# Final pass: rename/move
 147my (@deletedfiles,@addedfiles,@changedfiles);
 148$bad = "";
 149while(scalar @srcs > 0) {
 150    $src = shift @srcs;
 151    $dst = shift @dsts;
 152
 153    if ($opt_n || $opt_v) { print "Renaming $src to $dst\n"; }
 154    if (!$opt_n) {
 155        if (!rename($src,$dst)) {
 156            $bad = "renaming '$src' failed: $!";
 157            if ($opt_k) {
 158                print "Warning: skipped: $bad\n";
 159                $bad = "";
 160                next;
 161            }
 162            last;
 163        }
 164    }
 165
 166    $safesrc = quotemeta($src);
 167    @srcfiles = grep /^$safesrc(\/|$)/, @allfiles;
 168    @dstfiles = @srcfiles;
 169    s/^$safesrc(\/|$)/$dst$1/ for @dstfiles;
 170
 171    push @deletedfiles, @srcfiles;
 172    if (scalar @srcfiles == 1) {
 173        # $dst can be a directory with 1 file inside
 174        if ($overwritten{$dst} ==1) {
 175            push @changedfiles, $dstfiles[0];
 176
 177        } else {
 178            push @addedfiles, $dstfiles[0];
 179        }
 180    }
 181    else {
 182        push @addedfiles, @dstfiles;
 183    }
 184}
 185
 186if ($opt_n) {
 187    if (@changedfiles) {
 188        print "Changed  : ". join(", ", @changedfiles) ."\n";
 189    }
 190    if (@addedfiles) {
 191        print "Adding   : ". join(", ", @addedfiles) ."\n";
 192    }
 193    if (@deletedfiles) {
 194        print "Deleting : ". join(", ", @deletedfiles) ."\n";
 195    }
 196}
 197else {
 198    if (@changedfiles) {
 199        open(H, "| git-update-index -z --stdin")
 200                or die "git-update-index failed to update changed files with code $!\n";
 201        foreach my $fileName (@changedfiles) {
 202                print H "$fileName\0";
 203        }
 204        close(H);
 205    }
 206    if (@addedfiles) {
 207        open(H, "| git-update-index --add -z --stdin")
 208                or die "git-update-index failed to add new names with code $!\n";
 209        foreach my $fileName (@addedfiles) {
 210                print H "$fileName\0";
 211        }
 212        close(H);
 213    }
 214    if (@deletedfiles) {
 215        open(H, "| git-update-index --remove -z --stdin")
 216                or die "git-update-index failed to remove old names with code $!\n";
 217        foreach my $fileName (@deletedfiles) {
 218                print H "$fileName\0";
 219        }
 220        close(H);
 221    }
 222}
 223
 224if ($bad ne "") {
 225    print "Error: $bad\n";
 226    exit(1);
 227}