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