5 use Getopt::Long qw(:config bundling no_ignore_case);
10 GetOptions ("F=s" => \$message_file,
18 $0, for importing a tarball as a Git branch
19 usage: $0 [OPTIONS] TARBALL BRANCH
20 where TARBALL is the name of a tarball and BRANCH is a branch to create or
21 update. (If you want it to be a normal user-visible Git branch then BRANCH
22 should start with "refs/heads".)
25 -F FILE Read commit message from FILE.
26 -m MESSAGE Use MESSAGE as commit message.
27 --help Print this usage message and exit
32 die "$0: exactly two nonoption arguments are required (use --help for help)\n"
35 our ($tarball, $branch) = @ARGV;
37 my $new_branch = system ("git rev-parse --verify $branch >/dev/null 2>&1") != 0;
39 if (defined ($message_file)) {
40 open (MESSAGE, '<', $message_file)
41 or die "$0: failed to open \"$message_file\": $!\n";
42 $message .= join ('', <MESSAGE>);
44 } elsif (!defined ($message)) {
45 $message = "Import of $tarball.\n";
49 die "$0: $tarball: not found\n";
50 } elsif ($tarball =~ /gz$/) {
51 open (TARBALL, "-|", "zcat $tarball")
52 or die "$0: \"zcat $tarball\" failed to start: $!\n";
54 open (TARBALL, '<', $tarball)
55 or die "$0: failed to open \"$tarball\": $!\n";
58 my $committer_ident = `git var GIT_COMMITTER_IDENT`;
59 chomp $committer_ident;
61 open (GFI, "|-", "git fast-import --date-format=raw --quiet")
62 or die "$0: \"git fast-import\" failed to start: $!\n";
70 my (%member) = read_tar_header ();
72 next if $member{NAME} eq '.'; # Skip root directory.
74 my $type = $member{TYPE};
76 # Add to Git commit, if it's a file type that Git supports, or
77 # make Git ignore it otherwise.
79 my ($gitmode) = $member{MODE} & 0111 ? "755" : "644";
80 push(@commit, "M $gitmode :$mark $member{NAME}\n");
82 my $size = $member{SIZE};
84 print GFI "mark :", $mark++, "\n";
85 print GFI "data $size\n";
86 my $remaining = $size;
87 while ($remaining > 0) {
88 my $chunk = $remaining > 65536 ? 65536 : $remaining;
89 my $data = read_fully ($chunk);
93 read_fully(512 - $size % 512) if $size % 512;
95 } elsif ($type eq 'l') {
96 push(@commit, "M 120000 :$mark $member{NAME}\n");
99 print GFI "mark :", $mark++, "\n";
100 print GFI "data ", length($member{LINKNAME}), "\n";
101 print GFI $member{LINKNAME}, "\n";
102 } elsif ($type eq 'd') {
103 # We don't do anything about directories. In particular,
104 # ignoring them is a bad idea because files added under them
105 # will then also be ignored.
107 print STDERR "$0: tar member $member{NAME} has type '$type' that Git cannot represent, ignoring\n"
111 my $commit = $mark++;
112 print GFI "commit $branch\n";
113 print GFI "mark :$commit\n";
114 print GFI "committer $committer_ident\n";
115 print GFI "data ", length($message), "\n";
116 print GFI $message, "\n";
117 print GFI "merge $branch^0\n" if !$new_branch;
118 print GFI "deleteall\n";
119 print GFI $_ foreach @commit;
122 close (TARBALL) or die "$0: \"zcat $tarball\" exited with status $?\n";
123 close (GFI) or die "$0: \"git fast-import\" exited with status $?\n";
127 my $magic = substr($header, 257, 5);
128 my $version = substr($header, 263, 2);
129 my $chksum = oct(substr($header, 148, 8));
130 if (checksum($header) != $chksum) {
131 fail("$tarball: bad header checksum (is this a tar archive?)");
138 substr($header, 148, 8) = ' ' x 8;
140 $chksum += ord($_) foreach split('', $header);
147 while (length($data) < $nbytes) {
148 my $chunk = $nbytes - length($data);
149 my $bytes_read = sysread (TARBALL, $data, $chunk, length($data));
150 $offset += $bytes_read;
151 fail("$tarball: read error: $!") if !defined($bytes_read);
152 fail("$tarball: unexpected end of file") if !$bytes_read;
157 sub zero_header_size {
159 substr($header, 124, 12) = ("0" x 11) . "\0";
160 substr($header, 148, 8) = sprintf("%07o", checksum($header)) . "\0";
167 die "$msg at $tarball offset $offset\n";
172 fail("$tarball: contains file with empty name or linkname") if $name eq '';
173 fail("$tarball: contains file with .. in name")
174 if grep($_ eq '..', split('/', $name));
175 $name = join('/', grep($_ ne '' && $_ ne '.', split('/', $name)));
176 $name = '.' if $name eq '';
180 sub read_tar_header {
181 my ($header, $type, $name, $linkname);
183 $header = read_fully(512);
184 return () if $header eq "\0" x 512;
185 check_header($header);
187 $type = substr($header, 156, 1);
188 last if $type !~ /[KL]/;
190 my ($size) = oct(unpack("Z*", substr($header, 124, 12)));
191 fail("bad longname size $size") if $size < 0;
193 my ($string) = read_fully($size);
194 read_fully (512 - $size % 512) if $size % 512;
195 ($type eq 'L' ? $name : $linkname) = $string;
199 if ($type =~ /[70\0]/) {
201 } elsif ($type !~ tr/123456/hlcbdp/) {
202 fail("unknown file type '$type'");
205 # Get name and linkname, if we didn't already.
206 if (!defined($name)) {
207 $name = unpack("Z100", $header);
208 my ($prefix) = unpack("Z*", substr($header, 345));
209 $name = "$prefix/$name" if $prefix ne '';
211 $linkname = unpack("Z*", substr($header, 157, 100)) if !defined($linkname);
214 $name = normalize_name($name);
215 $linkname = normalize_name($linkname) if $type eq 'h';
218 my ($size) = oct(unpack("Z*", substr($header, 124, 12)));
219 fail("bad size $size") if $size < 0;
220 $size = 0 if $type eq 'd';
222 # Get other information.
223 my ($mode) = oct(substr($header, 100, 8));
225 if ($type !~ /[-hlcbdp]/) {
226 # Read and discard any data.
227 my $remaining = int($size / 512) * 512;
228 $size += 512 if $size % 512;
229 while ($remaining > 0) {
230 my $chunk = $remaining > 65536 ? 65536 : $remaining;
231 my $data = read_fully($chunk);
232 $remaining -= $chunk;
236 return (TYPE => $type,
240 LINKNAME => $linkname,