1#!/usr/bin/perl
2#
3# Copyright 2005, Ryan Anderson <ryan@michonline.com>
4# Josef Weidendorfer <Josef.Weidendorfer@gmx.de>
5#
6# This file is licensed under the GPL v2, or a later version
7# at the discretion of Linus Torvalds.
8
9
10use warnings;
11use strict;
12use Getopt::Std;
13
14sub usage() {
15 print <<EOT;
16$0 [-f] [-n] <source> <destination>
17$0 [-f] [-n] [-k] <source> ... <destination directory>
18EOT
19 exit(1);
20}
21
22my $GIT_DIR = `git rev-parse --git-dir`;
23exit 1 if $?; # rev-parse would have given "not a git dir" message.
24chomp($GIT_DIR);
25
26our ($opt_n, $opt_f, $opt_h, $opt_k, $opt_v);
27getopts("hnfkv") || usage;
28usage() if $opt_h;
29@ARGV >= 1 or usage;
30
31my (@srcArgs, @dstArgs, @srcs, @dsts);
32my ($src, $dst, $base, $dstDir);
33
34my $argCount = scalar @ARGV;
35if (-d $ARGV[$argCount-1]) {
36 $dstDir = $ARGV[$argCount-1];
37 # remove any trailing slash
38 $dstDir =~ s/\/$//;
39 @srcArgs = @ARGV[0..$argCount-2];
40
41 foreach $src (@srcArgs) {
42 $base = $src;
43 $base =~ s/^.*\///;
44 $dst = "$dstDir/". $base;
45 push @dstArgs, $dst;
46 }
47}
48else {
49 if ($argCount != 2) {
50 print "Error: moving to directory '"
51 . $ARGV[$argCount-1]
52 . "' not possible; not exisiting\n";
53 exit(1);
54 }
55 @srcArgs = ($ARGV[0]);
56 @dstArgs = ($ARGV[1]);
57 $dstDir = "";
58}
59
60my (@allfiles,@srcfiles,@dstfiles);
61my $safesrc;
62my (%overwritten, %srcForDst);
63
64$/ = "\0";
65open(F, 'git-ls-files -z |')
66 or die "Failed to open pipe from git-ls-files: " . $!;
67
68@allfiles = map { chomp; $_; } <F>;
69close(F);
70
71
72my ($i, $bad);
73while(scalar @srcArgs > 0) {
74 $src = shift @srcArgs;
75 $dst = shift @dstArgs;
76 $bad = "";
77
78 for ($src, $dst) {
79 # Be nicer to end-users by doing ".//a/./b/.//./c" ==> "a/b/c"
80 s|^\./||;
81 s|/\./|/| while (m|/\./|);
82 s|//+|/|g;
83 # Also "a/b/../c" ==> "a/c"
84 1 while (s,(^|/)[^/]+/\.\./,$1,);
85 }
86
87 if ($opt_v) {
88 print "Checking rename of '$src' to '$dst'\n";
89 }
90
91 unless (-f $src || -l $src || -d $src) {
92 $bad = "bad source '$src'";
93 }
94
95 $safesrc = quotemeta($src);
96 @srcfiles = grep /^$safesrc(\/|$)/, @allfiles;
97
98 $overwritten{$dst} = 0;
99 if (($bad eq "") && -e $dst) {
100 $bad = "destination '$dst' already exists";
101 if ($opt_f) {
102 # only files can overwrite each other: check both source and destination
103 if (-f $dst && (scalar @srcfiles == 1)) {
104 print "Warning: $bad; will overwrite!\n";
105 $bad = "";
106 $overwritten{$dst} = 1;
107 }
108 else {
109 $bad = "Can not overwrite '$src' with '$dst'";
110 }
111 }
112 }
113
114 if (($bad eq "") && ($dst =~ /^$safesrc\//)) {
115 $bad = "can not move directory '$src' into itself";
116 }
117
118 if ($bad eq "") {
119 if (scalar @srcfiles == 0) {
120 $bad = "'$src' not under version control";
121 }
122 }
123
124 if ($bad eq "") {
125 if (defined $srcForDst{$dst}) {
126 $bad = "can not move '$src' to '$dst'; already target of ";
127 $bad .= "'".$srcForDst{$dst}."'";
128 }
129 else {
130 $srcForDst{$dst} = $src;
131 }
132 }
133
134 if ($bad ne "") {
135 if ($opt_k) {
136 print "Warning: $bad; skipping\n";
137 next;
138 }
139 print "Error: $bad\n";
140 exit(1);
141 }
142 push @srcs, $src;
143 push @dsts, $dst;
144}
145
146# Final pass: rename/move
147my (@deletedfiles,@addedfiles,@changedfiles);
148$bad = "";
149while(scalar @srcs > 0) {
150 $src = shift @srcs;
151 $dst = shift @dsts;
152
153 if ($opt_n || $opt_v) { print "Renaming $src to $dst\n"; }
154 if (!$opt_n) {
155 if (!rename($src,$dst)) {
156 $bad = "renaming '$src' failed: $!";
157 if ($opt_k) {
158 print "Warning: skipped: $bad\n";
159 $bad = "";
160 next;
161 }
162 last;
163 }
164 }
165
166 $safesrc = quotemeta($src);
167 @srcfiles = grep /^$safesrc(\/|$)/, @allfiles;
168 @dstfiles = @srcfiles;
169 s/^$safesrc(\/|$)/$dst$1/ for @dstfiles;
170
171 push @deletedfiles, @srcfiles;
172 if (scalar @srcfiles == 1) {
173 # $dst can be a directory with 1 file inside
174 if ($overwritten{$dst} ==1) {
175 push @changedfiles, $dstfiles[0];
176
177 } else {
178 push @addedfiles, $dstfiles[0];
179 }
180 }
181 else {
182 push @addedfiles, @dstfiles;
183 }
184}
185
186if ($opt_n) {
187 if (@changedfiles) {
188 print "Changed : ". join(", ", @changedfiles) ."\n";
189 }
190 if (@addedfiles) {
191 print "Adding : ". join(", ", @addedfiles) ."\n";
192 }
193 if (@deletedfiles) {
194 print "Deleting : ". join(", ", @deletedfiles) ."\n";
195 }
196}
197else {
198 if (@changedfiles) {
199 open(H, "| git-update-index -z --stdin")
200 or die "git-update-index failed to update changed files with code $!\n";
201 foreach my $fileName (@changedfiles) {
202 print H "$fileName\0";
203 }
204 close(H);
205 }
206 if (@addedfiles) {
207 open(H, "| git-update-index --add -z --stdin")
208 or die "git-update-index failed to add new names with code $!\n";
209 foreach my $fileName (@addedfiles) {
210 print H "$fileName\0";
211 }
212 close(H);
213 }
214 if (@deletedfiles) {
215 open(H, "| git-update-index --remove -z --stdin")
216 or die "git-update-index failed to remove old names with code $!\n";
217 foreach my $fileName (@deletedfiles) {
218 print H "$fileName\0";
219 }
220 close(H);
221 }
222}
223
224if ($bad ne "") {
225 print "Error: $bad\n";
226 exit(1);
227}