# HG changeset patch # User Matt Mackall # Date 1185054278 18000 # Node ID 4106dde15aed344dc2730e90ca153acf4c3f9365 # Parent ee983d0dbea8af9b0a538406a52b69ba998151be# Parent a28661788f2fd211d7caf5ab63c77ca32c0e9bb0 Merge with crew diff --git a/hgext/convert/hg.py b/hgext/convert/hg.py --- a/hgext/convert/hg.py +++ b/hgext/convert/hg.py @@ -26,8 +26,8 @@ class convert_mercurial(converter_sink): def putfile(self, f, e, data): self.repo.wwrite(f, data, e) - if self.repo.dirstate.state(f) == '?': - self.repo.dirstate.update([f], "a") + if f not in self.repo.dirstate: + self.repo.dirstate.add(f) def copyfile(self, source, dest): self.repo.copy(source, dest) diff --git a/hgext/fetch.py b/hgext/fetch.py --- a/hgext/fetch.py +++ b/hgext/fetch.py @@ -23,29 +23,29 @@ def fetch(ui, repo, source='default', ** if modheads == 0: return 0 if modheads == 1: - return hg.clean(repo, repo.changelog.tip(), wlock=wlock) + return hg.clean(repo, repo.changelog.tip()) newheads = repo.heads(parent) newchildren = [n for n in repo.heads(parent) if n != parent] newparent = parent if newchildren: newparent = newchildren[0] - hg.clean(repo, newparent, wlock=wlock) + hg.clean(repo, newparent) newheads = [n for n in repo.heads() if n != newparent] err = False if newheads: ui.status(_('merging with new head %d:%s\n') % (repo.changelog.rev(newheads[0]), short(newheads[0]))) - err = hg.merge(repo, newheads[0], remind=False, wlock=wlock) + err = hg.merge(repo, newheads[0], remind=False) if not err and len(newheads) > 1: ui.status(_('not merging with %d other new heads ' '(use "hg heads" and "hg merge" to merge them)') % (len(newheads) - 1)) if not err: - mod, add, rem = repo.status(wlock=wlock)[:3] + mod, add, rem = repo.status()[:3] message = (cmdutil.logmessage(opts) or (_('Automated merge with %s') % other.url())) n = repo.commit(mod + add + rem, message, - opts['user'], opts['date'], lock=lock, wlock=wlock, + opts['user'], opts['date'], force_editor=opts.get('force_editor')) ui.status(_('new changeset %d:%s merges remote changes ' 'with local\n') % (repo.changelog.rev(n), @@ -60,7 +60,7 @@ def fetch(ui, repo, source='default', ** raise util.Abort(_("fetch -r doesn't work for remote repositories yet")) elif opts['rev']: revs = [other.lookup(rev) for rev in opts['rev']] - modheads = repo.pull(other, heads=revs, lock=lock) + modheads = repo.pull(other, heads=revs) return postincoming(other, modheads) parent, p2 = repo.dirstate.parents() @@ -69,10 +69,11 @@ def fetch(ui, repo, source='default', ** '(use "hg update" to check out tip)')) if p2 != nullid: raise util.Abort(_('outstanding uncommitted merge')) - wlock = repo.wlock() - lock = repo.lock() + wlock = lock = None try: - mod, add, rem = repo.status(wlock=wlock)[:3] + wlock = repo.wlock() + lock = repo.lock() + mod, add, rem = repo.status()[:3] if mod or add or rem: raise util.Abort(_('outstanding uncommitted changes')) if len(repo.heads()) > 1: @@ -80,8 +81,7 @@ def fetch(ui, repo, source='default', ** '(use "hg heads" and "hg merge" to merge)')) return pull() finally: - lock.release() - wlock.release() + del lock, wlock cmdtable = { 'fetch': diff --git a/hgext/gpg.py b/hgext/gpg.py --- a/hgext/gpg.py +++ b/hgext/gpg.py @@ -240,7 +240,7 @@ def sign(ui, repo, *revs, **opts): repo.wfile(".hgsigs", "ab").write(sigmessage) - if repo.dirstate.state(".hgsigs") == '?': + if '.hgsigs' not in repo.dirstate: repo.add([".hgsigs"]) if opts["no_commit"]: diff --git a/hgext/mq.py b/hgext/mq.py --- a/hgext/mq.py +++ b/hgext/mq.py @@ -323,10 +323,10 @@ class queue: patch.diff(repo, node1, node2, fns, match=matchfn, fp=fp, changes=changes, opts=self.diffopts()) - def mergeone(self, repo, mergeq, head, patch, rev, wlock): + def mergeone(self, repo, mergeq, head, patch, rev): # first try just applying the patch (err, n) = self.apply(repo, [ patch ], update_status=False, - strict=True, merge=rev, wlock=wlock) + strict=True, merge=rev) if err == 0: return (err, n) @@ -337,15 +337,14 @@ class queue: self.ui.warn("patch didn't work out, merging %s\n" % patch) # apply failed, strip away that rev and merge. - hg.clean(repo, head, wlock=wlock) - self.strip(repo, n, update=False, backup='strip', wlock=wlock) + hg.clean(repo, head) + self.strip(repo, n, update=False, backup='strip') ctx = repo.changectx(rev) - ret = hg.merge(repo, rev, wlock=wlock) + ret = hg.merge(repo, rev) if ret: raise util.Abort(_("update returned %d") % ret) - n = repo.commit(None, ctx.description(), ctx.user(), - force=1, wlock=wlock) + n = repo.commit(None, ctx.description(), ctx.user(), force=1) if n == None: raise util.Abort(_("repo commit failed")) try: @@ -381,7 +380,7 @@ class queue: return pp[1] return pp[0] - def mergepatch(self, repo, mergeq, series, wlock): + def mergepatch(self, repo, mergeq, series): if len(self.applied) == 0: # each of the patches merged in will have two parents. This # can confuse the qrefresh, qdiff, and strip code because it @@ -390,8 +389,7 @@ class queue: # the first patch in the queue is never a merge patch # pname = ".hg.patches.merge.marker" - n = repo.commit(None, '[mq]: merge marker', user=None, force=1, - wlock=wlock) + n = repo.commit(None, '[mq]: merge marker', user=None, force=1) self.removeundo(repo) self.applied.append(statusentry(revlog.hex(n), pname)) self.applied_dirty = 1 @@ -412,7 +410,7 @@ class queue: self.ui.warn("patch %s is not applied\n" % patch) return (1, None) rev = revlog.bin(info[1]) - (err, head) = self.mergeone(repo, mergeq, head, patch, rev, wlock) + (err, head) = self.mergeone(repo, mergeq, head, patch, rev) if head: self.applied.append(statusentry(revlog.hex(head), patch)) self.applied_dirty = 1 @@ -437,30 +435,30 @@ class queue: return (True, files, fuzz) 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() + strict=False, patchdir=None, merge=None, all_files={}): + wlock = 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: + 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, 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, - lock=None, all_files={}): + strict=False, patchdir=None, merge=None, all_files={}): # TODO unify with commands.py if not patchdir: patchdir = self.path @@ -497,17 +495,18 @@ class queue: removed = [] merged = [] for f in files: - if os.path.exists(repo.dirstate.wjoin(f)): + if os.path.exists(repo.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') + for f in removed: + repo.dirstate.remove(f) + for f in merged: + repo.dirstate.merge(f) p1, p2 = repo.dirstate.parents() repo.dirstate.setparents(p1, merge) - files = patch.updatedir(self.ui, repo, files, wlock=wlock) - n = repo.commit(files, message, user, date, force=1, lock=lock, - wlock=wlock) + files = patch.updatedir(self.ui, repo, files) + n = repo.commit(files, message, user, date, force=1) if n == None: raise util.Abort(_("repo commit failed")) @@ -614,44 +613,49 @@ 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) + else: + n = repo.commit(commitfiles, "[mq]: %s" % patch, force=True) + 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: + def strip(self, repo, rev, update=True, backup="all"): + wlock = lock = None + try: wlock = repo.wlock() - lock = repo.lock() + 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) + 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)""" @@ -735,157 +739,161 @@ class queue: raise util.Abort(_("patch %s not in series") % patch) def push(self, repo, patch=None, force=False, list=False, - 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 + mergeq=None): + wlock = repo.wlock() + 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) + else: + ret = self.apply(repo, s, list, all_files=all_files) + except: + self.ui.warn(_('cleaning up working directory...')) + node = repo.dirstate.parents()[0] + hg.revert(repo, node, None) + unknown = repo.status()[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): + def pop(self, repo, patch=None, force=False, update=True, all=False): def getfile(f, rev): t = repo.file(f).read(rev) repo.wfile(f, "w").write(t) - 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) + wlock = repo.wlock() + 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)) - repo.dirstate.update(m + r, 'n') - 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 - if a: - repo.dirstate.forget(a) - 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') + 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) @@ -902,175 +910,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.update(a, 'a') - # 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) - repo.dirstate.update(r, 'r') - # 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] - repo.dirstate.update(m, 'n') - repo.dirstate.update(mm, 'n', st_mtime=-1, st_size=-1) - repo.dirstate.forget(forget) + 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(added) - self.pop(repo, force=True, wlock=wlock) - self.push(repo, force=True, wlock=wlock) + self.strip(repo, top, update=False, + backup='strip') + n = repo.commit(filelist, message, changes[1], match=matchfn, + force=1) + 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) + self.push(repo, force=True) + finally: + del wlock def init(self, repo, create=False): if not create and os.path.isdir(self.path): @@ -1866,10 +1883,13 @@ def rename(ui, repo, patch, name=None, * r = q.qrepo() if r: wlock = r.wlock() - if r.dirstate.state(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]) + r.copy(patch, name) + r.remove([patch], False) + finally: + del wlock q.save_dirty() diff --git a/hgext/transplant.py b/hgext/transplant.py --- a/hgext/transplant.py +++ b/hgext/transplant.py @@ -96,9 +96,10 @@ class transplanter: diffopts = patch.diffopts(self.ui, opts) diffopts.git = True - wlock = repo.wlock() - lock = repo.lock() + lock = wlock = None try: + wlock = repo.wlock() + lock = repo.lock() for rev in revs: node = revmap[rev] revstr = '%s:%s' % (rev, revlog.short(node)) @@ -118,9 +119,8 @@ class transplanter: continue if pulls: if source != repo: - repo.pull(source, heads=pulls, lock=lock) - merge.update(repo, pulls[-1], False, False, None, - wlock=wlock) + repo.pull(source, heads=pulls) + merge.update(repo, pulls[-1], False, False, None) p1, p2 = repo.dirstate.parents() pulls = [] @@ -131,7 +131,7 @@ class transplanter: # fail. domerge = True if not hasnode(repo, node): - repo.pull(source, heads=[node], lock=lock) + repo.pull(source, heads=[node]) if parents[1] != revlog.nullid: self.ui.note(_('skipping merge changeset %s:%s\n') @@ -146,11 +146,11 @@ class transplanter: del revmap[rev] if patchfile or domerge: try: - n = self.applyone(repo, node, source.changelog.read(node), + n = self.applyone(repo, node, + source.changelog.read(node), patchfile, merge=domerge, log=opts.get('log'), - filter=opts.get('filter'), - lock=lock, wlock=wlock) + filter=opts.get('filter')) if n and domerge: self.ui.status(_('%s merged at %s\n') % (revstr, revlog.short(n))) @@ -161,11 +161,12 @@ class transplanter: if patchfile: os.unlink(patchfile) if pulls: - repo.pull(source, heads=pulls, lock=lock) - merge.update(repo, pulls[-1], False, False, None, wlock=wlock) + repo.pull(source, heads=pulls) + merge.update(repo, pulls[-1], False, False, None) finally: self.saveseries(revmap, merges) self.transplants.write() + del lock, wlock def filter(self, filter, changelog, patchfile): '''arbitrarily rewrite changeset before applying it''' @@ -193,7 +194,7 @@ class transplanter: return (user, date, msg) def applyone(self, repo, node, cl, patchfile, merge=False, log=False, - filter=None, lock=None, wlock=None): + filter=None): '''apply the patch in patchfile to the repository as a transplant''' (manifest, user, (time, timezone), files, message) = cl[:5] date = "%d %d" % (time, timezone) @@ -219,7 +220,7 @@ class transplanter: self.ui.warn(_('%s: empty changeset') % revlog.hex(node)) return None finally: - files = patch.updatedir(self.ui, repo, files, wlock=wlock) + files = patch.updatedir(self.ui, repo, files) except Exception, inst: if filter: os.unlink(patchfile) @@ -237,8 +238,7 @@ class transplanter: p1, p2 = repo.dirstate.parents() repo.dirstate.setparents(p1, node) - n = repo.commit(files, message, user, date, lock=lock, wlock=wlock, - extra=extra) + n = repo.commit(files, message, user, date, extra=extra) if not merge: self.transplants.set(n, node) @@ -272,20 +272,24 @@ class transplanter: extra = {'transplant_source': node} wlock = repo.wlock() - p1, p2 = repo.dirstate.parents() - if p1 != parents[0]: - raise util.Abort(_('working dir not at transplant parent %s') % - revlog.hex(parents[0])) - if merge: - repo.dirstate.setparents(p1, parents[1]) - n = repo.commit(None, message, user, date, wlock=wlock, extra=extra) - if not n: - raise util.Abort(_('commit failed')) - if not merge: - self.transplants.set(n, node) - self.unlog() + try: + p1, p2 = repo.dirstate.parents() + if p1 != parents[0]: + raise util.Abort( + _('working dir not at transplant parent %s') % + revlog.hex(parents[0])) + if merge: + repo.dirstate.setparents(p1, parents[1]) + n = repo.commit(None, message, user, date, extra=extra) + if not n: + raise util.Abort(_('commit failed')) + if not merge: + self.transplants.set(n, node) + self.unlog() - return n, node + return n, node + finally: + del wlock def readseries(self): nodes = [] diff --git a/mercurial/bundlerepo.py b/mercurial/bundlerepo.py --- a/mercurial/bundlerepo.py +++ b/mercurial/bundlerepo.py @@ -61,7 +61,7 @@ class bundlerevlog(revlog.revlog): if self.version == revlog.REVLOGV0: e = (start, size, None, link, p1, p2, node) else: - e = (self.offset_type(start, 0), size, -1, None, link, + e = (revlog.offset_type(start, 0), size, -1, None, link, self.rev(p1), self.rev(p2), node) self.basemap[n] = prev self.index.append(e) diff --git a/mercurial/cmdutil.py b/mercurial/cmdutil.py --- a/mercurial/cmdutil.py +++ b/mercurial/cmdutil.py @@ -628,8 +628,7 @@ def findrenames(repo, added=None, remove if bestname: yield bestname, a, bestscore -def addremove(repo, pats=[], opts={}, wlock=None, dry_run=None, - similarity=None): +def addremove(repo, pats=[], opts={}, dry_run=None, similarity=None): if dry_run is None: dry_run = opts.get('dry_run') if similarity is None: @@ -638,19 +637,19 @@ def addremove(repo, pats=[], opts={}, wl mapping = {} for src, abs, rel, exact in walk(repo, pats, opts): target = repo.wjoin(abs) - if src == 'f' and repo.dirstate.state(abs) == '?': + if src == 'f' and abs not in repo.dirstate: add.append(abs) mapping[abs] = rel, exact if repo.ui.verbose or not exact: repo.ui.status(_('adding %s\n') % ((pats and rel) or abs)) - if repo.dirstate.state(abs) != 'r' and not util.lexists(target): + if repo.dirstate[abs] != 'r' and not util.lexists(target): remove.append(abs) mapping[abs] = rel, exact if repo.ui.verbose or not exact: repo.ui.status(_('removing %s\n') % ((pats and rel) or abs)) if not dry_run: - repo.add(add, wlock=wlock) - repo.remove(remove, wlock=wlock) + repo.add(add) + repo.remove(remove) if similarity > 0: for old, new, score in findrenames(repo, add, remove, similarity): oldrel, oldexact = mapping[old] @@ -660,7 +659,7 @@ def addremove(repo, pats=[], opts={}, wl '(%d%% similar)\n') % (oldrel, newrel, score * 100)) if not dry_run: - repo.copy(old, new, wlock=wlock) + repo.copy(old, new) def service(opts, parentfn=None, initfn=None, runfn=None): '''Run a command as a service.''' diff --git a/mercurial/commands.py b/mercurial/commands.py --- a/mercurial/commands.py +++ b/mercurial/commands.py @@ -33,7 +33,7 @@ def add(ui, repo, *pats, **opts): if ui.verbose: ui.status(_('adding %s\n') % rel) names.append(abs) - elif repo.dirstate.state(abs) == '?': + elif abs not in repo.dirstate: ui.status(_('adding %s\n') % rel) names.append(abs) if not opts.get('dry_run'): @@ -456,7 +456,7 @@ def commit(ui, repo, *pats, **opts): elif not (stat.S_ISREG(mode) or stat.S_ISLNK(mode)): raise util.Abort(_("can't commit %s: " "unsupported file type!") % rf) - elif repo.dirstate.state(f) == '?': + elif f not in repo.dirstate: raise util.Abort(_("file %s not tracked!") % rf) else: files = [] @@ -466,7 +466,7 @@ def commit(ui, repo, *pats, **opts): except ValueError, inst: raise util.Abort(str(inst)) -def docopy(ui, repo, pats, opts, wlock): +def docopy(ui, repo, pats, opts): # called with the repo lock held # # hgsep => pathname that uses "/" to separate directories @@ -482,7 +482,7 @@ def docopy(ui, repo, pats, opts, wlock): def okaytocopy(abs, rel, exact): reasons = {'?': _('is not managed'), 'r': _('has been marked for remove')} - state = repo.dirstate.state(abs) + state = repo.dirstate[abs] reason = reasons.get(state) if reason: if exact: @@ -510,7 +510,7 @@ def docopy(ui, repo, pats, opts, wlock): repo.pathto(prevsrc, cwd))) return if (not opts['after'] and os.path.exists(target) or - opts['after'] and repo.dirstate.state(abstarget) not in '?ar'): + opts['after'] and repo.dirstate[abstarget] in 'mn'): if not opts['force']: ui.warn(_('%s: not overwriting - file exists\n') % reltarget) @@ -525,16 +525,16 @@ def docopy(ui, repo, pats, opts, wlock): if not os.path.isdir(targetdir) and not opts.get('dry_run'): os.makedirs(targetdir) try: - restore = repo.dirstate.state(abstarget) == 'r' + restore = repo.dirstate[abstarget] == 'r' if restore and not opts.get('dry_run'): - repo.undelete([abstarget], wlock) + repo.undelete([abstarget]) try: if not opts.get('dry_run'): util.copyfile(src, target) restore = False finally: if restore: - repo.remove([abstarget], wlock=wlock) + repo.remove([abstarget]) except IOError, inst: if inst.errno == errno.ENOENT: ui.warn(_('%s: deleted in working copy\n') % relsrc) @@ -547,15 +547,15 @@ def docopy(ui, repo, pats, opts, wlock): ui.status(_('copying %s to %s\n') % (relsrc, reltarget)) targets[abstarget] = abssrc if abstarget != origsrc: - if repo.dirstate.state(origsrc) == 'a': + if repo.dirstate[origsrc] == 'a': if not ui.quiet: ui.warn(_("%s has not been committed yet, so no copy " "data will be stored for %s.\n") % (repo.pathto(origsrc, cwd), reltarget)) if abstarget not in repo.dirstate and not opts.get('dry_run'): - repo.add([abstarget], wlock) + repo.add([abstarget]) elif not opts.get('dry_run'): - repo.copy(origsrc, abstarget, wlock) + repo.copy(origsrc, abstarget) copied.append((abssrc, relsrc, exact)) # pat: ossep @@ -675,8 +675,11 @@ def copy(ui, repo, *pats, **opts): This command takes effect in the next commit. To undo a copy before that, see hg revert. """ - wlock = repo.wlock(0) - errs, copied = docopy(ui, repo, pats, opts, wlock) + wlock = repo.wlock(False) + try: + errs, copied = docopy(ui, repo, pats, opts) + finally: + del wlock return errs def debugancestor(ui, index, rev1, rev2): @@ -713,17 +716,19 @@ def debugrebuildstate(ui, repo, rev=""): ctx = repo.changectx(rev) files = ctx.manifest() wlock = repo.wlock() - repo.dirstate.rebuild(rev, files) + try: + repo.dirstate.rebuild(rev, files) + finally: + del wlock def debugcheckstate(ui, repo): """validate the correctness of the current dirstate""" parent1, parent2 = repo.dirstate.parents() - dc = repo.dirstate m1 = repo.changectx(parent1).manifest() m2 = repo.changectx(parent2).manifest() errors = 0 - for f in dc: - state = repo.dirstate.state(f) + for f in repo.dirstate: + state = repo.dirstate[f] if state in "nr" and f not in m1: ui.warn(_("%s in state %s, but not in manifest1\n") % (f, state)) errors += 1 @@ -735,7 +740,7 @@ def debugcheckstate(ui, repo): (f, state)) errors += 1 for f in m1: - state = repo.dirstate.state(f) + state = repo.dirstate[f] if state not in "nrm": ui.warn(_("%s in manifest1, but listed as state %s") % (f, state)) errors += 1 @@ -783,12 +788,14 @@ def debugsetparents(ui, repo, rev1, rev2 try: repo.dirstate.setparents(repo.lookup(rev1), repo.lookup(rev2)) finally: - wlock.release() + del wlock def debugstate(ui, repo): """show the contents of the current dirstate""" - dc = repo.dirstate - for file_ in dc: + dc = repo.dirstate._map + k = dc.keys() + k.sort() + for file_ in k: if dc[file_][3] == -1: # Pad or slice to locale representation locale_len = len(time.strftime("%x %X", time.localtime(0))) @@ -1581,70 +1588,75 @@ def import_(ui, repo, patch1, *patches, d = opts["base"] strip = opts["strip"] - - wlock = repo.wlock() - lock = repo.lock() - - for p in patches: - pf = os.path.join(d, p) - - if pf == '-': - ui.status(_("applying patch from stdin\n")) - tmpname, message, user, date, branch, nodeid, p1, p2 = patch.extract(ui, sys.stdin) - else: - ui.status(_("applying %s\n") % p) - tmpname, message, user, date, branch, nodeid, p1, p2 = patch.extract(ui, file(pf, 'rb')) - - if tmpname is None: - raise util.Abort(_('no diffs found')) - - try: - cmdline_message = cmdutil.logmessage(opts) - if cmdline_message: - # pickup the cmdline msg - message = cmdline_message - elif message: - # pickup the patch msg - message = message.strip() + wlock = lock = None + try: + wlock = repo.wlock() + lock = repo.lock() + for p in patches: + pf = os.path.join(d, p) + + if pf == '-': + ui.status(_("applying patch from stdin\n")) + data = patch.extract(ui, sys.stdin) else: - # launch the editor - message = None - ui.debug(_('message:\n%s\n') % message) - - wp = repo.workingctx().parents() - if opts.get('exact'): - if not nodeid or not p1: - raise util.Abort(_('not a mercurial patch')) - p1 = repo.lookup(p1) - p2 = repo.lookup(p2 or hex(nullid)) - - if p1 != wp[0].node(): - hg.clean(repo, p1, wlock=wlock) - repo.dirstate.setparents(p1, p2) - elif p2: - try: + ui.status(_("applying %s\n") % p) + data = patch.extract(ui, file(pf, 'rb')) + + tmpname, message, user, date, branch, nodeid, p1, p2 = data + + if tmpname is None: + raise util.Abort(_('no diffs found')) + + try: + cmdline_message = cmdutil.logmessage(opts) + if cmdline_message: + # pickup the cmdline msg + message = cmdline_message + elif message: + # pickup the patch msg + message = message.strip() + else: + # launch the editor + message = None + ui.debug(_('message:\n%s\n') % message) + + wp = repo.workingctx().parents() + if opts.get('exact'): + if not nodeid or not p1: + raise util.Abort(_('not a mercurial patch')) p1 = repo.lookup(p1) - p2 = repo.lookup(p2) - if p1 == wp[0].node(): - repo.dirstate.setparents(p1, p2) - except hg.RepoError: - pass - if opts.get('exact') or opts.get('import_branch'): - repo.dirstate.setbranch(branch or 'default') - - files = {} - try: - fuzz = patch.patch(tmpname, ui, strip=strip, cwd=repo.root, - files=files) + p2 = repo.lookup(p2 or hex(nullid)) + + if p1 != wp[0].node(): + hg.clean(repo, p1) + repo.dirstate.setparents(p1, p2) + elif p2: + try: + p1 = repo.lookup(p1) + p2 = repo.lookup(p2) + if p1 == wp[0].node(): + repo.dirstate.setparents(p1, p2) + except hg.RepoError: + pass + if opts.get('exact') or opts.get('import_branch'): + repo.dirstate.setbranch(branch or 'default') + + files = {} + try: + fuzz = patch.patch(tmpname, ui, strip=strip, cwd=repo.root, + files=files) + finally: + files = patch.updatedir(ui, repo, files) + n = repo.commit(files, message, user, date) + if opts.get('exact'): + if hex(n) != nodeid: + repo.rollback() + raise util.Abort(_('patch is damaged' + + ' or loses information')) finally: - files = patch.updatedir(ui, repo, files, wlock=wlock) - n = repo.commit(files, message, user, date, wlock=wlock, lock=lock) - if opts.get('exact'): - if hex(n) != nodeid: - repo.rollback(wlock=wlock, lock=lock) - raise util.Abort(_('patch is damaged or loses information')) - finally: - os.unlink(tmpname) + os.unlink(tmpname) + finally: + del wlock, lock def incoming(ui, repo, source="default", **opts): """show new changesets found in source @@ -1759,7 +1771,7 @@ def locate(ui, repo, *pats, **opts): default='relglob'): if src == 'b': continue - if not node and repo.dirstate.state(abs) == '?': + if not node and abs not in repo.dirstate: continue if opts['fullpath']: ui.write(os.path.join(repo.root, abs), end) @@ -2213,7 +2225,7 @@ def remove(ui, repo, *pats, **opts): forget.append(abs) continue reason = _('has been marked for add (use -f to force removal)') - elif repo.dirstate.state(abs) == '?': + elif abs not in repo.dirstate: reason = _('is not managed') elif opts['after'] and not exact and abs not in deleted: continue @@ -2243,16 +2255,19 @@ def rename(ui, repo, *pats, **opts): This command takes effect in the next commit. To undo a rename before that, see hg revert. """ - wlock = repo.wlock(0) - errs, copied = docopy(ui, repo, pats, opts, wlock) - names = [] - for abs, rel, exact in copied: - if ui.verbose or not exact: - ui.status(_('removing %s\n') % rel) - names.append(abs) - if not opts.get('dry_run'): - repo.remove(names, True, wlock=wlock) - return errs + wlock = repo.wlock(False) + try: + errs, copied = docopy(ui, repo, pats, opts) + names = [] + for abs, rel, exact in copied: + if ui.verbose or not exact: + ui.status(_('removing %s\n') % rel) + names.append(abs) + if not opts.get('dry_run'): + repo.remove(names, True) + return errs + finally: + del wlock def revert(ui, repo, *pats, **opts): """revert files or dirs to their states as of some revision @@ -2306,8 +2321,6 @@ def revert(ui, repo, *pats, **opts): else: pmf = None - wlock = repo.wlock() - # need all matching names in dirstate and manifest of target rev, # so have to walk both. do not print errors if files exist in one # but not other. @@ -2315,109 +2328,116 @@ def revert(ui, repo, *pats, **opts): names = {} target_only = {} - # walk dirstate. - - for src, abs, rel, exact in cmdutil.walk(repo, pats, opts, - badmatch=mf.has_key): - names[abs] = (rel, exact) - if src == 'b': - target_only[abs] = True - - # walk target manifest. - - def badmatch(path): - if path in names: - return True - path_ = path + '/' - for f in names: - if f.startswith(path_): + wlock = repo.wlock() + try: + # walk dirstate. + for src, abs, rel, exact in cmdutil.walk(repo, pats, opts, + badmatch=mf.has_key): + names[abs] = (rel, exact) + if src == 'b': + target_only[abs] = True + + # walk target manifest. + + def badmatch(path): + if path in names: return True - return False - - for src, abs, rel, exact in cmdutil.walk(repo, pats, opts, node=node, - badmatch=badmatch): - if abs in names or src == 'b': - continue - names[abs] = (rel, exact) - target_only[abs] = True - - changes = repo.status(match=names.has_key, wlock=wlock)[:5] - modified, added, removed, deleted, unknown = map(dict.fromkeys, changes) - - revert = ([], _('reverting %s\n')) - add = ([], _('adding %s\n')) - remove = ([], _('removing %s\n')) - forget = ([], _('forgetting %s\n')) - undelete = ([], _('undeleting %s\n')) - update = {} - - disptable = ( - # dispatch table: - # file state - # action if in target manifest - # action if not in target manifest - # make backup if in target manifest - # make backup if not in target manifest - (modified, revert, remove, True, True), - (added, revert, forget, True, False), - (removed, undelete, None, False, False), - (deleted, revert, remove, False, False), - (unknown, add, None, True, False), - (target_only, add, None, False, False), - ) - - entries = names.items() - entries.sort() - - for abs, (rel, exact) in entries: - mfentry = mf.get(abs) - target = repo.wjoin(abs) - def handle(xlist, dobackup): - xlist[0].append(abs) - update[abs] = 1 - if dobackup and not opts['no_backup'] and util.lexists(target): - bakname = "%s.orig" % rel - ui.note(_('saving current version of %s as %s\n') % - (rel, bakname)) - if not opts.get('dry_run'): - util.copyfile(target, bakname) - if ui.verbose or not exact: - ui.status(xlist[1] % rel) - for table, hitlist, misslist, backuphit, backupmiss in disptable: - if abs not in table: continue - # file has changed in dirstate - if mfentry: - handle(hitlist, backuphit) - elif misslist is not None: - handle(misslist, backupmiss) + path_ = path + '/' + for f in names: + if f.startswith(path_): + return True + return False + + for src, abs, rel, exact in cmdutil.walk(repo, pats, opts, node=node, + badmatch=badmatch): + if abs in names or src == 'b': + continue + names[abs] = (rel, exact) + target_only[abs] = True + + changes = repo.status(match=names.has_key)[:5] + modified, added, removed, deleted, unknown = map(dict.fromkeys, changes) + + revert = ([], _('reverting %s\n')) + add = ([], _('adding %s\n')) + remove = ([], _('removing %s\n')) + forget = ([], _('forgetting %s\n')) + undelete = ([], _('undeleting %s\n')) + update = {} + + disptable = ( + # dispatch table: + # file state + # action if in target manifest + # action if not in target manifest + # make backup if in target manifest + # make backup if not in target manifest + (modified, revert, remove, True, True), + (added, revert, forget, True, False), + (removed, undelete, None, False, False), + (deleted, revert, remove, False, False), + (unknown, add, None, True, False), + (target_only, add, None, False, False), + ) + + entries = names.items() + entries.sort() + + for abs, (rel, exact) in entries: + mfentry = mf.get(abs) + target = repo.wjoin(abs) + def handle(xlist, dobackup): + xlist[0].append(abs) + update[abs] = 1 + if dobackup and not opts['no_backup'] and util.lexists(target): + bakname = "%s.orig" % rel + ui.note(_('saving current version of %s as %s\n') % + (rel, bakname)) + if not opts.get('dry_run'): + util.copyfile(target, bakname) + if ui.verbose or not exact: + ui.status(xlist[1] % rel) + for table, hitlist, misslist, backuphit, backupmiss in disptable: + if abs not in table: continue + # file has changed in dirstate + if mfentry: + handle(hitlist, backuphit) + elif misslist is not None: + handle(misslist, backupmiss) + else: + if exact: ui.warn(_('file not managed: %s\n') % rel) + break else: - if exact: ui.warn(_('file not managed: %s\n') % rel) - break - else: - # file has not changed in dirstate - if node == parent: - if exact: ui.warn(_('no changes needed to %s\n') % rel) - continue - if pmf is None: - # only need parent manifest in this unlikely case, - # so do not read by default - pmf = repo.changectx(parent).manifest() - if abs in pmf: - if mfentry: - # if version of file is same in parent and target - # manifests, do nothing - if pmf[abs] != mfentry: - handle(revert, False) - else: - handle(remove, False) - - if not opts.get('dry_run'): - repo.dirstate.forget(forget[0]) - r = hg.revert(repo, node, update.has_key, wlock) - repo.dirstate.update(add[0], 'a') - repo.dirstate.update(undelete[0], 'n') - repo.dirstate.update(remove[0], 'r') - return r + # file has not changed in dirstate + if node == parent: + if exact: ui.warn(_('no changes needed to %s\n') % rel) + continue + if pmf is None: + # only need parent manifest in this unlikely case, + # so do not read by default + pmf = repo.changectx(parent).manifest() + if abs in pmf: + if mfentry: + # if version of file is same in parent and target + # manifests, do nothing + if pmf[abs] != mfentry: + handle(revert, False) + else: + handle(remove, False) + + if not opts.get('dry_run'): + for f in forget[0]: + repo.dirstate.forget(f) + r = hg.revert(repo, node, update.has_key) + for f in add[0]: + repo.dirstate.add(f) + for f in undelete[0]: + repo.dirstate.normal(f) + for f in remove[0]: + repo.dirstate.remove(f) + return r + finally: + del wlock def rollback(ui, repo): """roll back the last transaction in this repository diff --git a/mercurial/context.py b/mercurial/context.py --- a/mercurial/context.py +++ b/mercurial/context.py @@ -60,6 +60,18 @@ class changectx(object): else: raise AttributeError, name + def __contains__(self, key): + return key in self._manifest + + def __getitem__(self, key): + return self.filectx(key) + + def __iter__(self): + a = self._manifest.keys() + a.sort() + for f in a: + return f + def changeset(self): return self._changeset def manifest(self): return self._manifest diff --git a/mercurial/dirstate.py b/mercurial/dirstate.py --- a/mercurial/dirstate.py +++ b/mercurial/dirstate.py @@ -20,8 +20,8 @@ class dirstate(object): def __init__(self, opener, ui, root): self._opener = opener self._root = root - self._dirty = 0 - self._dirtypl = 0 + self._dirty = False + self._dirtypl = False self._ui = ui def __getattr__(self, name): @@ -53,7 +53,7 @@ class dirstate(object): self._incpath(f) return self._dirs elif name == '_ignore': - files = [self.wjoin('.hgignore')] + files = [self._join('.hgignore')] for name, path in self._ui.configitems("ui"): if name == 'ignore' or name.startswith('ignore.'): files.append(os.path.expanduser(path)) @@ -65,7 +65,7 @@ class dirstate(object): else: raise AttributeError, name - def wjoin(self, f): + def _join(self, f): return os.path.join(self._root, f) def getcwd(self): @@ -89,11 +89,14 @@ class dirstate(object): return path.replace(os.sep, '/') return path - def __del__(self): - self.write() - def __getitem__(self, key): - return self._map[key] + ''' current states: + n normal + m needs merging + r marked for removal + a marked for addition + ? not tracked''' + return self._map.get(key, ("?",))[0] def __contains__(self, key): return key in self._map @@ -110,21 +113,14 @@ class dirstate(object): def branch(self): return self._branch - def markdirty(self): - self._dirty = 1 - def setparents(self, p1, p2=nullid): - self.markdirty() - self._dirtypl = 1 + self._dirty = self._dirtypl = True self._pl = p1, p2 def setbranch(self, branch): self._branch = branch self._opener("branch", "w").write(branch + '\n') - def state(self, key): - return self._map.get(key, ("?",))[0] - def _read(self): self._map = {} self._copymap = {} @@ -166,10 +162,10 @@ class dirstate(object): for a in "_map _copymap _branch _pl _dirs _ignore".split(): if a in self.__dict__: delattr(self, a) - self._dirty = 0 + self._dirty = False def copy(self, source, dest): - self.markdirty() + self._dirty = True self._copymap[dest] = source def copied(self, file): @@ -204,41 +200,54 @@ class dirstate(object): raise util.Abort(_('file named %r already in dirstate') % d) self._incpath(f) - def update(self, files, state, **kw): - ''' current states: - n normal - m needs merging - r marked for removal - a marked for addition''' + def normal(self, f): + 'mark a file normal' + self._dirty = True + s = os.lstat(self._join(f)) + self._map[f] = ('n', s.st_mode, s.st_size, s.st_mtime) + if self._copymap.has_key(f): + del self._copymap[f] - if not files: return - self.markdirty() - for f in files: - if self._copymap.has_key(f): - del self._copymap[f] + def normaldirty(self, f): + 'mark a file normal, but possibly dirty' + self._dirty = True + s = os.lstat(self._join(f)) + self._map[f] = ('n', s.st_mode, -1, -1) + if f in self._copymap: + del self._copymap[f] + + def add(self, f): + 'mark a file added' + self._dirty = True + self._incpathcheck(f) + self._map[f] = ('a', 0, -1, -1) + if f in self._copymap: + del self._copymap[f] - if state == "r": - self._map[f] = ('r', 0, 0, 0) - self._decpath(f) - continue - else: - if state == "a": - self._incpathcheck(f) - s = os.lstat(self.wjoin(f)) - st_size = kw.get('st_size', s.st_size) - st_mtime = kw.get('st_mtime', s.st_mtime) - self._map[f] = (state, s.st_mode, st_size, st_mtime) + def remove(self, f): + 'mark a file removed' + self._dirty = True + self._map[f] = ('r', 0, 0, 0) + self._decpath(f) + if f in self._copymap: + del self._copymap[f] - def forget(self, files): - if not files: return - self.markdirty() - for f in files: - try: - del self._map[f] - self._decpath(f) - except KeyError: - self._ui.warn(_("not in dirstate: %s!\n") % f) - pass + def merge(self, f): + 'mark a file merged' + self._dirty = True + s = os.lstat(self._join(f)) + self._map[f] = ('m', s.st_mode, s.st_size, s.st_mtime) + if f in self._copymap: + del self._copymap[f] + + def forget(self, f): + 'forget a file' + self._dirty = True + try: + del self._map[f] + self._decpath(f) + except KeyError: + self._ui.warn(_("not in dirstate: %s!\n") % f) def rebuild(self, parent, files): self.invalidate() @@ -248,7 +257,7 @@ class dirstate(object): else: self._map[f] = ('n', 0666, -1, 0) self._pl = (parent, nullid) - self.markdirty() + self._dirty = True def write(self): if not self._dirty: @@ -265,10 +274,9 @@ class dirstate(object): st = self._opener("dirstate", "w", atomictemp=True) st.write(cs.getvalue()) st.rename() - self._dirty = 0 - self._dirtypl = 0 + self._dirty = self._dirtypl = False - def filterfiles(self, files): + def _filter(self, files): ret = {} unknown = [] @@ -339,7 +347,7 @@ class dirstate(object): dc = self._map.copy() else: files = util.unique(files) - dc = self.filterfiles(files) + dc = self._filter(files) def imatch(file_): if file_ not in dc and self._ignore(file_): @@ -405,7 +413,7 @@ class dirstate(object): files.sort() for ff in files: nf = util.normpath(ff) - f = self.wjoin(ff) + f = self._join(ff) try: st = os.lstat(f) except OSError, inst: @@ -442,14 +450,13 @@ class dirstate(object): if not seen(k) and imatch(k): yield 'm', k, None - def status(self, files=None, match=util.always, list_ignored=False, - list_clean=False): + def status(self, files, match, list_ignored, list_clean): lookup, modified, added, unknown, ignored = [], [], [], [], [] removed, deleted, clean = [], [], [] for src, fn, st in self.statwalk(files, match, ignored=list_ignored): try: - type_, mode, size, time = self[fn] + type_, mode, size, time = self._map[fn] except KeyError: if list_ignored and self._ignore(fn): ignored.append(fn) @@ -460,7 +467,7 @@ class dirstate(object): nonexistent = True if not st: try: - st = os.lstat(self.wjoin(fn)) + st = os.lstat(self._join(fn)) except OSError, inst: if inst.errno != errno.ENOENT: raise @@ -476,7 +483,7 @@ class dirstate(object): # check the common case first if type_ == 'n': if not st: - st = os.lstat(self.wjoin(fn)) + st = os.lstat(self._join(fn)) if (size >= 0 and (size != st.st_size or (mode ^ st.st_mode) & 0100) or fn in self._copymap): diff --git a/mercurial/hg.py b/mercurial/hg.py --- a/mercurial/hg.py +++ b/mercurial/hg.py @@ -130,103 +130,99 @@ def clone(ui, source, dest=None, pull=Fa if self.dir_: self.rmtree(self.dir_, True) - dir_cleanup = None - if islocal(dest): - dir_cleanup = DirCleanup(dest) + src_lock = dest_lock = dir_cleanup = None + try: + if islocal(dest): + dir_cleanup = DirCleanup(dest) - abspath = origsource - copy = False - if src_repo.local() and islocal(dest): - abspath = os.path.abspath(origsource) - copy = not pull and not rev + abspath = origsource + copy = False + if src_repo.local() and islocal(dest): + abspath = os.path.abspath(origsource) + copy = not pull and not rev - src_lock, dest_lock = None, None - if copy: - try: - # we use a lock here because if we race with commit, we - # can end up with extra data in the cloned revlogs that's - # not pointed to by changesets, thus causing verify to - # fail - src_lock = src_repo.lock() - except lock.LockException: - copy = False + if copy: + try: + # we use a lock here because if we race with commit, we + # can end up with extra data in the cloned revlogs that's + # not pointed to by changesets, thus causing verify to + # fail + src_lock = src_repo.lock() + except lock.LockException: + copy = False - if copy: - def force_copy(src, dst): - try: - util.copyfiles(src, dst) - except OSError, inst: - if inst.errno != errno.ENOENT: - raise + if copy: + def force_copy(src, dst): + try: + util.copyfiles(src, dst) + except OSError, inst: + if inst.errno != errno.ENOENT: + raise - src_store = os.path.realpath(src_repo.spath) - if not os.path.exists(dest): - os.mkdir(dest) - dest_path = os.path.realpath(os.path.join(dest, ".hg")) - os.mkdir(dest_path) - if src_repo.spath != src_repo.path: - dest_store = os.path.join(dest_path, "store") - os.mkdir(dest_store) - else: - dest_store = dest_path - # copy the requires file - force_copy(src_repo.join("requires"), - os.path.join(dest_path, "requires")) - # we lock here to avoid premature writing to the target - dest_lock = lock.lock(os.path.join(dest_store, "lock")) + src_store = os.path.realpath(src_repo.spath) + if not os.path.exists(dest): + os.mkdir(dest) + dest_path = os.path.realpath(os.path.join(dest, ".hg")) + os.mkdir(dest_path) + if src_repo.spath != src_repo.path: + dest_store = os.path.join(dest_path, "store") + os.mkdir(dest_store) + else: + dest_store = dest_path + # copy the requires file + force_copy(src_repo.join("requires"), + os.path.join(dest_path, "requires")) + # we lock here to avoid premature writing to the target + dest_lock = lock.lock(os.path.join(dest_store, "lock")) - files = ("data", - "00manifest.d", "00manifest.i", - "00changelog.d", "00changelog.i") - for f in files: - src = os.path.join(src_store, f) - dst = os.path.join(dest_store, f) - force_copy(src, dst) + files = ("data", + "00manifest.d", "00manifest.i", + "00changelog.d", "00changelog.i") + for f in files: + src = os.path.join(src_store, f) + dst = os.path.join(dest_store, f) + force_copy(src, dst) + + # we need to re-init the repo after manually copying the data + # into it + dest_repo = repository(ui, dest) + + else: + dest_repo = repository(ui, dest, create=True) - # we need to re-init the repo after manually copying the data - # into it - dest_repo = repository(ui, dest) - - else: - dest_repo = repository(ui, dest, create=True) + revs = None + if rev: + if 'lookup' not in src_repo.capabilities: + raise util.Abort(_("src repository does not support revision " + "lookup and so doesn't support clone by " + "revision")) + revs = [src_repo.lookup(r) for r in rev] - revs = None - if rev: - if 'lookup' not in src_repo.capabilities: - raise util.Abort(_("src repository does not support revision " - "lookup and so doesn't support clone by " - "revision")) - revs = [src_repo.lookup(r) for r in rev] + if dest_repo.local(): + dest_repo.clone(src_repo, heads=revs, stream=stream) + elif src_repo.local(): + src_repo.push(dest_repo, revs=revs) + else: + raise util.Abort(_("clone from remote to remote not supported")) if dest_repo.local(): - dest_repo.clone(src_repo, heads=revs, stream=stream) - elif src_repo.local(): - src_repo.push(dest_repo, revs=revs) - else: - raise util.Abort(_("clone from remote to remote not supported")) - - if src_lock: - src_lock.release() + fp = dest_repo.opener("hgrc", "w", text=True) + fp.write("[paths]\n") + fp.write("default = %s\n" % abspath) + fp.close() - if dest_repo.local(): - fp = dest_repo.opener("hgrc", "w", text=True) - fp.write("[paths]\n") - fp.write("default = %s\n" % abspath) - fp.close() - - if dest_lock: - dest_lock.release() + if update: + try: + checkout = dest_repo.lookup("default") + except: + checkout = dest_repo.changelog.tip() + _update(dest_repo, checkout) + if dir_cleanup: + dir_cleanup.close() - if update: - try: - checkout = dest_repo.lookup("default") - except: - checkout = dest_repo.changelog.tip() - _update(dest_repo, checkout) - if dir_cleanup: - dir_cleanup.close() - - return src_repo, dest_repo + return src_repo, dest_repo + finally: + del src_lock, dest_lock, dir_cleanup def _showstats(repo, stats): stats = ((stats[0], _("updated")), @@ -241,7 +237,7 @@ def _update(repo, node): return update(r def update(repo, node): """update the working directory to node, merging linear changes""" pl = repo.parents() - stats = _merge.update(repo, node, False, False, None, None) + stats = _merge.update(repo, node, False, False, None) _showstats(repo, stats) if stats[3]: repo.ui.status(_("There are unresolved merges with" @@ -255,15 +251,15 @@ def update(repo, node): % (pl[0].rev(), repo.changectx(node).rev())) return stats[3] -def clean(repo, node, wlock=None, show_stats=True): +def clean(repo, node, show_stats=True): """forcibly switch the working directory to node, clobbering changes""" - stats = _merge.update(repo, node, False, True, None, wlock) + stats = _merge.update(repo, node, False, True, None) if show_stats: _showstats(repo, stats) return stats[3] -def merge(repo, node, force=None, remind=True, wlock=None): +def merge(repo, node, force=None, remind=True): """branch merge with node, resolving changes""" - stats = _merge.update(repo, node, True, force, False, wlock) + stats = _merge.update(repo, node, True, force, False) _showstats(repo, stats) if stats[3]: pl = repo.parents() @@ -276,9 +272,9 @@ def merge(repo, node, force=None, remind repo.ui.status(_("(branch merge, don't forget to commit)\n")) return stats[3] -def revert(repo, node, choose, wlock): +def revert(repo, node, choose): """revert changes to revision in node without updating dirstate""" - return _merge.update(repo, node, False, True, choose, wlock)[3] + return _merge.update(repo, node, False, True, choose)[3] def verify(repo): """verify the consistency of a repository""" diff --git a/mercurial/hgweb/hgweb_mod.py b/mercurial/hgweb/hgweb_mod.py --- a/mercurial/hgweb/hgweb_mod.py +++ b/mercurial/hgweb/hgweb_mod.py @@ -1168,7 +1168,7 @@ class hgweb(object): req.write('%d\n' % ret) req.write(val) finally: - lock.release() + del lock except (OSError, IOError), inst: req.write('0\n') filename = getattr(inst, 'filename', '') diff --git a/mercurial/localrepo.py b/mercurial/localrepo.py --- a/mercurial/localrepo.py +++ b/mercurial/localrepo.py @@ -8,7 +8,7 @@ from node import * from i18n import _ import repo, changegroup -import changelog, dirstate, filelog, manifest, context +import changelog, dirstate, filelog, manifest, context, weakref import re, lock, transaction, tempfile, stat, mdiff, errno, ui import os, revlog, time, util, extensions, hook @@ -16,8 +16,6 @@ class localrepository(repo.repository): capabilities = ('lookup', 'changegroupsubset') supported = ('revlogv1', 'store') - def __del__(self): - self.transhandle = None def __init__(self, parentui, path=None, create=0): repo.repository.__init__(self) self.path = path @@ -84,7 +82,7 @@ class localrepository(repo.repository): self.branchcache = None self.nodetagscache = None self.filterpats = {} - self.transhandle = None + self._transref = self._lockref = self._wlockref = None def __getattr__(self, name): if name == 'changelog': @@ -125,7 +123,7 @@ class localrepository(repo.repository): fp.write('%s %s\n' % (hex(node), munge and munge(name) or name)) fp.close() self.hook('tag', node=hex(node), tag=name, local=local) - + prevtags = '' if local: try: @@ -156,7 +154,7 @@ class localrepository(repo.repository): # committed tags are stored in UTF-8 writetag(fp, name, util.fromlocal, prevtags) - if use_dirstate and self.dirstate.state('.hgtags') == '?': + if use_dirstate and '.hgtags' not in self.dirstate: self.add(['.hgtags']) tagnode = self.commit(['.hgtags'], message, user, date, p1=parent, @@ -396,6 +394,11 @@ class localrepository(repo.repository): n = self.changelog._partialmatch(key) if n: return n + try: + if len(key) == 20: + key = hex(key) + except: + pass raise repo.RepoError(_("unknown revision '%s'") % key) def dev(self): @@ -495,9 +498,8 @@ class localrepository(repo.repository): return self._filter("decode", filename, data) def transaction(self): - tr = self.transhandle - if tr != None and tr.running(): - return tr.nest() + if self._transref and self._transref(): + return self._transref().nest() # save dirstate for rollback try: @@ -511,33 +513,38 @@ class localrepository(repo.repository): tr = transaction.transaction(self.ui.warn, self.sopener, self.sjoin("journal"), aftertrans(renames)) - self.transhandle = tr + self._transref = weakref.ref(tr) return tr def recover(self): l = self.lock() - if os.path.exists(self.sjoin("journal")): - self.ui.status(_("rolling back interrupted transaction\n")) - transaction.rollback(self.sopener, self.sjoin("journal")) - self.invalidate() - return True - else: - self.ui.warn(_("no interrupted transaction available\n")) - return False + try: + if os.path.exists(self.sjoin("journal")): + self.ui.status(_("rolling back interrupted transaction\n")) + transaction.rollback(self.sopener, self.sjoin("journal")) + self.invalidate() + return True + else: + self.ui.warn(_("no interrupted transaction available\n")) + return False + finally: + del l - def rollback(self, wlock=None, lock=None): - if not wlock: + def rollback(self): + wlock = lock = None + try: wlock = self.wlock() - if not lock: lock = self.lock() - if os.path.exists(self.sjoin("undo")): - self.ui.status(_("rolling back last transaction\n")) - transaction.rollback(self.sopener, self.sjoin("undo")) - util.rename(self.join("undo.dirstate"), self.join("dirstate")) - self.invalidate() - self.dirstate.invalidate() - else: - self.ui.warn(_("no rollback information available\n")) + if os.path.exists(self.sjoin("undo")): + self.ui.status(_("rolling back last transaction\n")) + transaction.rollback(self.sopener, self.sjoin("undo")) + util.rename(self.join("undo.dirstate"), self.join("dirstate")) + self.invalidate() + self.dirstate.invalidate() + else: + self.ui.warn(_("no rollback information available\n")) + finally: + del wlock, lock def invalidate(self): for a in "changelog manifest".split(): @@ -546,8 +553,7 @@ class localrepository(repo.repository): self.tagscache = None self.nodetagscache = None - def do_lock(self, lockname, wait, releasefn=None, acquirefn=None, - desc=None): + def _lock(self, lockname, wait, releasefn, acquirefn, desc): try: l = lock.lock(lockname, 0, releasefn, desc=desc) except lock.LockHeld, inst: @@ -562,15 +568,24 @@ class localrepository(repo.repository): acquirefn() return l - def lock(self, wait=1): - return self.do_lock(self.sjoin("lock"), wait, - acquirefn=self.invalidate, - desc=_('repository %s') % self.origroot) + def lock(self, wait=True): + if self._lockref and self._lockref(): + return self._lockref() + + l = self._lock(self.sjoin("lock"), wait, None, self.invalidate, + _('repository %s') % self.origroot) + self._lockref = weakref.ref(l) + return l - def wlock(self, wait=1): - return self.do_lock(self.join("wlock"), wait, self.dirstate.write, - self.dirstate.invalidate, - desc=_('working directory of %s') % self.origroot) + def wlock(self, wait=True): + if self._wlockref and self._wlockref(): + return self._wlockref() + + l = self._lock(self.join("wlock"), wait, self.dirstate.write, + self.dirstate.invalidate, _('working directory of %s') % + self.origroot) + self._wlockref = weakref.ref(l) + return l def filecommit(self, fn, manifest1, manifest2, linkrev, transaction, changelist): """ @@ -632,171 +647,176 @@ class localrepository(repo.repository): changelist.append(fn) return fl.add(t, meta, transaction, linkrev, fp1, fp2) - def rawcommit(self, files, text, user, date, p1=None, p2=None, wlock=None, extra={}): + def rawcommit(self, files, text, user, date, p1=None, p2=None, extra={}): if p1 is None: p1, p2 = self.dirstate.parents() return self.commit(files=files, text=text, user=user, date=date, - p1=p1, p2=p2, wlock=wlock, extra=extra) + p1=p1, p2=p2, extra=extra) def commit(self, files=None, text="", user=None, date=None, - match=util.always, force=False, lock=None, wlock=None, - force_editor=False, p1=None, p2=None, extra={}): - - commit = [] - remove = [] - changed = [] - use_dirstate = (p1 is None) # not rawcommit - extra = extra.copy() + match=util.always, force=False, force_editor=False, + p1=None, p2=None, extra={}): + wlock = lock = tr = None + try: + commit = [] + remove = [] + changed = [] + use_dirstate = (p1 is None) # not rawcommit + extra = extra.copy() - if use_dirstate: - if files: - for f in files: - s = self.dirstate.state(f) - if s in 'nmai': - commit.append(f) - elif s == 'r': - remove.append(f) - else: - self.ui.warn(_("%s not tracked!\n") % f) + if use_dirstate: + if files: + for f in files: + s = self.dirstate[f] + if s in 'nma': + commit.append(f) + elif s == 'r': + remove.append(f) + else: + self.ui.warn(_("%s not tracked!\n") % f) + else: + changes = self.status(match=match)[:5] + modified, added, removed, deleted, unknown = changes + commit = modified + added + remove = removed else: - changes = self.status(match=match)[:5] - modified, added, removed, deleted, unknown = changes - commit = modified + added - remove = removed - else: - commit = files + commit = files - if use_dirstate: - p1, p2 = self.dirstate.parents() - update_dirstate = True - else: - p1, p2 = p1, p2 or nullid - update_dirstate = (self.dirstate.parents()[0] == p1) + if use_dirstate: + p1, p2 = self.dirstate.parents() + update_dirstate = True + else: + p1, p2 = p1, p2 or nullid + update_dirstate = (self.dirstate.parents()[0] == p1) - c1 = self.changelog.read(p1) - c2 = self.changelog.read(p2) - m1 = self.manifest.read(c1[0]).copy() - m2 = self.manifest.read(c2[0]) + c1 = self.changelog.read(p1) + c2 = self.changelog.read(p2) + m1 = self.manifest.read(c1[0]).copy() + m2 = self.manifest.read(c2[0]) - if use_dirstate: - branchname = self.workingctx().branch() - try: - branchname = branchname.decode('UTF-8').encode('UTF-8') - except UnicodeDecodeError: - raise util.Abort(_('branch name not in UTF-8!')) - else: - branchname = "" + if use_dirstate: + branchname = self.workingctx().branch() + try: + branchname = branchname.decode('UTF-8').encode('UTF-8') + except UnicodeDecodeError: + raise util.Abort(_('branch name not in UTF-8!')) + else: + branchname = "" - if use_dirstate: - oldname = c1[5].get("branch") # stored in UTF-8 - if (not commit and not remove and not force and p2 == nullid - and branchname == oldname): - self.ui.status(_("nothing changed\n")) - return None + if use_dirstate: + oldname = c1[5].get("branch") # stored in UTF-8 + if (not commit and not remove and not force and p2 == nullid + and branchname == oldname): + self.ui.status(_("nothing changed\n")) + return None - xp1 = hex(p1) - if p2 == nullid: xp2 = '' - else: xp2 = hex(p2) - - self.hook("precommit", throw=True, parent1=xp1, parent2=xp2) + xp1 = hex(p1) + if p2 == nullid: xp2 = '' + else: xp2 = hex(p2) - if not wlock: + self.hook("precommit", throw=True, parent1=xp1, parent2=xp2) + wlock = self.wlock() - if not lock: lock = self.lock() - tr = self.transaction() + tr = self.transaction() - # check in files - new = {} - linkrev = self.changelog.count() - commit.sort() - is_exec = util.execfunc(self.root, m1.execf) - is_link = util.linkfunc(self.root, m1.linkf) - for f in commit: - self.ui.note(f + "\n") - try: - new[f] = self.filecommit(f, m1, m2, linkrev, tr, changed) - new_exec = is_exec(f) - new_link = is_link(f) - if not changed or changed[-1] != f: - # mention the file in the changelog if some flag changed, - # even if there was no content change. - old_exec = m1.execf(f) - old_link = m1.linkf(f) - if old_exec != new_exec or old_link != new_link: - changed.append(f) - m1.set(f, new_exec, new_link) - except (OSError, IOError): - if use_dirstate: - self.ui.warn(_("trouble committing %s!\n") % f) - raise - else: - remove.append(f) + # check in files + new = {} + linkrev = self.changelog.count() + commit.sort() + is_exec = util.execfunc(self.root, m1.execf) + is_link = util.linkfunc(self.root, m1.linkf) + for f in commit: + self.ui.note(f + "\n") + try: + new[f] = self.filecommit(f, m1, m2, linkrev, tr, changed) + new_exec = is_exec(f) + new_link = is_link(f) + if not changed or changed[-1] != f: + # mention the file in the changelog if some + # flag changed, even if there was no content + # change. + old_exec = m1.execf(f) + old_link = m1.linkf(f) + if old_exec != new_exec or old_link != new_link: + changed.append(f) + m1.set(f, new_exec, new_link) + except (OSError, IOError): + if use_dirstate: + self.ui.warn(_("trouble committing %s!\n") % f) + raise + else: + remove.append(f) - # update manifest - m1.update(new) - remove.sort() - removed = [] + # update manifest + m1.update(new) + remove.sort() + removed = [] - for f in remove: - if f in m1: - del m1[f] - removed.append(f) - elif f in m2: - removed.append(f) - mn = self.manifest.add(m1, tr, linkrev, c1[0], c2[0], (new, removed)) + for f in remove: + if f in m1: + del m1[f] + removed.append(f) + elif f in m2: + removed.append(f) + mn = self.manifest.add(m1, tr, linkrev, c1[0], c2[0], + (new, removed)) - # add changeset - new = new.keys() - new.sort() + # add changeset + new = new.keys() + new.sort() - user = user or self.ui.username() - if not text or force_editor: - edittext = [] - if text: - edittext.append(text) - edittext.append("") - edittext.append("HG: user: %s" % user) - if p2 != nullid: - edittext.append("HG: branch merge") - if branchname: - edittext.append("HG: branch %s" % util.tolocal(branchname)) - edittext.extend(["HG: changed %s" % f for f in changed]) - edittext.extend(["HG: removed %s" % f for f in removed]) - if not changed and not remove: - edittext.append("HG: no files changed") - edittext.append("") - # run editor in the repository root - olddir = os.getcwd() - os.chdir(self.root) - text = self.ui.edit("\n".join(edittext), user) - os.chdir(olddir) + user = user or self.ui.username() + if not text or force_editor: + edittext = [] + if text: + edittext.append(text) + edittext.append("") + edittext.append("HG: user: %s" % user) + if p2 != nullid: + edittext.append("HG: branch merge") + if branchname: + edittext.append("HG: branch %s" % util.tolocal(branchname)) + edittext.extend(["HG: changed %s" % f for f in changed]) + edittext.extend(["HG: removed %s" % f for f in removed]) + if not changed and not remove: + edittext.append("HG: no files changed") + edittext.append("") + # run editor in the repository root + olddir = os.getcwd() + os.chdir(self.root) + text = self.ui.edit("\n".join(edittext), user) + os.chdir(olddir) - lines = [line.rstrip() for line in text.rstrip().splitlines()] - while lines and not lines[0]: - del lines[0] - if not lines: - return None - text = '\n'.join(lines) - if branchname: - extra["branch"] = branchname - n = self.changelog.add(mn, changed + removed, text, tr, p1, p2, - user, date, extra) - self.hook('pretxncommit', throw=True, node=hex(n), parent1=xp1, - parent2=xp2) - tr.close() + lines = [line.rstrip() for line in text.rstrip().splitlines()] + while lines and not lines[0]: + del lines[0] + if not lines: + return None + text = '\n'.join(lines) + if branchname: + extra["branch"] = branchname + n = self.changelog.add(mn, changed + removed, text, tr, p1, p2, + user, date, extra) + self.hook('pretxncommit', throw=True, node=hex(n), parent1=xp1, + parent2=xp2) + tr.close() - if self.branchcache and "branch" in extra: - self.branchcache[util.tolocal(extra["branch"])] = n + if self.branchcache and "branch" in extra: + self.branchcache[util.tolocal(extra["branch"])] = n - if use_dirstate or update_dirstate: - self.dirstate.setparents(n) - if use_dirstate: - self.dirstate.update(new, "n") - self.dirstate.forget(removed) + if use_dirstate or update_dirstate: + self.dirstate.setparents(n) + if use_dirstate: + for f in new: + self.dirstate.normal(f) + for f in removed: + self.dirstate.forget(f) - self.hook("commit", node=hex(n), parent1=xp1, parent2=xp2) - return n + self.hook("commit", node=hex(n), parent1=xp1, parent2=xp2) + return n + finally: + del lock, wlock, tr def walk(self, node=None, files=[], match=util.always, badmatch=None): ''' @@ -841,7 +861,7 @@ class localrepository(repo.repository): yield src, fn def status(self, node1=None, node2=None, files=[], match=util.always, - wlock=None, list_ignored=False, list_clean=False): + list_ignored=False, list_clean=False): """return status of files between two nodes or node and working directory If node1 is None, use the first dirstate parent instead. @@ -873,8 +893,6 @@ class localrepository(repo.repository): # all the revisions in parent->child order. mf1 = mfmatches(node1) - mywlock = False - # are we comparing the working directory? if not node2: (lookup, modified, added, removed, deleted, unknown, @@ -884,24 +902,30 @@ class localrepository(repo.repository): # are we comparing working dir against its parent? if compareworking: if lookup: + fixup = [] # do a full compare of any files that might have changed - mnode = self.changelog.read(self.dirstate.parents()[0])[0] - getnode = lambda fn: (self.manifest.find(mnode, fn)[0] or - nullid) + ctx = self.changectx() for f in lookup: - if fcmp(f, getnode): + if f not in ctx or ctx[f].cmp(self.wread(f)): modified.append(f) else: + fixup.append(f) if list_clean: clean.append(f) - if not wlock and not mywlock: - mywlock = True - try: - wlock = self.wlock(wait=0) - except lock.LockException: - pass + + # update dirstate for files that are actually clean + if fixup: + wlock = None + try: + try: + wlock = self.wlock(False) + except lock.LockException: + pass if wlock: - self.dirstate.update([f], "n") + for f in fixup: + self.dirstate.normal(f) + finally: + del wlock else: # we are comparing working dir against non-parent # generate a pseudo-manifest for the working dir @@ -916,8 +940,6 @@ class localrepository(repo.repository): if f in mf2: del mf2[f] - if mywlock and wlock: - wlock.release() else: # we are comparing two revisions mf2 = mfmatches(node2) @@ -950,85 +972,98 @@ class localrepository(repo.repository): l.sort() return (modified, added, removed, deleted, unknown, ignored, clean) - def add(self, list, wlock=None): - if not wlock: - wlock = self.wlock() - for f in list: - p = self.wjoin(f) - try: - st = os.lstat(p) - except: - self.ui.warn(_("%s does not exist!\n") % f) - continue - if st.st_size > 10000000: - self.ui.warn(_("%s: files over 10MB may cause memory and" - " performance problems\n" - "(use 'hg revert %s' to unadd the file)\n") - % (f, f)) - if not (stat.S_ISREG(st.st_mode) or stat.S_ISLNK(st.st_mode)): - self.ui.warn(_("%s not added: only files and symlinks " - "supported currently\n") % f) - elif self.dirstate.state(f) in 'an': - self.ui.warn(_("%s already tracked!\n") % f) - else: - self.dirstate.update([f], "a") - - def forget(self, list, wlock=None): - if not wlock: - wlock = self.wlock() - for f in list: - if self.dirstate.state(f) not in 'ai': - self.ui.warn(_("%s not added!\n") % f) - else: - self.dirstate.forget([f]) - - def remove(self, list, unlink=False, wlock=None): - if unlink: + def add(self, list): + wlock = self.wlock() + try: for f in list: + p = self.wjoin(f) try: - util.unlink(self.wjoin(f)) - except OSError, inst: - if inst.errno != errno.ENOENT: - raise - if not wlock: - wlock = self.wlock() - for f in list: - if unlink and os.path.exists(self.wjoin(f)): - self.ui.warn(_("%s still exists!\n") % f) - elif self.dirstate.state(f) == 'a': - self.dirstate.forget([f]) - elif f not in self.dirstate: - self.ui.warn(_("%s not tracked!\n") % f) - else: - self.dirstate.update([f], "r") + st = os.lstat(p) + except: + self.ui.warn(_("%s does not exist!\n") % f) + continue + if st.st_size > 10000000: + self.ui.warn(_("%s: files over 10MB may cause memory and" + " performance problems\n" + "(use 'hg revert %s' to unadd the file)\n") + % (f, f)) + if not (stat.S_ISREG(st.st_mode) or stat.S_ISLNK(st.st_mode)): + self.ui.warn(_("%s not added: only files and symlinks " + "supported currently\n") % f) + elif self.dirstate[f] in 'an': + self.ui.warn(_("%s already tracked!\n") % f) + else: + self.dirstate.add(f) + finally: + del wlock - def undelete(self, list, wlock=None): - p = self.dirstate.parents()[0] - mn = self.changelog.read(p)[0] - m = self.manifest.read(mn) - if not wlock: + def forget(self, list): + wlock = self.wlock() + try: + for f in list: + if self.dirstate[f] != 'a': + self.ui.warn(_("%s not added!\n") % f) + else: + self.dirstate.forget(f) + finally: + del wlock + + def remove(self, list, unlink=False): + wlock = None + try: + if unlink: + for f in list: + try: + util.unlink(self.wjoin(f)) + except OSError, inst: + if inst.errno != errno.ENOENT: + raise wlock = self.wlock() - for f in list: - if self.dirstate.state(f) not in "r": - self.ui.warn("%s not removed!\n" % f) - else: - t = self.file(f).read(m[f]) - self.wwrite(f, t, m.flags(f)) - self.dirstate.update([f], "n") + for f in list: + if unlink and os.path.exists(self.wjoin(f)): + self.ui.warn(_("%s still exists!\n") % f) + elif self.dirstate[f] == 'a': + self.dirstate.forget(f) + elif f not in self.dirstate: + self.ui.warn(_("%s not tracked!\n") % f) + else: + self.dirstate.remove(f) + finally: + del wlock - def copy(self, source, dest, wlock=None): - p = self.wjoin(dest) - if not (os.path.exists(p) or os.path.islink(p)): - self.ui.warn(_("%s does not exist!\n") % dest) - elif not (os.path.isfile(p) or os.path.islink(p)): - self.ui.warn(_("copy failed: %s is not a file or a " - "symbolic link\n") % dest) - else: - if not wlock: + def undelete(self, list): + wlock = None + try: + p = self.dirstate.parents()[0] + mn = self.changelog.read(p)[0] + m = self.manifest.read(mn) + wlock = self.wlock() + for f in list: + if self.dirstate[f] != 'r': + self.ui.warn("%s not removed!\n" % f) + else: + t = self.file(f).read(m[f]) + self.wwrite(f, t, m.flags(f)) + self.dirstate.normal(f) + finally: + del wlock + + def copy(self, source, dest): + wlock = None + try: + p = self.wjoin(dest) + if not (os.path.exists(p) or os.path.islink(p)): + self.ui.warn(_("%s does not exist!\n") % dest) + elif not (os.path.isfile(p) or os.path.islink(p)): + self.ui.warn(_("copy failed: %s is not a file or a " + "symbolic link\n") % dest) + else: wlock = self.wlock() - if self.dirstate.state(dest) == '?': - self.dirstate.update([dest], "a") - self.dirstate.copy(source, dest) + if dest not in self.dirstate: + self.dirstate.add(dest) + self.dirstate.copy(source, dest) + finally: + del wlock def heads(self, start=None): heads = self.changelog.heads(start) @@ -1305,12 +1340,8 @@ class localrepository(repo.repository): else: return subset - def pull(self, remote, heads=None, force=False, lock=None): - mylock = False - if not lock: - lock = self.lock() - mylock = True - + def pull(self, remote, heads=None, force=False): + lock = self.lock() try: fetch = self.findincoming(remote, force=force) if fetch == [nullid]: @@ -1328,8 +1359,7 @@ class localrepository(repo.repository): cg = remote.changegroupsubset(fetch, heads, 'pull') return self.addchangegroup(cg, 'pull', remote.url()) finally: - if mylock: - lock.release() + del lock def push(self, remote, force=False, revs=None): # there are two ways to push to remote repo: @@ -1402,12 +1432,14 @@ class localrepository(repo.repository): def push_addchangegroup(self, remote, force, revs): lock = remote.lock() - - ret = self.prepush(remote, force, revs) - if ret[0] is not None: - cg, remote_heads = ret - return remote.addchangegroup(cg, 'push', self.url()) - return ret[1] + try: + ret = self.prepush(remote, force, revs) + if ret[0] is not None: + cg, remote_heads = ret + return remote.addchangegroup(cg, 'push', self.url()) + return ret[1] + finally: + del lock def push_unbundle(self, remote, force, revs): # local repo finds heads on server, finds out what revs it @@ -1791,65 +1823,67 @@ class localrepository(repo.repository): changesets = files = revisions = 0 - tr = self.transaction() - # write changelog data to temp files so concurrent readers will not see # inconsistent view cl = self.changelog cl.delayupdate() oldheads = len(cl.heads()) - # pull off the changeset group - self.ui.status(_("adding changesets\n")) - cor = cl.count() - 1 - chunkiter = changegroup.chunkiter(source) - if cl.addgroup(chunkiter, csmap, tr, 1) is None: - raise util.Abort(_("received changelog group is empty")) - cnr = cl.count() - 1 - changesets = cnr - cor + tr = self.transaction() + try: + # pull off the changeset group + self.ui.status(_("adding changesets\n")) + cor = cl.count() - 1 + chunkiter = changegroup.chunkiter(source) + if cl.addgroup(chunkiter, csmap, tr, 1) is None: + raise util.Abort(_("received changelog group is empty")) + cnr = cl.count() - 1 + changesets = cnr - cor - # pull off the manifest group - self.ui.status(_("adding manifests\n")) - chunkiter = changegroup.chunkiter(source) - # no need to check for empty manifest group here: - # if the result of the merge of 1 and 2 is the same in 3 and 4, - # no new manifest will be created and the manifest group will - # be empty during the pull - self.manifest.addgroup(chunkiter, revmap, tr) + # pull off the manifest group + self.ui.status(_("adding manifests\n")) + chunkiter = changegroup.chunkiter(source) + # no need to check for empty manifest group here: + # if the result of the merge of 1 and 2 is the same in 3 and 4, + # no new manifest will be created and the manifest group will + # be empty during the pull + self.manifest.addgroup(chunkiter, revmap, tr) - # process the files - self.ui.status(_("adding file changes\n")) - while 1: - f = changegroup.getchunk(source) - if not f: - break - self.ui.debug(_("adding %s revisions\n") % f) - fl = self.file(f) - o = fl.count() - chunkiter = changegroup.chunkiter(source) - if fl.addgroup(chunkiter, revmap, tr) is None: - raise util.Abort(_("received file revlog group is empty")) - revisions += fl.count() - o - files += 1 + # process the files + self.ui.status(_("adding file changes\n")) + while 1: + f = changegroup.getchunk(source) + if not f: + break + self.ui.debug(_("adding %s revisions\n") % f) + fl = self.file(f) + o = fl.count() + chunkiter = changegroup.chunkiter(source) + if fl.addgroup(chunkiter, revmap, tr) is None: + raise util.Abort(_("received file revlog group is empty")) + revisions += fl.count() - o + files += 1 + + # make changelog see real files again + cl.finalize(tr) - # make changelog see real files again - cl.finalize(tr) + newheads = len(self.changelog.heads()) + heads = "" + if oldheads and newheads != oldheads: + heads = _(" (%+d heads)") % (newheads - oldheads) - newheads = len(self.changelog.heads()) - heads = "" - if oldheads and newheads != oldheads: - heads = _(" (%+d heads)") % (newheads - oldheads) + self.ui.status(_("added %d changesets" + " with %d changes to %d files%s\n") + % (changesets, revisions, files, heads)) - self.ui.status(_("added %d changesets" - " with %d changes to %d files%s\n") - % (changesets, revisions, files, heads)) + if changesets > 0: + self.hook('pretxnchangegroup', throw=True, + node=hex(self.changelog.node(cor+1)), source=srctype, + url=url) - if changesets > 0: - self.hook('pretxnchangegroup', throw=True, - node=hex(self.changelog.node(cor+1)), source=srctype, - url=url) - - tr.close() + tr.close() + finally: + del tr if changesets > 0: self.hook("changegroup", node=hex(self.changelog.node(cor+1)), diff --git a/mercurial/merge.py b/mercurial/merge.py --- a/mercurial/merge.py +++ b/mercurial/merge.py @@ -447,25 +447,25 @@ def recordupdates(repo, action, branchme f, m = a[:2] if m == "r": # remove if branchmerge: - repo.dirstate.update([f], 'r') + repo.dirstate.remove(f) else: - repo.dirstate.forget([f]) + repo.dirstate.forget(f) elif m == "f": # forget - repo.dirstate.forget([f]) + repo.dirstate.forget(f) elif m == "g": # get if branchmerge: - repo.dirstate.update([f], 'n', st_mtime=-1) + repo.dirstate.normaldirty(f) else: - repo.dirstate.update([f], 'n') + repo.dirstate.normal(f) elif m == "m": # merge f2, fd, flag, move = a[2:] if branchmerge: # We've done a branch merge, mark this file as merged # so that we properly record the merger later - repo.dirstate.update([fd], 'm') + repo.dirstate.merge(fd) if f != f2: # copy/rename if move: - repo.dirstate.update([f], 'r') + repo.dirstate.remove(f) if f != fd: repo.dirstate.copy(f, fd) else: @@ -476,95 +476,94 @@ def recordupdates(repo, action, branchme # of that file some time in the past. Thus our # merge will appear as a normal local file # modification. - repo.dirstate.update([fd], 'n', st_size=-1, st_mtime=-1) + repo.dirstate.normaldirty(fd) if move: - repo.dirstate.forget([f]) + repo.dirstate.forget(f) elif m == "d": # directory rename f2, fd, flag = a[2:] if not f2 and f not in repo.dirstate: # untracked file moved continue if branchmerge: - repo.dirstate.update([fd], 'a') + repo.dirstate.add(fd) if f: - repo.dirstate.update([f], 'r') + repo.dirstate.remove(f) repo.dirstate.copy(f, fd) if f2: repo.dirstate.copy(f2, fd) else: - repo.dirstate.update([fd], 'n') + repo.dirstate.normal(fd) if f: - repo.dirstate.forget([f]) + repo.dirstate.forget(f) -def update(repo, node, branchmerge, force, partial, wlock): +def update(repo, node, branchmerge, force, partial): """ Perform a merge between the working directory and the given node branchmerge = whether to merge between branches force = whether to force branch merging or file overwriting partial = a function to filter file lists (dirstate not updated) - wlock = working dir lock, if already held """ - if not wlock: - wlock = repo.wlock() - - wc = repo.workingctx() - if node is None: - # tip of current branch - try: - node = repo.branchtags()[wc.branch()] - except KeyError: - raise util.Abort(_("branch %s not found") % wc.branch()) - overwrite = force and not branchmerge - forcemerge = force and branchmerge - pl = wc.parents() - p1, p2 = pl[0], repo.changectx(node) - pa = p1.ancestor(p2) - fp1, fp2, xp1, xp2 = p1.node(), p2.node(), str(p1), str(p2) - fastforward = False + wlock = repo.wlock() + try: + wc = repo.workingctx() + if node is None: + # tip of current branch + try: + node = repo.branchtags()[wc.branch()] + except KeyError: + raise util.Abort(_("branch %s not found") % wc.branch()) + overwrite = force and not branchmerge + forcemerge = force and branchmerge + pl = wc.parents() + p1, p2 = pl[0], repo.changectx(node) + pa = p1.ancestor(p2) + fp1, fp2, xp1, xp2 = p1.node(), p2.node(), str(p1), str(p2) + fastforward = False - ### check phase - if not overwrite and len(pl) > 1: - raise util.Abort(_("outstanding uncommitted merges")) - if pa == p1 or pa == p2: # is there a linear path from p1 to p2? - if branchmerge: - if p1.branch() != p2.branch() and pa != p2: - fastforward = True - else: - raise util.Abort(_("there is nothing to merge, just use " - "'hg update' or look at 'hg heads'")) - elif not (overwrite or branchmerge): - raise util.Abort(_("update spans branches, use 'hg merge' " - "or 'hg update -C' to lose changes")) - if branchmerge and not forcemerge: - if wc.files(): - raise util.Abort(_("outstanding uncommitted changes")) + ### check phase + if not overwrite and len(pl) > 1: + raise util.Abort(_("outstanding uncommitted merges")) + if pa == p1 or pa == p2: # is there a linear path from p1 to p2? + if branchmerge: + if p1.branch() != p2.branch() and pa != p2: + fastforward = True + else: + raise util.Abort(_("there is nothing to merge, just use " + "'hg update' or look at 'hg heads'")) + elif not (overwrite or branchmerge): + raise util.Abort(_("update spans branches, use 'hg merge' " + "or 'hg update -C' to lose changes")) + if branchmerge and not forcemerge: + if wc.files(): + raise util.Abort(_("outstanding uncommitted changes")) - ### calculate phase - action = [] - if not force: - checkunknown(wc, p2) - if not util.checkfolding(repo.path): - checkcollision(p2) - if not branchmerge: - action += forgetremoved(wc, p2) - action += manifestmerge(repo, wc, p2, pa, overwrite, partial) + ### calculate phase + action = [] + if not force: + checkunknown(wc, p2) + if not util.checkfolding(repo.path): + checkcollision(p2) + if not branchmerge: + action += forgetremoved(wc, p2) + action += manifestmerge(repo, wc, p2, pa, overwrite, partial) - ### apply phase - if not branchmerge: # just jump to the new rev - fp1, fp2, xp1, xp2 = fp2, nullid, xp2, '' - if not partial: - repo.hook('preupdate', throw=True, parent1=xp1, parent2=xp2) + ### apply phase + if not branchmerge: # just jump to the new rev + fp1, fp2, xp1, xp2 = fp2, nullid, xp2, '' + if not partial: + repo.hook('preupdate', throw=True, parent1=xp1, parent2=xp2) - stats = applyupdates(repo, action, wc, p2) + stats = applyupdates(repo, action, wc, p2) - if not partial: - recordupdates(repo, action, branchmerge) - repo.dirstate.setparents(fp1, fp2) - if not branchmerge and not fastforward: - repo.dirstate.setbranch(p2.branch()) - repo.hook('update', parent1=xp1, parent2=xp2, error=stats[3]) + if not partial: + recordupdates(repo, action, branchmerge) + repo.dirstate.setparents(fp1, fp2) + if not branchmerge and not fastforward: + repo.dirstate.setbranch(p2.branch()) + repo.hook('update', parent1=xp1, parent2=xp2, error=stats[3]) - return stats - + return stats + finally: + del wlock diff --git a/mercurial/patch.py b/mercurial/patch.py --- a/mercurial/patch.py +++ b/mercurial/patch.py @@ -1013,7 +1013,7 @@ def diffopts(ui, opts={}, untrusted=Fals ignorewsamount=get('ignore_space_change', 'ignorewsamount'), ignoreblanklines=get('ignore_blank_lines', 'ignoreblanklines')) -def updatedir(ui, repo, patches, wlock=None): +def updatedir(ui, repo, patches): '''Update dirstate after patch application according to metadata''' if not patches: return @@ -1035,11 +1035,11 @@ def updatedir(ui, repo, patches, wlock=N for src, dst, after in copies: if not after: copyfile(src, dst, repo.root) - repo.copy(src, dst, wlock=wlock) + repo.copy(src, dst) removes = removes.keys() if removes: removes.sort() - repo.remove(removes, True, wlock=wlock) + repo.remove(removes, True) for f in patches: ctype, gp = patches[f] if gp and gp.mode: @@ -1050,7 +1050,7 @@ def updatedir(ui, repo, patches, wlock=N repo.wwrite(gp.path, '', x and 'x' or '') else: util.set_exec(dst, x) - cmdutil.addremove(repo, cfiles, wlock=wlock) + cmdutil.addremove(repo, cfiles) files = patches.keys() files.extend([r for r in removes if r not in files]) files.sort() diff --git a/mercurial/revlog.py b/mercurial/revlog.py --- a/mercurial/revlog.py +++ b/mercurial/revlog.py @@ -281,6 +281,17 @@ class lazymap(object): class RevlogError(Exception): pass class LookupError(RevlogError): pass +def getoffset(q): + if q & 0xFFFF: + raise RevlogError(_('incompatible revision flag %x') % q) + return int(q >> 16) + +def gettype(q): + return int(q & 0xFFFF) + +def offset_type(offset, type): + return long(long(offset) << 16 | type) + class revlog(object): """ the underlying revision storage object @@ -325,14 +336,16 @@ class revlog(object): self.defversion = opener.defversion if self.defversion & REVLOGNG: self.defversion |= REVLOGNGINLINEDATA - self.load() + self._load() - def load(self): + def _load(self): v = self.defversion try: f = self.opener(self.indexfile) i = f.read(4) f.seek(0) + if len(i) > 0: + v = struct.unpack(versionformat, i)[0] except IOError, inst: if inst.errno != errno.ENOENT: raise @@ -351,8 +364,6 @@ class revlog(object): and st.st_size == oldst.st_size): return self.indexstat = st - if len(i) > 0: - v = struct.unpack(versionformat, i)[0] flags = v & ~0xFFFF fmt = v & 0xFFFF if fmt == REVLOGV0: @@ -375,29 +386,28 @@ class revlog(object): shaoffset = ngshaoffset if i: - if (lazyparser.safe_to_use and not self.inlinedata() and + if (lazyparser.safe_to_use and not self._inline() and st and st.st_size > 10000): # big index, let's parse it on demand parser = lazyparser(f, st.st_size, self.indexformat, shaoffset) self.index = lazyindex(parser) self.nodemap = lazymap(parser) else: - self.parseindex(f, st) + self._parseindex(f, st) if self.version != REVLOGV0: e = list(self.index[0]) - type = self.ngtype(e[0]) - e[0] = self.offset_type(0, type) + type = gettype(e[0]) + e[0] = offset_type(0, type) self.index[0] = e else: self.nodemap = {nullid: nullrev} self.index = [] - - def parseindex(self, fp, st): + def _parseindex(self, fp, st): s = struct.calcsize(self.indexformat) self.index = [] self.nodemap = {nullid: nullrev} - inline = self.inlinedata() + inline = self._inline() n = 0 leftover = None while True: @@ -408,7 +418,7 @@ class revlog(object): data = fp.read() if not data: break - if n == 0 and self.inlinedata(): + if n == 0 and self._inline(): # cache the first chunk self.chunkcache = (0, data) if leftover: @@ -438,37 +448,26 @@ class revlog(object): break - def ngoffset(self, q): - if q & 0xFFFF: - raise RevlogError(_('%s: incompatible revision flag %x') % - (self.indexfile, q)) - return long(q >> 16) - - def ngtype(self, q): - return int(q & 0xFFFF) - - def offset_type(self, offset, type): - return long(long(offset) << 16 | type) - - def loadindex(self, start, end): + def _loadindex(self, start, end): """load a block of indexes all at once from the lazy parser""" if isinstance(self.index, lazyindex): self.index.p.loadindex(start, end) - def loadindexmap(self): + def _loadindexmap(self): """loads both the map and the index from the lazy parser""" if isinstance(self.index, lazyindex): p = self.index.p p.loadindex() self.nodemap = p.map - def loadmap(self): + def _loadmap(self): """loads the map from the lazy parser""" if isinstance(self.nodemap, lazymap): self.nodemap.p.loadmap() self.nodemap = self.nodemap.p.map - def inlinedata(self): return self.version & REVLOGNGINLINEDATA + def _inline(self): return self.version & REVLOGNGINLINEDATA + def tip(self): return self.node(len(self.index) - 1) def count(self): return len(self.index) def node(self, rev): @@ -498,7 +497,7 @@ class revlog(object): if rev == nullrev: return 0 if self.version != REVLOGV0: - return self.ngoffset(self.index[rev][0]) + return getoffset(self.index[rev][0]) return self.index[rev][0] def end(self, rev): return self.start(rev) + self.length(rev) @@ -832,14 +831,6 @@ class revlog(object): p1, p2 = self.parents(node) return hash(text, p1, p2) != node - def makenode(self, node, text): - """calculate a file nodeid for text, descended or possibly - unchanged from node""" - - if self.cmp(node, text): - return hash(text, node, nullid) - return node - def diff(self, a, b): """return a delta between two revisions""" return mdiff.textdiff(a, b) @@ -850,7 +841,7 @@ class revlog(object): def chunk(self, rev, df=None, cachelen=4096): start, length = self.start(rev), self.length(rev) - inline = self.inlinedata() + inline = self._inline() if inline: start += (rev + 1) * struct.calcsize(self.indexformat) end = start + length @@ -908,7 +899,7 @@ class revlog(object): rev = self.rev(node) base = self.base(rev) - if self.inlinedata(): + if self._inline(): # we probably have the whole chunk cached df = None else: @@ -918,9 +909,9 @@ class revlog(object): if self.cache and self.cache[1] >= base and self.cache[1] < rev: base = self.cache[1] text = self.cache[2] - self.loadindex(base, rev + 1) + self._loadindex(base, rev + 1) else: - self.loadindex(base, rev + 1) + self._loadindex(base, rev + 1) text = self.chunk(base, df=df) bins = [] @@ -938,7 +929,7 @@ class revlog(object): return text def checkinlinesize(self, tr, fp=None): - if not self.inlinedata(): + if not self._inline(): return if not fp: fp = self.opener(self.indexfile, 'r') @@ -995,7 +986,7 @@ class revlog(object): p1, p2 - the parent nodeids of the revision d - an optional precomputed delta """ - if not self.inlinedata(): + if not self._inline(): dfh = self.opener(self.datafile, "a") else: dfh = None @@ -1042,14 +1033,14 @@ class revlog(object): if self.version == REVLOGV0: e = (offset, l, base, link, p1, p2, node) else: - e = (self.offset_type(offset, 0), l, len(text), + e = (offset_type(offset, 0), l, len(text), base, link, self.rev(p1), self.rev(p2), node) self.index.append(e) self.nodemap[node] = n entry = struct.pack(self.indexformat, *e) - if not self.inlinedata(): + if not self._inline(): transaction.add(self.datafile, offset) transaction.add(self.indexfile, n * len(entry)) if data[0]: @@ -1067,7 +1058,7 @@ class revlog(object): ifh.write(entry) - if self.inlinedata(): + if self._inline(): ifh.write(data[0]) ifh.write(data[1]) self.checkinlinesize(transaction, ifh) @@ -1144,7 +1135,7 @@ class revlog(object): ifh = self.opener(self.indexfile, "a+") ifh.seek(0, 2) transaction.add(self.indexfile, ifh.tell(), self.count()) - if self.inlinedata(): + if self._inline(): dfh = None else: transaction.add(self.datafile, end) @@ -1193,7 +1184,7 @@ class revlog(object): text = self.patches(text, [delta]) chk = self._addrevision(text, transaction, link, p1, p2, None, ifh, dfh) - if not dfh and not self.inlinedata(): + if not dfh and not self._inline(): # addrevision switched from inline to conventional # reopen the index dfh = self.opener(self.datafile, "a") @@ -1205,15 +1196,15 @@ class revlog(object): if self.version == REVLOGV0: e = (end, len(cdelta), base, link, p1, p2, node) else: - e = (self.offset_type(end, 0), len(cdelta), textlen, base, + e = (offset_type(end, 0), len(cdelta), textlen, base, link, self.rev(p1), self.rev(p2), node) self.index.append(e) self.nodemap[node] = r - if self.inlinedata(): + if self._inline(): ifh.write(struct.pack(self.indexformat, *e)) ifh.write(cdelta) self.checkinlinesize(transaction, ifh) - if not self.inlinedata(): + if not self._inline(): dfh = self.opener(self.datafile, "a") ifh = self.opener(self.indexfile, "a") else: @@ -1232,7 +1223,7 @@ class revlog(object): return if isinstance(self.index, lazyindex): - self.loadindexmap() + self._loadindexmap() # When stripping away a revision, we need to make sure it # does not actually belong to an older changeset. @@ -1245,7 +1236,7 @@ class revlog(object): # first truncate the files on disk end = self.start(rev) - if not self.inlinedata(): + if not self._inline(): df = self.opener(self.datafile, "a") df.truncate(end) end = rev * struct.calcsize(self.indexformat) @@ -1285,7 +1276,7 @@ class revlog(object): s = struct.calcsize(self.indexformat) i = actual / s di = actual - (i * s) - if self.inlinedata(): + if self._inline(): databytes = 0 for r in xrange(self.count()): databytes += self.length(r) diff --git a/mercurial/streamclone.py b/mercurial/streamclone.py --- a/mercurial/streamclone.py +++ b/mercurial/streamclone.py @@ -66,22 +66,25 @@ def stream_out(repo, fileobj, untrusted= # get consistent snapshot of repo. lock during scan so lock not # needed while we stream, and commits can happen. + lock = None try: - repolock = repo.lock() - except (lock.LockHeld, lock.LockUnavailable), inst: - repo.ui.warn('locking the repository failed: %s\n' % (inst,)) - fileobj.write('2\n') - return + try: + repolock = repo.lock() + except (lock.LockHeld, lock.LockUnavailable), inst: + repo.ui.warn('locking the repository failed: %s\n' % (inst,)) + fileobj.write('2\n') + return - fileobj.write('0\n') - repo.ui.debug('scanning\n') - entries = [] - total_bytes = 0 - for name, size in walkrepo(repo.spath): - name = repo.decodefn(util.pconvert(name)) - entries.append((name, size)) - total_bytes += size - repolock.release() + fileobj.write('0\n') + repo.ui.debug('scanning\n') + entries = [] + total_bytes = 0 + for name, size in walkrepo(repo.spath): + name = repo.decodefn(util.pconvert(name)) + entries.append((name, size)) + total_bytes += size + finally: + del repolock repo.ui.debug('%d files, %d bytes to transfer\n' % (len(entries), total_bytes)) diff --git a/mercurial/verify.py b/mercurial/verify.py --- a/mercurial/verify.py +++ b/mercurial/verify.py @@ -10,6 +10,13 @@ from i18n import _ import revlog, mdiff def verify(repo): + lock = repo.lock() + try: + return _verify(repo) + finally: + del lock + +def _verify(repo): filelinkrevs = {} filenodes = {} changesets = revisions = files = 0 @@ -17,8 +24,6 @@ def verify(repo): warnings = [0] neededmanifests = {} - lock = repo.lock() - def err(msg): repo.ui.warn(msg + "\n") errors[0] += 1 diff --git a/tests/test-abort-checkin.out b/tests/test-abort-checkin.out --- a/tests/test-abort-checkin.out +++ b/tests/test-abort-checkin.out @@ -1,8 +1,8 @@ error: pretxncommit.nocommits hook failed: no commits allowed -abort: no commits allowed transaction abort! rollback completed +abort: no commits allowed error: pretxncommit.nocommits hook failed: no commits allowed -abort: no commits allowed transaction abort! rollback completed +abort: no commits allowed diff --git a/tests/test-acl.out b/tests/test-acl.out --- a/tests/test-acl.out +++ b/tests/test-acl.out @@ -129,9 +129,9 @@ acl: acl.allow enabled, 0 entries for us acl: acl.deny not enabled acl: user fred not allowed on foo/file.txt error: pretxnchangegroup.acl hook failed: acl: access denied for changeset ef1ea85a6374 -abort: acl: access denied for changeset ef1ea85a6374 transaction abort! rollback completed +abort: acl: access denied for changeset ef1ea85a6374 no rollback information available 0:6675d58eff77 @@ -170,9 +170,9 @@ acl: allowing changeset ef1ea85a6374 acl: allowing changeset f9cafe1212c8 acl: user fred not allowed on quux/file.py error: pretxnchangegroup.acl hook failed: acl: access denied for changeset 911600dab2ae -abort: acl: access denied for changeset 911600dab2ae transaction abort! rollback completed +abort: acl: access denied for changeset 911600dab2ae no rollback information available 0:6675d58eff77 @@ -210,9 +210,9 @@ acl: acl.allow enabled, 0 entries for us acl: acl.deny enabled, 0 entries for user barney acl: user barney not allowed on foo/file.txt error: pretxnchangegroup.acl hook failed: acl: access denied for changeset ef1ea85a6374 -abort: acl: access denied for changeset ef1ea85a6374 transaction abort! rollback completed +abort: acl: access denied for changeset ef1ea85a6374 no rollback information available 0:6675d58eff77 @@ -253,9 +253,9 @@ acl: allowing changeset ef1ea85a6374 acl: allowing changeset f9cafe1212c8 acl: user fred not allowed on quux/file.py error: pretxnchangegroup.acl hook failed: acl: access denied for changeset 911600dab2ae -abort: acl: access denied for changeset 911600dab2ae transaction abort! rollback completed +abort: acl: access denied for changeset 911600dab2ae no rollback information available 0:6675d58eff77 @@ -296,9 +296,9 @@ acl: acl.deny enabled, 2 entries for use acl: allowing changeset ef1ea85a6374 acl: user fred denied on foo/Bar/file.txt error: pretxnchangegroup.acl hook failed: acl: access denied for changeset f9cafe1212c8 -abort: acl: access denied for changeset f9cafe1212c8 transaction abort! rollback completed +abort: acl: access denied for changeset f9cafe1212c8 no rollback information available 0:6675d58eff77 @@ -338,9 +338,9 @@ acl: acl.allow enabled, 0 entries for us acl: acl.deny enabled, 0 entries for user barney acl: user barney not allowed on foo/file.txt error: pretxnchangegroup.acl hook failed: acl: access denied for changeset ef1ea85a6374 -abort: acl: access denied for changeset ef1ea85a6374 transaction abort! rollback completed +abort: acl: access denied for changeset ef1ea85a6374 no rollback information available 0:6675d58eff77 @@ -427,9 +427,9 @@ acl: allowing changeset ef1ea85a6374 acl: allowing changeset f9cafe1212c8 acl: user wilma not allowed on quux/file.py error: pretxnchangegroup.acl hook failed: acl: access denied for changeset 911600dab2ae -abort: acl: access denied for changeset 911600dab2ae transaction abort! rollback completed +abort: acl: access denied for changeset 911600dab2ae no rollback information available 0:6675d58eff77 @@ -471,9 +471,9 @@ adding quux/file.py revisions added 3 changesets with 3 changes to 3 files calling hook pretxnchangegroup.acl: hgext.acl.hook error: pretxnchangegroup.acl hook failed: unable to open ../acl.config: No such file or directory -abort: unable to open ../acl.config: No such file or directory transaction abort! rollback completed +abort: unable to open ../acl.config: No such file or directory no rollback information available 0:6675d58eff77 @@ -524,9 +524,9 @@ acl: allowing changeset ef1ea85a6374 acl: allowing changeset f9cafe1212c8 acl: user betty not allowed on quux/file.py error: pretxnchangegroup.acl hook failed: acl: access denied for changeset 911600dab2ae -abort: acl: access denied for changeset 911600dab2ae transaction abort! rollback completed +abort: acl: access denied for changeset 911600dab2ae no rollback information available 0:6675d58eff77 diff --git a/tests/test-clone-pull-corruption.out b/tests/test-clone-pull-corruption.out --- a/tests/test-clone-pull-corruption.out +++ b/tests/test-clone-pull-corruption.out @@ -1,8 +1,8 @@ 1 files updated, 0 files merged, 0 files removed, 0 files unresolved pulling from ../source -abort: pretxncommit hook exited with status 1 transaction abort! rollback completed +abort: pretxncommit hook exited with status 1 searching for changes adding changesets adding manifests diff --git a/tests/test-committer.out b/tests/test-committer.out --- a/tests/test-committer.out +++ b/tests/test-committer.out @@ -22,7 +22,7 @@ user: foo@bar.com date: Mon Jan 12 13:46:40 1970 +0000 summary: commit-1 -abort: Please specify a username. transaction abort! rollback completed +abort: Please specify a username. No username found, using user@host instead diff --git a/tests/test-hook.out b/tests/test-hook.out --- a/tests/test-hook.out +++ b/tests/test-hook.out @@ -60,9 +60,9 @@ precommit hook: HG_PARENT1=8ea2ef7ad3e8c pretxncommit hook: HG_NODE=fad284daf8c032148abaffcd745dafeceefceb61 HG_PARENT1=8ea2ef7ad3e8cac946c72f1e0c79d6aebc301198 5:fad284daf8c0 pretxncommit.forbid hook: HG_NODE=fad284daf8c032148abaffcd745dafeceefceb61 HG_PARENT1=8ea2ef7ad3e8cac946c72f1e0c79d6aebc301198 -abort: pretxncommit.forbid1 hook exited with status 1 transaction abort! rollback completed +abort: pretxncommit.forbid1 hook exited with status 1 4:8ea2ef7ad3e8 precommit hook: HG_PARENT1=8ea2ef7ad3e8cac946c72f1e0c79d6aebc301198 precommit.forbid hook: HG_PARENT1=8ea2ef7ad3e8cac946c72f1e0c79d6aebc301198 @@ -86,9 +86,9 @@ adding changesets adding manifests adding file changes added 1 changesets with 1 changes to 1 files -abort: pretxnchangegroup.forbid1 hook exited with status 1 transaction abort! rollback completed +abort: pretxnchangegroup.forbid1 hook exited with status 1 3:4c52fb2e4022 preoutgoing hook: HG_SOURCE=pull outgoing hook: HG_NODE=8ea2ef7ad3e8cac946c72f1e0c79d6aebc301198 HG_SOURCE=pull