git-commit-vandalism/git-mv.perl
Josef Weidendorfer f676fa76e2 Strip any trailing slash on destination argument
Needed because generating a target paths will add another slash.
This fixes e.g. "git-mv file dir/", which removed "file" from
version control by renaming it to "dir//file", as
git-update-index does not accept such paths.

Thanks goes to Ben Lau for noting this bug.

Signed-off-by: Josef Weidendorfer <Josef.Weidendorfer@gmx.de>
Signed-off-by: Junio C Hamano <junkio@cox.net>
2005-11-01 14:45:49 -08:00

198 lines
4.6 KiB
Perl
Executable File

#!/usr/bin/perl
#
# Copyright 2005, Ryan Anderson <ryan@michonline.com>
# Josef Weidendorfer <Josef.Weidendorfer@gmx.de>
#
# This file is licensed under the GPL v2, or a later version
# at the discretion of Linus Torvalds.
use warnings;
use strict;
use Getopt::Std;
sub usage() {
print <<EOT;
$0 [-f] [-n] <source> <dest>
$0 [-f] [-k] [-n] <source> ... <dest directory>
In the first form, source must exist and be either a file,
symlink or directory, dest must not exist. It renames source to dest.
In the second form, the last argument has to be an existing
directory; the given sources will be moved into this directory.
Updates the git cache to reflect the change.
Use "git commit" to make the change permanently.
Options:
-f Force renaming/moving, even if target exists
-k Continue on error by skipping
not-existing or not revision-controlled source
-n Do nothing; show what would happen
EOT
exit(1);
}
# Sanity checks:
my $GIT_DIR = $ENV{'GIT_DIR'} || ".git";
unless ( -d $GIT_DIR && -d $GIT_DIR . "/objects" &&
-d $GIT_DIR . "/objects/" && -d $GIT_DIR . "/refs") {
print "Git repository not found.";
usage();
}
our ($opt_n, $opt_f, $opt_h, $opt_k, $opt_v);
getopts("hnfkv") || usage;
usage() if $opt_h;
@ARGV >= 1 or usage;
my (@srcArgs, @dstArgs, @srcs, @dsts);
my ($src, $dst, $base, $dstDir);
my $argCount = scalar @ARGV;
if (-d $ARGV[$argCount-1]) {
$dstDir = $ARGV[$argCount-1];
# remove any trailing slash
$dstDir =~ s/\/$//;
@srcArgs = @ARGV[0..$argCount-2];
foreach $src (@srcArgs) {
$base = $src;
$base =~ s/^.*\///;
$dst = "$dstDir/". $base;
push @dstArgs, $dst;
}
}
else {
if ($argCount != 2) {
print "Error: moving to directory '"
. $ARGV[$argCount-1]
. "' not possible; not exisiting\n";
usage;
}
@srcArgs = ($ARGV[0]);
@dstArgs = ($ARGV[1]);
$dstDir = "";
}
my (@allfiles,@srcfiles,@dstfiles);
my $safesrc;
my (%overwritten, %srcForDst);
$/ = "\0";
open(F,"-|","git-ls-files","-z")
or die "Failed to open pipe from git-ls-files: " . $!;
@allfiles = map { chomp; $_; } <F>;
close(F);
my ($i, $bad);
while(scalar @srcArgs > 0) {
$src = shift @srcArgs;
$dst = shift @dstArgs;
$bad = "";
if ($opt_v) {
print "Checking rename of '$src' to '$dst'\n";
}
unless (-f $src || -l $src || -d $src) {
$bad = "bad source '$src'";
}
$overwritten{$dst} = 0;
if (($bad eq "") && -e $dst) {
$bad = "destination '$dst' already exists";
if (-f $dst && $opt_f) {
print "Warning: $bad; will overwrite!\n";
$bad = "";
$overwritten{$dst} = 1;
}
}
if (($bad eq "") && ($src eq $dstDir)) {
$bad = "can not move directory '$src' into itself";
}
if ($bad eq "") {
$safesrc = quotemeta($src);
@srcfiles = grep /^$safesrc(\/|$)/, @allfiles;
if (scalar @srcfiles == 0) {
$bad = "'$src' not under version control";
}
}
if ($bad eq "") {
if (defined $srcForDst{$dst}) {
$bad = "can not move '$src' to '$dst'; already target of ";
$bad .= "'".$srcForDst{$dst}."'";
}
else {
$srcForDst{$dst} = $src;
}
}
if ($bad ne "") {
if ($opt_k) {
print "Warning: $bad; skipping\n";
next;
}
print "Error: $bad\n";
usage();
}
push @srcs, $src;
push @dsts, $dst;
}
# Final pass: rename/move
my (@deletedfiles,@addedfiles,@changedfiles);
while(scalar @srcs > 0) {
$src = shift @srcs;
$dst = shift @dsts;
if ($opt_n || $opt_v) { print "Renaming $src to $dst\n"; }
if (!$opt_n) {
rename($src,$dst)
or die "rename failed: $!";
}
$safesrc = quotemeta($src);
@srcfiles = grep /^$safesrc(\/|$)/, @allfiles;
@dstfiles = @srcfiles;
s/^$safesrc(\/|$)/$dst$1/ for @dstfiles;
push @deletedfiles, @srcfiles;
if (scalar @srcfiles == 1) {
if ($overwritten{$dst} ==1) {
push @changedfiles, $dst;
} else {
push @addedfiles, $dst;
}
}
else {
push @addedfiles, @dstfiles;
}
}
if ($opt_n) {
print "Changed : ". join(", ", @changedfiles) ."\n";
print "Adding : ". join(", ", @addedfiles) ."\n";
print "Deleting : ". join(", ", @deletedfiles) ."\n";
exit(1);
}
my $rc;
if (scalar @changedfiles >0) {
$rc = system("git-update-index","--",@changedfiles);
die "git-update-index failed to update changed files with code $?\n" if $rc;
}
if (scalar @addedfiles >0) {
$rc = system("git-update-index","--add","--",@addedfiles);
die "git-update-index failed to add new names with code $?\n" if $rc;
}
$rc = system("git-update-index","--remove","--",@deletedfiles);
die "git-update-index failed to remove old names with code $?\n" if $rc;