[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