git-mv.perlon commit format-patch: fix two-argument special case, and make it easier to pick single commits (88b5a74)
   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> <dest>
  17$0 [-f] [-k] [-n] <source> ... <dest directory>
  18
  19In the first form, source must exist and be either a file,
  20symlink or directory, dest must not exist. It renames source to dest.
  21In the second form, the last argument has to be an existing
  22directory; the given sources will be moved into this directory.
  23
  24Updates the git cache to reflect the change.
  25Use "git commit" to make the change permanently.
  26
  27Options:
  28  -f   Force renaming/moving, even if target exists
  29  -k   Continue on error by skipping
  30       not-existing or not revision-controlled source
  31  -n   Do nothing; show what would happen
  32EOT
  33        exit(1);
  34}
  35
  36# Sanity checks:
  37my $GIT_DIR = $ENV{'GIT_DIR'} || ".git";
  38
  39unless ( -d $GIT_DIR && -d $GIT_DIR . "/objects" && 
  40        -d $GIT_DIR . "/objects/" && -d $GIT_DIR . "/refs") {
  41    print "Git repository not found.";
  42    usage();
  43}
  44
  45
  46our ($opt_n, $opt_f, $opt_h, $opt_k, $opt_v);
  47getopts("hnfkv") || usage;
  48usage() if $opt_h;
  49@ARGV >= 1 or usage;
  50
  51my (@srcArgs, @dstArgs, @srcs, @dsts);
  52my ($src, $dst, $base, $dstDir);
  53
  54my $argCount = scalar @ARGV;
  55if (-d $ARGV[$argCount-1]) {
  56        $dstDir = $ARGV[$argCount-1];
  57        # remove any trailing slash
  58        $dstDir =~ s/\/$//;
  59        @srcArgs = @ARGV[0..$argCount-2];
  60        
  61        foreach $src (@srcArgs) {
  62                $base = $src;
  63                $base =~ s/^.*\///;
  64                $dst = "$dstDir/". $base;
  65                push @dstArgs, $dst;
  66        }
  67}
  68else {
  69    if ($argCount != 2) {
  70        print "Error: moving to directory '"
  71            . $ARGV[$argCount-1]
  72            . "' not possible; not exisiting\n";
  73        usage;
  74    }
  75    @srcArgs = ($ARGV[0]);
  76    @dstArgs = ($ARGV[1]);
  77    $dstDir = "";
  78}
  79
  80my (@allfiles,@srcfiles,@dstfiles);
  81my $safesrc;
  82my (%overwritten, %srcForDst);
  83
  84$/ = "\0";
  85open(F,"-|","git-ls-files","-z")
  86        or die "Failed to open pipe from git-ls-files: " . $!;
  87
  88@allfiles = map { chomp; $_; } <F>;
  89close(F);
  90
  91
  92my ($i, $bad);
  93while(scalar @srcArgs > 0) {
  94    $src = shift @srcArgs;
  95    $dst = shift @dstArgs;
  96    $bad = "";
  97
  98    if ($opt_v) {
  99        print "Checking rename of '$src' to '$dst'\n";
 100    }
 101
 102    unless (-f $src || -l $src || -d $src) {
 103        $bad = "bad source '$src'";
 104    }
 105
 106    $overwritten{$dst} = 0;
 107    if (($bad eq "") && -e $dst) {
 108        $bad = "destination '$dst' already exists";
 109        if (-f $dst && $opt_f) {
 110            print "Warning: $bad; will overwrite!\n";
 111            $bad = "";
 112            $overwritten{$dst} = 1;
 113        }
 114    }
 115    
 116    if (($bad eq "") && ($src eq $dstDir)) {
 117        $bad = "can not move directory '$src' into itself";
 118    }
 119
 120    if ($bad eq "") {
 121        $safesrc = quotemeta($src);
 122        @srcfiles = grep /^$safesrc(\/|$)/, @allfiles;
 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        usage();
 145    }
 146    push @srcs, $src;
 147    push @dsts, $dst;
 148}
 149
 150# Final pass: rename/move
 151my (@deletedfiles,@addedfiles,@changedfiles);
 152while(scalar @srcs > 0) {
 153    $src = shift @srcs;
 154    $dst = shift @dsts;
 155
 156    if ($opt_n || $opt_v) { print "Renaming $src to $dst\n"; }
 157    if (!$opt_n) {
 158        rename($src,$dst)
 159            or die "rename failed: $!";
 160    }
 161
 162    $safesrc = quotemeta($src);
 163    @srcfiles = grep /^$safesrc(\/|$)/, @allfiles;
 164    @dstfiles = @srcfiles;
 165    s/^$safesrc(\/|$)/$dst$1/ for @dstfiles;
 166
 167    push @deletedfiles, @srcfiles;
 168    if (scalar @srcfiles == 1) {
 169        if ($overwritten{$dst} ==1) {
 170            push @changedfiles, $dst;
 171        } else {
 172            push @addedfiles, $dst;
 173        }
 174    }
 175    else {
 176        push @addedfiles, @dstfiles;
 177    }
 178}
 179
 180if ($opt_n) {
 181        print "Changed  : ". join(", ", @changedfiles) ."\n";
 182        print "Adding   : ". join(", ", @addedfiles) ."\n";
 183        print "Deleting : ". join(", ", @deletedfiles) ."\n";
 184        exit(1);
 185}
 186        
 187my $rc;
 188if (scalar @changedfiles >0) {
 189        $rc = system("git-update-index","--",@changedfiles);
 190        die "git-update-index failed to update changed files with code $?\n" if $rc;
 191}
 192if (scalar @addedfiles >0) {
 193        $rc = system("git-update-index","--add","--",@addedfiles);
 194        die "git-update-index failed to add new names with code $?\n" if $rc;
 195}
 196$rc = system("git-update-index","--remove","--",@deletedfiles);
 197die "git-update-index failed to remove old names with code $?\n" if $rc;