Add a tool to import external repositories
[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 $result = GetOptions("help|?" => \$help,
25                         "man" => \$man,
26                         "externaldir=s" => \$externalDir);
27                 
28 pod2usage(1) if $help;
29 pod2usage(-existatus => 0, -verbose =>2) if $man;
30
31 my $module = shift;
32 my $clonePath = shift;
33 my $commitish = shift;
34
35 pod2usage(2) if !defined($module) || !defined($clonePath);
36
37 if (!$commitish) {
38   $commitish = "HEAD";
39 }
40
41 # Use the PROGRAM_NAME to work out where we should be importing to.
42 if (!$externalDir) {
43   $externalDir = dirname(Cwd::abs_path($PROGRAM_NAME));
44 }
45
46 # Read in our mapping file
47 my %mapping;
48 my $fh = IO::File->new("$externalDir/$module-files")
49   or die "Couldn't open mapping file : $!\n";
50 while (<$fh>) {
51   next if /^\s#/;
52   if (/^(.+)\s+(.+)$/) {
53     $mapping{$1} = $2;
54   } elsif (/\w+/) {
55     die "Unrecognised line in mapping file : $_\n";
56   }
57 }
58 undef $fh;
59
60 #¬†Create the external directory, if it doesn't exist.
61 mkdir "$externalDir/$module" if (! -d "$externalDir/$module");
62
63 # Make ourselves a temporary directory
64 my $tempdir = File::Temp::tempdir(CLEANUP => 1);
65
66 # Write a list of all of the files that we're going to want out of the other
67 # repository in a format we can use with tar.
68 $fh = IO::File->new($tempdir."/filelist", "w")
69   or die "Can't open temporary file list for writing\n";
70 foreach (sort keys(%mapping)) {
71   $fh->print("source/".$_."\n");
72 }
73 undef $fh;
74
75 # Change directory to the root of the source repository
76 chdir $clonePath
77   or die "Unable to change directory to $clonePath : $!\n";
78
79 # Figure out some better names for the commit object we're using
80 my $commitSha1 = `git rev-parse $commitish`;
81 my $commitDesc = `git describe $commitish`;
82 chomp $commitSha1;
83 chomp $commitDesc;
84
85 # Populate our temporary directory with the originals of everything that was
86 # listed in the mapping file
87 system("git archive --format=tar --prefix=source/ $commitish".
88        "  | tar -x -C $tempdir -T $tempdir/filelist") == 0
89  or die "git archive and tar failed : $!\n";
90
91 # change our CWD to the module directory - git ls-files seems to require this
92 chdir "$externalDir/$module"
93   or die "Unable to change directory to $externalDir/$module : $!\n";
94
95 # Now we're about to start fiddling with local state. Make a note of where we
96 # were.
97
98 # Use git stash to preserve whatever state there may be in the current
99 # working tree. Sadly git stash returns a 0 exit status if there are no
100 # local changes, so we need to check for local changes first.
101
102 my $stashed;
103 if (system("git diff-index --quiet --cached HEAD --ignore-submodules") != 0 ||
104     system("git diff-files --quiet --ignore-submodules") != 0) {
105   if (system("git stash") != 0) {
106     die "git stash failed with : $!\n";
107   }
108   $stashed = 1;
109 }
110
111 eval {
112   # Use git-ls-files to get the list of currently committed files for the module
113   my $lspipe = IO::Pipe->new();
114   $lspipe->reader(qw(git ls-files));
115
116   my %filesInTree;
117   while(<$lspipe>) {
118     chomp;
119     $filesInTree{$_}++;
120   }
121
122   foreach my $source (sort keys(%mapping)) {
123     if (-f "$tempdir/source/$source") {
124       File::Path::make_path(File::Basename::dirname($mapping{$source}));
125       system("cp $tempdir/source/$source ".
126              "   $externalDir/$module/".$mapping{$source}) == 0
127          or die "Copy failed with $!\n";
128       system("git add $externalDir/$module/".$mapping{$source}) == 0
129          or die "git add failed with $!\n";
130       delete $filesInTree{$mapping{$source}}
131     } else {
132       die "Couldn't find file $source in original tree\n";
133     }
134   }
135
136   # Use git rm to delete everything that's committed that we don't have a
137   # relacement for.
138   foreach my $missing (keys(%filesInTree)) {
139     system("git rm $missing") == 0
140       or die "Couldn't git rm $missing : $!\n";
141   }
142
143   if (system("git status") == 0) {
144     my $fh=IO::File->new("$tempdir/commit-msg", "w")
145       or die "Unable to write commit message\n";
146     $fh->print("Import of code from $module\n");
147     $fh->print("\n");
148     $fh->print("This commit updates the code imported from the external\n");
149     $fh->print("$module git repository to their revision\n$commitSha1\n");
150     $fh->print("which is described as $commitDesc\n");
151     undef $fh;
152     system("git commit -F $tempdir/commit-msg") == 0
153       or die "Commit failed : $!\n";
154   }
155 };
156
157 my $code = 0;
158
159 if ($@) {
160   print STDERR "Import failed with $@\n";
161   print STDERR "Attempting to reset back to where we were ...\n";
162   system("git reset --hard HEAD") == 0
163     or die "Unable to reset, sorry. You'll need to pick up the pieces\n";
164   $code = 1;
165
166
167 if ($stashed) {
168   system("git stash pop") == 0
169     or die "git stash pop failed with : $!\n";
170 }
171
172 exit $code;
173
174 __END__
175
176 =head1 NAME
177
178 import-external-git - Import bits of an external git repo to OpenAFS
179
180 =head1 SYNOPSIS
181
182 import-external-git [options] <module> <repository> [<commitish>]
183
184   Options
185     --help              brief help message
186     --man               full documentation
187     --externalDir       exact path to import into
188
189 =head1 DESCRIPTION
190
191 import-external-git imports selected files from an external git repository
192 into the OpenAFS src/external tree. For a given <module> it assumes that
193 src/external/<module>-files already exists, and contains a space separated
194 list of source and destination file names. <repository> should point to a
195 local clone of the external project's git repository, and <commitish> points
196 to an object within that tree. If <commitish> isn't specified, the current
197 branch HEAD of that repository is used.
198
199 =cut