[PATCH 3 of 3] debugbuilddag: build a changelog dag from a concise description

Peter Arrenbrecht peter.arrenbrecht at gmail.com
Sat Feb 13 15:02:22 CST 2010


# HG changeset patch
# User Peter Arrenbrecht <peter.arrenbrecht at gmail.com>
# Date 1266093976 -3600
debugbuilddag: build a changelog dag from a concise description

Useful in tests to quickly build a complex DAG in an empty repo.
Handles local tags and named branches.
Options to, at each rev,
 - create a new file,
 - overwrite the same file,
 - append to the same file,
 - write to a specific line in a mergeable file.
Can run shell commands during DAG buildup.

diff --git a/mercurial/commands.py b/mercurial/commands.py
--- a/mercurial/commands.py
+++ b/mercurial/commands.py
@@ -756,6 +756,123 @@
     a = r.ancestor(lookup(rev1), lookup(rev2))
     ui.write("%d:%s\n" % (r.rev(a), hex(a)))
 
+def debugbuilddag(ui, repo, text,
+                  mergeable_file=False,
+                  appended_file=False,
+                  overwritten_file=False,
+                  new_file=False):
+    """builds a repo with a given dag from scratch in the current empty repo
+
+    Elements:
+
+     - "+n" is a linear run of n nodes based on the current default parent
+     - "." is a single node based on the current default parent
+     - "$" resets the default parent to null (implied at the start);
+           otherwise the default parent is always the last node created
+     - "<p" sets the default parent to the backref p
+     - "*p" is a fork at parent p, which is a backref
+     - "*p1/p2" is a merge of parents p1 and p2, which are backrefs
+     - "/p2" is a merge of the preceding node and p2
+     - ":tag" defines a local tag for the preceding node
+     - "@branch" sets the named branch for subsequent nodes
+     - "!command" runs the command using your shell
+     - "!!my command\\n" is like "!", but to the end of the line
+     - "#...\\n" is a comment up to the end of the line
+
+    Whitespace between the above elements is ignored.
+
+    A backref is either
+
+     - a number n, which references the node curr-n, where curr is the current
+       node, or
+     - the name of a local tag you placed earlier using ":tag", or
+     - empty to denote the default parent.
+
+    All string valued-elements are either strictly alphanumeric, or must
+    be enclosed in double quotes ("..."), with "\" as escape character.
+
+    Note that the --overwritten-file and --appended-file options imply the
+    use of "HGMERGE=internal:local" during DAG buildup.
+    """
+
+    if not (mergeable_file or appended_file or overwritten_file or new_file):
+        raise Exception(_('need at least one of -m, -a, -o, -n'))
+
+    if len(repo.changelog) > 0:
+        raise Exception(_('repository is not empty'))
+
+    if overwritten_file or appended_file:
+        # we don't want to fail in merges during buildup
+        os.environ['HGMERGE'] = 'internal:local'
+
+    def writefile(fname, text, fmode="w"):
+        f = open(fname, fmode)
+        try:
+            f.write(text)
+        finally:
+            f.close()
+
+    if mergeable_file:
+        linesperrev = 2
+        # determine number of revs in DAG
+        n = 0
+        for type, data in dagparser.parsedag(text):
+            if type == 'n':
+                n += 1
+        # make a file with k lines per rev
+        writefile("mf", "\n".join(str(i) for i in xrange(0, n * linesperrev))
+                  + "\n")
+
+    at = -1
+    atbranch = 'default'
+    for type, data in dagparser.parsedag(text):
+        if type == 'n':
+            ui.status('node %s\n' % format(data))
+            id, ps = data
+            p1 = ps[0]
+            if p1 != at:
+                update(ui, repo, node=p1, clean=True)
+                at = p1
+            if repo.dirstate.branch() != atbranch:
+                branch(ui, repo, atbranch, force=True)
+            if len(ps) > 1:
+                p2 = ps[1]
+                merge(ui, repo, node=p2)
+
+            if mergeable_file:
+                f = open("mf", "r+")
+                try:
+                    lines = f.read().split("\n")
+                    lines[id * linesperrev] += " r%i" % id
+                    f.seek(0)
+                    f.write("\n".join(lines))
+                finally:
+                    f.close()
+
+            if appended_file:
+                writefile("af", "r%i\n" % id, "a")
+
+            if overwritten_file:
+                writefile("of", "r%i\n" % id)
+
+            if new_file:
+                writefile("nf%i" % id, "r%i\n" % id)
+
+            commit(ui, repo, addremove=True, message="r%i" % id, date=(id, 0))
+            at = id
+        elif type == 'l':
+            id, name = data
+            ui.status('tag %s\n' % name)
+            tag(ui, repo, name, local=True)
+        elif type == 'a':
+            ui.status('branch %s\n' % data)
+            atbranch = data
+        elif type in 'cC':
+            r = util.system(data, cwd=repo.root)
+            if r:
+                desc, r = util.explain_exit(r)
+                raise util.Abort(_('%s command %s') % (data, desc))
+
 def debugcommands(ui, cmd='', *args):
     for cmd, vals in sorted(table.iteritems()):
         cmd = cmd.split('|')[0].strip('^')
