# HG changeset patch # User Thomas Arendsen Hein # Date 1131844119 -3600 # Node ID 482b4efdf013773f9c6806ad7b796c1d16a623f4 # Parent 583b3696d24d2a023fae24b0fc8b6ae65594c6e1# Parent 7ae0ce7a3dc475ad7684b7818bd1d4fa9c407e8b Merge with upstream diff -r 583b3696d24d -r 482b4efdf013 mercurial/commands.py --- a/mercurial/commands.py Sun Nov 13 02:06:02 2005 +0100 +++ b/mercurial/commands.py Sun Nov 13 02:08:39 2005 +0100 @@ -2429,14 +2429,7 @@ cmd, args = args[0], args[1:] defaults = ui.config("defaults", cmd) if defaults: - # reparse with command defaults added - args = [cmd] + defaults.split() + args - try: - args = fancyopts.fancyopts(args, globalopts, options) - except fancyopts.getopt.GetoptError, inst: - raise ParseError(None, inst) - - cmd, args = args[0], args[1:] + args = defaults.split() + args aliases, i = find(cmd) cmd = aliases[0] diff -r 583b3696d24d -r 482b4efdf013 mercurial/dirstate.py --- a/mercurial/dirstate.py Sun Nov 13 02:06:02 2005 +0100 +++ b/mercurial/dirstate.py Sun Nov 13 02:08:39 2005 +0100 @@ -101,16 +101,15 @@ try: return self.map[key] except TypeError: - self.read() + self.lazyread() return self[key] def __contains__(self, key): - if not self.map: self.read() + self.lazyread() return key in self.map def parents(self): - if not self.pl: - self.read() + self.lazyread() return self.pl def markdirty(self): @@ -118,8 +117,7 @@ self.dirty = 1 def setparents(self, p1, p2=nullid): - if not self.pl: - self.read() + self.lazyread() self.markdirty() self.pl = p1, p2 @@ -129,9 +127,11 @@ except KeyError: return "?" + def lazyread(self): + if self.map is None: + self.read() + def read(self): - if self.map is not None: return self.map - self.map = {} self.pl = [nullid, nullid] try: @@ -154,7 +154,7 @@ pos += l def copy(self, source, dest): - self.read() + self.lazyread() self.markdirty() self.copies[dest] = source @@ -169,7 +169,7 @@ a marked for addition''' if not files: return - self.read() + self.lazyread() self.markdirty() for f in files: if state == "r": @@ -184,7 +184,7 @@ def forget(self, files): if not files: return - self.read() + self.lazyread() self.markdirty() for f in files: try: @@ -198,7 +198,7 @@ self.markdirty() def write(self): - st = self.opener("dirstate", "w") + st = self.opener("dirstate", "w", atomic=True) st.write("".join(self.pl)) for f, e in self.map.items(): c = self.copied(f) @@ -241,7 +241,7 @@ bs += 1 return ret - def supported_type(self, f, st, verbose=True): + def supported_type(self, f, st, verbose=False): if stat.S_ISREG(st.st_mode): return True if verbose: @@ -258,7 +258,7 @@ return False def statwalk(self, files=None, match=util.always, dc=None): - self.read() + self.lazyread() # walk all files by default if not files: @@ -352,7 +352,7 @@ continue self.blockignore = True if statmatch(ff, st): - if self.supported_type(ff, st): + if self.supported_type(ff, st, verbose=True): yield 'f', ff, st elif ff in dc: yield 'm', ff, st diff -r 583b3696d24d -r 482b4efdf013 mercurial/localrepo.py --- a/mercurial/localrepo.py Sun Nov 13 02:06:02 2005 +0100 +++ b/mercurial/localrepo.py Sun Nov 13 02:08:39 2005 +0100 @@ -232,13 +232,13 @@ return False def undo(self): + wlock = self.wlock() lock = self.lock() if os.path.exists(self.join("undo")): self.ui.status(_("rolling back last transaction\n")) transaction.rollback(self.opener, self.join("undo")) - self.dirstate = None util.rename(self.join("undo.dirstate"), self.join("dirstate")) - self.dirstate = dirstate.dirstate(self.opener, self.ui, self.root) + self.dirstate.read() else: self.ui.warn(_("no undo information available\n")) @@ -251,6 +251,17 @@ return lock.lock(self.join("lock"), wait) raise inst + def wlock(self, wait=1): + try: + wlock = lock.lock(self.join("wlock"), 0, self.dirstate.write) + except lock.LockHeld, inst: + if not wait: + raise inst + self.ui.warn(_("waiting for lock held by %s\n") % inst.args[0]) + wlock = lock.lock(self.join("wlock"), wait, self.dirstate.write) + self.dirstate.read() + return wlock + def rawcommit(self, files, text, user, date, p1=None, p2=None): orig_parent = self.dirstate.parents()[0] or nullid p1 = p1 or self.dirstate.parents()[0] or nullid @@ -267,6 +278,8 @@ else: update_dirstate = 0 + wlock = self.wlock() + lock = self.lock() tr = self.transaction() mm = m1.copy() mfm = mf1.copy() @@ -355,6 +368,7 @@ if not self.hook("precommit"): return None + wlock = self.wlock() lock = self.lock() tr = self.transaction() @@ -472,6 +486,10 @@ # are we comparing the working directory? if not node2: + try: + wlock = self.wlock(wait=0) + except lock.LockHeld: + wlock = None l, c, a, d, u = self.dirstate.changes(files, match) # are we comparing working dir against its parent? @@ -483,6 +501,8 @@ for f in l: if fcmp(f, mf2): c.append(f) + elif wlock is not None: + self.dirstate.update([f], "n") for l in c, a, d, u: l.sort() @@ -526,6 +546,7 @@ return (c, a, d, u) def add(self, list): + wlock = self.wlock() for f in list: p = self.wjoin(f) if not os.path.exists(p): @@ -538,6 +559,7 @@ self.dirstate.update([f], "a") def forget(self, list): + wlock = self.wlock() for f in list: if self.dirstate.state(f) not in 'ai': self.ui.warn(_("%s not added!\n") % f) @@ -551,6 +573,7 @@ util.unlink(self.wjoin(f)) except OSError, inst: if inst.errno != errno.ENOENT: raise + wlock = self.wlock() for f in list: p = self.wjoin(f) if os.path.exists(p): @@ -568,6 +591,7 @@ mn = self.changelog.read(p)[0] mf = self.manifest.readflags(mn) m = self.manifest.read(mn) + wlock = self.wlock() for f in list: if self.dirstate.state(f) not in "r": self.ui.warn("%s not removed!\n" % f) @@ -584,6 +608,7 @@ elif not os.path.isfile(p): self.ui.warn(_("copy failed: %s is not a file\n") % dest) else: + wlock = self.wlock() if self.dirstate.state(dest) == '?': self.dirstate.update([dest], "a") self.dirstate.copy(source, dest) @@ -1374,6 +1399,9 @@ mw[f] = "" mfw[f] = util.is_exec(self.wjoin(f), mfw.get(f, False)) + if moddirstate: + wlock = self.wlock() + for f in d: if f in mw: del mw[f] diff -r 583b3696d24d -r 482b4efdf013 mercurial/lock.py --- a/mercurial/lock.py Sun Nov 13 02:06:02 2005 +0100 +++ b/mercurial/lock.py Sun Nov 13 02:08:39 2005 +0100 @@ -12,10 +12,11 @@ pass class lock: - def __init__(self, file, wait=1): + def __init__(self, file, wait=1, releasefn=None): self.f = file self.held = 0 self.wait = wait + self.releasefn = releasefn self.lock() def __del__(self): @@ -43,6 +44,8 @@ def release(self): if self.held: self.held = 0 + if self.releasefn: + self.releasefn() try: os.unlink(self.f) except: pass diff -r 583b3696d24d -r 482b4efdf013 mercurial/manifest.py --- a/mercurial/manifest.py Sun Nov 13 02:06:02 2005 +0100 +++ b/mercurial/manifest.py Sun Nov 13 02:08:39 2005 +0100 @@ -9,13 +9,12 @@ from revlog import * from i18n import gettext as _ from demandload import * -demandload(globals(), "bisect") +demandload(globals(), "bisect array") class manifest(revlog): def __init__(self, opener): self.mapcache = None self.listcache = None - self.addlist = None revlog.__init__(self, opener, "00manifest.i", "00manifest.d") def read(self, node): @@ -25,8 +24,9 @@ text = self.revision(node) map = {} flag = {} - self.listcache = (text, text.splitlines(1)) - for l in self.listcache[1]: + self.listcache = array.array('c', text) + lines = text.splitlines(1) + for l in lines: (f, n) = l.split('\0') map[f] = bin(n[:40]) flag[f] = (n[40:-1] == "x") @@ -39,57 +39,67 @@ self.read(node) return self.mapcache[2] + def diff(self, a, b): + return mdiff.textdiff(str(a), str(b)) + def add(self, map, flags, transaction, link, p1=None, p2=None, changed=None): - # directly generate the mdiff delta from the data collected during - # the bisect loop below - def gendelta(delta): - i = 0 - result = [] - while i < len(delta): - start = delta[i][2] - end = delta[i][3] - l = delta[i][4] - if l == None: - l = "" - while i < len(delta) - 1 and start <= delta[i+1][2] \ - and end >= delta[i+1][2]: - if delta[i+1][3] > end: - end = delta[i+1][3] - if delta[i+1][4]: - l += delta[i+1][4] + + # returns a tuple (start, end). If the string is found + # m[start:end] are the line containing that string. If start == end + # the string was not found and they indicate the proper sorted + # insertion point. This was taken from bisect_left, and modified + # to find line start/end as it goes along. + # + # m should be a buffer or a string + # s is a string + # + def manifestsearch(m, s, lo=0, hi=None): + def advance(i, c): + while i < lenm and m[i] != c: i += 1 - result.append(struct.pack(">lll", start, end, len(l)) + l) - i += 1 - return result + return i + lenm = len(m) + if not hi: + hi = lenm + while lo < hi: + mid = (lo + hi) // 2 + start = mid + while start > 0 and m[start-1] != '\n': + start -= 1 + end = advance(start, '\0') + if m[start:end] < s: + # we know that after the null there are 40 bytes of sha1 + # this translates to the bisect lo = mid + 1 + lo = advance(end + 40, '\n') + 1 + else: + # this translates to the bisect hi = mid + hi = start + end = advance(lo, '\0') + found = m[lo:end] + if cmp(s, found) == 0: + # we know that after the null there are 40 bytes of sha1 + end = advance(end + 40, '\n') + return (lo, end+1) + else: + return (lo, lo) # apply the changes collected during the bisect loop to our addlist - def addlistdelta(addlist, delta): - # apply the deltas to the addlist. start from the bottom up + # return a delta suitable for addrevision + def addlistdelta(addlist, x): + # start from the bottom up # so changes to the offsets don't mess things up. - i = len(delta) + i = len(x) while i > 0: i -= 1 - start = delta[i][0] - end = delta[i][1] - if delta[i][4]: - addlist[start:end] = [delta[i][4]] + start = x[i][0] + end = x[i][1] + if x[i][2]: + addlist[start:end] = array.array('c', x[i][2]) else: del addlist[start:end] - return addlist - - # calculate the byte offset of the start of each line in the - # manifest - def calcoffsets(addlist): - offsets = [0] * (len(addlist) + 1) - offset = 0 - i = 0 - while i < len(addlist): - offsets[i] = offset - offset += len(addlist[i]) - i += 1 - offsets[i] = offset - return offsets + return "".join([struct.pack(">lll", d[0], d[1], len(d[2])) + d[2] \ + for d in x ]) # if we're using the listcache, make sure it is valid and # parented by the same node we're diffing against @@ -98,15 +108,13 @@ files = map.keys() files.sort() - self.addlist = ["%s\000%s%s\n" % + text = ["%s\000%s%s\n" % (f, hex(map[f]), flags[f] and "x" or '') for f in files] + self.listcache = array.array('c', "".join(text)) cachedelta = None else: - addlist = self.listcache[1] - - # find the starting offset for each line in the add list - offsets = calcoffsets(addlist) + addlist = self.listcache # combine the changed lists into one list for sorting work = [[x, 0] for x in changed[0]] @@ -114,45 +122,52 @@ work.sort() delta = [] - bs = 0 + dstart = None + dend = None + dline = [""] + start = 0 + # zero copy representation of addlist as a buffer + addbuf = buffer(addlist) + # start with a readonly loop that finds the offset of + # each line and creates the deltas for w in work: f = w[0] # bs will either be the index of the item or the insert point - bs = bisect.bisect(addlist, f, bs) - if bs < len(addlist): - fn = addlist[bs][:addlist[bs].index('\0')] - else: - fn = None + start, end = manifestsearch(addbuf, f, start) if w[1] == 0: l = "%s\000%s%s\n" % (f, hex(map[f]), flags[f] and "x" or '') else: - l = None - start = bs - if fn != f: - # item not found, insert a new one - end = bs - if w[1] == 1: - raise AssertionError( + l = "" + if start == end and w[1] == 1: + # item we want to delete was not found, error out + raise AssertionError( _("failed to remove %s from manifest\n") % f) + if dstart != None and dstart <= start and dend >= start: + if dend < end: + dend = end + if l: + dline.append(l) else: - # item is found, replace/delete the existing line - end = bs + 1 - delta.append([start, end, offsets[start], offsets[end], l]) + if dstart != None: + delta.append([dstart, dend, "".join(dline)]) + dstart = start + dend = end + dline = [l] - self.addlist = addlistdelta(addlist, delta) - if self.mapcache[0] == self.tip(): - cachedelta = "".join(gendelta(delta)) - else: - cachedelta = None + if dstart != None: + delta.append([dstart, dend, "".join(dline)]) + # apply the delta to the addlist, and get a delta for addrevision + cachedelta = addlistdelta(addlist, delta) - text = "".join(self.addlist) - if cachedelta and mdiff.patch(self.listcache[0], cachedelta) != text: - raise AssertionError(_("manifest delta failure\n")) - n = self.addrevision(text, transaction, link, p1, p2, cachedelta) + # the delta is only valid if we've been processing the tip revision + if self.mapcache[0] != self.tip(): + cachedelta = None + self.listcache = addlist + + n = self.addrevision(buffer(self.listcache), transaction, link, p1, \ + p2, cachedelta) self.mapcache = (n, map, flags) - self.listcache = (text, self.addlist) - self.addlist = None return n diff -r 583b3696d24d -r 482b4efdf013 mercurial/revlog.py --- a/mercurial/revlog.py Sun Nov 13 02:06:02 2005 +0100 +++ b/mercurial/revlog.py Sun Nov 13 02:08:39 2005 +0100 @@ -31,15 +31,15 @@ def compress(text): """ generate a possibly-compressed representation of text """ - if not text: return text + if not text: return ("", text) if len(text) < 44: - if text[0] == '\0': return text - return 'u' + text + if text[0] == '\0': return ("", text) + return ('u', text) bin = zlib.compress(text) if len(bin) > len(text): - if text[0] == '\0': return text - return 'u' + text - return bin + if text[0] == '\0': return ("", text) + return ('u', text) + return ("", bin) def decompress(bin): """ decompress the given input """ @@ -71,6 +71,9 @@ self.all = 0 self.revlog = revlog + def trunc(self, pos): + self.l = pos/self.s + def load(self, pos=None): if self.all: return if pos is not None: @@ -104,8 +107,12 @@ return self.p.index[pos] def __getitem__(self, pos): return self.p.index[pos] or self.load(pos) + def __delitem__(self, pos): + del self.p.index[pos] def append(self, e): self.p.index.append(e) + def trunc(self, pos): + self.p.trunc(pos) class lazymap: """a lazy version of the node map""" @@ -140,6 +147,8 @@ raise KeyError("node " + hex(key)) def __setitem__(self, key, val): self.p.map[key] = val + def __delitem__(self, key): + del self.p.map[key] class RevlogError(Exception): pass @@ -543,14 +552,16 @@ end = self.end(t) if not d: prev = self.revision(self.tip()) - d = self.diff(prev, text) + d = self.diff(prev, str(text)) data = compress(d) - dist = end - start + len(data) + l = len(data[1]) + len(data[0]) + dist = end - start + l # full versions are inserted when the needed deltas # become comparable to the uncompressed text if not n or dist > len(text) * 2: data = compress(text) + l = len(data[1]) + len(data[0]) base = n else: base = self.base(t) @@ -559,14 +570,17 @@ if t >= 0: offset = self.end(t) - e = (offset, len(data), base, link, p1, p2, node) + e = (offset, l, base, link, p1, p2, node) self.index.append(e) self.nodemap[node] = n entry = struct.pack(indexformat, *e) transaction.add(self.datafile, e[0]) - self.opener(self.datafile, "a").write(data) + f = self.opener(self.datafile, "a") + if data[0]: + f.write(data[0]) + f.write(data[1]) transaction.add(self.indexfile, n * len(entry)) self.opener(self.indexfile, "a").write(entry) @@ -801,7 +815,8 @@ # current size. if chain == prev: - cdelta = compress(delta) + tempd = compress(delta) + cdelta = tempd[0] + tempd[1] if chain != prev or (end - start + len(cdelta)) > measure * 2: # flush our writes here so we can read it in revision @@ -828,6 +843,36 @@ ifh.close() return node + def strip(self, rev, minlink): + if self.count() == 0 or rev >= self.count(): + return + + # When stripping away a revision, we need to make sure it + # does not actually belong to an older changeset. + # The minlink parameter defines the oldest revision + # we're allowed to strip away. + while minlink > self.index[rev][3]: + rev += 1 + if rev >= self.count(): + return + + # first truncate the files on disk + end = self.start(rev) + self.opener(self.datafile, "a").truncate(end) + end = rev * struct.calcsize(indexformat) + self.opener(self.indexfile, "a").truncate(end) + + # then reset internal state in memory to forget those revisions + self.cache = None + for p in self.index[rev:]: + del self.nodemap[p[6]] + del self.index[rev:] + + # truncating the lazyindex also truncates the lazymap. + if isinstance(self.index, lazyindex): + self.index.trunc(end) + + def checksize(self): expected = 0 if self.count(): diff -r 583b3696d24d -r 482b4efdf013 mercurial/util.py --- a/mercurial/util.py Sun Nov 13 02:06:02 2005 +0100 +++ b/mercurial/util.py Sun Nov 13 02:08:39 2005 +0100 @@ -362,7 +362,36 @@ remote file access from higher level code. """ p = base - def o(path, mode="r", text=False): + + def mktempcopy(name): + d, fn = os.path.split(name) + fd, temp = tempfile.mkstemp(prefix=fn, dir=d) + fp = os.fdopen(fd, "wb") + try: + fp.write(file(name, "rb").read()) + except: + try: os.unlink(temp) + except: pass + raise + fp.close() + st = os.lstat(name) + os.chmod(temp, st.st_mode) + return temp + + class atomicfile(file): + """the file will only be copied on close""" + def __init__(self, name, mode, atomic=False): + self.__name = name + self.temp = mktempcopy(name) + file.__init__(self, self.temp, mode) + def close(self): + if not self.closed: + rename(self.temp, self.__name) + file.close(self) + def __del__(self): + self.close() + + def o(path, mode="r", text=False, atomic=False): f = os.path.join(p, path) if not text: @@ -376,21 +405,10 @@ if not os.path.isdir(d): os.makedirs(d) else: + if atomic: + return atomicfile(f, mode) if nlink > 1: - d, fn = os.path.split(f) - fd, temp = tempfile.mkstemp(prefix=fn, dir=d) - fp = os.fdopen(fd, "wb") - try: - fp.write(file(f, "rb").read()) - except: - try: os.unlink(temp) - except: pass - raise - fp.close() - st = os.lstat(f) - os.chmod(temp, st.st_mode) - rename(temp, f) - + rename(mktempcopy(f), f) return file(f, mode) return o diff -r 583b3696d24d -r 482b4efdf013 tests/test-symlinks --- a/tests/test-symlinks Sun Nov 13 02:06:02 2005 +0100 +++ b/tests/test-symlinks Sun Nov 13 02:08:39 2005 +0100 @@ -39,3 +39,4 @@ mkfifo a.c # it should show a.c, dir/a.o and dir/b.o removed hg status +hg status a.c diff -r 583b3696d24d -r 482b4efdf013 tests/test-symlinks.out --- a/tests/test-symlinks.out Sun Nov 13 02:06:02 2005 +0100 +++ b/tests/test-symlinks.out Sun Nov 13 02:08:39 2005 +0100 @@ -1,15 +1,11 @@ -bar: unsupported file type (type is symbolic link) adding foo -bar: unsupported file type (type is symbolic link) -bar: unsupported file type (type is symbolic link) adding bomb -bar: unsupported file type (type is symbolic link) adding a.c adding dir/a.o adding dir/b.o -a.c: unsupported file type (type is fifo) -dir/b.o: unsupported file type (type is symbolic link) R a.c R dir/a.o R dir/b.o ? .hgignore +a.c: unsupported file type (type is fifo) +R a.c