diff options
Diffstat (limited to 'git-mv.perl')
-rwxr-xr-x | git-mv.perl | 250 |
1 files changed, 250 insertions, 0 deletions
diff --git a/git-mv.perl b/git-mv.perl new file mode 100755 index 0000000000..75aa8feeb6 --- /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); +} |