@@ -3564,6 +3681,14 @@
          ] + walkopts + dryrunopts,
          _('[OPTION]... [SOURCE]... DEST')),
     "debugancestor": (debugancestor, [], _('[INDEX] REV1 REV2')),
+    "debugbuilddag":
+        (debugbuilddag,
+         [('m', 'mergeable-file', None, _('add single file mergeable changes')),
+          ('a', 'appended-file', None, _('add single file all revs append to')),
+          ('o', 'overwritten-file', None, _('add single file all revs overwrite')),
+          ('n', 'new-file', None, _('add new file at each rev')),
+         ],
+         _('[OPTION]... TEXT')),
     "debugcheckstate": (debugcheckstate, [], ''),
     "debugcommands": (debugcommands, [], _('[COMMAND]')),
     "debugcomplete":
diff --git a/tests/test-debugbuilddag b/tests/test-debugbuilddag
new file mode 100755
--- /dev/null
+++ b/tests/test-debugbuilddag
@@ -0,0 +1,70 @@
+#! /bin/sh
+
+echo "[extensions]" >> $HGRCPATH
+echo "graphlog=" >> $HGRCPATH
+
+
+
+echo ---- overwritten and appended files
+
+rm -rf repo
+hg init repo
+cd repo
+hg debugbuilddag '+2:f +3:p2 @temp <f+4 @default /p2 +2' -q -oa
+echo -- dag
+hg debugdag -t -b
+echo -- glog
+hg glog --template '{rev}: {desc} [{branches}] @ {date}\n'
+echo -- glog of
+hg glog --template '{rev}: {desc} [{branches}]\n' of
+echo -- glog af
+hg glog --template '{rev}: {desc} [{branches}]\n' af
+echo -- tags
+hg tags -v
+echo -- cat of
+hg cat of
+echo -- cat af
+hg cat af
+cd ..
+
+echo ---- new and mergeable files
+
+rm -rf repo
+hg init repo
+cd repo
+hg debugbuilddag '+2:f +3:p2 @temp <f+4 @default /p2 +2' -q -mn
+echo -- dag
+hg debugdag -t -b
+echo -- glog
+hg glog --template '{rev}: {desc} [{branches}] @ {date}\n'
+echo -- glog mf
+hg glog --template '{rev}: {desc} [{branches}]\n' mf
+
+echo -- man r4
+hg manifest -r4
+echo -- cat r4 mf
+hg cat -r4 mf
+echo -- man r8
+hg manifest -r8
+echo -- cat r8 mf
+hg cat -r8 mf
+echo -- man
+hg manifest
+echo -- cat mf
+hg cat mf
+cd ..
+
+echo ---- command
+
+rm -rf repo
+hg init repo
+cd repo
+hg debugbuilddag '+2 !"touch X" +2' -q -o
+echo -- dag
+hg debugdag -t -b
+echo -- glog
+hg glog --template '{rev}: {desc} [{branches}]\n'
+echo -- glog X
+hg glog --template '{rev}: {desc} [{branches}]\n' X
+cd ..
+
diff --git a/tests/test-debugbuilddag.out b/tests/test-debugbuilddag.out
new file mode 100644
--- /dev/null
+++ b/tests/test-debugbuilddag.out
@@ -0,0 +1,272 @@
+---- overwritten and appended files
+-- dag
++2:f
++3:p2
+ at temp*f+3
+ at default*/p2+2:tip
+-- glog
+@  11: r11 [] @ 11.00
+|
+o  10: r10 [] @ 10.00
+|
+o    9: r9 [] @ 9.00
+|\
+| o  8: r8 [temp] @ 8.00
+| |
+| o  7: r7 [temp] @ 7.00
+| |
+| o  6: r6 [temp] @ 6.00
+| |
+| o  5: r5 [temp] @ 5.00
+| |
+o |  4: r4 [] @ 4.00
+| |
+o |  3: r3 [] @ 3.00
+| |
+o |  2: r2 [] @ 2.00
+|/
+o  1: r1 [] @ 1.00
+|
+o  0: r0 [] @ 0.00
+
+-- glog of
+@  11: r11 []
+|
+o  10: r10 []
+|
+o    9: r9 []
+|\
+| o  8: r8 [temp]
+| |
+| o  7: r7 [temp]
+| |
+| o  6: r6 [temp]
+| |
+| o  5: r5 [temp]
+| |
+o |  4: r4 []
+| |
+o |  3: r3 []
+| |
+o |  2: r2 []
+|/
+o  1: r1 []
+|
+o  0: r0 []
+
+-- glog af
+@  11: r11 []
+|
+o  10: r10 []
+|
+o    9: r9 []
+|\
+| o  8: r8 [temp]
+| |
+| o  7: r7 [temp]
+| |
+| o  6: r6 [temp]
+| |
+| o  5: r5 [temp]
+| |
+o |  4: r4 []
+| |
+o |  3: r3 []
+| |
+o |  2: r2 []
+|/
+o  1: r1 []
+|
+o  0: r0 []
+
+-- tags
+tip                               11:f96e381c614c
+p2                                 4:d9d6db981b55 local
+f                                  1:73253def624e local
+-- cat of
+r11
+-- cat af
+r0
+r1
+r5
+r6
+r7
+r8
+r9
+r10
+r11
+---- new and mergeable files
+-- dag
++2:f
++3:p2
+ at temp*f+3
+ at default*/p2+2:tip
+-- glog
+@  11: r11 [] @ 11.00
+|
+o  10: r10 [] @ 10.00
+|
+o    9: r9 [] @ 9.00
+|\
+| o  8: r8 [temp] @ 8.00
+| |
+| o  7: r7 [temp] @ 7.00
+| |
+| o  6: r6 [temp] @ 6.00
+| |
+| o  5: r5 [temp] @ 5.00
+| |
+o |  4: r4 [] @ 4.00
+| |
+o |  3: r3 [] @ 3.00
+| |
+o |  2: r2 [] @ 2.00
+|/
+o  1: r1 [] @ 1.00
+|
+o  0: r0 [] @ 0.00
+
+-- glog mf
+@  11: r11 []
+|
+o  10: r10 []
+|
+o    9: r9 []
+|\
+| o  8: r8 [temp]
+| |
+| o  7: r7 [temp]
+| |
+| o  6: r6 [temp]
+| |
+| o  5: r5 [temp]
+| |
+o |  4: r4 []
+| |
+o |  3: r3 []
+| |
+o |  2: r2 []
+|/
+o  1: r1 []
+|
+o  0: r0 []
+
+-- man r4
+mf
+nf0
+nf1
+nf2
+nf3
+nf4
+-- cat r4 mf
+0 r0
+1
+2 r1
+3
+4 r2
+5
+6 r3
+7
+8 r4
+9
+10
+11
+12
+13
+14
+15
+16
+17
+18
+19
+20
+21
+22
+23
+-- man r8
+mf
+nf0
+nf1
+nf5
+nf6
+nf7
+nf8
+-- cat r8 mf
+0 r0
+1
+2 r1
+3
+4
+5
+6
+7
+8
+9
+10 r5
+11
+12 r6
+13
+14 r7
+15
+16 r8
+17
+18
+19
+20
+21
+22
+23
+-- man
+mf
+nf0
+nf1
+nf10
+nf11
+nf2
+nf3
+nf4
+nf5
+nf6
+nf7
+nf8
+nf9
+-- cat mf
+0 r0
+1
+2 r1
+3
+4 r2
+5
+6 r3
+7
+8 r4
+9
+10 r5
+11
+12 r6
+13
+14 r7
+15
+16 r8
+17
+18 r9
+19
+20 r10
+21
+22 r11
+23
+---- command
+-- dag
++4:tip
+-- glog
+@  3: r3 []
+|
+o  2: r2 []
+|
+o  1: r1 []
+|
+o  0: r0 []
+
+-- glog X
+o  2: r2 []
+
diff --git a/tests/test-doctest.py b/tests/test-doctest.py
--- a/tests/test-doctest.py
+++ b/tests/test-doctest.py
@@ -16,7 +16,7 @@
 doctest.testmod(mercurial.util)
 
 import mercurial.dagparser
-doctest.testmod(mercurial.dagparser)
+doctest.testmod(mercurial.dagparser, optionflags=doctest.NORMALIZE_WHITESPACE)
 
 import hgext.convert.cvsps
 doctest.testmod(hgext.convert.cvsps)


More information about the Mercurial-devel mailing list