diff --git a/hgext/mq.py b/hgext/mq.py --- a/hgext/mq.py +++ b/hgext/mq.py @@ -471,8 +471,16 @@ class queue: patcherr = not patcherr if merge and files: - # Mark as merged and update dirstate parent info - repo.dirstate.update(repo.dirstate.filterfiles(files.keys()), 'm') + # Mark as removed/merged and update dirstate parent info + removed = [] + merged = [] + for f in files: + if os.path.exists(repo.dirstate.wjoin(f)): + merged.append(f) + else: + removed.append(f) + repo.dirstate.update(repo.dirstate.filterfiles(removed), 'r') + repo.dirstate.update(repo.dirstate.filterfiles(merged), 'm') p1, p2 = repo.dirstate.parents() repo.dirstate.setparents(p1, merge) files = patch.updatedir(self.ui, repo, files, wlock=wlock) diff --git a/mercurial/dirstate.py b/mercurial/dirstate.py --- a/mercurial/dirstate.py +++ b/mercurial/dirstate.py @@ -314,7 +314,7 @@ class dirstate(object): def write(self): if not self.dirty: return - st = self.opener("dirstate", "w", atomic=True) + st = self.opener("dirstate", "w", atomictemp=True) st.write("".join(self.pl)) for f, e in self.map.items(): c = self.copied(f) @@ -322,6 +322,7 @@ class dirstate(object): f = f + "\0" + c e = struct.pack(self.format, e[0], e[1], e[2], e[3], len(f)) st.write(e + f) + st.rename() self.dirty = 0 def filterfiles(self, files): diff --git a/mercurial/localrepo.py b/mercurial/localrepo.py --- a/mercurial/localrepo.py +++ b/mercurial/localrepo.py @@ -417,10 +417,11 @@ class localrepository(repo.repository): def _writebranchcache(self, branches, tip, tiprev): try: - f = self.opener("branch.cache", "w") + f = self.opener("branch.cache", "w", atomictemp=True) f.write("%s %s\n" % (hex(tip), tiprev)) for label, node in branches.iteritems(): f.write("%s %s\n" % (hex(node), label)) + f.rename() except IOError: pass diff --git a/mercurial/templater.py b/mercurial/templater.py --- a/mercurial/templater.py +++ b/mercurial/templater.py @@ -27,7 +27,7 @@ class templater(object): is treated as name of template file. templater is asked to expand a key in map. it looks up key, and - looks for atrings like this: {foo}. it expands {foo} by looking up + looks for strings like this: {foo}. it expands {foo} by looking up foo in map, and substituting it. expansion is recursive: it stops when there is no more {foo} to replace. diff --git a/mercurial/util.py b/mercurial/util.py --- a/mercurial/util.py +++ b/mercurial/util.py @@ -766,6 +766,9 @@ def checkfolding(path): except: return True +_umask = os.umask(0) +os.umask(_umask) + def checkexec(path): """ Check whether the given path is on a filesystem with UNIX-like exec flags @@ -1103,18 +1106,32 @@ def opener(base, audit=True): p = base audit_p = audit - def mktempcopy(name): + def mktempcopy(name, emptyok=False): d, fn = os.path.split(name) fd, temp = tempfile.mkstemp(prefix='.%s-' % fn, dir=d) os.close(fd) - ofp = posixfile(temp, "wb") + # Temporary files are created with mode 0600, which is usually not + # what we want. If the original file already exists, just copy + # its mode. Otherwise, manually obey umask. + try: + st_mode = os.lstat(name).st_mode + except OSError, inst: + if inst.errno != errno.ENOENT: + raise + st_mode = 0666 & ~_umask + os.chmod(temp, st_mode) + if emptyok: + return temp try: try: ifp = posixfile(name, "rb") except IOError, inst: + if inst.errno == errno.ENOENT: + return temp if not getattr(inst, 'filename', None): inst.filename = name raise + ofp = posixfile(temp, "wb") for chunk in filechunkiter(ifp): ofp.write(chunk) ifp.close() @@ -1123,15 +1140,13 @@ def opener(base, audit=True): try: os.unlink(temp) except: pass raise - st = os.lstat(name) - os.chmod(temp, st.st_mode) return temp class atomictempfile(posixfile): """the file will only be copied when rename is called""" def __init__(self, name, mode): self.__name = name - self.temp = mktempcopy(name) + self.temp = mktempcopy(name, emptyok=('w' in mode)) posixfile.__init__(self, self.temp, mode) def rename(self): if not self.closed: @@ -1165,16 +1180,16 @@ def opener(base, audit=True): try: nlink = nlinks(f) except OSError: + nlink = 0 d = os.path.dirname(f) if not os.path.isdir(d): os.makedirs(d) - else: - if atomic: - return atomicfile(f, mode) - elif atomictemp: - return atomictempfile(f, mode) - if nlink > 1: - rename(mktempcopy(f), f) + if atomic: + return atomicfile(f, mode) + elif atomictemp: + return atomictempfile(f, mode) + if nlink > 1: + rename(mktempcopy(f), f) return posixfile(f, mode) return o diff --git a/tests/test-mq-merge b/tests/test-mq-merge new file mode 100755 --- /dev/null +++ b/tests/test-mq-merge @@ -0,0 +1,39 @@ +#!/bin/sh + +# Test issue 529 - mq aborts when merging patch deleting files + +rewrite_path() +{ + sed -e 's:\\:/:g' -e 's:[^ ]*/t/::g' +} + +echo "[extensions]" >> $HGRCPATH +echo "hgext.mq=" >> $HGRCPATH + +# Commit two dummy files in "init" changeset +hg init t +cd t +echo a > a +echo b > b +hg ci -Am init +hg tag -l init + +# Create a patch removing a +hg qnew rm_a +hg rm a +hg qrefresh -m "rm a" + +# Save the patch queue so we can merge it later +hg qsave -c -e 2>&1 | rewrite_path + +# Update b and commit in an "update" changeset +hg up -C init +echo b >> b +hg st +hg ci -m update + +# Here, qpush used to abort with : +# The system cannot find the file specified => a +hg manifest +hg qpush -a -m 2>&1 | rewrite_path +hg manifest diff --git a/tests/test-mq-merge.out b/tests/test-mq-merge.out new file mode 100644 --- /dev/null +++ b/tests/test-mq-merge.out @@ -0,0 +1,11 @@ +adding a +adding b +copy .hg/patches to .hg/patches.1 +1 files updated, 0 files merged, 0 files removed, 0 files unresolved +M b +a +b +merging with queue at: .hg/patches.1 +applying rm_a +Now at: rm_a +b