Code

Fix what to do and how to detect when hardlinking fails
[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.
10 use warnings;
11 use strict;
12 use Getopt::Std;
14 sub usage() {
15         print <<EOT;
16 $0 [-f] [-n] <source> <dest>
17 $0 [-f] [-k] [-n] <source> ... <dest directory>
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.
24 Updates the git cache to reflect the change.
25 Use "git commit" to make the change permanently.
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 }
36 # Sanity checks:
37 my $GIT_DIR = $ENV{'GIT_DIR'} || ".git";
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 }
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;
51 my (@srcArgs, @dstArgs, @srcs, @dsts);
52 my ($src, $dst, $base, $dstDir);
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 }
78 my (@allfiles,@srcfiles,@dstfiles);
79 my $safesrc;
80 my (%overwritten, %srcForDst);
82 $/ = "\0";
83 open(F,"-|","git-ls-files","-z")
84         or die "Failed to open pipe from git-ls-files: " . $!;
86 @allfiles = map { chomp; $_; } <F>;
87 close(F);
90 my ($i, $bad);
91 while(scalar @srcArgs > 0) {
92     $src = shift @srcArgs;
93     $dst = shift @dstArgs;
94     $bad = "";
96     if ($opt_v) {
97         print "Checking rename of '$src' to '$dst'\n";
98     }
100     unless (-f $src || -l $src || -d $src) {
101         $bad = "bad source '$src'";
102     }
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     }
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     }
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     }
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;
148 # Final pass: rename/move
149 my (@deletedfiles,@addedfiles,@changedfiles);
150 while(scalar @srcs > 0) {
151     $src = shift @srcs;
152     $dst = shift @dsts;
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     }
160     $safesrc = quotemeta($src);
161     @srcfiles = grep /^$safesrc(\/|$)/, @allfiles;
162     @dstfiles = @srcfiles;
163     s/^$safesrc(\/|$)/$dst$1/ for @dstfiles;
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     }
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);
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;
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;
194 $rc = system("git-update-index","--remove","--",@deletedfiles);
195 die "git-update-index failed to remove old names with code $?\n" if $rc;