summaryrefslogtreecommitdiff
path: root/git-mv.perl
diff options
context:
space:
mode:
Diffstat (limited to 'git-mv.perl')
-rwxr-xr-xgit-mv.perl250
1 files changed, 250 insertions, 0 deletions
diff --git a/git-mv.perl b/git-mv.perl
new file mode 100755
index 0000000..75aa8fe
--- /dev/null
+++ b/git-mv.perl
@@ -0,0 +1,250 @@
+#!/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 = "";
+}
+
+my $subdir_prefix = `git rev-parse --show-prefix`;
+chomp($subdir_prefix);
+
+# run in git base directory, so that git-ls-files lists all revisioned files
+chdir "$GIT_DIR/..";
+
+# 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) {
+ # prepend git prefix as we run from base directory
+ $_ = $subdir_prefix.$_;
+ 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);
+}