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