Skip to content

Commit 102fc37

Browse files
pugmajereLinus Torvalds
authored andcommitted
[PATCH] Add git-relink-script to fix up missing hardlinks
This will scan 2 or more object repositories and look for common objects, check if they are hardlinked, and replace one with a hardlink to the other if not. This version warns when skipping files because of size differences, and handle more than 2 repositories automatically. Signed-off-by: Ryan Anderson <ryan@michonline.com> Cheered-on-by: Jeff Garzik <jgarzik@pobox.com> Acked-by: Junio C Hamano <junkio@cox.net> Signed-off-by: Linus Torvalds <torvalds@osdl.org>
1 parent 042a4ed commit 102fc37

File tree

2 files changed

+174
-1
lines changed

2 files changed

+174
-1
lines changed

Makefile

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -25,7 +25,7 @@ SCRIPTS=git git-apply-patch-script git-merge-one-file-script git-prune-script \
2525
git-deltafy-script git-fetch-script git-status-script git-commit-script \
2626
git-log-script git-shortlog git-cvsimport-script git-diff-script \
2727
git-reset-script git-add-script git-checkout-script git-clone-script \
28-
gitk git-cherry git-rebase-script
28+
gitk git-cherry git-rebase-script git-relink-script
2929

3030
PROG= git-update-cache git-diff-files git-init-db git-write-tree \
3131
git-read-tree git-commit-tree git-cat-file git-fsck-cache \

git-relink-script

