hgext/infinitepush/infinitepushcommands.py
changeset 37187 03ff17a4bf53
child 37188 de4c2f3af97f
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/hgext/infinitepush/infinitepushcommands.py	Fri Feb 09 13:39:15 2018 +0530
@@ -0,0 +1,102 @@
+# Copyright 2016 Facebook, Inc.
+#
+# This software may be used and distributed according to the terms of the
+# GNU General Public License version 2 or any later version.
+"""
+config::
+
+    [infinitepush]
+    # limit number of files in the node metadata. This is to make sure we don't
+    # waste too much space on huge codemod commits.
+    metadatafilelimit = 100
+"""
+
+from __future__ import absolute_import
+
+import json
+
+from mercurial.node import bin
+from mercurial.i18n import _
+
+from mercurial import (
+    copies as copiesmod,
+    encoding,
+    error,
+    hg,
+    patch,
+    registrar,
+    scmutil,
+    util,
+)
+
+from . import (
+    backupcommands,
+    common,
+)
+
+downloadbundle = common.downloadbundle
+
+cmdtable = backupcommands.cmdtable
+command = registrar.command(cmdtable)
+
+@command('debugfillinfinitepushmetadata',
+         [('', 'node', [], 'node to fill metadata for')])
+def debugfillinfinitepushmetadata(ui, repo, **opts):
+    '''Special command that fills infinitepush metadata for a node
+    '''
+
+    nodes = opts['node']
+    if not nodes:
+        raise error.Abort(_('nodes are not specified'))
+
+    filelimit = ui.configint('infinitepush', 'metadatafilelimit', 100)
+    nodesmetadata = {}
+    for node in nodes:
+        index = repo.bundlestore.index
+        if not bool(index.getbundle(node)):
+            raise error.Abort(_('node %s is not found') % node)
+
+        if node not in repo:
+            newbundlefile = downloadbundle(repo, bin(node))
+            bundlepath = "bundle:%s+%s" % (repo.root, newbundlefile)
+            bundlerepo = hg.repository(ui, bundlepath)
+            repo = bundlerepo
+
+        p1 = repo[node].p1().node()
+        diffopts = patch.diffallopts(ui, {})
+        match = scmutil.matchall(repo)
+        chunks = patch.diff(repo, p1, node, match, None, diffopts, relroot='')
+        difflines = util.iterlines(chunks)
+
+        states = 'modified added removed deleted unknown ignored clean'.split()
+        status = repo.status(p1, node)
+        status = zip(states, status)
+
+        filestatus = {}
+        for state, files in status:
+            for f in files:
+                filestatus[f] = state
+
+        diffstat = patch.diffstatdata(difflines)
+        changed_files = {}
+        copies = copiesmod.pathcopies(repo[p1], repo[node])
+        for filename, adds, removes, isbinary in diffstat[:filelimit]:
+            # use special encoding that allows non-utf8 filenames
+            filename = encoding.jsonescape(filename, paranoid=True)
+            changed_files[filename] = {
+                'adds': adds, 'removes': removes, 'isbinary': isbinary,
+                'status': filestatus.get(filename, 'unknown')
+            }
+            if filename in copies:
+                changed_files[filename]['copies'] = copies[filename]
+
+        output = {}
+        output['changed_files'] = changed_files
+        if len(diffstat) > filelimit:
+            output['changed_files_truncated'] = True
+        nodesmetadata[node] = output
+
+    with index:
+        for node, metadata in nodesmetadata.iteritems():
+            dumped = json.dumps(metadata, sort_keys=True)
+            index.saveoptionaljsonmetadata(node, dumped)