9e7c73de0b
Moving a directory ending in a slash was not working as the destination was not calculated correctly. E.g. in the git repo, git-mv t/ Documentation gave the error Error: destination 'Documentation' already exists To get rid of this problem, strip trailing slashes from all arguments. The comment in cg-mv made me curious about this issue; Pasky, thanks! As result, the workaround in cg-mv is not needed any more. Also, another bug was shown by cg-mv. When moving files outside of a subdirectory, it typically calls git-mv with something like git-mv Documentation/git.txt Documentation/../git-mv.txt which triggers the following error from git-update-index: Ignoring path Documentation/../git-mv.txt The result is a moved file, removed from git revisioning, but not added again. To fix this, the paths have to be normalized not have ".." in the middle. This was already done in git-mv, but only for a better visual appearance :( Signed-off-by: Josef Weidendorfer <Josef.Weidendorfer@gmx.de> Signed-off-by: Junio C Hamano <junkio@cox.net>
234 lines
5.1 KiB
Perl
Executable File
234 lines
5.1 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> <destination>
|
|
$0 [-f] [-n] [-k] <source> ... <destination directory>
|
|
EOT
|
|
exit(1);
|
|
}
|
|
|
|
our ($opt_n, $opt_f, $opt_h, $opt_k, $opt_v);
|
|
getopts("hnfkv") || usage;
|
|
usage() if $opt_h;
|
|
@ARGV >= 1 or usage;
|
|
|
|
my $GIT_DIR = `git rev-parse --git-dir`;
|
|
exit 1 if $?; # rev-parse would have given "not a git dir" message.
|
|
chomp($GIT_DIR);
|
|
|
|
my (@srcArgs, @dstArgs, @srcs, @dsts);
|
|
my ($src, $dst, $base, $dstDir);
|
|
|
|
# remove any trailing slash in arguments
|
|
for (@ARGV) { s/\/*$//; }
|
|
|
|
my $argCount = scalar @ARGV;
|
|
if (-d $ARGV[$argCount-1]) {
|
|
$dstDir = $ARGV[$argCount-1];
|
|
@srcArgs = @ARGV[0..$argCount-2];
|
|
|
|
foreach $src (@srcArgs) {
|
|
$base = $src;
|
|
$base =~ s/^.*\///;
|
|
$dst = "$dstDir/". $base;
|
|
push @dstArgs, $dst;
|
|
}
|
|
}
|
|
else {
|
|
if ($argCount < 2) {
|
|
print "Error: need at least two arguments\n";
|
|
exit(1);
|
|
}
|
|
if ($argCount > 2) {
|
|
print "Error: moving to directory '"
|
|
. $ARGV[$argCount-1]
|
|
. "' not possible; not existing\n";
|
|
exit(1);
|
|
}
|
|
@srcArgs = ($ARGV[0]);
|
|
@dstArgs = ($ARGV[1]);
|
|
$dstDir = "";
|
|
}
|
|
|
|
# normalize paths, needed to compare against versioned files and update-index
|
|
# also, this is nicer to end-users by doing ".//a/./b/.//./c" ==> "a/b/c"
|
|
for (@srcArgs, @dstArgs) {
|
|
s|^\./||;
|
|
s|/\./|/| while (m|/\./|);
|
|
s|//+|/|g;
|
|
# Also "a/b/../c" ==> "a/c"
|
|
1 while (s,(^|/)[^/]+/\.\./,$1,);
|
|
}
|
|
|
|
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'";
|
|
}
|
|
|
|
$safesrc = quotemeta($src);
|
|
@srcfiles = grep /^$safesrc(\/|$)/, @allfiles;
|
|
|
|
$overwritten{$dst} = 0;
|
|
if (($bad eq "") && -e $dst) {
|
|
$bad = "destination '$dst' already exists";
|
|
if ($opt_f) {
|
|
# only files can overwrite each other: check both source and destination
|
|
if (-f $dst && (scalar @srcfiles == 1)) {
|
|
print "Warning: $bad; will overwrite!\n";
|
|
$bad = "";
|
|
$overwritten{$dst} = 1;
|
|
}
|
|
else {
|
|
$bad = "Can not overwrite '$src' with '$dst'";
|
|
}
|
|
}
|
|
}
|
|
|
|
if (($bad eq "") && ($dst =~ /^$safesrc\//)) {
|
|
$bad = "can not move directory '$src' into itself";
|
|
}
|
|
|
|
if ($bad eq "") {
|
|
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";
|
|
exit(1);
|
|
}
|
|
push @srcs, $src;
|
|
push @dsts, $dst;
|
|
}
|
|
|
|
# Final pass: rename/move
|
|
my (@deletedfiles,@addedfiles,@changedfiles);
|
|
$bad = "";
|
|
while(scalar @srcs > 0) {
|
|
$src = shift @srcs;
|
|
$dst = shift @dsts;
|
|
|
|
if ($opt_n || $opt_v) { print "Renaming $src to $dst\n"; }
|
|
if (!$opt_n) {
|
|
if (!rename($src,$dst)) {
|
|
$bad = "renaming '$src' failed: $!";
|
|
if ($opt_k) {
|
|
print "Warning: skipped: $bad\n";
|
|
$bad = "";
|
|
next;
|
|
}
|
|
last;
|
|
}
|
|
}
|
|
|
|
$safesrc = quotemeta($src);
|
|
@srcfiles = grep /^$safesrc(\/|$)/, @allfiles;
|
|
@dstfiles = @srcfiles;
|
|
s/^$safesrc(\/|$)/$dst$1/ for @dstfiles;
|
|
|
|
push @deletedfiles, @srcfiles;
|
|
if (scalar @srcfiles == 1) {
|
|
# $dst can be a directory with 1 file inside
|
|
if ($overwritten{$dst} ==1) {
|
|
push @changedfiles, $dstfiles[0];
|
|
|
|
} else {
|
|
push @addedfiles, $dstfiles[0];
|
|
}
|
|
}
|
|
else {
|
|
push @addedfiles, @dstfiles;
|
|
}
|
|
}
|
|
|
|
if ($opt_n) {
|
|
if (@changedfiles) {
|
|
print "Changed : ". join(", ", @changedfiles) ."\n";
|
|
}
|
|
if (@addedfiles) {
|
|
print "Adding : ". join(", ", @addedfiles) ."\n";
|
|
}
|
|
if (@deletedfiles) {
|
|
print "Deleting : ". join(", ", @deletedfiles) ."\n";
|
|
}
|
|
}
|
|
else {
|
|
if (@changedfiles) {
|
|
open(H, "| git-update-index -z --stdin")
|
|
or die "git-update-index failed to update changed files with code $!\n";
|
|
foreach my $fileName (@changedfiles) {
|
|
print H "$fileName\0";
|
|
}
|
|
close(H);
|
|
}
|
|
if (@addedfiles) {
|
|
open(H, "| git-update-index --add -z --stdin")
|
|
or die "git-update-index failed to add new names with code $!\n";
|
|
foreach my $fileName (@addedfiles) {
|
|
print H "$fileName\0";
|
|
}
|
|
close(H);
|
|
}
|
|
if (@deletedfiles) {
|
|
open(H, "| git-update-index --remove -z --stdin")
|
|
or die "git-update-index failed to remove old names with code $!\n";
|
|
foreach my $fileName (@deletedfiles) {
|
|
print H "$fileName\0";
|
|
}
|
|
close(H);
|
|
}
|
|
}
|
|
|
|
if ($bad ne "") {
|
|
print "Error: $bad\n";
|
|
exit(1);
|
|
}
|