git-mv.perlon commit Be marginally more careful about removing objects (41f222e)
   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        @srcArgs = @ARGV[0..$argCount-2];
  58        
  59        foreach $src (@srcArgs) {
  60                $base = $src;
  61                $base =~ s/^.*\///;
  62                $dst = "$dstDir/". $base;
  63                push @dstArgs, $dst;
  64        }
  65}
  66else {
  67    if ($argCount != 2) {
  68        print "Error: moving to directory '"
  69            . $ARGV[$argCount-1]
  70            . "' not possible; not exisiting\n";
  71        usage;
  72    }
  73    @srcArgs = ($ARGV[0]);
  74    @dstArgs = ($ARGV[1]);
  75    $dstDir = "";
  76}
  77
  78my (@allfiles,@srcfiles,@dstfiles);
  79my $safesrc;
  80my (%overwritten, %srcForDst);
  81
  82$/ = "\0";
  83open(F,"-|","git-ls-files","-z")
  84        or die "Failed to open pipe from git-ls-files: " . $!;
  85
  86@allfiles = map { chomp; $_; } <F>;
  87close(F);
  88
  89
  90my ($i, $bad);
  91while(scalar @srcArgs > 0) {
  92    $src = shift @srcArgs;
  93    $dst = shift @dstArgs;
  94    $bad = "";
  95
  96    if ($opt_v) {
  97        print "Checking rename of '$src' to '$dst'\n";
  98    }
  99
 100    unless (-f $src || -l $src || -d $src) {
 101        $bad = "bad source '$src'";
 102    }
 103
 104    $overwritten{$dst} = 0;
 105    if (($bad eq "") && -e $dst) {
 106        $bad = "destination '$dst' already exists";
 107        if (-f $dst && $opt_f) {
 108            print "Warning: $bad; will overwrite!\n";
 109            $bad = "";
 110            $overwritten{$dst} = 1;
 111        }
 112    }
 113    
 114    if (($bad eq "") && ($src eq $dstDir)) {
 115        $bad = "can not move directory '$src' into itself";
 116    }
 117
 118    if ($bad eq "") {
 119        $safesrc = quotemeta($src);
 120        @srcfiles = grep /^$safesrc(\/|$)/, @allfiles;
 121        if (scalar @srcfiles == 0) {
 122            $bad = "'$src' not under version control";
 123        }
 124    }
 125
 126    if ($bad eq "") {
 127       if (defined $srcForDst{$dst}) {
 128           $bad = "can not move '$src' to '$dst'; already target of ";
 129           $bad .= "'".$srcForDst{$dst}."'";
 130       }
 131       else {
 132           $srcForDst{$dst} = $src;
 133       }
 134    }
 135
 136    if ($bad ne "") {
 137        if ($opt_k) {
 138            print "Warning: $bad; skipping\n";
 139            next;
 140        }
 141        print "Error: $bad\n";
 142        usage();
 143    }
 144    push @srcs, $src;
 145    push @dsts, $dst;
 146}
 147
 148# Final pass: rename/move
 149my (@deletedfiles,@addedfiles,@changedfiles);
 150while(scalar @srcs > 0) {
 151    $src = shift @srcs;
 152    $dst = shift @dsts;
 153
 154    if ($opt_n || $opt_v) { print "Renaming $src to $dst\n"; }
 155    if (!$opt_n) {
 156        rename($src,$dst)
 157            or die "rename failed: $!";
 158    }
 159
 160    $safesrc = quotemeta($src);
 161    @srcfiles = grep /^$safesrc(\/|$)/, @allfiles;
 162    @dstfiles = @srcfiles;
 163    s/^$safesrc(\/|$)/$dst$1/ for @dstfiles;
 164
 165    push @deletedfiles, @srcfiles;
 166    if (scalar @srcfiles == 1) {
 167        if ($overwritten{$dst} ==1) {
 168            push @changedfiles, $dst;
 169        } else {
 170            push @addedfiles, $dst;
 171        }
 172    }
 173    else {
 174        push @addedfiles, @dstfiles;
 175    }
 176}
 177
 178if ($opt_n) {
 179        print "Changed  : ". join(", ", @changedfiles) ."\n";
 180        print "Adding   : ". join(", ", @addedfiles) ."\n";
 181        print "Deleting : ". join(", ", @deletedfiles) ."\n";
 182        exit(1);
 183}
 184        
 185my $rc;
 186if (scalar @changedfiles >0) {
 187        $rc = system("git-update-index","--",@changedfiles);
 188        die "git-update-index failed to update changed files with code $?\n" if $rc;
 189}
 190if (scalar @addedfiles >0) {
 191        $rc = system("git-update-index","--add","--",@addedfiles);
 192        die "git-update-index failed to add new names with code $?\n" if $rc;
 193}
 194$rc = system("git-update-index","--remove","--",@deletedfiles);
 195die "git-update-index failed to remove old names with code $?\n" if $rc;