# # # patch "TODO" # from [2ca97a19c1baa2f2de56b7f314b3a153658e37cc] # to [c5efe0fd195a8bb369f40218c69aebf4b0cb175f] # # patch "file.psp" # from [65cc6687cfdb8c71581d09cac1d94ef63e1b3a5f] # to [9cafbcda1e0ea426893d5fc02adfed2af5bc8c4e] # # patch "fileinbranch.psp" # from [4a02ad29a9eb9486ddde1d67bf19851cc421009c] # to [044caac4a12a1326f3aed19c92078614926368e6] # # patch "manifest.psp" # from [359ab37292121fc7ce029ca53216da8c43203795] # to [a1ff2a8d4f3166b174a649341772d60d440ab7b0] # # patch "monotone.py" # from [1b7a44df0f9dc832315e440f0f6ee544a24eb840] # to [2ff57530c29ccc9e7bef17975925d0be8ef48981] # # patch "revision.psp" # from [1517bd776db3bcdf0119021d92d419a63501aae0] # to [eaa0dfc96d1f9c9e93878cb589a87680cfcede9c] # # patch "tarofbranch.psp" # from [1af97621a341b580ee0f92f701cf18ae95087c83] # to [7fcb5ab84ad5b6538d0c64228440cb5acbdd9110] # # patch "tests/automate.py" # from [450a10277f7c31855022fa79213db13de7b3612d] # to [f04942987a6f0f4c71b14d9be69256d5dffa2bfa] # ============================================================ --- TODO 2ca97a19c1baa2f2de56b7f314b3a153658e37cc +++ TODO c5efe0fd195a8bb369f40218c69aebf4b0cb175f @@ -23,6 +23,13 @@ * some sort of contest for a logo (with some sort of cool prize, perhaps con someone into putting one up on offer or pay up myself.) + * a revision selector interface - make sure it prints out the revision + selector to the person, so that they can use it on the command line later + + * optionally pop-up a window (or some sort of AJAX box lurking someplace) + which updates with the commands that have been run - useful for debugging, + also useful for beginners to see how to do stuff. + * Use monotone automate graph to do the ancestry graphing, and show some future information. Perhaps cache based on the mtime of the db viewmtn is looking at? Might be good enough. @@ -76,6 +83,11 @@ * rewrite the basic_io parser; make sure code outside of Monotone.py isn't accessing those structures directly. + * from [mrb] + support for multiple databases (perhaps some sort of dropdown to say which + database you want to look in) - perhaps also make the branches page show the + branches in each of the DBs, for ease. + AJAX ideas: * When hovering over nodes in the ancestry graph, display the ChangeLog. ============================================================ --- file.psp 65cc6687cfdb8c71581d09cac1d94ef63e1b3a5f +++ file.psp 9cafbcda1e0ea426893d5fc02adfed2af5bc8c4e @@ -33,7 +33,7 @@ revision = mt.revision(id) if not revision.has_key('new_manifest'): raise Exception("There is no manifest in this revision ID.") -manifest_id = revision['new_manifest'][0][0][1] +manifest_id = revision['new_manifest'][0][1] manifest = mt.manifest(manifest_id) matching_file_id = None ============================================================ --- fileinbranch.psp 4a02ad29a9eb9486ddde1d67bf19851cc421009c +++ fileinbranch.psp 044caac4a12a1326f3aed19c92078614926368e6 @@ -27,7 +27,7 @@ file_version = {} for id in heads: revision = mt.revision(id) - manifest_id = revision['new_manifest'][0][0][1] + manifest_id = revision['new_manifest'][0][1] file_revisions = filter(lambda x: x[1] == path, mt.manifest(manifest_id)) if len(file_revisions) > 1: raise Exception("More than one file matches path?") ============================================================ --- manifest.psp 359ab37292121fc7ce029ca53216da8c43203795 +++ manifest.psp a1ff2a8d4f3166b174a649341772d60d440ab7b0 @@ -32,7 +32,7 @@ revision = mt.revision(id) if not revision.has_key('new_manifest'): raise Exception("There is no manifest in this revision ID.") -manifest_id = revision['new_manifest'][0][0][1] +manifest_id = revision['new_manifest'][0][1] manifest = mt.manifest(manifest_id) info = { @@ -97,12 +97,12 @@ found = 0 for ancestor in ancestors: rev = mt.revision(ancestor) - for key in rev.keys(): - for stanza in rev[key]: + for type in rev.keys(): + for stanza in rev[type]: affected = None - type = stanza[0][0] - if type == "patch" or type == "add_file" or type == "delete_file" or type == "delete_dir": affected = stanza[0][1] - elif type == "rename_file" or type == "rename_dir": affected = stanza[1][1] + if type == "patch" or type == "add_file" or type == "delete_file" or type == "delete_dir": + affected = stanza[1] + elif type == "rename_file" or type == "rename_dir": affected = stanza[3] if affected != None and file_to_rev.has_key(affected) and file_to_rev[affected] == None: file_to_rev[affected] = ancestor found += 1 ============================================================ --- monotone.py 1b7a44df0f9dc832315e440f0f6ee544a24eb840 +++ monotone.py 2ff57530c29ccc9e7bef17975925d0be8ef48981 @@ -132,7 +132,7 @@ def basic_io_parser(self, data): """returns a list of lists of (key, value) tuples. hashes are returned with []s around them; strings are returned raw.""" - def unescape_string_value(str): + def unescape_string_value(str): rv = "" is_terminated = False in_escape = False @@ -154,44 +154,64 @@ else: rv += c return is_terminated, rv - rv = [] + + # 14:46 < tbrownaw> list>>, with the outer list divided according to + # what item starts a stanza? + + rv = {} + stanza = [] ongoing_string = None - for line in data.split('\n'): - if not ongoing_string: - if line == '' and len(stanza) != 0: - rv.append(stanza) - stanza = [] - m = basic_io_hex_re.match(line) - if m: - key, value = m.groups() - stanza.append((key, value)) - continue - m = basic_io_string_re.match(line) - if m: - key, value = m.groups() - is_terminated, e_value = unescape_string_value(value) - if not is_terminated: ongoing_string = value - else: stanza.append((key, e_value)) - continue - else: + + for line in data.split('\n'): + if ongoing_string != None: ongoing_string += '\n' + line is_terminated, e_value = unescape_string_value(ongoing_string) if is_terminated: - stanza.append((key, e_value)) + stanza += [key, e_value[1:-1]] ongoing_string = None + continue + + if line == '' and len(stanza) != 0: + rv.setdefault(stanza[0], []).append(stanza) + stanza = [] + continue + + m = basic_io_hex_re.match(line) + if m: + key, value = m.groups() + stanza += [key, value[1:-1]] + continue + + m = basic_io_string_re.match(line) + if m: + key, value = m.groups() + is_terminated, e_value = unescape_string_value(value) + if not is_terminated: ongoing_string = value + else: stanza += [key, value[1:-1]] + continue return rv def certs(self, id): + "returns a list of certs, each a list of tuples (attribute,value)" error, data = self.automate.run('certs', [id]) if error != 0: raise Exception("Error obtaining cert for %s: %s" % (id, data)) - return self.basic_io_parser(data) + parsed = self.basic_io_parser(data) + if len(parsed.keys()) != 1 or parsed.keys()[0] != 'key': + raise Exception("basic_io format for certs has changed: unknown cert types '%s' found" % (str(parsed.keys()))) + certs = parsed['key'] + rv = [] + for cert in certs: + rv.append([(cert[t], cert[t+1]) for t in range(0, len(cert), 2)]) + return rv def ancestors(self, ids): + if type(ids) == type(""): ids = [ids] error, result = self.automate.run('ancestors', ids) if error != 0: - raise Exception("Unable to get ancestors of %s: %s" % (id, data)) + raise Exception("Unable to get ancestors of %s: %s" % (id, result)) else: return filter(None, result.split('\n')) def toposort(self, ids): + if type(ids) == type(""): ids = [ids] error, result = self.automate.run('toposort', ids) if error != 0: raise Exception("Unable to toposort: %s" % (result)) @@ -201,20 +221,8 @@ error, result = self.automate.run('get_revision', [id]) if error != 0: raise Exception("Unable to get revision %s: %s" % (id, result)) - rv = {} - cv = [] - for line in result.split('\n'): - if not line: - if len(cv) != 0: - stanza_type = cv[0][0] - if not rv.has_key(stanza_type): rv[stanza_type] = [] - rv[stanza_type].append(cv) - cv = [] - continue - m = basic_io_re.match(line) - if not m: continue - cv.append(m.groups()) - return rv + + return self.basic_io_parser(result) def manifest(self, id): error, result = self.automate.run('get_manifest', [id]) if error != 0: ============================================================ --- revision.psp 1517bd776db3bcdf0119021d92d419a63501aae0 +++ revision.psp eaa0dfc96d1f9c9e93878cb589a87680cfcede9c @@ -87,40 +87,39 @@ <% old_revision = None -for key in revision.keys(): +for type in revision.keys(): value = "" - for stanza in revision[key]: - type = stanza[0][0] + for stanza in revision[type]: if type == "patch": - fname, from_id, to_id = stanza[0][1], stanza[1][1], stanza[2][1] + fname, from_id, to_id = stanza[1], stanza[3], stanza[5] if not from_id: value += 'Add file %s with revision %s
' % (hq(fname), link("file", [id, fname])) else: # FIXME; possible bug here; we might need N diff links where N is the number of ancestors value += 'Patch file %s from %s to %s (%s)
' % (hq(fname), link("download", [from_id, fname]), link("download", [to_id, fname]), link("diff", [old_revision, id, fname])) elif type == "old_revision": - old_revision, old_manifest = stanza[0][1], stanza[1][1] + old_revision, old_manifest = stanza[1], stanza[3] value += 'Old revision is: %s (%s)
Old manifest: %s
' % (link("revision", old_revision), link("diff", [old_revision, id]), link("manifest", old_revision)) elif type == "new_manifest": # swallow this, it's not useful - new_manifest = stanza[0][1] + new_manifest = stanza[1] value += 'New manifest is: %s
' % (link("manifest", id)) elif type == "add_file": - new_file = stanza[0][1] + new_file = stanza[1] value += "Add file: %s
" % (hq(new_file)) elif type == "delete_file": - delete_file = stanza[0][1] + delete_file = stanza[1] value += "Delete file: %s
" % (hq(delete_file)) elif type == "delete_dir": - delete_directory = stanza[0][1] + delete_directory = stanza[1] value += "Delete directory: %s
" % (hq(delete_directory)) elif type == "rename_file": - old_name, new_name = stanza[0][1], stanza[1][1] + old_name, new_name = stanza[1], stanza[3] value += "Rename file %s as %s
" % (hq(old_name), hq(new_name)) elif type == "rename_dir": - old_name, new_name = stanza[0][1], stanza[1][1] + old_name, new_name = stanza[1], stanza[3] value += "Rename directory %s as %s
" % (hq(old_name), hq(new_name)) - req.write('' % (prettify(key), value)) + req.write('' % (prettify(type), value)) %>
%s%s
%s%s
============================================================ --- tarofbranch.psp 1af97621a341b580ee0f92f701cf18ae95087c83 +++ tarofbranch.psp 7fcb5ab84ad5b6538d0c64228440cb5acbdd9110 @@ -19,7 +19,7 @@ raise Exception("No head ID can be determined for this branch.") elif len(heads) == 1: revision = mt.revision(heads[0]) - manifest_id = revision['new_manifest'][0][0][1] + manifest_id = revision['new_manifest'][0][1] psp.redirect("gettar.py?id=%s" % (urllib.quote(manifest_id))) else: info = {'title' : "Latest tar file of branch %s" % (hq(branch))} @@ -36,7 +36,7 @@ <% for id in heads: revision = mt.revision(id) - manifest_id = revision['new_manifest'][0][0][1] + manifest_id = revision['new_manifest'][0][1] req.write('%s%s' % (link("revision", id), link("tar", manifest_id, "download tar archive"))) %> ============================================================ --- tests/automate.py 450a10277f7c31855022fa79213db13de7b3612d +++ tests/automate.py f04942987a6f0f4c71b14d9be69256d5dffa2bfa @@ -1,7 +1,8 @@ #!/usr/bin/env python import sys sys.path.append("..") +import time import monotone import config import traceback @@ -27,9 +28,44 @@ def test_certs(mt): """Test the mt.certs method""" result = mt.certs(config.test_revision) + print result if len(result) == 0: raise Exception("No certs") +def test_ancestors(mt): + """Test the mt.ancestors method""" + result = mt.ancestors(config.test_revision) + if len(result) == 0: + raise Exception("No certs") + +def test_toposort(mt): + """Test the mt.toposort method""" + tosort = mt.heads(config.test_branch) + result = mt.toposort(tosort) + if len(result) == 0: + raise Exception("No result to toposort") + +def test_revision(mt): + """Test the mt.revision method""" + rev = mt.revision(config.test_revision) + if rev == None: + raise Exception("Unable to retrieve revision") + +def test_manifest(mt): + """Test the mt.manifest method""" + rev = mt.revision(config.test_revision) + print rev + return + if not rev.has_key('new_manifest'): + raise Exception("Couldn't find manifest.") + print rev + return + manifest = rev['new_manifest'][0][1] + print manifest + +def test_file(mt): + pass + def test_crazy_toposort(mt): """Test huge (silly) arguments""" #ancestors = mt.heads(config.test_branch) @@ -54,10 +90,20 @@ except Exception: pass tests = [ - test_branches, - test_tags, + +# disabled as they don't use automate +# test_branches, +# test_tags, + + test_ancestors, + test_toposort, + test_revision, + test_manifest, + test_file, + test_heads, test_certs, + test_crazy_toposort, test_error_string, test_invalid_data, @@ -69,12 +115,13 @@ print "#%-3d %s" % (idx, s) log(test.__doc__) try: - if mt.automate.process: print "Child process is: %d" % mt.automate.process.pid test(mt) + except KeyboardInterrupt: + break except: log ("Test FAILED; traceback follows") traceback.print_exc() - sys.exit(1) + print if __name__ == "__main__": mt = monotone.Monotone(config.monotone, config.dbfile) @@ -85,5 +132,6 @@ while attempts > 0: test() + time.sleep(10) attempts -= 1 mt.automate.stop()