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