diff --git a/contrib/phabricator.py b/contrib/phabricator.py --- a/contrib/phabricator.py +++ b/contrib/phabricator.py @@ -503,6 +503,99 @@ meta[r'parent'] = commit[r'parents'][0] return meta +# The constants are from https://secure.phabricator.com/diffusion/ARC/ +# browse/master/src/parser/diff/ArcanistDiffChangeType.php +# They are very stable - haven't been changed since Jan 2011, the first commit +# of the arcanist project. +class changetype(object): + ADD = 1 + CHANGE = 2 + DELETE = 3 + MOVE_AWAY = 4 + COPY_AWAY = 5 + MOVE_HERE = 6 + COPY_HERE = 7 + MULTICOPY = 8 + +class filetype(object): + TEXT = 1 + +def getpatchbodyfromdiff(diff): + """get patch body (without commit message or metadata) from a diff dict + + This is similar to differential.getrawdiff API. But we reconstruct the diff + from a diff object fetched earlier via differential.querydiffs API. + + Currently it does not support binary files. However it seems the + "differential.createrawdiff" API (used by phabsend) couldn't really handle + base85 binaries so binary files are broken at uploading time, therefore + this limitation is probably fine. + """ + out = util.stringio() + write = out.write + + for c in diff[r'changes']: + path = encoding.unitolocal(c[r'currentPath'] or u'') + oldpath = encoding.unitolocal(c[r'oldPath'] or u'') + patha = 'a/%s' % (oldpath or path) + pathb = 'b/%s' % (path or oldpath) + + ftype = int(c[r'fileType']) + if ftype != filetype.TEXT: + raise error.Abort(_('unsupported file type %s: %s') % (ftype, path)) + + ctype = int(c[r'type']) + if ctype in [changetype.MULTICOPY, changetype.MOVE_AWAY, + changetype.COPY_AWAY]: + # ignore these, COPY_HERE or MOVE_HERE will cover them + continue + + def getmode(name): + s = (c[name] or {}).get(r'unix:filemode', r'100644') + return encoding.unitolocal(s) + + oldmode = getmode(r'oldProperties') + newmode = getmode(r'newProperties') + + write('diff --git %s %s\n' % (patha, pathb)) + if ctype == changetype.ADD: + write('new file mode %s\n' % newmode) + patha = '/dev/null' + elif ctype == changetype.DELETE: + write('deleted file mode %s\n' % oldmode) + pathb = '/dev/null' + else: + if oldmode != newmode: + write('old mode %s\nnew mode %s\n' % (oldmode, newmode)) + if ctype == changetype.CHANGE: + pass + elif ctype == changetype.MOVE_HERE: + write('rename from %s\nrename to %s\n' % (oldpath, path)) + elif ctype == changetype.COPY_HERE: + write('copy from %s\ncopy to %s\n' % (oldpath, path)) + else: + raise error.Abort(_('unsupported change type %s: %s') + % (ctype, path)) + + if c[r'hunks']: + write('--- %s\n+++ %s\n' % (patha, pathb)) + for h in c[r'hunks']: + oldoff = int(h[r'oldOffset']) + oldlen = int(h[r'oldLength']) + newoff = int(h[r'newOffset']) + newlen = int(h[r'newLength']) + write('@@ -%d,%d +%d,%d @@\n' % (oldoff, oldlen, newoff, newlen)) + write(encoding.unitolocal(h[r'corpus'])) + + # normalize the patch by trimming context to 3 lines + headers = patch.parsepatch(out.getvalue(), maxcontext=3) + out = util.stringio() + for header in headers: + header.write(out) + for hunk in header.hunks: + hunk.write(out) + return out.getvalue() + def readpatch(repo, params, write, stack=False): """generate plain-text patch readable by 'hg import' @@ -518,10 +611,8 @@ # Generate patch for each drev for drev in drevs: - repo.ui.note(_('reading D%s\n') % drev[r'id']) - diffid = max(int(v) for v in drev[r'diffs']) - body = callconduit(repo, 'differential.getrawdiff', {'diffID': diffid}) + body = getpatchbodyfromdiff(diffs[str(diffid)]) desc = getdescfromdrev(drev) header = '# HG changeset patch\n'