|
|
|
#!/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);
|
|
|
|
|
git-mv: fixes for path handling
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>
19 years ago
|
|
|
# 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];
|
git-mv: fixes for path handling
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>
19 years ago
|
|
|
|
|
|
|
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 = "";
|
|
|
|
}
|
|
|
|
|
git-mv: fixes for path handling
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>
19 years ago
|
|
|
# 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 = "";
|
|
|
|
|
|
|
|
for ($src, $dst) {
|
|
|
|
# Be nicer to end-users by doing ".//a/./b/.//./c" ==> "a/b/c"
|
|
|
|
s|^\./||;
|
|
|
|
s|/\./|/| while (m|/\./|);
|
|
|
|
s|//+|/|g;
|
|
|
|
# Also "a/b/../c" ==> "a/c"
|
|
|
|
1 while (s,(^|/)[^/]+/\.\./,$1,);
|
|
|
|
}
|
|
|
|
|
|
|
|
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);
|
|
|
|
}
|