Add hardlink support for tar archive upload.

"Why the fuck would anybody want that", you could reasonably ask.
Well, most wouldn't want this. However, if you wanted to use git-pages
to deduplicate your backups, you might find it that some backups
include hardlinks.

"Why the fuck would anybody put their backups in git-pages", you could
even more reasonably ask. Well, almost nobody would! However, tarsnap
doesn't let you download deduplicated data (even though it deduplicates
data in storage), restic can't ingest tarballs, I didn't have
a partition I could format for btrfs, and git-pages performed much
better than alternatives like juicefs.

In the end this is correct and not expensive to do, just very niche.
This commit is contained in:
Catherine
2026-03-28 17:01:23 +00:00
parent e28d8cf0f2
commit 2fdf0b805d

View File

@@ -87,6 +87,7 @@ func ExtractTar(ctx context.Context, reader io.Reader, oldManifest *Manifest) (*
index := IndexManifestByGitHash(oldManifest)
missing := []string{}
manifest := NewManifest()
hardLinks := map[string]*Entry{}
for {
header, err := archive.Next()
if err == io.EOF {
@@ -107,11 +108,13 @@ func ExtractTar(ctx context.Context, reader io.Reader, oldManifest *Manifest) (*
if err != nil {
return nil, fmt.Errorf("tar: %s: %w", fileName, err)
}
AddFile(manifest, fileName, fileData)
entry := AddFile(manifest, fileName, fileData)
hardLinks[header.Name] = entry
dataBytesTransferred += int64(len(fileData))
case tar.TypeSymlink:
entry := addSymlinkOrBlobReference(
manifest, fileName, header.Linkname, index, &missing)
hardLinks[header.Name] = entry
switch {
case entry == nil:
// unresolved blob reference
@@ -120,6 +123,12 @@ func ExtractTar(ctx context.Context, reader io.Reader, oldManifest *Manifest) (*
default:
dataBytesTransferred += int64(len(header.Linkname)) // actual symlink
}
case tar.TypeLink:
if entry, found := hardLinks[header.Linkname]; found {
manifest.Contents[fileName] = entry
} else {
AddProblem(manifest, fileName, "tar: invalid hardlink %q", header.Linkname)
}
case tar.TypeDir:
AddDirectory(manifest, fileName)
default: