Document the use of "current directory" as pull source.
[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> <dest>
17 $0 [-f] [-k] [-n] <source> ... <dest directory>
18
19 In the first form, source must exist and be either a file,
20 symlink or directory, dest must not exist. It renames source to dest.
21 In the second form, the last argument has to be an existing
22 directory; the given sources will be moved into this directory.
23
24 Updates the git cache to reflect the change.
25 Use "git commit" to make the change permanently.
26
27 Options:
28   -f   Force renaming/moving, even if target exists
29   -k   Continue on error by skipping
30        not-existing or not revision-controlled source
31   -n   Do nothing; show what would happen
32 EOT
33         exit(1);
34 }
35
36 # Sanity checks:
37 my $GIT_DIR = $ENV{'GIT_DIR'} || ".git";
38
39 unless ( -d $GIT_DIR && -d $GIT_DIR . "/objects" && 
40         -d $GIT_DIR . "/objects/" && -d $GIT_DIR . "/refs") {
41     print "Git repository not found.";
42     usage();
43 }
44
45
46 our ($opt_n, $opt_f, $opt_h, $opt_k, $opt_v);
47 getopts("hnfkv") || usage;
48 usage() if $opt_h;
49 @ARGV >= 1 or usage;
50
51 my (@srcArgs, @dstArgs, @srcs, @dsts);
52 my ($src, $dst, $base, $dstDir);
53
54 my $argCount = scalar @ARGV;
55 if (-d $ARGV[$argCount-1]) {
56         $dstDir = $ARGV[$argCount-1];
57         @srcArgs = @ARGV[0..$argCount-2];
58         
59         foreach $src (@srcArgs) {
60                 $base = $src;
61                 $base =~ s/^.*\///;
62                 $dst = "$dstDir/". $base;
63                 push @dstArgs, $dst;
64         }
65 }
66 else {
67     if ($argCount != 2) {
68         print "Error: moving to directory '"
69             . $ARGV[$argCount-1]
70             . "' not possible; not exisiting\n";
71         usage;
72     }
73     @srcArgs = ($ARGV[0]);
74     @dstArgs = ($ARGV[1]);
75     $dstDir = "";
76 }
77
78 my (@allfiles,@srcfiles,@dstfiles);
79 my $safesrc;
80 my (%overwritten, %srcForDst);
81
82 $/ = "\0";
83 open(F,"-|","git-ls-files","-z")
84         or die "Failed to open pipe from git-ls-files: " . $!;
85
86 @allfiles = map { chomp; $_; } <F>;
87 close(F);
88
89
90 my ($i, $bad);
91 while(scalar @srcArgs > 0) {
92     $src = shift @srcArgs;
93     $dst = shift @dstArgs;
94     $bad = "";
95
96     if ($opt_v) {
97         print "Checking rename of '$src' to '$dst'\n";
98     }
99
100     unless (-f $src || -l $src || -d $src) {
101         $bad = "bad source '$src'";
102     }
103
104     $overwritten{$dst} = 0;
105     if (($bad eq "") && -e $dst) {
106         $bad = "destination '$dst' already exists";
107         if (-f $dst && $opt_f) {
108             print "Warning: $bad; will overwrite!\n";
109             $bad = "";
110             $overwritten{$dst} = 1;
111         }
112     }
113     
114     if (($bad eq "") && ($src eq $dstDir)) {
115         $bad = "can not move directory '$src' into itself";
116     }
117
118     if ($bad eq "") {
119         $safesrc = quotemeta($src);
120         @srcfiles = grep /^$safesrc(\/|$)/, @allfiles;
121         if (scalar @srcfiles == 0) {
122             $bad = "'$src' not under version control";
123         }
124     }
125
126     if ($bad eq "") {
127        if (defined $srcForDst{$dst}) {
128            $bad = "can not move '$src' to '$dst'; already target of ";
129            $bad .= "'".$srcForDst{$dst}."'";
130        }
131        else {
132            $srcForDst{$dst} = $src;
133        }
134     }
135
136     if ($bad ne "") {
137         if ($opt_k) {
138             print "Warning: $bad; skipping\n";
139             next;
140         }
141         print "Error: $bad\n";
142         usage();
143     }
144     push @srcs, $src;
145     push @dsts, $dst;
146 }
147
148 # Final pass: rename/move
149 my (@deletedfiles,@addedfiles,@changedfiles);
150 while(scalar @srcs > 0) {
151     $src = shift @srcs;
152     $dst = shift @dsts;
153
154     if ($opt_n || $opt_v) { print "Renaming $src to $dst\n"; }
155     if (!$opt_n) {
156         rename($src,$dst)
157             or die "rename failed: $!";
158     }
159
160     $safesrc = quotemeta($src);
161     @srcfiles = grep /^$safesrc(\/|$)/, @allfiles;
162     @dstfiles = @srcfiles;
163     s/^$safesrc(\/|$)/$dst$1/ for @dstfiles;
164
165     push @deletedfiles, @srcfiles;
166     if (scalar @srcfiles == 1) {
167         if ($overwritten{$dst} ==1) {
168             push @changedfiles, $dst;
169         } else {
170             push @addedfiles, $dst;
171         }
172     }
173     else {
174         push @addedfiles, @dstfiles;
175     }
176 }
177
178 if ($opt_n) {
179         print "Changed  : ". join(", ", @changedfiles) ."\n";
180         print "Adding   : ". join(", ", @addedfiles) ."\n";
181         print "Deleting : ". join(", ", @deletedfiles) ."\n";
182         exit(1);
183 }
184         
185 my $rc;
186 if (scalar @changedfiles >0) {
187         $rc = system("git-update-index","--",@changedfiles);
188         die "git-update-index failed to update changed files with code $?\n" if $rc;
189 }
190 if (scalar @addedfiles >0) {
191         $rc = system("git-update-index","--add","--",@addedfiles);
192         die "git-update-index failed to add new names with code $?\n" if $rc;
193 }
194 $rc = system("git-update-index","--remove","--",@deletedfiles);
195 die "git-update-index failed to remove old names with code $?\n" if $rc;