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