Lines changed: 173 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,173 @@
1+
#!/usr/bin/env perl
2+
# Copyright 2005, Ryan Anderson <ryan@michonline.com>
3+
# Distribution permitted under the GPL v2, as distributed
4+
# by the Free Software Foundation.
5+
# Later versions of the GPL at the discretion of Linus Torvalds
6+
#
7+
# Scan two git object-trees, and hardlink any common objects between them.
8+
9+
use 5.006;
10+
use strict;
11+
use warnings;
12+
use Getopt::Long;
13+
14+
sub get_canonical_form($);
15+
sub do_scan_directory($$$);
16+
sub compare_two_files($$);
17+
sub usage();
18+
sub link_two_files($$);
19+
20+
# stats
21+
my $total_linked = 0;
22+
my $total_already = 0;
23+
my ($linked,$already);
24+
25+
my $fail_on_different_sizes = 0;
26+
my $help = 0;
27+
GetOptions("safe" => \$fail_on_different_sizes,
28+
"help" => \$help);
29+
30+
usage() if $help;
31+
32+
my (@dirs) = @ARGV;
33+
34+
usage() if (!defined $dirs[0] || !defined $dirs[1]);
35+
36+
$_ = get_canonical_form($_) foreach (@dirs);
37+
38+
my $master_dir = pop @dirs;
39+
40+
opendir(D,$master_dir . "objects/")
41+
or die "Failed to open $master_dir/objects/ : $!";
42+
43+
my @hashdirs = grep !/^\.{1,2}$/, readdir(D);
44+
45+
foreach my $repo (@dirs) {
46+
$linked = 0;
47+
$already = 0;
48+
printf("Searching '%s' and '%s' for common objects and hardlinking them...\n",
49+
$master_dir,$repo);
50+
51+
foreach my $hashdir (@hashdirs) {
52+
do_scan_directory($master_dir, $hashdir, $repo);
53+
}
54+
55+
printf("Linked %d files, %d were already linked.\n",$linked, $already);
56+
57+
$total_linked += $linked;
58+
$total_already += $already;
59+
}
60+
61+
printf("Totals: Linked %d files, %d were already linked.\n",
62+
$total_linked, $total_already);
63+
64+
65+
sub do_scan_directory($$$) {
66+
my ($srcdir, $subdir, $dstdir) = @_;
67+
68+
my $sfulldir = sprintf("%sobjects/%s/",$srcdir,$subdir);
69+
my $dfulldir = sprintf("%sobjects/%s/",$dstdir,$subdir);
70+
71+
opendir(S,$sfulldir)
72+
or die "Failed to opendir $sfulldir: $!";
73+
74+
foreach my $file (grep(!/\.{1,2}$/, readdir(S))) {
75+
my $sfilename = $sfulldir . $file;
76+
my $dfilename = $dfulldir . $file;
77+
78+
compare_two_files($sfilename,$dfilename);
79+
80+
}
81+
closedir(S);
82+
}
83+
84+
sub compare_two_files($$) {
85+
my ($sfilename, $dfilename) = @_;
86+
87+
# Perl's stat returns relevant information as follows:
88+
# 0 = dev number
89+
# 1 = inode number
90+
# 7 = size
91+
my @sstatinfo = stat($sfilename);
92+
my @dstatinfo = stat($dfilename);
93+
94+
if (@sstatinfo == 0 && @dstatinfo == 0) {
95+
die sprintf("Stat of both %s and %s failed: %s\n",$sfilename, $dfilename, $!);
96+
97+
} elsif (@dstatinfo == 0) {
98+
return;
99+
}
100+
101+
if ( ($sstatinfo[0] == $dstatinfo[0]) &&
102+
($sstatinfo[1] != $dstatinfo[1])) {
103+
if ($sstatinfo[7] == $dstatinfo[7]) {
104+
link_two_files($sfilename, $dfilename);
105+
106+
} else {
107+
my $err = sprintf("ERROR: File sizes are not the same, cannot relink %s to %s.\n",
108+
$sfilename, $dfilename);
109+
if ($fail_on_different_sizes) {
110+
die $err;
111+
} else {
112+
warn $err;
113+
}
114+
}
115+
116+
} elsif ( ($sstatinfo[0] == $dstatinfo[0]) &&
117+
($sstatinfo[1] == $dstatinfo[1])) {
118+
$already++;
119+
}
120+
}
121+
122+
sub get_canonical_form($) {
123+
my $dir = shift;
124+
my $original = $dir;
125+
126+
die "$dir is not a directory." unless -d $dir;
127+
128+
$dir .= "/" unless $dir =~ m#/$#;
129+
$dir .= ".git/" unless $dir =~ m#\.git/$#;
130+
131+
die "$original does not have a .git/ subdirectory.\n" unless -d $dir;
132+
133+
return $dir;
134+
}
135+
136+
sub link_two_files($$) {
137+
my ($sfilename, $dfilename) = @_;
138+
my $tmpdname = sprintf("%s.old",$dfilename);
139+
rename($dfilename,$tmpdname)
140+
or die sprintf("Failure renaming %s to %s: %s",
141+
$dfilename, $tmpdname, $!);
142+
143+
if (! link($sfilename,$dfilename)) {
144+
my $failtxt = "";
145+
unless (rename($tmpdname,$dfilename)) {
146+
$failtxt = sprintf(
147+
"Git Repository containing %s is probably corrupted, " .
148+
"please copy '%s' to '%s' to fix.\n",
149+
$tmpdname, $dfilename);
150+
}
151+
152+
die sprintf("Failed to link %s to %s: %s\n%s" .
153+
$sfilename, $dfilename,
154+
$!, $dfilename, $failtxt);
155+
}
156+
157+
unlink($tmpdname)
158+
or die sprintf("Unlink of %s failed: %s\n",
159+
$dfilename, $!);
160+
161+
$linked++;
162+
}
163+
164+
165+
sub usage() {
166+
print("Usage: $0 [--safe] <dir> [<dir> ...] <master_dir> \n");
167+
print("All directories should contain a .git/objects/ subdirectory.\n");
168+
print("Options\n");
169+
print("\t--safe\t" .
170+
"Stops if two objects with the same hash exist but " .
171+
"have different sizes. Default is to warn and continue.\n");
172+
exit(1);
173+
}

0 commit comments

Comments
 (0)