# HG changeset patch # User Martin Geisler # Date 2011-11-24 17:12:13 # Node ID 809788118aa26d6220815d5d2239305428b8e609 # Parent 0f208626d503e7ba070233b49ac2c3862aa6e59e largefiles: write .hg/largefiles/ files atomically Before, it was possible to create a .hg/largefiles/hash file with truncated content, i.e., content where SHA-1(content) != hash This breaks the fundamental invariant in largefiles that the file content for files in .hg/largefiles hash to the filename. diff --git a/hgext/largefiles/lfutil.py b/hgext/largefiles/lfutil.py --- a/hgext/largefiles/lfutil.py +++ b/hgext/largefiles/lfutil.py @@ -228,8 +228,11 @@ def copytostoreabsolute(repo, file, hash if inusercache(repo.ui, hash): link(usercachepath(repo.ui, hash), storepath(repo, hash)) else: - shutil.copyfile(file, storepath(repo, hash)) - os.chmod(storepath(repo, hash), os.stat(file).st_mode) + dst = util.atomictempfile(storepath(repo, hash)) + for chunk in util.filechunkiter(open(file)): + dst.write(chunk) + dst.close() + util.copymode(file, storepath(repo, hash)) linktousercache(repo, hash) def linktousercache(repo, hash): diff --git a/tests/test-largefiles-small-disk.t b/tests/test-largefiles-small-disk.t new file mode 100644 --- /dev/null +++ b/tests/test-largefiles-small-disk.t @@ -0,0 +1,39 @@ +Test how largefiles abort in case the disk runs full + + $ cat > criple.py < import os, errno, shutil + > from mercurial import util + > # + > # this makes the original largefiles code abort: + > def copyfileobj(fsrc, fdst, length=16*1024): + > fdst.write(fsrc.read(4)) + > raise IOError(errno.ENOSPC, os.strerror(errno.ENOSPC)) + > shutil.copyfileobj = copyfileobj + > # + > # this makes the rewritten code abort: + > def filechunkiter(f, size=65536, limit=None): + > yield f.read(4) + > raise IOError(errno.ENOSPC, os.strerror(errno.ENOSPC)) + > util.filechunkiter = filechunkiter + > EOF + + $ echo "[extensions]" >> $HGRCPATH + $ echo "largefiles =" >> $HGRCPATH + + $ hg init alice + $ cd alice + $ echo "this is a very big file" > big + $ hg add --large big + $ hg commit --config extensions.criple=$TESTTMP/criple.py -m big + abort: No space left on device + [255] + +The largefile is not created in .hg/largefiles: + + $ ls .hg/largefiles + dirstate + +The user cache is not even created: + + >>> import os; os.path.exists("$HOME/.cache/largefiles/") + False