Import Upstream version 1.8.5
[hcoop/debian/openafs.git] / src / external / import-external-git.pl
1 #!/usr/bin/perl
2
3 use strict;
4 use warnings;
5
6 use English;
7 use Getopt::Long;
8 use File::Basename;
9 use File::Temp;
10 use File::Path;
11 use IO::File;
12 use IO::Pipe;
13 use Pod::Usage;
14 use Cwd;
15
16 # Import an external git repository into the OpenAFS tree, taking the path
17 # to a local clone of that repository, a file containing a list of mappings
18 # between that repository and the location in the OpenAFS one, and optionally
19 # a commit-ish
20
21 my $help;
22 my $man;
23 my $externalDir;
24 my $nowhitespace;
25 my $result = GetOptions("help|?" => \$help,
26 "nofixwhitespace" => \$nowhitespace,
27 "man" => \$man,
28 "externaldir=s" => \$externalDir);
29
30 pod2usage(1) if $help;
31 pod2usage(-existatus => 0, -verbose =>2) if $man;
32
33 my $module = shift;
34 my $clonePath = shift;
35 my $commitish = shift;
36
37 pod2usage(2) if !defined($module) || !defined($clonePath);
38
39 if (!$commitish) {
40 $commitish = "HEAD";
41 }
42
43 # Use the PROGRAM_NAME to work out where we should be importing to.
44 if (!$externalDir) {
45 $externalDir = dirname(Cwd::abs_path($PROGRAM_NAME));
46 }
47
48 # Read in our mapping file
49 my %mapping;
50 my $fh = IO::File->new("$externalDir/$module-files")
51 or die "Couldn't open mapping file : $!\n";
52 while (<$fh>) {
53 next if /^\s#/;
54 if (/^(\S+)\s+(\S+)$/) {
55 $mapping{$1} = $2;
56 } elsif (/\w+/) {
57 die "Unrecognised line in mapping file : $_\n";
58 }
59 }
60 undef $fh;
61
62 # Read in our last-sha1 file
63 my $last;
64
65 $fh = IO::File->new("$externalDir/$module-last");
66 if ($fh) {
67 $last = $fh->getline;
68 chomp $last;
69 }
70 undef $fh;
71
72 my $author;
73 $fh = IO::File->new("$externalDir/$module-author");
74 if ($fh) {
75 $author = $fh->getline;
76 chomp $author;
77 }
78 undef $fh;
79
80 # Create the external directory, if it doesn't exist.
81 mkdir "$externalDir/$module" if (! -d "$externalDir/$module");
82
83 # Make ourselves a temporary directory
84 my $tempdir = File::Temp::tempdir(CLEANUP => 1);
85
86 # Write a list of all of the files that we're going to want out of the other
87 # repository in a format we can use with tar.
88 $fh = IO::File->new($tempdir."/filelist", "w")
89 or die "Can't open temporary file list for writing\n";
90 foreach (sort keys(%mapping)) {
91 $fh->print("source/".$_."\n");
92 }
93 undef $fh;
94
95 # Change directory to the root of the source repository
96 chdir $clonePath
97 or die "Unable to change directory to $clonePath : $!\n";
98
99 # Figure out some better names for the commit object we're using
100 my $commitSha1 = `git rev-parse $commitish`;
101 my $commitDesc = `git describe $commitish`;
102 chomp $commitSha1;
103 chomp $commitDesc;
104
105 # If we know what our last import was, then get a list of all of the changes
106 # since that import
107 my $changes;
108 if ($last) {
109 my $filelist = join(' ', sort keys(%mapping));
110 $changes = `git shortlog $last..$commitish $filelist`;
111 }
112
113 # Populate our temporary directory with the originals of everything that was
114 # listed in the mapping file
115 system("git archive --format=tar --prefix=source/ $commitish".
116 " | tar -x -C $tempdir -T $tempdir/filelist") == 0
117 or die "git archive and tar failed : $!\n";
118
119 # change our CWD to the module directory - git ls-files seems to require this
120 chdir "$externalDir/$module"
121 or die "Unable to change directory to $externalDir/$module : $!\n";
122
123 # Now we're about to start fiddling with local state. Make a note of where we
124 # were.
125
126 # Use git stash to preserve whatever state there may be in the current
127 # working tree. Sadly git stash returns a 0 exit status if there are no
128 # local changes, so we need to check for local changes first.
129
130 my $stashed;
131 if (system("git diff-index --quiet --cached HEAD --ignore-submodules") != 0 ||
132 system("git diff-files --quiet --ignore-submodules") != 0) {
133 if (system("git stash") != 0) {
134 die "git stash failed with : $!\n";
135 }
136 $stashed = 1;
137 }
138
139
140 eval {
141 my @addedFiles;
142 my @deletedFiles;
143
144 # Use git-ls-files to get the list of currently committed files for the module
145 my $lspipe = IO::Pipe->new();
146 $lspipe->reader(qw(git ls-files));
147
148 my %filesInTree;
149 while(<$lspipe>) {
150 chomp;
151 $filesInTree{$_}++;
152 }
153
154 foreach my $source (sort keys(%mapping)) {
155 if (-f "$tempdir/source/$source") {
156 File::Path::make_path(File::Basename::dirname($mapping{$source}));
157 if (!-f "$externalDir/$module/".$mapping{$source}) {
158 push @addedFiles, $mapping{$source};
159 }
160 system("cp $tempdir/source/$source ".
161 " $externalDir/$module/".$mapping{$source}) == 0
162 or die "Copy failed with $!\n";
163 system("git add $externalDir/$module/".$mapping{$source}) == 0
164 or die "git add failed with $!\n";
165 delete $filesInTree{$mapping{$source}}
166 } else {
167 die "Couldn't find file $source in original tree\n";
168 }
169 }
170
171 # Use git rm to delete everything that's committed that we don't have a
172 # relacement for.
173 foreach my $missing (keys(%filesInTree)) {
174 system("git rm $missing") == 0
175 or die "Couldn't git rm $missing : $!\n";
176 push @deletedFiles, $missing;
177 }
178
179 if (system("git status") == 0) {
180 my $fh=IO::File->new("$externalDir/$module-last", "w");
181 $fh->print($commitSha1."\n");
182 undef $fh;
183 system("git add $externalDir/$module-last") == 0
184 or die "Git add of last file failed with $!\n";
185
186 $fh=IO::File->new("$tempdir/commit-msg", "w")
187 or die "Unable to write commit message\n";
188 $fh->print("Import of code from $module\n");
189 $fh->print("\n");
190 $fh->print("This commit updates the code imported from $module to\n");
191 $fh->print("$commitSha1 ($commitDesc)\n");
192 if ($changes) {
193 $fh->print("\n");
194 $fh->print("Upstream changes are:\n\n");
195 $fh->print($changes);
196 }
197 if (@addedFiles) {
198 $fh->print("\n");
199 $fh->print("New files are:\n");
200 $fh->print(join("\n", map { "\t".$_ } sort @addedFiles));
201 $fh->print("\n");
202 }
203 if (@deletedFiles) {
204 $fh->print("\n");
205 $fh->print("Deleted files are:\n");
206 $fh->print(join("\n", map { "\t".$_ } sort @deletedFiles));
207 $fh->print("\n");
208 }
209 undef $fh;
210 $author="--author '$author'" if ($author);
211 system("git commit --no-verify -F $tempdir/commit-msg $author") == 0
212 or die "Commit failed : $!\n";
213 if ($nowhitespace) {
214 print STDERR "WARNING: not fixing whitespace errors.\n";
215 } else {
216 system("git rebase --whitespace=fix HEAD^") == 0
217 or print STDERR "WARNING: Fixing whitespace errors failed.\n";
218 }
219 system("GIT_EDITOR=true git commit --amend") == 0
220 or print STDERR "WARNING: Firing commit msg hooks failed.\n";
221 }
222 };
223
224 my $code = 0;
225
226 if ($@) {
227 print STDERR "Import failed with $@\n";
228 print STDERR "Attempting to reset back to where we were ...\n";
229 system("git reset --hard HEAD") == 0
230 or die "Unable to reset, sorry. You'll need to pick up the pieces\n";
231 $code = 1;
232 }
233
234 if ($stashed) {
235 system("git stash pop") == 0
236 or die "git stash pop failed with : $!\n";
237 }
238
239 exit $code;
240
241 __END__
242
243 =head1 NAME
244
245 import-external-git - Import bits of an external git repo to OpenAFS
246
247 =head1 SYNOPSIS
248
249 import-external-git [options] <module> <repository> [<commitish>]
250
251 Options
252 --help brief help message
253 --man full documentation
254 --externalDir exact path to import into
255 --nofixwhitespace don't apply whitespace fixes
256
257 =head1 DESCRIPTION
258
259 import-external-git imports selected files from an external git repository
260 into the OpenAFS src/external tree. For a given <module> it assumes that
261 src/external/<module>-files already exists, and contains a space separated
262 list of source and destination file names. <repository> should point to a
263 local clone of the external project's git repository, and <commitish> points
264 to an object within that tree. If <commitish> isn't specified, the current
265 branch HEAD of that repository is used.
266
267 =cut