Make repack less likely to corrupt repository
authorJunio C Hamano <gitster@pobox.com>
Tue, 10 Feb 2009 20:16:31 +0000 (12:16 -0800)
committerJunio C Hamano <gitster@pobox.com>
Thu, 12 Feb 2009 02:32:16 +0000 (18:32 -0800)
Some platforms refuse to rename a file that is open. When repacking an
already packed repository without adding any new object, the resulting
pack will contain the same set of objects as an existing pack, and on such
platforms, a newly created packfile cannot replace the existing one.

The logic detected this issue but did not try hard enough to recover from
it. Especially because the files that needs renaming come in pairs, there
potentially are different failure modes that one can be renamed but the
others cannot. Asking manual recovery to end users were error prone.

This patch tries to make it more robust by first making sure all the
existing files that need to be renamed have been renamed before
continuing, and attempts to roll back if some failed to rename.

This is based on an initial patch by Robin Rosenberg.

Signed-off-by: Junio C Hamano <gitster@pobox.com>
git-repack.sh
index 458a497af810c7bb188a5aafb80c32aa0bc05264..be6db5e805d62e0440f7de3c1f6ee3fcd16a2ed3 100755 (executable)
@@ -88,32 +88,79 @@ if [ -z "$names" ]; then
                echo Nothing new to pack.
        fi
 fi
-for name in $names ; do
-       fullbases="$fullbases pack-$name"
-       chmod a-w "$PACKTMP-$name.pack"
-       chmod a-w "$PACKTMP-$name.idx"
-       mkdir -p "$PACKDIR" || exit
 
+# Ok we have prepared all new packfiles.
+mkdir -p "$PACKDIR" || exit
+
+# First see if there are packs of the same name and if so
+# if we can move them out of the way (this can happen if we
+# repacked immediately after packing fully.
+rollback=
+failed=
+for name in $names
+do
        for sfx in pack idx
        do
-               if test -f "$PACKDIR/pack-$name.$sfx"
-               then
-                       mv -f "$PACKDIR/pack-$name.$sfx" \
-                               "$PACKDIR/old-pack-$name.$sfx"
-               fi
-       done &&
+               file=pack-$name.$sfx
+               test -f "$PACKDIR/$file" || continue
+               rm -f "$PACKDIR/old-$file" &&
+               mv "$PACKDIR/$file" "$PACKDIR/old-$file" || {
+                       failed=t
+                       break
+               }
+               rollback="$rollback $file"
+       done
+       test -z "$failed" || break
+done
+
+# If renaming failed for any of them, roll the ones we have
+# already renamed back to their original names.
+if test -n "$failed"
+then
+       rollback_failure=
+       for file in $rollback
+       do
+               mv "$PACKDIR/old-$file" "$PACKDIR/$file" ||
+               rollback_failure="$rollback_failure $file"
+       done
+       if test -n "$rollback_failure"
+       then
+               echo >&2 "WARNING: Some packs in use have been renamed by"
+               echo >&2 "WARNING: prefixing old- to their name, in order to"
+               echo >&2 "WARNING: replace them with the new version of the"
+               echo >&2 "WARNING: file.  But the operation failed, and"
+               echo >&2 "WARNING: attempt to rename them back to their"
+               echo >&2 "WARNING: original names also failed."
+               echo >&2 "WARNING: Please rename them in $PACKDIR manually:"
+               for file in $rollback_failure
+               do
+                       echo >&2 "WARNING:   old-$file -> $file"
+               done
+       fi
+       exit 1
+fi
+
+# Now the ones with the same name are out of the way...
+fullbases=
+for name in $names
+do
+       fullbases="$fullbases pack-$name"
+       chmod a-w "$PACKTMP-$name.pack"
+       chmod a-w "$PACKTMP-$name.idx"
        mv -f "$PACKTMP-$name.pack" "$PACKDIR/pack-$name.pack" &&
-       mv -f "$PACKTMP-$name.idx"  "$PACKDIR/pack-$name.idx" &&
-       test -f "$PACKDIR/pack-$name.pack" &&
-       test -f "$PACKDIR/pack-$name.idx" || {
-               echo >&2 "Couldn't replace the existing pack with updated one."
-               echo >&2 "The original set of packs have been saved as"
-               echo >&2 "old-pack-$name.{pack,idx} in $PACKDIR."
-               exit 1
-       }
-       rm -f "$PACKDIR/old-pack-$name.pack" "$PACKDIR/old-pack-$name.idx"
+       mv -f "$PACKTMP-$name.idx"  "$PACKDIR/pack-$name.idx" ||
+       exit
+done
+
+# Remove the "old-" files
+for name in $names
+do
+       rm -f "$PACKDIR/old-pack-$name.idx"
+       rm -f "$PACKDIR/old-pack-$name.pack"
 done
 
+# End of pack replacement.
+
 if test "$remove_redundant" = t
 then
        # We know $existing are all redundant.