gc.sh: fall back to cp plus mv if ln fails
commit181145220488a4b4fca3d0707630026a17e0d73b
authorKyle J. McKay <mackyle@gmail.com>
Sat, 10 Feb 2018 18:06:44 +0000 (10 10:06 -0800)
committerKyle J. McKay <mackyle@gmail.com>
Sat, 10 Feb 2018 18:06:44 +0000 (10 10:06 -0800)
treec00ec5f42fa663f01588a8ec64025260c44e8e27
parent586e51f0bc33727388df71f725dcd5c1a6f68d28
gc.sh: fall back to cp plus mv if ln fails

When performing garbage collection, a fair amount of pack renaming
takes place.

Every attempt is made to do this safely and atomically.

The ideal and quick method simply hard links the pack files (in
the proper extension order of course) to the new name and then
if that was fully successful removes the old names.

This technique provides a quick, reliable and atomic means to
rename pack files in a way that does not disrupt any current
Git operation (Git might "reprepare_packed_git" to find the pack
under the new name, but it does that automatically).

Unfortunately when dealing with more current operating systems and
shared Git repositories there can be a problem with this technique.

Regardless of the core.sharedRepository setting, Git insists on
making pack files (all the .ext types) have mode 0444 (-r--r--r--).

On some newer operating systems it's not possible to create a hard
link to such a file even if the hard link creator has write permission
to the directory unless the creator is either the owner of the
target file or has write permission to it (or, obviously, is root).

The "write permission" check is a new "feature" added by more recent
versions of some common operating systems.

Unfortunately, when dealing with shared Git repositories, existing
packs may have several different owners (e.g. anything pushed by a
Girocco ssh chroot jail user).

The pre-receive hook attempts to mitigate this issue by adding ug+w
permission to incoming packs.  Until very recently it only did this
for the ".pack" extension to facilitate "freshening" of a pack.
Now it does it for all pack extension types.

Nevertheless, packs may still exist that are not owned by the Girocco
maintenance user performing garbage collection (either because they
predate the pre-receive hook change or perhaps were created via
some other mechanism).

Handle this situation by using a fallback strategy that is also
atomic but not quite as quick nor as efficient.

If the hard link attempt fails, instead attempt a "cp -fp" copy to
a temporary file and then "mv -f" that into place.

The downside is that a "cp" operation is not nearly as fast nor as
efficient as a hard link operation.

While "unreachable" packs are indeed hard linked into child forks,
they are generated only by the Girocco maintenance user and will
therefore never need to use the fallback so their hard links will
not be broken.

All other packs will only have a link count of 1 and although the
fallback strategy will indeed create a copy, the extra space used
by the copy will be reclaimed almost immediately thereafter when
the original gets removed at the end of the rename operation.

With the recent change to the pre-receive hook, the fallback strategy
should rarely be needed so it's a win all around.

Signed-off-by: Kyle J. McKay <mackyle@gmail.com>
jobd/gc.sh