Update markers during hg pullbackup

Summary:
Before this change `hg pullbackup` did not set correct markers on commits.

This change make possible to see what changes already landed even when we are restoring repository from backup.
Before the change `fbclone` + `hg pullbackup` of repo with `C1` commit landed would result in:
```
o  o C2
|    |
o  o C1
|  /
|
o
```
after:
```
o  o C2
|    |
o  x C1
|  /
|
o
```

Reviewed By: StanislavGlebik

Differential Revision: D7032572

fbshipit-source-id: ffee3c7cc23c24a3df9a89c999c9dd2de226dbff
This commit is contained in:
Mateusz Moneta 2018-03-07 14:32:31 -08:00 committed by Saurabh Singh
parent af8ecd5f80
commit 4cfb665650
11 changed files with 336 additions and 74 deletions

View File

@ -118,6 +118,8 @@ class Client(object):
}
}
}
created_time
updated_time
differential_diffs {
count
}
@ -147,6 +149,8 @@ class Client(object):
if status == 'Closed':
status = 'Committed'
info['status'] = status
info['created'] = node['created_time']
info['updated'] = node['updated_time']
if 'latest_active_diff' not in node:
continue

View File

@ -37,9 +37,14 @@
# This information includes the local revision number and unix timestamp
# of the last time we successfully made a backup.
savelatestbackupinfo = False
"""
# Enable creating obsolete markers when backup is restored.
createlandedasmarkers = False
"""
from __future__ import absolute_import
import ConfigParser
import collections
import errno
import json
import os
@ -49,10 +54,6 @@ import stat
import subprocess
import time
from .bundleparts import (
getscratchbookmarkspart,
getscratchbranchparts,
)
from mercurial import (
bundle2,
changegroup,
@ -65,21 +66,17 @@ from mercurial import (
hg,
localrepo,
lock as lockmod,
node,
phases,
policy,
registrar,
scmutil,
util,
)
from collections import defaultdict, namedtuple
from mercurial import policy
from mercurial.extensions import wrapfunction, unwrapfunction
from mercurial.node import bin, hex, nullrev, short
from mercurial.i18n import _
from .. import shareutil
from ConfigParser import ConfigParser
from . import bundleparts
osutil = policy.importmod(r'osutil')
@ -91,8 +88,8 @@ templatekeyword = registrar.templatekeyword()
localoverridesfile = 'generated.infinitepushbackups.rc'
secondsinhour = 60 * 60
backupbookmarktuple = namedtuple('backupbookmarktuple',
['hostname', 'reporoot', 'localbookmark'])
backupbookmarktuple = collections.namedtuple(
'backupbookmarktuple', ['hostname', 'reporoot', 'localbookmark'])
class backupstate(object):
def __init__(self):
@ -204,7 +201,7 @@ def backupdisable(ui, repo, **opts):
timestamp = int(time.time()) + duration
config = ConfigParser()
config = ConfigParser.ConfigParser()
config.add_section('infinitepushbackup')
config.set('infinitepushbackup', 'disableduntil', timestamp)
@ -280,13 +277,21 @@ def restore(ui, repo, dest=None, **opts):
if x not in backupstate.heads]
if dest:
pullopts['source'] = dest
maxrevbeforepull = len(repo.changelog)
result = pullcmd(ui, repo, **pullopts)
maxrevafterpull = len(repo.changelog)
if ui.config('infinitepushbackup', 'createlandedasmarkers', False):
ext = extensions.find('pullcreatemarkers')
ext.createmarkers(result, repo, maxrevbeforepull, maxrevafterpull,
fromdrafts=False)
with repo.wlock(), repo.lock(), repo.transaction('bookmark') as tr:
changes = []
for book, hexnode in backupstate.localbookmarks.iteritems():
if hexnode in repo:
changes.append((book, bin(hexnode)))
changes.append((book, node.bin(hexnode)))
else:
ui.warn(_('%s not found, not creating %s bookmark') %
(hexnode, book))
@ -315,7 +320,7 @@ def getavailablebackups(ui, repo, dest=None, **opts):
sourcehostname, namingmgr)
if opts.get('json'):
jsondict = defaultdict(list)
jsondict = collections.defaultdict(list)
for hostname, reporoot in allbackupstates.keys():
jsondict[hostname].append(reporoot)
# make sure the output is sorted. That's not an efficient way to
@ -489,7 +494,7 @@ def smartlogsummary(ui, repo):
ui.warn(_('note: %d changesets are not backed up.\n') % count)
else:
ui.warn(_('note: changeset %s is not backed up.\n') %
short(repo[singleunbackeduprev].node()))
node.short(repo[singleunbackeduprev].node()))
ui.warn(_('Run `hg pushbackup` to perform a backup. If this fails,\n'
'please report to the Source Control @ FB group.\n'))
@ -589,23 +594,23 @@ def _dobackup(ui, repo, dest, **opts):
# Wrap deltaparent function to make sure that bundle takes less space
# See _deltaparent comments for details
wrapfunction(changegroup.cg2packer, 'deltaparent', _deltaparent)
extensions.wrapfunction(changegroup.cg2packer, 'deltaparent', _deltaparent)
try:
bundler = _createbundler(ui, repo, other)
bundler.addparam("infinitepush", "True")
backup = False
if outgoing and outgoing.missing:
backup = True
parts = getscratchbranchparts(repo, other, outgoing,
confignonforwardmove=False,
ui=ui, bookmark=None,
create=False)
parts = bundleparts.getscratchbranchparts(
repo, other, outgoing, confignonforwardmove=False,
ui=ui, bookmark=None, create=False)
for part in parts:
bundler.addpart(part)
if bookmarkstobackup:
backup = True
bundler.addpart(getscratchbookmarkspart(other, bookmarkstobackup))
bundler.addpart(bundleparts.getscratchbookmarkspart(
other, bookmarkstobackup))
if backup:
_sendbundle(bundler, other)
@ -623,7 +628,8 @@ def _dobackup(ui, repo, dest, **opts):
except Exception:
ui.warn(_('remote connection cleanup failed\n'))
ui.status(_('finished in %f seconds\n') % (time.time() - start))
unwrapfunction(changegroup.cg2packer, 'deltaparent', _deltaparent)
extensions.unwrapfunction(
changegroup.cg2packer, 'deltaparent', _deltaparent)
return 0
def _dobackgroundbackup(ui, repo, dest=None):
@ -712,8 +718,8 @@ def _getlocalinfo(repo):
def _getlocalbookmarks(repo):
localbookmarks = {}
for bookmark, node in repo._bookmarks.iteritems():
hexnode = hex(node)
for bookmark, data in repo._bookmarks.iteritems():
hexnode = node.hex(data)
localbookmarks[bookmark] = hexnode
return localbookmarks
@ -736,7 +742,7 @@ def _filterbookmarks(localbookmarks, repo, headstobackup):
def _downloadbackupstate(ui, other, sourcereporoot, sourcehostname, namingmgr):
pattern = namingmgr.getcommonuserprefix()
fetchedbookmarks = other.listkeyspatterns('bookmarks', patterns=[pattern])
allbackupstates = defaultdict(backupstate)
allbackupstates = collections.defaultdict(backupstate)
for book, hexnode in fetchedbookmarks.iteritems():
parsed = _parsebackupbookmark(book, namingmgr)
if parsed:
@ -855,9 +861,9 @@ def _getcommandandoptions(command):
def _deltaparent(orig, self, revlog, rev, p1, p2, prev):
# This version of deltaparent prefers p1 over prev to use less space
dp = revlog.deltaparent(rev)
if dp == nullrev and not revlog.storedeltachains:
if dp == node.nullrev and not revlog.storedeltachains:
# send full snapshot only if revlog configured to do so
return nullrev
return node.nullrev
return p1
def _getbookmarkstobackup(repo, newbookmarks, removedbookmarks,

View File

@ -17,6 +17,8 @@ from .extlib.phabricator import (
graphql,
)
COMMITTEDSTATUS = 'Committed'
def memoize(f):
"""
NOTE: This is a hack

View File

@ -11,12 +11,12 @@
# relationship between a draft commit and its landed counterpart.
# Thanks to these markers, less information is displayed and rebases can have
# less irrelevant conflicts.
from mercurial import commands
from mercurial import obsolete
from mercurial import phases
from mercurial import extensions
from .extlib.phabricator import diffprops
from .phabstatus import COMMITTEDSTATUS, getdiffstatus
def getdiff(rev):
phabrev = diffprops.parserevfromcommitmsg(rev.description())
@ -28,35 +28,62 @@ def extsetup(ui):
def _pull(orig, ui, repo, *args, **opts):
if not obsolete.isenabled(repo, obsolete.createmarkersopt):
return orig(ui, repo, *args, **opts)
maxrevbeforepull = len(repo.changelog)
r = orig(ui, repo, *args, **opts)
maxrevafterpull = len(repo.changelog)
# Collect the diff number of the landed diffs
landeddiffs = {}
for rev in range(maxrevbeforepull, maxrevafterpull):
n = repo[rev]
if n.phase() == phases.public:
diff = getdiff(n)
if diff is not None:
landeddiffs[diff] = n
createmarkers(r, repo, maxrevbeforepull, maxrevafterpull)
return r
def createmarkers(pullres, repo, start, stop, fromdrafts=True):
landeddiffs = getlandeddiffs(repo, start, stop, onlypublic=fromdrafts)
if not landeddiffs:
return r
return
# Try to find match with the drafts
tocreate = []
unfiltered = repo.unfiltered()
for rev in unfiltered.revs("draft() - obsolete()"):
n = unfiltered[rev]
diff = getdiff(n)
if diff in landeddiffs and landeddiffs[diff].rev() != n.rev():
tocreate.append((n, (landeddiffs[diff],)))
tocreate = getmarkersfromdrafts(repo, landeddiffs) if fromdrafts else \
getmarkers(repo, landeddiffs)
if not tocreate:
return r
return
unfiltered = repo.unfiltered()
with unfiltered.lock(), unfiltered.transaction('pullcreatemarkers'):
obsolete.createmarkers(unfiltered, tocreate)
return r
def getlandeddiffs(repo, start, stop, onlypublic=True):
landeddiffs = {}
for rev in range(start, stop):
rev = repo[rev]
if not onlypublic or rev.phase() == phases.public:
diff = getdiff(rev)
if diff is not None:
landeddiffs[diff] = rev
return landeddiffs
def getmarkers(repo, landeddiffs):
return [(landeddiffs[rev], tuple())
for rev in getlandedrevsiter(repo, landeddiffs)]
def getmarkersfromdrafts(repo, landeddiffs):
tocreate = []
unfiltered = repo.unfiltered()
for rev in unfiltered.revs("draft() - obsolete()"):
rev = unfiltered[rev]
diff = getdiff(rev)
if diff in landeddiffs and landeddiffs[diff].rev() != rev.rev():
marker = (rev, (landeddiffs[diff],))
tocreate.append(marker)
return tocreate
def getlandedrevsiter(repo, landeddiffs):
statuses = (status for status in getdiffstatus(repo, *landeddiffs.keys())
if status != 'Error')
return (diff for status, diff in zip(statuses, landeddiffs.keys())
if status['status'] == COMMITTEDSTATUS)

View File

@ -16,6 +16,7 @@ setupcommon() {
cat >> $HGRCPATH << EOF
[extensions]
infinitepush=
pullcreatemarkers=
[ui]
ssh = python "$TESTDIR/dummyssh"
[infinitepush]

View File

@ -122,7 +122,7 @@ New errors are not allowed. Warnings are strongly discouraged.
hgext/morestatus.py:48:
> os.getcwd()) for path in unresolvedlist])
use pycompat.getcwd instead (py3)
hgext/phabstatus.py:78:
hgext/phabstatus.py:80:
> repodir=os.getcwd(), ca_bundle=ca_certs, repo=repo)
use pycompat.getcwd instead (py3)
hgext/tweakdefaults.py:275:
@ -163,6 +163,9 @@ New errors are not allowed. Warnings are strongly discouraged.
Skipping tests/comprehensive/test-hgsubversion-verify-and-startrev.py it has no-che?k-code (glob)
Skipping tests/conduithttp.py it has no-che?k-code (glob)
Skipping tests/fixtures/rsvn.py it has no-che?k-code (glob)
tests/test-fb-hgext-infinitepush-pullbackup-markers.t:166:
> $ sed -i s/createlandedasmarkers=True// $HGRCPATH
don't use 'sed -i', use a temporary file
Skipping tests/test-fb-hgext-remotefilelog-bad-configs.t it has no-che?k-code (glob)
tests/test-hggit-git-submodules.t:61:
> $ grep 'submodule "subrepo2"' -A2 .gitmodules > .gitmodules-new

View File

@ -110,18 +110,7 @@ outputs, which should be fixed later.
hgext/infinitepush/__init__.py:137: direct symbol import encodelist, decodelist from mercurial.wireproto
hgext/infinitepush/__init__.py:137: symbol import follows non-symbol import: mercurial.wireproto
hgext/infinitepush/__init__.py:137: imports from mercurial.wireproto not lexically sorted: decodelist < encodelist
hgext/infinitepush/backupcommands.py:52: direct symbol import getscratchbookmarkspart, getscratchbranchparts from hgext.infinitepush.bundleparts
hgext/infinitepush/backupcommands.py:74: relative import of stdlib module
hgext/infinitepush/backupcommands.py:74: direct symbol import defaultdict, namedtuple from collections
hgext/infinitepush/backupcommands.py:74: symbol import follows non-symbol import: collections
hgext/infinitepush/backupcommands.py:76: direct symbol import wrapfunction, unwrapfunction from mercurial.extensions
hgext/infinitepush/backupcommands.py:76: symbol import follows non-symbol import: mercurial.extensions
hgext/infinitepush/backupcommands.py:76: imports from mercurial.extensions not lexically sorted: unwrapfunction < wrapfunction
hgext/infinitepush/backupcommands.py:77: symbol import follows non-symbol import: mercurial.node
hgext/infinitepush/backupcommands.py:78: symbol import follows non-symbol import: mercurial.i18n
hgext/infinitepush/backupcommands.py:82: relative import of stdlib module
hgext/infinitepush/backupcommands.py:82: direct symbol import ConfigParser from ConfigParser
hgext/infinitepush/backupcommands.py:82: symbol import follows non-symbol import: ConfigParser
hgext/infinitepush/backupcommands.py:76: symbol import follows non-symbol import: mercurial.i18n
hgext/infinitepush/infinitepushcommands.py:18: direct symbol import cmdtable from hgext.infinitepush.backupcommands
hgext/infinitepush/infinitepushcommands.py:31: direct symbol import downloadbundle from hgext.infinitepush.common
hgext/infinitepush/infinitepushcommands.py:31: symbol import follows non-symbol import: hgext.infinitepush.common

View File

@ -44,7 +44,9 @@ Now progressively test the response handling for variations of missing data
> [{"data": {"query": [{"results": {"nodes": [{
> "number": 1,
> "diff_status_name": "Needs Review",
> "differential_diffs": {"count": 3}
> "differential_diffs": {"count": 3},
> "created_time": 123,
> "updated_time": 222
> }]}}]}}]
> EOF
$ HG_ARC_CONDUIT_MOCK=$TESTTMP/mockduit hg diff --since-last-arc-diff
@ -54,7 +56,9 @@ Now progressively test the response handling for variations of missing data
$ cat > $TESTTMP/mockduit << EOF
> [{"data": {"query": [{"results": {"nodes": [{
> "number": 1,
> "diff_status_name": "Needs Review"
> "diff_status_name": "Needs Review",
> "created_time": 123,
> "updated_time": 222
> }]}}]}}]
> EOF
$ HG_ARC_CONDUIT_MOCK=$TESTTMP/mockduit hg diff --since-last-arc-diff
@ -75,7 +79,9 @@ there is no diff since what was landed.
> ]
> }
> },
> "differential_diffs": {"count": 1}
> "differential_diffs": {"count": 1},
> "created_time": 123,
> "updated_time": 222
> }]}}]}}]
> EOF
$ HG_ARC_CONDUIT_MOCK=$TESTTMP/mockduit hg diff --since-last-arc-diff
@ -96,7 +102,9 @@ assert that we order the commits consistently based on the time field.
> ]
> }
> },
> "differential_diffs": {"count": 1}
> "differential_diffs": {"count": 1},
> "created_time": 123,
> "updated_time": 222
> }]}}]}}]
> EOF
$ HG_ARC_CONDUIT_MOCK=$TESTTMP/mockduit hg diff --since-last-arc-diff --nodates

View File

@ -0,0 +1,212 @@
$ . helpers-usechg.sh
$ . "$TESTDIR/library.sh"
$ . "$TESTDIR/infinitepush/library.sh"
$ setupcommon
$ cat >> $HGRCPATH << EOF
> [extensions]
> arcconfig=$TESTDIR/../hgext/extlib/phabricator/arcconfig.py
> smartlog=
> pullcreatemarkers=
> phabstatus=
> [infinitepushbackup]
> createlandedasmarkers=True
> logdir=$TESTTMP/logs
> [experimental]
> evolution= createmarkers
> EOF
$ mkcommit() {
> echo "$1" > "$1"
> hg add "$1"
> echo "add $1" > msg
> echo "" >> msg
> url="https://phabricator.fb.com"
> if [ -n "$3" ]; then
> url="$3"
> fi
> [ -z "$2" ] || echo "Differential Revision: $url/D$2" >> msg
> hg ci -l msg
> }
$ echo '{}' > .arcrc
$ echo '{"config" : {"default" : "https://a.com/api"}, "hosts" : {"https://a.com/api/" : { "user" : "testuser", "cert" : "garbage_cert"}}}' > .arcconfig
Setup server
$ hg init repo
$ cd repo
$ setupserver
$ cd ..
Set up server repository
$ cd repo
$ mkcommit initial
$ mkcommit secondcommit
$ hg book master
$ cd ..
Set up clients repository
$ hg clone ssh://user@dummy/repo client -q
$ hg clone ssh://user@dummy/repo otherclient -q
Add two commits, one "pushed" to differential
$ cd otherclient
$ hg up 0
0 files updated, 0 files merged, 1 files removed, 0 files unresolved
$ mkcommit b 123
$ mkcommit c
$ cd ..
Add commit which mimics previous differential one merged to master
$ cd client
$ hg up master
0 files updated, 0 files merged, 0 files removed, 0 files unresolved
(activating bookmark master)
$ mkcommit b 123
$ hg push --to master
pushing to ssh://user@dummy/repo
searching for changes
remote: adding changesets
remote: adding manifests
remote: adding file changes
remote: added 1 changesets with 1 changes to 1 files
updating bookmark master
$ cd ..
Push all pulled commit to backup
$ cd otherclient
$ hg pull
pulling from ssh://user@dummy/repo
searching for changes
adding changesets
adding manifests
adding file changes
added 1 changesets with 0 changes to 1 files
updating bookmark master
new changesets 948715751816
(run 'hg update' to get a working copy)
obsoleted 1 changesets
$ hg pushbackup --config extensions.lockfail=$TESTDIR/lockfail.py
starting backup .* (re)
searching for changes
remote: pushing 2 commits:
remote: 9b3ead1d8005 add b
remote: 3969cd9723d1 add c
finished in \d+\.(\d+)? seconds (re)
$ cd ..
Clone fresh repo and try to restore from backup
$ hg clone ssh://user@dummy/repo frombackup -q
$ cd frombackup
$ hg sl --all
@ changeset: 2:948715751816
| bookmark: master
~ tag: tip
user: test
date: Thu Jan 01 00:00:00 1970 +0000
summary: add b
(re)
$ NOW=`date +%s`
$ cat > $TESTTMP/mockduit << EOF
> [{"data": {"query": [{"results": {"nodes": [{
> "number": 123,
> "diff_status_name": "Closed",
> "created_time": 0,
> "updated_time": ${NOW}
> }]}}]}}]
> EOF
$ HG_ARC_CONDUIT_MOCK=$TESTTMP/mockduit hg pullbackup
pulling from ssh://user@dummy/repo
searching for changes
adding changesets
adding manifests
adding file changes
added 2 changesets with 1 changes to 2 files (+1 heads)
new changesets 9b3ead1d8005:3969cd9723d1
(run 'hg heads' to see heads, 'hg merge' to merge)
obsoleted 1 changesets
$ hg sl --all
@ changeset: 2:948715751816
: bookmark: master
: user: test
: date: Thu Jan 01 00:00:00 1970 +0000
: summary: add b
:
: o changeset: 4:3969cd9723d1
: | tag: tip
: | user: test
: | date: Thu Jan 01 00:00:00 1970 +0000
: | instability: orphan
: | summary: add c
: |
: x changeset: 3:9b3ead1d8005
:/ parent: 0:c255e4a1ae9d
: user: test
: date: Thu Jan 01 00:00:00 1970 +0000
: obsolete: pruned
: summary: add b
:
o changeset: 0:c255e4a1ae9d
user: test
date: Thu Jan 01 00:00:00 1970 +0000
summary: add initial
(re)
$ hg debugobsolete
9b3ead1d8005d305582e9d72eb8a4c8959873249 0 {c255e4a1ae9dd17d77787816cff012162a122798} (Thu Jan 01 00:00:00 1970 +0000) {'user': 'test'}
$ cd ..
Test createlandedasmarkers option disabled
$ rm -r frombackup
$ sed -i s/createlandedasmarkers=True// $HGRCPATH
$ hg clone ssh://user@dummy/repo frombackup -q
$ cd frombackup
$ NOW=`date +%s`
$ cat > $TESTTMP/mockduit << EOF
> [{"data": {"query": [{"results": {"nodes": [{
> "number": 123,
> "diff_status_name": "Closed",
> "created_time": 0,
> "updated_time": ${NOW}
> }]}}]}}]
> EOF
$ HG_ARC_CONDUIT_MOCK=$TESTTMP/mockduit hg pullbackup
pulling from ssh://user@dummy/repo
searching for changes
adding changesets
adding manifests
adding file changes
added 2 changesets with 1 changes to 2 files (+1 heads)
new changesets 9b3ead1d8005:3969cd9723d1
(run 'hg heads' to see heads, 'hg merge' to merge)
$ hg sl --all
@ changeset: 2:948715751816
: bookmark: master
: user: test
: date: Thu Jan 01 00:00:00 1970 +0000
: summary: add b
:
: o changeset: 4:3969cd9723d1
: | tag: tip
: | user: test
: | date: Thu Jan 01 00:00:00 1970 +0000
: | summary: add c
: |
: o changeset: 3:9b3ead1d8005
:/ parent: 0:c255e4a1ae9d
: user: test
: date: Thu Jan 01 00:00:00 1970 +0000
: summary: add b
:
o changeset: 0:c255e4a1ae9d
user: test
date: Thu Jan 01 00:00:00 1970 +0000
summary: add initial
(re)
$ hg debugobsolete

View File

@ -60,7 +60,7 @@ And now with bad responses:
Missing status field is treated as an error
$ cat > $TESTTMP/mockduit << EOF
> [{"data": {"query": [{"results": {"nodes": [
> {"number": 1}
> {"number": 1, "created_time": 0, "updated_time": 2}
> ]}}]}}]
> EOF
$ HG_ARC_CONDUIT_MOCK=$TESTTMP/mockduit hg log -T '{phabstatus}\n' -r .
@ -72,7 +72,8 @@ And finally, the success case
$ cat > $TESTTMP/mockduit << EOF
> [{"data": {"query": [{"results": {"nodes": [
> {"number": 1, "diff_status_name": "Needs Review"}
> {"number": 1, "diff_status_name": "Needs Review",
> "created_time": 0, "updated_time": 2}
> ]}}]}}]
> EOF
$ HG_ARC_CONDUIT_MOCK=$TESTTMP/mockduit hg log -T '{phabstatus}\n' -r .
@ -82,7 +83,8 @@ Make sure the code works without the smartlog extensions
$ cat > $TESTTMP/mockduit << EOF
> [{"data": {"query": [{"results": {"nodes": [
> {"number": 1, "diff_status_name": "Needs Review"}
> {"number": 1, "diff_status_name": "Needs Review",
> "created_time": 0, "updated_time": 2}
> ]}}]}}]
> EOF
$ HG_ARC_CONDUIT_MOCK=$TESTTMP/mockduit hg --config 'extensions.smartlog=!' log -T '{phabstatus}\n' -r .

View File

@ -53,7 +53,9 @@ Missing status field is treated as an error
> ]
> }
> },
> "differential_diffs": {"count": 3}
> "differential_diffs": {"count": 3},
> "created_time": 123,
> "updated_time": 222
> }]}}]}}]
> EOF
$ HG_ARC_CONDUIT_MOCK=$TESTTMP/mockduit hg log -T '{syncstatus}\n' -r .
@ -73,7 +75,9 @@ Missing count field is treated as an error
> {"property_value": "{\"lolwut\": {\"time\": 0, \"commit\": \"lolwut\"}}"}
> ]
> }
> }
> },
> "created_time": 123,
> "updated_time": 222
> }]}}]}}]
> EOF
$ HG_ARC_CONDUIT_MOCK=$TESTTMP/mockduit hg log -T '{syncstatus}\n' -r .
@ -94,7 +98,9 @@ Missing hash field is treated as unsync
> ]
> }
> },
> "differential_diffs": {"count": 3}
> "differential_diffs": {"count": 3},
> "created_time": 123,
> "updated_time": 222
> }]}}]}}]
> EOF
$ HG_ARC_CONDUIT_MOCK=$TESTTMP/mockduit hg log -T '{syncstatus}\n' -r .
@ -113,7 +119,9 @@ And finally, the success case
> ]
> }
> },
> "differential_diffs": {"count": 3}
> "differential_diffs": {"count": 3},
> "created_time": 123,
> "updated_time": 222
> }]}}]}}]
> EOF
$ HG_ARC_CONDUIT_MOCK=$TESTTMP/mockduit hg log -T '{syncstatus}\n' -r .