git-mv.perlon commit Perly Git: make sure we do test the freshly built one. (d3140f5)
   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
   9BEGIN {
  10        unless (exists $ENV{'RUNNING_GIT_TESTS'}) {
  11                unshift @INC, '@@INSTLIBDIR@@';
  12        }
  13}
  14use warnings;
  15use strict;
  16use Getopt::Std;
  17use Git;
  18
  19sub usage() {
  20        print <<EOT;
  21$0 [-f] [-n] <source> <destination>
  22$0 [-f] [-n] [-k] <source> ... <destination directory>
  23EOT
  24        exit(1);
  25}
  26
  27our ($opt_n, $opt_f, $opt_h, $opt_k, $opt_v);
  28getopts("hnfkv") || usage;
  29usage() if $opt_h;
  30@ARGV >= 1 or usage;
  31
  32my $repo = Git->repository();
  33
  34my (@srcArgs, @dstArgs, @srcs, @dsts);
  35my ($src, $dst, $base, $dstDir);
  36
  37# remove any trailing slash in arguments
  38for (@ARGV) { s/\/*$//; }
  39
  40my $argCount = scalar @ARGV;
  41if (-d $ARGV[$argCount-1]) {
  42        $dstDir = $ARGV[$argCount-1];
  43        @srcArgs = @ARGV[0..$argCount-2];
  44
  45        foreach $src (@srcArgs) {
  46                $base = $src;
  47                $base =~ s/^.*\///;
  48                $dst = "$dstDir/". $base;
  49                push @dstArgs, $dst;
  50        }
  51}
  52else {
  53    if ($argCount < 2) {
  54        print "Error: need at least two arguments\n";
  55        exit(1);
  56    }
  57    if ($argCount > 2) {
  58        print "Error: moving to directory '"
  59            . $ARGV[$argCount-1]
  60            . "' not possible; not existing\n";
  61        exit(1);
  62    }
  63    @srcArgs = ($ARGV[0]);
  64    @dstArgs = ($ARGV[1]);
  65    $dstDir = "";
  66}
  67
  68my $subdir_prefix = $repo->wc_subdir();
  69
  70# run in git base directory, so that git-ls-files lists all revisioned files
  71chdir $repo->wc_path();
  72$repo->wc_chdir('');
  73
  74# normalize paths, needed to compare against versioned files and update-index
  75# also, this is nicer to end-users by doing ".//a/./b/.//./c" ==> "a/b/c"
  76for (@srcArgs, @dstArgs) {
  77    # prepend git prefix as we run from base directory
  78    $_ = $subdir_prefix.$_;
  79    s|^\./||;
  80    s|/\./|/| while (m|/\./|);
  81    s|//+|/|g;
  82    # Also "a/b/../c" ==> "a/c"
  83    1 while (s,(^|/)[^/]+/\.\./,$1,);
  84}
  85
  86my (@allfiles,@srcfiles,@dstfiles);
  87my $safesrc;
  88my (%overwritten, %srcForDst);
  89
  90{
  91        local $/ = "\0";
  92        @allfiles = $repo->command('ls-files', '-z');
  93}
  94
  95
  96my ($i, $bad);
  97while(scalar @srcArgs > 0) {
  98    $src = shift @srcArgs;
  99    $dst = shift @dstArgs;
 100    $bad = "";
 101
 102    for ($src, $dst) {
 103        # Be nicer to end-users by doing ".//a/./b/.//./c" ==> "a/b/c"
 104        s|^\./||;
 105        s|/\./|/| while (m|/\./|);
 106        s|//+|/|g;
 107        # Also "a/b/../c" ==> "a/c"
 108        1 while (s,(^|/)[^/]+/\.\./,$1,);
 109    }
 110
 111    if ($opt_v) {
 112        print "Checking rename of '$src' to '$dst'\n";
 113    }
 114
 115    unless (-f $src || -l $src || -d $src) {
 116        $bad = "bad source '$src'";
 117    }
 118
 119    $safesrc = quotemeta($src);
 120    @srcfiles = grep /^$safesrc(\/|$)/, @allfiles;
 121
 122    $overwritten{$dst} = 0;
 123    if (($bad eq "") && -e $dst) {
 124        $bad = "destination '$dst' already exists";
 125        if ($opt_f) {
 126            # only files can overwrite each other: check both source and destination
 127            if (-f $dst && (scalar @srcfiles == 1)) {
 128                print "Warning: $bad; will overwrite!\n";
 129                $bad = "";
 130                $overwritten{$dst} = 1;
 131            }
 132            else {
 133                $bad = "Can not overwrite '$src' with '$dst'";
 134            }
 135        }
 136    }
 137    
 138    if (($bad eq "") && ($dst =~ /^$safesrc\//)) {
 139        $bad = "can not move directory '$src' into itself";
 140    }
 141
 142    if ($bad eq "") {
 143        if (scalar @srcfiles == 0) {
 144            $bad = "'$src' not under version control";
 145        }
 146    }
 147
 148    if ($bad eq "") {
 149       if (defined $srcForDst{$dst}) {
 150           $bad = "can not move '$src' to '$dst'; already target of ";
 151           $bad .= "'".$srcForDst{$dst}."'";
 152       }
 153       else {
 154           $srcForDst{$dst} = $src;
 155       }
 156    }
 157
 158    if ($bad ne "") {
 159        if ($opt_k) {
 160            print "Warning: $bad; skipping\n";
 161            next;
 162        }
 163        print "Error: $bad\n";
 164        exit(1);
 165    }
 166    push @srcs, $src;
 167    push @dsts, $dst;
 168}
 169
 170# Final pass: rename/move
 171my (@deletedfiles,@addedfiles,@changedfiles);
 172$bad = "";
 173while(scalar @srcs > 0) {
 174    $src = shift @srcs;
 175    $dst = shift @dsts;
 176
 177    if ($opt_n || $opt_v) { print "Renaming $src to $dst\n"; }
 178    if (!$opt_n) {
 179        if (!rename($src,$dst)) {
 180            $bad = "renaming '$src' failed: $!";
 181            if ($opt_k) {
 182                print "Warning: skipped: $bad\n";
 183                $bad = "";
 184                next;
 185            }
 186            last;
 187        }
 188    }
 189
 190    $safesrc = quotemeta($src);
 191    @srcfiles = grep /^$safesrc(\/|$)/, @allfiles;
 192    @dstfiles = @srcfiles;
 193    s/^$safesrc(\/|$)/$dst$1/ for @dstfiles;
 194
 195    push @deletedfiles, @srcfiles;
 196    if (scalar @srcfiles == 1) {
 197        # $dst can be a directory with 1 file inside
 198        if ($overwritten{$dst} ==1) {
 199            push @changedfiles, $dstfiles[0];
 200
 201        } else {
 202            push @addedfiles, $dstfiles[0];
 203        }
 204    }
 205    else {
 206        push @addedfiles, @dstfiles;
 207    }
 208}
 209
 210if ($opt_n) {
 211    if (@changedfiles) {
 212        print "Changed  : ". join(", ", @changedfiles) ."\n";
 213    }
 214    if (@addedfiles) {
 215        print "Adding   : ". join(", ", @addedfiles) ."\n";
 216    }
 217    if (@deletedfiles) {
 218        print "Deleting : ". join(", ", @deletedfiles) ."\n";
 219    }
 220}
 221else {
 222    if (@changedfiles) {
 223        my ($fd, $ctx) = $repo->command_input_pipe('update-index', '-z', '--stdin');
 224        foreach my $fileName (@changedfiles) {
 225                print $fd "$fileName\0";
 226        }
 227        git_cmd_try { $repo->command_close_pipe($fd, $ctx); }
 228                'git-update-index failed to update changed files with code %d';
 229    }
 230    if (@addedfiles) {
 231        my ($fd, $ctx) = $repo->command_input_pipe('update-index', '--add', '-z', '--stdin');
 232        foreach my $fileName (@addedfiles) {
 233                print $fd "$fileName\0";
 234        }
 235        git_cmd_try { $repo->command_close_pipe($fd, $ctx); }
 236                'git-update-index failed to add new files with code %d';
 237    }
 238    if (@deletedfiles) {
 239        my ($fd, $ctx) = $repo->command_input_pipe('update-index', '--remove', '-z', '--stdin');
 240        foreach my $fileName (@deletedfiles) {
 241                print $fd "$fileName\0";
 242        }
 243        git_cmd_try { $repo->command_close_pipe($fd, $ctx); }
 244                'git-update-index failed to remove old files with code %d';
 245    }
 246}
 247
 248if ($bad ne "") {
 249    print "Error: $bad\n";
 250    exit(1);
 251}