diff --git a/hgext/mq.py b/hgext/mq.py --- a/hgext/mq.py +++ b/hgext/mq.py @@ -439,24 +439,28 @@ class queue: def apply(self, repo, series, list=False, update_status=True, strict=False, patchdir=None, merge=None, wlock=None, all_files={}): - if not wlock: - wlock = repo.wlock() - lock = repo.lock() - tr = repo.transaction() + lock = tr = None try: - ret = self._apply(tr, repo, series, list, update_status, - strict, patchdir, merge, wlock, - lock=lock, all_files=all_files) - tr.close() - self.save_dirty() - return ret - except: + if not wlock: + wlock = repo.wlock() + lock = repo.lock() + tr = repo.transaction() try: - tr.abort() - finally: - repo.invalidate() - repo.dirstate.invalidate() - raise + ret = self._apply(tr, repo, series, list, update_status, + strict, patchdir, merge, wlock, + lock=lock, all_files=all_files) + tr.close() + self.save_dirty() + return ret + except: + try: + tr.abort() + finally: + repo.invalidate() + repo.dirstate.invalidate() + raise + finally: + del lock, wlock, tr def _apply(self, tr, repo, series, list=False, update_status=True, strict=False, patchdir=None, merge=None, wlock=None, @@ -616,44 +620,51 @@ class queue: commitfiles = m + a + r self.check_toppatch(repo) wlock = repo.wlock() - insert = self.full_series_end() - if msg: - n = repo.commit(commitfiles, msg, force=True, wlock=wlock) - else: - n = repo.commit(commitfiles, - "[mq]: %s" % patch, force=True, wlock=wlock) - if n == None: - raise util.Abort(_("repo commit failed")) - self.full_series[insert:insert] = [patch] - self.applied.append(statusentry(revlog.hex(n), patch)) - self.parse_series() - self.series_dirty = 1 - self.applied_dirty = 1 - p = self.opener(patch, "w") - if msg: - msg = msg + "\n" - p.write(msg) - p.close() - wlock = None - r = self.qrepo() - if r: r.add([patch]) - if commitfiles: - self.refresh(repo, short=True) - self.removeundo(repo) + try: + insert = self.full_series_end() + if msg: + n = repo.commit(commitfiles, msg, force=True, wlock=wlock) + else: + n = repo.commit(commitfiles, + "[mq]: %s" % patch, force=True, wlock=wlock) + if n == None: + raise util.Abort(_("repo commit failed")) + self.full_series[insert:insert] = [patch] + self.applied.append(statusentry(revlog.hex(n), patch)) + self.parse_series() + self.series_dirty = 1 + self.applied_dirty = 1 + p = self.opener(patch, "w") + if msg: + msg = msg + "\n" + p.write(msg) + p.close() + wlock = None + r = self.qrepo() + if r: r.add([patch]) + if commitfiles: + self.refresh(repo, short=True) + self.removeundo(repo) + finally: + del wlock def strip(self, repo, rev, update=True, backup="all", wlock=None): - if not wlock: - wlock = repo.wlock() - lock = repo.lock() + lock = None + try: + if not wlock: + wlock = repo.wlock() + lock = repo.lock() - if update: - self.check_localchanges(repo, refresh=False) - urev = self.qparents(repo, rev) - hg.clean(repo, urev, wlock=wlock) - repo.dirstate.write() + if update: + self.check_localchanges(repo, refresh=False) + urev = self.qparents(repo, rev) + hg.clean(repo, urev, wlock=wlock) + repo.dirstate.write() - self.removeundo(repo) - repair.strip(self.ui, repo, rev, backup) + self.removeundo(repo) + repair.strip(self.ui, repo, rev, backup) + finally: + del lock, wlock def isapplied(self, patch): """returns (index, rev, patch)""" @@ -740,69 +751,74 @@ class queue: mergeq=None, wlock=None): if not wlock: wlock = repo.wlock() - patch = self.lookup(patch) - # Suppose our series file is: A B C and the current 'top' patch is B. - # qpush C should be performed (moving forward) - # qpush B is a NOP (no change) - # qpush A is an error (can't go backwards with qpush) - if patch: - info = self.isapplied(patch) - if info: - if info[0] < len(self.applied) - 1: - raise util.Abort(_("cannot push to a previous patch: %s") % - patch) - if info[0] < len(self.series) - 1: - self.ui.warn(_('qpush: %s is already at the top\n') % patch) - else: - self.ui.warn(_('all patches are currently applied\n')) - return + try: + patch = self.lookup(patch) + # Suppose our series file is: A B C and the current 'top' + # patch is B. qpush C should be performed (moving forward) + # qpush B is a NOP (no change) qpush A is an error (can't + # go backwards with qpush) + if patch: + info = self.isapplied(patch) + if info: + if info[0] < len(self.applied) - 1: + raise util.Abort( + _("cannot push to a previous patch: %s") % patch) + if info[0] < len(self.series) - 1: + self.ui.warn( + _('qpush: %s is already at the top\n') % patch) + else: + self.ui.warn(_('all patches are currently applied\n')) + return - # Following the above example, starting at 'top' of B: - # qpush should be performed (pushes C), but a subsequent qpush without - # an argument is an error (nothing to apply). This allows a loop - # of "...while hg qpush..." to work as it detects an error when done - if self.series_end() == len(self.series): - self.ui.warn(_('patch series already fully applied\n')) - return 1 - if not force: - self.check_localchanges(repo) + # Following the above example, starting at 'top' of B: + # qpush should be performed (pushes C), but a subsequent + # qpush without an argument is an error (nothing to + # apply). This allows a loop of "...while hg qpush..." to + # work as it detects an error when done + if self.series_end() == len(self.series): + self.ui.warn(_('patch series already fully applied\n')) + return 1 + if not force: + self.check_localchanges(repo) - self.applied_dirty = 1; - start = self.series_end() - if start > 0: - self.check_toppatch(repo) - if not patch: - patch = self.series[start] - end = start + 1 - else: - end = self.series.index(patch, start) + 1 - s = self.series[start:end] - all_files = {} - try: - if mergeq: - ret = self.mergepatch(repo, mergeq, s, wlock) + self.applied_dirty = 1; + start = self.series_end() + if start > 0: + self.check_toppatch(repo) + if not patch: + patch = self.series[start] + end = start + 1 else: - ret = self.apply(repo, s, list, wlock=wlock, - all_files=all_files) - except: - self.ui.warn(_('cleaning up working directory...')) - node = repo.dirstate.parents()[0] - hg.revert(repo, node, None, wlock) - unknown = repo.status(wlock=wlock)[4] - # only remove unknown files that we know we touched or - # created while patching - for f in unknown: - if f in all_files: - util.unlink(repo.wjoin(f)) - self.ui.warn(_('done\n')) - raise - top = self.applied[-1].name - if ret[0]: - self.ui.write("Errors during apply, please fix and refresh %s\n" % - top) - else: - self.ui.write("Now at: %s\n" % top) - return ret[0] + end = self.series.index(patch, start) + 1 + s = self.series[start:end] + all_files = {} + try: + if mergeq: + ret = self.mergepatch(repo, mergeq, s, wlock) + else: + ret = self.apply(repo, s, list, wlock=wlock, + all_files=all_files) + except: + self.ui.warn(_('cleaning up working directory...')) + node = repo.dirstate.parents()[0] + hg.revert(repo, node, None, wlock) + unknown = repo.status(wlock=wlock)[4] + # only remove unknown files that we know we touched or + # created while patching + for f in unknown: + if f in all_files: + util.unlink(repo.wjoin(f)) + self.ui.warn(_('done\n')) + raise + top = self.applied[-1].name + if ret[0]: + self.ui.write( + "Errors during apply, please fix and refresh %s\n" % top) + else: + self.ui.write("Now at: %s\n" % top) + return ret[0] + finally: + del wlock def pop(self, repo, patch=None, force=False, update=True, all=False, wlock=None): @@ -812,82 +828,85 @@ class queue: if not wlock: wlock = repo.wlock() - if patch: - # index, rev, patch - info = self.isapplied(patch) - if not info: - patch = self.lookup(patch) - info = self.isapplied(patch) - if not info: - raise util.Abort(_("patch %s is not applied") % patch) + try: + if patch: + # index, rev, patch + info = self.isapplied(patch) + if not info: + patch = self.lookup(patch) + info = self.isapplied(patch) + if not info: + raise util.Abort(_("patch %s is not applied") % patch) - if len(self.applied) == 0: - # Allow qpop -a to work repeatedly, - # but not qpop without an argument - self.ui.warn(_("no patches applied\n")) - return not all + if len(self.applied) == 0: + # Allow qpop -a to work repeatedly, + # but not qpop without an argument + self.ui.warn(_("no patches applied\n")) + return not all - if not update: - parents = repo.dirstate.parents() - rr = [ revlog.bin(x.rev) for x in self.applied ] - for p in parents: - if p in rr: - self.ui.warn("qpop: forcing dirstate update\n") - update = True + if not update: + parents = repo.dirstate.parents() + rr = [ revlog.bin(x.rev) for x in self.applied ] + for p in parents: + if p in rr: + self.ui.warn("qpop: forcing dirstate update\n") + update = True - if not force and update: - self.check_localchanges(repo) + if not force and update: + self.check_localchanges(repo) - self.applied_dirty = 1; - end = len(self.applied) - if not patch: - if all: - popi = 0 + self.applied_dirty = 1; + end = len(self.applied) + if not patch: + if all: + popi = 0 + else: + popi = len(self.applied) - 1 else: - popi = len(self.applied) - 1 - else: - popi = info[0] + 1 - if popi >= end: - self.ui.warn("qpop: %s is already at the top\n" % patch) - return - info = [ popi ] + [self.applied[popi].rev, self.applied[popi].name] + popi = info[0] + 1 + if popi >= end: + self.ui.warn("qpop: %s is already at the top\n" % patch) + return + info = [ popi ] + [self.applied[popi].rev, self.applied[popi].name] - start = info[0] - rev = revlog.bin(info[1]) + start = info[0] + rev = revlog.bin(info[1]) - # we know there are no local changes, so we can make a simplified - # form of hg.update. - if update: - top = self.check_toppatch(repo) - qp = self.qparents(repo, rev) - changes = repo.changelog.read(qp) - mmap = repo.manifest.read(changes[0]) - m, a, r, d, u = repo.status(qp, top)[:5] - if d: - raise util.Abort("deletions found between repo revs") - for f in m: - getfile(f, mmap[f]) - for f in r: - getfile(f, mmap[f]) - util.set_exec(repo.wjoin(f), mmap.execf(f)) - for f in m + r: - repo.dirstate.normal(f) - for f in a: - try: - os.unlink(repo.wjoin(f)) - except OSError, e: - if e.errno != errno.ENOENT: - raise - try: os.removedirs(os.path.dirname(repo.wjoin(f))) - except: pass - repo.dirstate.forget(f) - repo.dirstate.setparents(qp, revlog.nullid) - self.strip(repo, rev, update=False, backup='strip', wlock=wlock) - del self.applied[start:end] - if len(self.applied): - self.ui.write("Now at: %s\n" % self.applied[-1].name) - else: - self.ui.write("Patch queue now empty\n") + # we know there are no local changes, so we can make a simplified + # form of hg.update. + if update: + top = self.check_toppatch(repo) + qp = self.qparents(repo, rev) + changes = repo.changelog.read(qp) + mmap = repo.manifest.read(changes[0]) + m, a, r, d, u = repo.status(qp, top)[:5] + if d: + raise util.Abort("deletions found between repo revs") + for f in m: + getfile(f, mmap[f]) + for f in r: + getfile(f, mmap[f]) + util.set_exec(repo.wjoin(f), mmap.execf(f)) + for f in m + r: + repo.dirstate.normal(f) + for f in a: + try: + os.unlink(repo.wjoin(f)) + except OSError, e: + if e.errno != errno.ENOENT: + raise + try: os.removedirs(os.path.dirname(repo.wjoin(f))) + except: pass + repo.dirstate.forget(f) + repo.dirstate.setparents(qp, revlog.nullid) + self.strip(repo, rev, update=False, backup='strip', wlock=wlock) + del self.applied[start:end] + if len(self.applied): + self.ui.write("Now at: %s\n" % self.applied[-1].name) + else: + self.ui.write("Patch queue now empty\n") + finally: + del wlock def diff(self, repo, pats, opts): top = self.check_toppatch(repo) @@ -904,179 +923,184 @@ class queue: self.ui.write("No patches applied\n") return 1 wlock = repo.wlock() - self.check_toppatch(repo) - (top, patchfn) = (self.applied[-1].rev, self.applied[-1].name) - top = revlog.bin(top) - cparents = repo.changelog.parents(top) - patchparent = self.qparents(repo, top) - message, comments, user, date, patchfound = self.readheaders(patchfn) - - patchf = self.opener(patchfn, 'r+') - - # if the patch was a git patch, refresh it as a git patch - for line in patchf: - if line.startswith('diff --git'): - self.diffopts().git = True - break - patchf.seek(0) - patchf.truncate() + try: + self.check_toppatch(repo) + (top, patchfn) = (self.applied[-1].rev, self.applied[-1].name) + top = revlog.bin(top) + cparents = repo.changelog.parents(top) + patchparent = self.qparents(repo, top) + message, comments, user, date, patchfound = self.readheaders(patchfn) - msg = opts.get('msg', '').rstrip() - if msg: - if comments: - # Remove existing message. - ci = 0 - subj = None - for mi in xrange(len(message)): - if comments[ci].lower().startswith('subject: '): - subj = comments[ci][9:] - while message[mi] != comments[ci] and message[mi] != subj: - ci += 1 - del comments[ci] - comments.append(msg) - if comments: - comments = "\n".join(comments) + '\n\n' - patchf.write(comments) + patchf = self.opener(patchfn, 'r+') + + # if the patch was a git patch, refresh it as a git patch + for line in patchf: + if line.startswith('diff --git'): + self.diffopts().git = True + break + patchf.seek(0) + patchf.truncate() - if opts.get('git'): - self.diffopts().git = True - fns, matchfn, anypats = cmdutil.matchpats(repo, pats, opts) - tip = repo.changelog.tip() - if top == tip: - # if the top of our patch queue is also the tip, there is an - # optimization here. We update the dirstate in place and strip - # off the tip commit. Then just commit the current directory - # tree. We can also send repo.commit the list of files - # changed to speed up the diff - # - # in short mode, we only diff the files included in the - # patch already - # - # this should really read: - # mm, dd, aa, aa2, uu = repo.status(tip, patchparent)[:5] - # but we do it backwards to take advantage of manifest/chlog - # caching against the next repo.status call - # - mm, aa, dd, aa2, uu = repo.status(patchparent, tip)[:5] - changes = repo.changelog.read(tip) - man = repo.manifest.read(changes[0]) - aaa = aa[:] - if opts.get('short'): - filelist = mm + aa + dd - match = dict.fromkeys(filelist).__contains__ - else: - filelist = None - match = util.always - m, a, r, d, u = repo.status(files=filelist, match=match)[:5] + msg = opts.get('msg', '').rstrip() + if msg: + if comments: + # Remove existing message. + ci = 0 + subj = None + for mi in xrange(len(message)): + if comments[ci].lower().startswith('subject: '): + subj = comments[ci][9:] + while message[mi] != comments[ci] and message[mi] != subj: + ci += 1 + del comments[ci] + comments.append(msg) + if comments: + comments = "\n".join(comments) + '\n\n' + patchf.write(comments) - # we might end up with files that were added between tip and - # the dirstate parent, but then changed in the local dirstate. - # in this case, we want them to only show up in the added section - for x in m: - if x not in aa: - mm.append(x) - # we might end up with files added by the local dirstate that - # were deleted by the patch. In this case, they should only - # show up in the changed section. - for x in a: - if x in dd: - del dd[dd.index(x)] - mm.append(x) + if opts.get('git'): + self.diffopts().git = True + fns, matchfn, anypats = cmdutil.matchpats(repo, pats, opts) + tip = repo.changelog.tip() + if top == tip: + # if the top of our patch queue is also the tip, there is an + # optimization here. We update the dirstate in place and strip + # off the tip commit. Then just commit the current directory + # tree. We can also send repo.commit the list of files + # changed to speed up the diff + # + # in short mode, we only diff the files included in the + # patch already + # + # this should really read: + # mm, dd, aa, aa2, uu = repo.status(tip, patchparent)[:5] + # but we do it backwards to take advantage of manifest/chlog + # caching against the next repo.status call + # + mm, aa, dd, aa2, uu = repo.status(patchparent, tip)[:5] + changes = repo.changelog.read(tip) + man = repo.manifest.read(changes[0]) + aaa = aa[:] + if opts.get('short'): + filelist = mm + aa + dd + match = dict.fromkeys(filelist).__contains__ else: - aa.append(x) - # make sure any files deleted in the local dirstate - # are not in the add or change column of the patch - forget = [] - for x in d + r: - if x in aa: - del aa[aa.index(x)] - forget.append(x) - continue - elif x in mm: - del mm[mm.index(x)] - dd.append(x) + filelist = None + match = util.always + m, a, r, d, u = repo.status(files=filelist, match=match)[:5] - m = util.unique(mm) - r = util.unique(dd) - a = util.unique(aa) - c = [filter(matchfn, l) for l in (m, a, r, [], u)] - filelist = util.unique(c[0] + c[1] + c[2]) - patch.diff(repo, patchparent, files=filelist, match=matchfn, - fp=patchf, changes=c, opts=self.diffopts()) - patchf.close() + # we might end up with files that were added between + # tip and the dirstate parent, but then changed in the + # local dirstate. in this case, we want them to only + # show up in the added section + for x in m: + if x not in aa: + mm.append(x) + # we might end up with files added by the local dirstate that + # were deleted by the patch. In this case, they should only + # show up in the changed section. + for x in a: + if x in dd: + del dd[dd.index(x)] + mm.append(x) + else: + aa.append(x) + # make sure any files deleted in the local dirstate + # are not in the add or change column of the patch + forget = [] + for x in d + r: + if x in aa: + del aa[aa.index(x)] + forget.append(x) + continue + elif x in mm: + del mm[mm.index(x)] + dd.append(x) + + m = util.unique(mm) + r = util.unique(dd) + a = util.unique(aa) + c = [filter(matchfn, l) for l in (m, a, r, [], u)] + filelist = util.unique(c[0] + c[1] + c[2]) + patch.diff(repo, patchparent, files=filelist, match=matchfn, + fp=patchf, changes=c, opts=self.diffopts()) + patchf.close() - repo.dirstate.setparents(*cparents) - copies = {} - for dst in a: - src = repo.dirstate.copied(dst) - if src is None: - continue - copies.setdefault(src, []).append(dst) - repo.dirstate.add(dst) - # remember the copies between patchparent and tip - # this may be slow, so don't do it if we're not tracking copies - if self.diffopts().git: - for dst in aaa: - f = repo.file(dst) - src = f.renamed(man[dst]) - if src: - copies[src[0]] = copies.get(dst, []) - if dst in a: - copies[src[0]].append(dst) - # we can't copy a file created by the patch itself - if dst in copies: - del copies[dst] - for src, dsts in copies.iteritems(): - for dst in dsts: - repo.dirstate.copy(src, dst) - for f in r: - repo.dirstate.remove(f) - # if the patch excludes a modified file, mark that file with mtime=0 - # so status can see it. - mm = [] - for i in xrange(len(m)-1, -1, -1): - if not matchfn(m[i]): - mm.append(m[i]) - del m[i] - for f in m: - repo.dirstate.normal(f) - for f in mm: - repo.dirstate.normaldirty(f) - for f in forget: - repo.dirstate.forget(f) + repo.dirstate.setparents(*cparents) + copies = {} + for dst in a: + src = repo.dirstate.copied(dst) + if src is None: + continue + copies.setdefault(src, []).append(dst) + repo.dirstate.add(dst) + # remember the copies between patchparent and tip + # this may be slow, so don't do it if we're not tracking copies + if self.diffopts().git: + for dst in aaa: + f = repo.file(dst) + src = f.renamed(man[dst]) + if src: + copies[src[0]] = copies.get(dst, []) + if dst in a: + copies[src[0]].append(dst) + # we can't copy a file created by the patch itself + if dst in copies: + del copies[dst] + for src, dsts in copies.iteritems(): + for dst in dsts: + repo.dirstate.copy(src, dst) + for f in r: + repo.dirstate.remove(f) + # if the patch excludes a modified file, mark that + # file with mtime=0 so status can see it. + mm = [] + for i in xrange(len(m)-1, -1, -1): + if not matchfn(m[i]): + mm.append(m[i]) + del m[i] + for f in m: + repo.dirstate.normal(f) + for f in mm: + repo.dirstate.normaldirty(f) + for f in forget: + repo.dirstate.forget(f) - if not msg: - if not message: - message = "[mq]: %s\n" % patchfn + if not msg: + if not message: + message = "[mq]: %s\n" % patchfn + else: + message = "\n".join(message) else: - message = "\n".join(message) - else: - message = msg + message = msg - self.strip(repo, top, update=False, backup='strip', wlock=wlock) - n = repo.commit(filelist, message, changes[1], match=matchfn, - force=1, wlock=wlock) - self.applied[-1] = statusentry(revlog.hex(n), patchfn) - self.applied_dirty = 1 - self.removeundo(repo) - else: - self.printdiff(repo, patchparent, fp=patchf) - patchf.close() - added = repo.status()[1] - for a in added: - f = repo.wjoin(a) - try: - os.unlink(f) - except OSError, e: - if e.errno != errno.ENOENT: - raise - try: os.removedirs(os.path.dirname(f)) - except: pass - # forget the file copies in the dirstate - # push should readd the files later on - repo.dirstate.forget(a) - self.pop(repo, force=True, wlock=wlock) - self.push(repo, force=True, wlock=wlock) + self.strip(repo, top, update=False, + backup='strip', wlock=wlock) + n = repo.commit(filelist, message, changes[1], match=matchfn, + force=1, wlock=wlock) + self.applied[-1] = statusentry(revlog.hex(n), patchfn) + self.applied_dirty = 1 + self.removeundo(repo) + else: + self.printdiff(repo, patchparent, fp=patchf) + patchf.close() + added = repo.status()[1] + for a in added: + f = repo.wjoin(a) + try: + os.unlink(f) + except OSError, e: + if e.errno != errno.ENOENT: + raise + try: os.removedirs(os.path.dirname(f)) + except: pass + # forget the file copies in the dirstate + # push should readd the files later on + repo.dirstate.forget(a) + self.pop(repo, force=True, wlock=wlock) + self.push(repo, force=True, wlock=wlock) + finally: + del wlock def init(self, repo, create=False): if not create and os.path.isdir(self.path): @@ -1872,10 +1896,13 @@ def rename(ui, repo, patch, name=None, * r = q.qrepo() if r: wlock = r.wlock() - if r.dirstate[name] == 'r': - r.undelete([name], wlock) - r.copy(patch, name, wlock) - r.remove([patch], False, wlock) + try: + if r.dirstate[name] == 'r': + r.undelete([name], wlock) + r.copy(patch, name, wlock) + r.remove([patch], False, wlock) + finally: + del wlock q.save_dirty()