Code

Perly Git: make sure we do test the freshly built one.
[git.git] / git-mv.perl
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.
9 BEGIN {
10         unless (exists $ENV{'RUNNING_GIT_TESTS'}) {
11                 unshift @INC, '@@INSTLIBDIR@@';
12         }
13 }
14 use warnings;
15 use strict;
16 use Getopt::Std;
17 use Git;
19 sub usage() {
20         print <<EOT;
21 $0 [-f] [-n] <source> <destination>
22 $0 [-f] [-n] [-k] <source> ... <destination directory>
23 EOT
24         exit(1);
25 }
27 our ($opt_n, $opt_f, $opt_h, $opt_k, $opt_v);
28 getopts("hnfkv") || usage;
29 usage() if $opt_h;
30 @ARGV >= 1 or usage;
32 my $repo = Git->repository();
34 my (@srcArgs, @dstArgs, @srcs, @dsts);
35 my ($src, $dst, $base, $dstDir);
37 # remove any trailing slash in arguments
38 for (@ARGV) { s/\/*$//; }
40 my $argCount = scalar @ARGV;
41 if (-d $ARGV[$argCount-1]) {
42         $dstDir = $ARGV[$argCount-1];
43         @srcArgs = @ARGV[0..$argCount-2];
45         foreach $src (@srcArgs) {
46                 $base = $src;
47                 $base =~ s/^.*\///;
48                 $dst = "$dstDir/". $base;
49                 push @dstArgs, $dst;
50         }
51 }
52 else {
53     if ($argCount < 2) {
54         print "Error: need at least two arguments\n";
55         exit(1);
56     }
57     if ($argCount > 2) {
58         print "Error: moving to directory '"
59             . $ARGV[$argCount-1]
60             . "' not possible; not existing\n";
61         exit(1);
62     }
63     @srcArgs = ($ARGV[0]);
64     @dstArgs = ($ARGV[1]);
65     $dstDir = "";
66 }
68 my $subdir_prefix = $repo->wc_subdir();
70 # run in git base directory, so that git-ls-files lists all revisioned files
71 chdir $repo->wc_path();
72 $repo->wc_chdir('');
74 # normalize paths, needed to compare against versioned files and update-index
75 # also, this is nicer to end-users by doing ".//a/./b/.//./c" ==> "a/b/c"
76 for (@srcArgs, @dstArgs) {
77     # prepend git prefix as we run from base directory
78     $_ = $subdir_prefix.$_;
79     s|^\./||;
80     s|/\./|/| while (m|/\./|);
81     s|//+|/|g;
82     # Also "a/b/../c" ==> "a/c"
83     1 while (s,(^|/)[^/]+/\.\./,$1,);
84 }
86 my (@allfiles,@srcfiles,@dstfiles);
87 my $safesrc;
88 my (%overwritten, %srcForDst);
90 {
91         local $/ = "\0";
92         @allfiles = $repo->command('ls-files', '-z');
93 }
96 my ($i, $bad);
97 while(scalar @srcArgs > 0) {
98     $src = shift @srcArgs;
99     $dst = shift @dstArgs;
100     $bad = "";
102     for ($src, $dst) {
103         # Be nicer to end-users by doing ".//a/./b/.//./c" ==> "a/b/c"
104         s|^\./||;
105         s|/\./|/| while (m|/\./|);
106         s|//+|/|g;
107         # Also "a/b/../c" ==> "a/c"
108         1 while (s,(^|/)[^/]+/\.\./,$1,);
109     }
111     if ($opt_v) {
112         print "Checking rename of '$src' to '$dst'\n";
113     }
115     unless (-f $src || -l $src || -d $src) {
116         $bad = "bad source '$src'";
117     }
119     $safesrc = quotemeta($src);
120     @srcfiles = grep /^$safesrc(\/|$)/, @allfiles;
122     $overwritten{$dst} = 0;
123     if (($bad eq "") && -e $dst) {
124         $bad = "destination '$dst' already exists";
125         if ($opt_f) {
126             # only files can overwrite each other: check both source and destination
127             if (-f $dst && (scalar @srcfiles == 1)) {
128                 print "Warning: $bad; will overwrite!\n";
129                 $bad = "";
130                 $overwritten{$dst} = 1;
131             }
132             else {
133                 $bad = "Can not overwrite '$src' with '$dst'";
134             }
135         }
136     }
137     
138     if (($bad eq "") && ($dst =~ /^$safesrc\//)) {
139         $bad = "can not move directory '$src' into itself";
140     }
142     if ($bad eq "") {
143         if (scalar @srcfiles == 0) {
144             $bad = "'$src' not under version control";
145         }
146     }
148     if ($bad eq "") {
149        if (defined $srcForDst{$dst}) {
150            $bad = "can not move '$src' to '$dst'; already target of ";
151            $bad .= "'".$srcForDst{$dst}."'";
152        }
153        else {
154            $srcForDst{$dst} = $src;
155        }
156     }
158     if ($bad ne "") {
159         if ($opt_k) {
160             print "Warning: $bad; skipping\n";
161             next;
162         }
163         print "Error: $bad\n";
164         exit(1);
165     }
166     push @srcs, $src;
167     push @dsts, $dst;
170 # Final pass: rename/move
171 my (@deletedfiles,@addedfiles,@changedfiles);
172 $bad = "";
173 while(scalar @srcs > 0) {
174     $src = shift @srcs;
175     $dst = shift @dsts;
177     if ($opt_n || $opt_v) { print "Renaming $src to $dst\n"; }
178     if (!$opt_n) {
179         if (!rename($src,$dst)) {
180             $bad = "renaming '$src' failed: $!";
181             if ($opt_k) {
182                 print "Warning: skipped: $bad\n";
183                 $bad = "";
184                 next;
185             }
186             last;
187         }
188     }
190     $safesrc = quotemeta($src);
191     @srcfiles = grep /^$safesrc(\/|$)/, @allfiles;
192     @dstfiles = @srcfiles;
193     s/^$safesrc(\/|$)/$dst$1/ for @dstfiles;
195     push @deletedfiles, @srcfiles;
196     if (scalar @srcfiles == 1) {
197         # $dst can be a directory with 1 file inside
198         if ($overwritten{$dst} ==1) {
199             push @changedfiles, $dstfiles[0];
201         } else {
202             push @addedfiles, $dstfiles[0];
203         }
204     }
205     else {
206         push @addedfiles, @dstfiles;
207     }
210 if ($opt_n) {
211     if (@changedfiles) {
212         print "Changed  : ". join(", ", @changedfiles) ."\n";
213     }
214     if (@addedfiles) {
215         print "Adding   : ". join(", ", @addedfiles) ."\n";
216     }
217     if (@deletedfiles) {
218         print "Deleting : ". join(", ", @deletedfiles) ."\n";
219     }
221 else {
222     if (@changedfiles) {
223         my ($fd, $ctx) = $repo->command_input_pipe('update-index', '-z', '--stdin');
224         foreach my $fileName (@changedfiles) {
225                 print $fd "$fileName\0";
226         }
227         git_cmd_try { $repo->command_close_pipe($fd, $ctx); }
228                 'git-update-index failed to update changed files with code %d';
229     }
230     if (@addedfiles) {
231         my ($fd, $ctx) = $repo->command_input_pipe('update-index', '--add', '-z', '--stdin');
232         foreach my $fileName (@addedfiles) {
233                 print $fd "$fileName\0";
234         }
235         git_cmd_try { $repo->command_close_pipe($fd, $ctx); }
236                 'git-update-index failed to add new files with code %d';
237     }
238     if (@deletedfiles) {
239         my ($fd, $ctx) = $repo->command_input_pipe('update-index', '--remove', '-z', '--stdin');
240         foreach my $fileName (@deletedfiles) {
241                 print $fd "$fileName\0";
242         }
243         git_cmd_try { $repo->command_close_pipe($fd, $ctx); }
244                 'git-update-index failed to remove old files with code %d';
245     }
248 if ($bad ne "") {
249     print "Error: $bad\n";
250     exit(1);