def testwalk(self): treepath = os.path.join(testtmp, "walk") tree = treestate.treestate(treepath, 0) count = 5000 files = list(itertools.islice(genfiles(), count)) expected = {} for path, bits, mode, size, mtime, copied in files: tree.insert(path, bits, mode, size, mtime, copied) expected[path] = (bits, mode, size, mtime, copied) def walk(setbits, unsetbits): return sorted( k for k, v in pycompat.iteritems(expected) if ((v[0] & unsetbits) == 0 and (v[0] & setbits) == setbits) ) def check(setbits, unsetbits): self.assertEqual( walk(setbits, unsetbits), sorted(tree.walk(setbits, unsetbits)) ) for i in ["in-memory", "flushed"]: for bit in [treestate.IGNORED, treestate.COPIED]: check(0, bit) check(bit, 0) check(treestate.EXIST_P1, treestate.EXIST_P2) rootid = tree.flush() tree = treestate.treestate(treepath, rootid)
def clear(self): self._threshold = 0 self._rootid = 0 self._parents = (node.nullid, node.nullid) # use a new file path = self._setfilename() self._tree = treestate.treestate(path, self._rootid)
def testfiltered(self): treepath = os.path.join(testtmp, "filtered") tree = treestate.treestate(treepath, 0) tree.insert("a/B/c", 1, 2, 3, 4, None) filtered = tree.getfiltered("A/B/C", lambda x: x.upper(), 1) self.assertEqual(filtered, ["a/B/c"]) filtered = tree.getfiltered("A/B/C", lambda x: x, 2) self.assertEqual(filtered, [])
def testsaveas(self): treepath = os.path.join(testtmp, "saveas") tree = treestate.treestate(treepath, 0) tree.insert("a", 1, 2, 3, 4, None) tree.setmetadata(b"1") tree.flush() tree.insert("b", 1, 2, 3, 4, None) tree.remove("a") treepath = "%s-savedas" % treepath tree.setmetadata(b"2") rootid = tree.saveas(treepath) tree = treestate.treestate(treepath, rootid) self.assertFalse("a" in tree) self.assertTrue("b" in tree) self.assertEqual(tree.getmetadata(), b"2")
def testdirfilter(self): treepath = os.path.join(testtmp, "walk") tree = treestate.treestate(treepath, 0) files = ["a/b", "a/b/c", "b/c", "c/d"] for path in files: tree.insert(path, 1, 2, 3, 4, None) self.assertEqual(tree.walk(1, 0, None), files) self.assertEqual(tree.walk(1, 0, lambda dir: dir in {"a/b/", "c/"}), ["a/b", "b/c"]) self.assertEqual(tree.walk(1, 0, lambda dir: True), [])
def testinsert(self): tree = treestate.treestate(os.path.join(testtmp, "insert"), 0) count = 5000 files = list(itertools.islice(genfiles(), count)) expected = {} for path, bits, mode, size, mtime, copied in files: tree.insert(path, bits, mode, size, mtime, copied) expected[path] = (bits, mode, size, mtime, copied) self.assertEqual(len(tree), len(expected)) for path in tree.walk(0, 0): self.assertTrue(tree.hasdir(os.path.dirname(path) + "/")) self.assertEqual(tree.get(path, None), expected[path])
def testflush(self): treepath = os.path.join(testtmp, "flush") tree = treestate.treestate(treepath, 0) tree.insert("a", 1, 2, 3, 4, None) tree.setmetadata(b"1") rootid1 = tree.flush() tree.remove("a") tree.insert("b", 1, 2, 3, 4, None) tree.setmetadata(b"2") rootid2 = tree.flush() tree = treestate.treestate(treepath, rootid1) self.assertTrue("a" in tree) self.assertFalse("b" in tree) self.assertEqual(tree.getmetadata(), b"1") tree = treestate.treestate(treepath, rootid2) self.assertFalse("a" in tree) self.assertTrue("b" in tree) self.assertEqual(tree.getmetadata(), b"2")
def testempty(self): tree = treestate.treestate(os.path.join(testtmp, "empty"), 0) self.assertEqual(len(tree), 0) self.assertEqual(tree.getmetadata(), "") self.assertEqual(tree.walk(0, 0), []) self.assertTrue(tree.hasdir("/")) for path in ["", "a", "/", "b/c", "d/"]: self.assertFalse(path in tree) if path and path != "/": self.assertFalse(tree.hasdir(path)) if path != "/": self.assertIsNone(tree.get(path, None))
def testgetdir(self): treepath = os.path.join(testtmp, "filtered") tree = treestate.treestate(treepath, 0) tree.insert("a/b/c", 3, 0, 0, 0, None) tree.insert("a/d", 5, 0, 0, 0, None) self.assertEqual(tree.getdir("/"), (3 | 5, 3 & 5)) self.assertEqual(tree.getdir("a/"), (3 | 5, 3 & 5)) self.assertEqual(tree.getdir("a/b/"), (3, 3)) self.assertIsNone(tree.getdir("a/b/c/")) tree.insert("a/e/f", 10, 0, 0, 0, None) self.assertEqual(tree.getdir("a/"), (3 | 5 | 10, 3 & 5 & 10)) tree.remove("a/e/f") self.assertEqual(tree.getdir("a/"), (3 | 5, 3 & 5))
def testsubdirquery(self): treepath = os.path.join(testtmp, "subdir") tree = treestate.treestate(treepath, 0) paths = ["a/b/c", "a/b/d", "a/c", "de"] for path in paths: tree.insert(path, 1, 2, 3, 4, None) self.assertEqual(tree.tracked(""), paths) self.assertEqual(tree.tracked("de"), ["de"]) self.assertEqual(tree.tracked("a"), []) self.assertEqual(tree.tracked("a/"), ["a/b/c", "a/b/d", "a/c"]) self.assertEqual(tree.tracked("a/b/"), ["a/b/c", "a/b/d"]) self.assertEqual(tree.tracked("a/b"), []) self.assertEqual(tree.tracked("a/c/"), []) self.assertEqual(tree.tracked("a/c"), ["a/c"])
def testpathcomplete(self): treepath = os.path.join(testtmp, "pathcomplete") tree = treestate.treestate(treepath, 0) paths = ["a/b/c", "a/b/d", "a/c", "de"] for path in paths: tree.insert(path, 1, 2, 3, 4, None) def complete(prefix, fullpath=False): completed = [] tree.pathcomplete(prefix, 0, 0, completed.append, fullpath) return completed self.assertEqual(complete(""), ["a/", "de"]) self.assertEqual(complete("d"), ["de"]) self.assertEqual(complete("a/"), ["a/b/", "a/c"]) self.assertEqual(complete("a/b/"), ["a/b/c", "a/b/d"]) self.assertEqual(complete("a/b/c"), ["a/b/c"]) self.assertEqual(complete("", True), paths)
def _read(self): """Read every metadata automatically""" content = b"" try: fp, _mode = txnutil.trypending(self._root, self._vfs, "dirstate") with fp: content = fp.read() except IOError as ex: if ex.errno != errno.ENOENT: raise p1, p2, filename, rootid, threshold = self._parsedirstate(content) self._parents = (p1, p2) self._threshold = threshold self._rootid = rootid path = self._setfilename(filename) try: tree = treestate.treestate(path, rootid) except IOError: if not rootid: # treestate.treestate is read-only if rootid is not None. # If rootid is None, treestate transparently creates an empty # tree (ex. right after "hg init"). IOError can happen if # treestate cannot write such an empty tree. It's hard to make # the Rust land support read-only operation in this case. So # just use a read-only, empty tree. tree = emptytree() else: raise # Double check p1 p2 against metadata stored in the tree. This is # redundant but many things depend on "dirstate" file format. # The metadata here contains (watchman) "clock" which does not exist # in "dirstate". metadata = _unpackmetadata(tree.getmetadata()) if metadata: if metadata.get("p1", node.nullhex) != node.hex(p1) or metadata.get( "p2", node.nullhex ) != node.hex(p2): raise error.Abort( _("working directory state appears damaged (metadata mismatch)!") ) self._tree = tree
def repairtreestate(ui, vfs, root, cl): """Attempt to fix treestate: - Fix the dirstate pointer to a valid treestate root node. - Fix the dirstate node to point to a valid changelog node. """ if not vfs.exists("treestate"): return needrebuild = False try: tmap = treestate.treestatemap(ui, vfs, root) p1node = tmap.parents()[0] if p1node not in cl.nodemap: needrebuild = True except Exception: needrebuild = True if not needrebuild: return nodemap = cl.nodemap def stat(name): return vfs.stat("treestate/%s" % name) def rebuild(filename, rootpos, p1hex): meta = {"p1": p1hex, "filename": filename, "rootid": rootpos} p1bin = bin(p1hex) with vfs.open("dirstate", "w", atomictemp=True) as f: # see treestate.py:treestatemap.write f.write(p1bin) f.write(nullid) f.write(treestate.HEADER) f.write(treestate._packmetadata(meta)) ui.write_err(_("treestate: repaired\n")) # Find a recent treestate (name, root) pair. for filename in sorted(vfs.listdir("treestate"), key=lambda n: -(stat(n).st_mtime)): data = vfs.read("treestate/%s" % filename) path = vfs.join("treestate/%s" % filename) end = len(data) while True: # Find the offset of "p1=". p1pos = data.rfind(b"p1=", 0, end) if p1pos < 0: break # Find a near root offset. A root offset has the property: # - It's before a "p1=" offset (if "p1=" is part of the root metadata, # "p1=" can also be part of a filename or other things). # - It starts with "\0". # See treestate/src/serialization.rs for details. searchrange = 300 end = max(p1pos, searchrange) - searchrange for rootpos in range(end, p1pos): # The first byte of the Root entry is "version", b"\0". # No need to try otherwise. if data[rootpos:rootpos + 1] != b"\0": continue try: rawtree = rawtreestate.treestate(path, rootpos) except Exception: # Root deserialization failed xxhash check. Try next. continue else: meta = treestate._unpackmetadata(rawtree.getmetadata()) p1hex = meta.get("p1") p2hex = meta.get("p2", nullhex) if p2hex != nullhex: # Do not restore to a merge commit. continue if p1hex is None or bin(p1hex) not in nodemap: # Try next - p1 not in changelog. continue rebuild(filename, rootpos, p1hex) return ui.write_err( _("treestate: cannot fix automatically (consider create a new workdir)\n" ))