[PATCH 1 of 1] Perforce push and outgoing implementations

Frank Kingswood frank at kingswood-consulting.co.uk
Sat Dec 19 09:13:38 UTC 2009


# HG changeset patch
# User Frank Kingswood <frank at kingswood-consulting.co.uk>
# Date 1261162546 0
# Node ID b47f0d55f8591e27d2e502373e1255dee3c60487
# Parent  0000000000000000000000000000000000000000
Perforce push and outgoing implementations.

diff -r 000000000000 -r b47f0d55f859 .hgignore
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/.hgignore	Fri Dec 18 18:55:46 2009 +0000
@@ -0,0 +1,3 @@
+perfarce.pyc
+depot
+src
diff -r 000000000000 -r b47f0d55f859 perfarce.py
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/perfarce.py	Fri Dec 18 18:55:46 2009 +0000
@@ -0,0 +1,296 @@
+# Mercurial extension to push to and pull from Perforce depots.
+#
+# Copyright 2009 Frank Kingswood <frank at kingswood-consulting.co.uk>
+#
+# This software may be used and distributed according to the terms of the
+# GNU General Public License version 2, incorporated herein by reference.
+
+'''Push to or pull from Perforce depots
+
+This extension modifies the remote repository handling so that repository
+paths that resemble
+    p4://[user@]?p4server[:port]/clientname
+cause operations on the named p4 client specification for the optionally
+named user on the p4 server.
+'''
+
+from mercurial import cmdutil, commands, extensions, hg, util
+from mercurial.i18n import _
+import marshal, tempfile, os, re
+import subprocess
+
+def uisetup(ui):
+    """monkeypatch pull and push for p4:// support"""
+    
+    extensions.wrapcommand(commands.table, 'pull', pull)
+    extensions.wrapcommand(commands.table, 'push', push)
+    extensions.wrapcommand(commands.table, 'incoming', incoming)
+    extensions.wrapcommand(commands.table, 'outgoing', outgoing)
+
+# --------------------------------------------------------------------------
+
+def latestp4changelist(repo):
+    """Find the most recent changelist which has the p4 extra data which
+    indicates the p4 changelist it was converted from"""
+
+    for rev in xrange(len(repo), 0, -1):
+        extra = repo[rev].extra()
+        if 'p4' in extra:
+            return rev, extra['p4']
+    raise util.Abort(_('No Perforce changelist revision found'))
+
+def loaditer(f):
+    "Yield the dictionary objects generated by p4"
+    try:
+        while True:
+            d = marshal.load(f)
+            if not d:
+                break
+            yield d
+    except EOFError:
+        pass
+
+def p4(ui, cmd, cwd=None, abort=True):
+    "Run a P4 command and yield the objects returned"
+
+    ui.debug("> %s\n"%cmd)
+    if cwd:
+        old=os.getcwd()
+        os.chdir(cwd)
+    for d in loaditer(util.popen(cmd, mode='rb')):
+        ui.debug("< %r\n"%d)
+        if 'code' in d and 'data' in d:
+            code = d['code']
+            data = d['data']
+            if abort and code == 'error':
+                raise util.Abort(_("p4: %s") % data)
+            elif code == 'info':
+                ui.note('p4: %s' % data)
+        yield d
+    if cwd:
+        os.chdir(old)
+
+def p4run(ui, cmd, cwd=None, abort=True):
+    "Run a P4 command and discard output"
+    for d in p4(ui, cmd, cwd, abort):
+        pass
+
+def p4root(ui, p4s, name):
+    "Find the root of the P4 client workarea"
+    root = []
+    for d in p4(ui, 'p4 -G -p "%s" client -o "%s"' % (p4s, name)):
+        for i in d:
+            if i == "Root" or i.startswith("AltRoots"):
+                if os.path.exists(d[i]):
+                    return d[i]
+    return None
+
+def p4urisplit(uri):
+    "Return (server, clientname) tuple from P4 URI, or None"
+    if uri.startswith("p4://"):
+        r = uri[5:].split('/')
+        if len(r) == 2:
+            if ':' not in r[0]:
+                r = ("%s:1666" % r[0], r[1])
+            return r
+    return None, None
+
+# --------------------------------------------------------------------------
+
+def pullcommon(original, ui, repo, source, **opts):
+    "Shared code for pull and incoming"
+
+    source, revs, checkout = hg.parseurl(ui.expandpath(source or 'default'), opts.get('rev'))
+    p4s, p4c = p4urisplit(source)
+    if not p4c:
+        return True, original(ui,repo,source,**opts)
+    return False, (source, revs, checkout)
+
+
+def pull(original, ui, repo, source=None, **opts):
+    done, r = pullcommon(original, ui, repo, source, **opts)
+
+    if done:
+        return r
+
+    print repr(r)
+
+
+def incoming(original, ui, repo, source="default", **opts):
+    done, r = pullcommon(original, ui, repo, source, **opts)
+
+    if done:
+        return r
+
+    print repr(r)
+
+
+# --------------------------------------------------------------------------
+
+def pushcommon(original, ui, repo, dest, **opts):
+    "Shared code for push and outgoing"
+
+    dest, revs, co = hg.parseurl(ui.expandpath(dest or 'default-push',
+                                               dest or 'default'), opts.get('rev'))
+    p4s, p4c = p4urisplit(dest)
+    if not p4c:
+        return True, original(ui,repo,dest,**opts)
+
+    p4rev, p4id = latestp4changelist(repo)
+    ctx1 = repo[p4rev]
+
+    if opts.get('rev'):
+        n1, n2 = cmdutil.revpair(repo, opts['rev'])
+        if n2:
+            ctx1 = repo[n1]
+            ctx2 = repo[n2]
+        else:
+            ctx2 = repo[n1]
+            ctx1 = ctx2.parents()[0]
+    else:
+        ctx2 = repo['tip']
+    
+    nodes = repo.changelog.nodesbetween([ctx1.node()], [ctx2.node()])[0][1:]
+
+    if ctx1 == ctx2:
+        mod = add = rem = None
+    else:
+        mod, add, rem = repo.status(node1=ctx1.node(), node2=ctx2.node())[:3]
+    
+    if not (mod or add or rem):
+        ui.status(_("no changes found\n"))
+        return True, 0
+
+    desc = []
+    for n in nodes:
+        desc.append(repo[n].description())
+
+    desc="\n* * *\n".join(desc) + "\n\n{{mercurial %s}}\n" % repo[nodes[-1]].hex()
+
+    return False, (p4s, p4c, dest, revs, p4rev, p4id, ctx2, desc, mod, add, rem)
+
+
+def push(original, ui, repo, dest=None, **opts):
+    """Wrap the push command to look for p4 paths, create p4 changelist"""
+
+    done, r = pushcommon(original, ui, repo, dest, **opts)
+    if done:
+        return r
+    
+    p4s, p4c, dest, revs, p4rev, p4id, ctx2, desc, mod, add, rem = r
+    root = p4root(ui, p4s, p4c)
+
+    # sync to latest revision
+    p4run(ui, 'p4 -G -p "%s" sync @%s' % (p4s, p4id), cwd=root, abort=False)
+
+    # attempt to reuse an existing changelist
+    use = ""
+    for d in p4(ui, 'p4 -G -p "%s" -c "%s" changes -s pending -L' % (p4s, p4c), root):
+        if d['desc'] == desc:
+            use = d['change']
+
+    # get changelist data, and update it
+    changelist = None
+    for d in p4(ui, 'p4 -G -p "%s" -c "%s" change -o %s' % (p4s, p4c, use), root):
+        d['Description'] = desc
+        changelist = d
+
+    fn = None
+    try:
+        # write changelist data to a temporary file
+        fd, fn = tempfile.mkstemp(prefix='hg-p4-')
+        fp = os.fdopen(fd, 'wb')
+        marshal.dump(changelist, fp)
+        fp.close()
+
+        # update p4 changelist
+        for d in p4(ui, 'p4 -G -p "%s" -c "%s" change -i <"%s"' % (p4s, p4c, fn), root):
+            data = d['data']
+            if d['code'] == 'info':
+                ui.status("p4: %s\n" % data)
+                if not use:
+                    m = re.match("(.+) ([0-9]+) (.+)", data)
+                    if m:
+                        use = m.group(2)
+            else:
+                raise util.Abort(_("Error creating p4 change: %s") % data)
+
+    finally:
+        try:
+            if fn: os.unlink(fn)
+        except: 
+            pass
+
+    if not use:
+        raise util.Abort(_("Did not get changelist number from p4"))
+
+    # revert any other changes to the files
+    ui.note(_('reverting: %s\n') % ', '.join(mod+add+rem))
+    p4run(ui, 'p4 -G -p "%s" -c "%s" revert %s' % (p4s, p4c, " ".join('"%s"'%f for f in mod + add + rem)), cwd=root, abort=False)
+
+    # now add/edit/delete the files
+    if mod:
+        ui.note(_('opening for edit: %s\n') % ', '.join(mod))
+        p4run(ui, 'p4 -G -p "%s" -c "%s" edit -c %s %s' % (p4s, p4c, use, " ".join('"%s"'%f for f in mod)), root)
+
+    if mod or add:
+        ui.note(_('Retrieving file contents...\n'))
+        m = cmdutil.match(repo, mod+add, opts={})
+        for abs in ctx2.walk(m):
+            out = os.path.join(root, abs)
+            ui.debug(_('writing: %s\n') % out)
+            util.makedirs(os.path.dirname(out))
+            fp = cmdutil.make_file(repo, out, ctx2.node(), pathname=abs)
+            data = ctx2[abs].data()
+            fp.write(data)
+            fp.close()
+
+    if add:
+        ui.note(_('opening for add: %s\n') % ', '.join(add))
+        p4run(ui, 'p4 -G -p "%s" -c "%s" add -c %s %s' % (p4s, p4c, use, " ".join('"%s"'%f for f in add)), root)
+
+    if rem:
+        ui.note(_('opening for delete: %s\n') % ', '.join(rem))
+        p4run(ui, 'p4 -G -p "%s" -c "%s" delete -c %s %s' % (p4s, p4c, use, " ".join('"%s"'%f for f in rem)), root)
+
+    # finally submit the changelist to perforce if --force was given
+    if opts['force']:
+        for d in p4(ui, 'p4 -G -p "%s" -c "%s" submit -c %s' % (p4s, p4c, use), root):
+            if d['code'] == 'error':
+                raise util.Abort(_("Error submitting p4 change %s: %s") % (use, d['data']))
+            #if d['code'] == 'stat':
+            #data = d['data']
+
+
+def outgoing(original, ui, repo, dest=None, **opts):
+    """Wrap the outgoing command to look for p4 paths, report changes"""
+    done, r = pushcommon(original, ui, repo, dest, **opts)
+    if done:
+        return r
+    
+    p4s, p4c, dest, revs, p4rev, p4id, ctx2, desc, mod, add, rem = r
+
+    ui.write(desc)
+    ui.write("\n\nfiles affected:\n")
+    cwd = repo.getcwd()
+    for char, files in zip('MAR', (mod, add, rem)):
+        for f in files:
+            ui.write("%s %s\n" % (char, repo.pathto(f, cwd)))
+    ui.write("\n")
+
+
+# --------------------------------------------------------------------------
+
+def submit(ui, repo, *args, **opts):
+    """do a p4 submit on the current branch"""
+    print "perfarce.submit()"
+
+
+cmdtable = {
+    # "command-name": (function-call, options-list, help-string)
+    "submit": 
+        (   submit,
+            [  ('v', 'verbose', None, 'be verbose about it'),
+            ],
+            "hg p4submit [options] arguments")
+}
diff -r 000000000000 -r b47f0d55f859 test-push-perforce
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/test-push-perforce	Fri Dec 18 18:55:46 2009 +0000
@@ -0,0 +1,95 @@
+#!/bin/sh
+
+set -e
+
+echo % clean up
+rm -fr $PWD/depot $PWD/src $PWD/dst || true
+
+echo % create p4 depot
+P4ROOT=$PWD/depot; export P4ROOT
+P4AUDIT=$P4ROOT/audit; export P4AUDIT
+P4JOURNAL=$P4ROOT/journal; export P4JOURNAL
+P4LOG=$P4ROOT/log; export P4LOG
+P4PORT=localhost:16661; export P4PORT
+P4DEBUG=1; export P4DEBUG
+
+echo % start the p4 server
+[ ! -d $P4ROOT ] && mkdir $P4ROOT
+p4d -f -J off >$P4ROOT/stdout 2>$P4ROOT/stderr &
+trap "echo % stop the p4 server ; p4 admin stop" EXIT
+
+# wait for the server to initialize
+while ! p4 ; do
+   sleep 1
+done >/dev/null 2>/dev/null
+
+echo % create a client spec
+mkdir src
+cd src
+
+P4CLIENT=hg-p4-import; export P4CLIENT
+DEPOTPATH=//depot/test-mercurial-push/...
+p4 client -o | sed '/^View:/,$ d' >p4client-$$
+echo View: >>p4client-$$
+echo " $DEPOTPATH //$P4CLIENT/..." >>p4client-$$
+p4 client -i <p4client-$$
+rm p4client-$$
+
+echo % populate the depot
+echo a > a
+mkdir b
+echo c > b/c
+p4 add a b/c
+p4 submit -d initial
+
+echo % change some files
+p4 edit a
+echo aa >> a
+p4 submit -d "p4 change a"
+
+p4 edit b/c
+echo cc >> b/c
+p4 submit -d "p4 change b/c"
+
+echo dd >>b/d
+p4 add b/d
+p4 submit -d "p4 add b/d"
+
+p4 delete b/c
+p4 submit -d "p4 delete b/c"
+
+cd ..
+
+echo % convert
+hg convert -s p4 $DEPOTPATH dst
+
+echo % now work in hg
+cd dst
+hg up
+
+echo % outgoing
+hg out p4://$P4PORT/$P4CLIENT
+
+echo % change some files in hg
+echo aab >> a
+hg commit -m "hg change a"
+
+echo ee >> b/e
+hg add b/e
+hg ci -m "hg add b/e"
+
+echo ddd >>b/d
+hg commit -m "hg change b/d"
+
+#echo % run bash
+#bash
+
+echo % outgoing
+hg out p4://$P4PORT/$P4CLIENT
+
+echo % push
+hg --debug push --force p4://$P4PORT/$P4CLIENT
+
+cd ../src
+p4 changes
+p4 describe -s 6



More information about the Mercurial mailing list