git-mv.perlon commit git-merge-tree: generalize the "traverse <n> trees in sync" functionality (164dcb9)
   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    if ($opt_v) {
  79        print "Checking rename of '$src' to '$dst'\n";
  80    }
  81
  82    unless (-f $src || -l $src || -d $src) {
  83        $bad = "bad source '$src'";
  84    }
  85
  86    $safesrc = quotemeta($src);
  87    @srcfiles = grep /^$safesrc(\/|$)/, @allfiles;
  88
  89    $overwritten{$dst} = 0;
  90    if (($bad eq "") && -e $dst) {
  91        $bad = "destination '$dst' already exists";
  92        if ($opt_f) {
  93            # only files can overwrite each other: check both source and destination
  94            if (-f $dst && (scalar @srcfiles == 1)) {
  95                print "Warning: $bad; will overwrite!\n";
  96                $bad = "";
  97                $overwritten{$dst} = 1;
  98            }
  99            else {
 100                $bad = "Can not overwrite '$src' with '$dst'";
 101            }
 102        }
 103    }
 104    
 105    if (($bad eq "") && ($dst =~ /^$safesrc\//)) {
 106        $bad = "can not move directory '$src' into itself";
 107    }
 108
 109    if ($bad eq "") {
 110        if (scalar @srcfiles == 0) {
 111            $bad = "'$src' not under version control";
 112        }
 113    }
 114
 115    if ($bad eq "") {
 116       if (defined $srcForDst{$dst}) {
 117           $bad = "can not move '$src' to '$dst'; already target of ";
 118           $bad .= "'".$srcForDst{$dst}."'";
 119       }
 120       else {
 121           $srcForDst{$dst} = $src;
 122       }
 123    }
 124
 125    if ($bad ne "") {
 126        if ($opt_k) {
 127            print "Warning: $bad; skipping\n";
 128            next;
 129        }
 130        print "Error: $bad\n";
 131        exit(1);
 132    }
 133    push @srcs, $src;
 134    push @dsts, $dst;
 135}
 136
 137# Final pass: rename/move
 138my (@deletedfiles,@addedfiles,@changedfiles);
 139$bad = "";
 140while(scalar @srcs > 0) {
 141    $src = shift @srcs;
 142    $dst = shift @dsts;
 143
 144    if ($opt_n || $opt_v) { print "Renaming $src to $dst\n"; }
 145    if (!$opt_n) {
 146        if (!rename($src,$dst)) {
 147            $bad = "renaming '$src' failed: $!";
 148            if ($opt_k) {
 149                print "Warning: skipped: $bad\n";
 150                $bad = "";
 151                next;
 152            }
 153            last;
 154        }
 155    }
 156
 157    $safesrc = quotemeta($src);
 158    @srcfiles = grep /^$safesrc(\/|$)/, @allfiles;
 159    @dstfiles = @srcfiles;
 160    s/^$safesrc(\/|$)/$dst$1/ for @dstfiles;
 161
 162    push @deletedfiles, @srcfiles;
 163    if (scalar @srcfiles == 1) {
 164        # $dst can be a directory with 1 file inside
 165        if ($overwritten{$dst} ==1) {
 166            push @changedfiles, $dstfiles[0];
 167
 168        } else {
 169            push @addedfiles, $dstfiles[0];
 170        }
 171    }
 172    else {
 173        push @addedfiles, @dstfiles;
 174    }
 175}
 176
 177if ($opt_n) {
 178    if (@changedfiles) {
 179        print "Changed  : ". join(", ", @changedfiles) ."\n";
 180    }
 181    if (@addedfiles) {
 182        print "Adding   : ". join(", ", @addedfiles) ."\n";
 183    }
 184    if (@deletedfiles) {
 185        print "Deleting : ". join(", ", @deletedfiles) ."\n";
 186    }
 187}
 188else {
 189    if (@changedfiles) {
 190        open(H, "| git-update-index -z --stdin")
 191                or die "git-update-index failed to update changed files with code $!\n";
 192        foreach my $fileName (@changedfiles) {
 193                print H "$fileName\0";
 194        }
 195        close(H);
 196    }
 197    if (@addedfiles) {
 198        open(H, "| git-update-index --add -z --stdin")
 199                or die "git-update-index failed to add new names with code $!\n";
 200        foreach my $fileName (@addedfiles) {
 201                print H "$fileName\0";
 202        }
 203        close(H);
 204    }
 205    if (@deletedfiles) {
 206        open(H, "| git-update-index --remove -z --stdin")
 207                or die "git-update-index failed to remove old names with code $!\n";
 208        foreach my $fileName (@deletedfiles) {
 209                print H "$fileName\0";
 210        }
 211        close(H);
 212    }
 213}
 214
 215if ($bad ne "") {
 216    print "Error: $bad\n";
 217    exit(1);
 218}