# HG changeset patch # User Martin Geisler # Date 1322155364 -3600 # Node ID a5e5c64cd90b3f1bd87d0d2834e997cec0c59e2e # Parent 3e13ade423f08031045c2d5a2ef2a87a863a2614# Parent 926bc23d0b6ab47803497e9977b1945340d7e574 merge with stable diff -r 3e13ade423f0 -r a5e5c64cd90b hgext/largefiles/lfutil.py --- a/hgext/largefiles/lfutil.py Mon Nov 21 01:49:20 2011 +0100 +++ b/hgext/largefiles/lfutil.py Thu Nov 24 18:22:44 2011 +0100 @@ -77,8 +77,11 @@ try: util.oslink(src, dest) except OSError: - # if hardlinks fail, fallback on copy - shutil.copyfile(src, dest) + # if hardlinks fail, fallback on atomic copy + dst = util.atomictempfile(dest) + for chunk in util.filechunkiter(open(src)): + dst.write(chunk) + dst.close() os.chmod(dest, os.stat(src).st_mode) def usercachepath(ui, hash): @@ -212,6 +215,8 @@ if path is None: return False util.makedirs(os.path.dirname(repo.wjoin(filename))) + # The write may fail before the file is fully written, but we + # don't use atomic writes in the working copy. shutil.copy(path, repo.wjoin(filename)) return True @@ -226,8 +231,11 @@ 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 -r 3e13ade423f0 -r a5e5c64cd90b tests/test-largefiles-small-disk.t --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/tests/test-largefiles-small-disk.t Thu Nov 24 18:22:44 2011 +0100 @@ -0,0 +1,68 @@ +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 + > # + > def oslink(src, dest): + > raise OSError("no hardlinks, try copying instead") + > util.oslink = oslink + > 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 + +Make the commit with space on the device: + + $ hg commit -m big + +Now make a clone with a full disk, and make sure lfutil.link function +makes copies instead of hardlinks: + + $ cd .. + $ hg --config extensions.criple=$TESTTMP/criple.py clone --pull alice bob + requesting all changes + adding changesets + adding manifests + adding file changes + added 1 changesets with 1 changes to 1 files + updating to branch default + 1 files updated, 0 files merged, 0 files removed, 0 files unresolved + getting changed largefiles + abort: No space left on device + [255] + +The largefile is not created in .hg/largefiles: + + $ ls bob/.hg/largefiles + dirstate