--- a/mercurial/archival.py Thu Sep 05 13:37:24 2024 +0200
+++ b/mercurial/archival.py Wed Nov 15 22:11:34 2023 +0100
@@ -293,8 +293,9 @@
):
"""create archive of repo as it was at node.
- dest can be name of directory, name of archive file, or file
- object to write archive to.
+ dest can be name of directory, name of archive file, a callable, or file
+ object to write archive to. If it is a callable, it will called to open
+ the actual file object before the first archive member is written.
kind is type of archive to create.
@@ -316,7 +317,37 @@
else:
prefix = tidyprefix(dest, kind, prefix)
+ archiver = None
+ ctx = repo[node]
+
+ def opencallback():
+ """Return the archiver instance, creating it if necessary.
+
+ This function is called when the first actual entry is created.
+ It may be called multiple times from different layers.
+ When serving the archive via hgweb, no errors should happen after
+ this point.
+ """
+ nonlocal archiver
+ if archiver is None:
+ if callable(dest):
+ output = dest()
+ else:
+ output = dest
+ archiver = archivers[kind](output, mtime or ctx.date()[0])
+ assert archiver is not None
+
+ if repo.ui.configbool(b"ui", b"archivemeta"):
+ metaname = b'.hg_archival.txt'
+ if match(metaname):
+ write(metaname, 0o644, False, lambda: buildmetadata(ctx))
+ return archiver
+
def write(name, mode, islink, getdata):
+ if archiver is None:
+ opencallback()
+ assert archiver is not None, "archive should be opened by now"
+
data = getdata()
if decode:
data = repo.wwritedata(name, data)
@@ -325,17 +356,9 @@
if kind not in archivers:
raise error.Abort(_(b"unknown archive type '%s'") % kind)
- ctx = repo[node]
- archiver = archivers[kind](dest, mtime or ctx.date()[0])
-
if not match:
match = scmutil.matchall(repo)
- if repo.ui.configbool(b"ui", b"archivemeta"):
- name = b'.hg_archival.txt'
- if match(name):
- write(name, 0o644, False, lambda: buildmetadata(ctx))
-
files = list(ctx.manifest().walk(match))
total = len(files)
if total:
@@ -358,10 +381,11 @@
sub = ctx.workingsub(subpath)
submatch = matchmod.subdirmatcher(subpath, match)
subprefix = prefix + subpath + b'/'
- total += sub.archive(archiver, subprefix, submatch, decode)
+ total += sub.archive(opencallback, subprefix, submatch, decode)
if total == 0:
raise error.Abort(_(b'no files match the archive pattern'))
+ assert archiver is not None, "archive should have been opened before"
archiver.done()
return total