##// END OF EJS Templates
exchangev2: fetch manifest revisions...
exchangev2: fetch manifest revisions Now that the server has support for retrieving manifest data, we can implement the client bits to call it. We teach the changeset fetching code to capture the manifest revisions that are encountered on incoming changesets. We then feed this into a new function which filters out known manifests and then batches up manifest data requests to the server. This is different from the previous wire protocol in a few notable ways. First, the client fetches manifest data separately and explicitly. Before, we'd ask the server for data pertaining to some changesets (via a "getbundle" command) and manifests (and files) would be sent automatically. Providing an API for looking up just manifest data separately gives clients much more flexibility for manifest management. For example, a client may choose to only fetch manifest data on demand instead of prefetching it (i.e. partial clone). Second, we send N commands to the server for manifest retrieval instead of 1. This property has a few nice side-effects. One is that the deterministic nature of the requests lends itself to server-side caching. For example, say the remote has 50,000 manifests. If the server is configured to cache responses, each time a new commit arrives, you will have a cache miss and need to regenerate all outgoing data. But if you makes N requests requesting 10,000 manifests each, a new commit will still yield cache hits on the initial, unchanged manifest batches/requests. A derived benefit from these properties is that resumable clone is conceptually simpler to implement. When making a monolithic request for all of the repository data, recovering from an interrupted clone is hard because the server was in the driver's seat and was maintaining state about all the data that needed transferred. With the client driving fetching, the client can persist the set of unfetched entities and retry/resume a fetch if something goes wrong. Or we can fetch all data N changesets at a time and slowly build up a repository. This approach is drastically easier to implement when we have server APIs exposing low-level repository primitives (such as manifests and files). We don't yet support tree manifests. But it should be possible to implement that with the existing wire protocol command. Differential Revision: https://phab.mercurial-scm.org/D4489

File last commit:

r38437:b34d0a6e default
r39674:d292328e default
Show More
progress.py
303 lines | 10.8 KiB | text/x-python | PythonLexer
Pierre-Yves David
progress: move most extension code into a 'mercurial.progress' module...
r25497 # progress.py progress bars related code
#
# Copyright (C) 2010 Augie Fackler <durin42@gmail.com>
#
# This software may be used and distributed according to the terms of the
# GNU General Public License version 2 or any later version.
Gregory Szorc
progress: use absolute_import
r25968 from __future__ import absolute_import
Yuya Nishihara
progress: retry ferr.flush() and .write() on EINTR (issue5532)...
r32049 import errno
Pierre-Yves David
progress: move most extension code into a 'mercurial.progress' module...
r25497 import threading
Gregory Szorc
progress: use absolute_import
r25968 import time
Pierre-Yves David
progress: move most extension code into a 'mercurial.progress' module...
r25497
Gregory Szorc
progress: use absolute_import
r25968 from .i18n import _
from . import encoding
Pierre-Yves David
progress: move most extension code into a 'mercurial.progress' module...
r25497
def spacejoin(*args):
return ' '.join(s for s in args if s)
def shouldprint(ui):
Matt Anderson
progress: display progress bar when HGPLAINEXCEPT contains "progress"...
r28171 return not (ui.quiet or ui.plain('progress')) and (
Yuya Nishihara
progress: obtain stderr from ui...
r30324 ui._isatty(ui.ferr) or ui.configbool('progress', 'assume-tty'))
Pierre-Yves David
progress: move most extension code into a 'mercurial.progress' module...
r25497
def fmtremaining(seconds):
Mads Kiilerich
spelling: trivial spell checking
r26781 """format a number of remaining seconds in human readable way
Pierre-Yves David
progress: move most extension code into a 'mercurial.progress' module...
r25497
This will properly display seconds, minutes, hours, days if needed"""
if seconds < 60:
# i18n: format XX seconds as "XXs"
return _("%02ds") % (seconds)
minutes = seconds // 60
if minutes < 60:
seconds -= minutes * 60
# i18n: format X minutes and YY seconds as "XmYYs"
return _("%dm%02ds") % (minutes, seconds)
# we're going to ignore seconds in this case
minutes += 1
hours = minutes // 60
minutes -= hours * 60
if hours < 30:
# i18n: format X hours and YY minutes as "XhYYm"
return _("%dh%02dm") % (hours, minutes)
# we're going to ignore minutes in this case
hours += 1
days = hours // 24
hours -= days * 24
if days < 15:
# i18n: format X days and YY hours as "XdYYh"
return _("%dd%02dh") % (days, hours)
# we're going to ignore hours in this case
days += 1
weeks = days // 7
days -= weeks * 7
if weeks < 55:
# i18n: format X weeks and YY days as "XwYYd"
return _("%dw%02dd") % (weeks, days)
# we're going to ignore days and treat a year as 52 weeks
weeks += 1
years = weeks // 52
weeks -= years * 52
# i18n: format X years and YY weeks as "XyYYw"
return _("%dy%02dw") % (years, weeks)
Yuya Nishihara
progress: retry ferr.flush() and .write() on EINTR (issue5532)...
r32049 # file_write() and file_flush() of Python 2 do not restart on EINTR if
# the file is attached to a "slow" device (e.g. a terminal) and raise
# IOError. We cannot know how many bytes would be written by file_write(),
# but a progress text is known to be short enough to be written by a
# single write() syscall, so we can just retry file_write() with the whole
# text. (issue5532)
#
# This should be a short-term workaround. We'll need to fix every occurrence
# of write() to a terminal or pipe.
def _eintrretry(func, *args):
while True:
try:
return func(*args)
except IOError as err:
if err.errno == errno.EINTR:
continue
raise
Pierre-Yves David
progress: move most extension code into a 'mercurial.progress' module...
r25497 class progbar(object):
def __init__(self, ui):
self.ui = ui
self._refreshlock = threading.Lock()
self.resetstate()
def resetstate(self):
self.topics = []
self.topicstates = {}
self.starttimes = {}
self.startvals = {}
self.printed = False
self.lastprint = time.time() + float(self.ui.config(
Jun Wu
codemod: register core configitems using a script...
r33499 'progress', 'delay'))
Pierre-Yves David
progress: move most extension code into a 'mercurial.progress' module...
r25497 self.curtopic = None
self.lasttopic = None
self.indetcount = 0
self.refresh = float(self.ui.config(
Jun Wu
codemod: register core configitems using a script...
r33499 'progress', 'refresh'))
Pierre-Yves David
progress: move most extension code into a 'mercurial.progress' module...
r25497 self.changedelay = max(3 * self.refresh,
float(self.ui.config(
Jun Wu
codemod: register core configitems using a script...
r33499 'progress', 'changedelay')))
Boris Feld
configitems: register the 'progress.format' config
r34747 self.order = self.ui.configlist('progress', 'format')
Jun Wu
progress: make ETA only consider progress made in the last minute...
r34315 self.estimateinterval = self.ui.configwith(
float, 'progress', 'estimateinterval')
Pierre-Yves David
progress: move most extension code into a 'mercurial.progress' module...
r25497
def show(self, now, topic, pos, item, unit, total):
if not shouldprint(self.ui):
return
termwidth = self.width()
self.printed = True
head = ''
needprogress = False
tail = ''
for indicator in self.order:
add = ''
if indicator == 'topic':
add = topic
elif indicator == 'number':
if total:
Yuya Nishihara
progress: use '%*d' to pad progress value...
r36220 add = b'%*d/%d' % (len(str(total)), pos, total)
Pierre-Yves David
progress: move most extension code into a 'mercurial.progress' module...
r25497 else:
Augie Fackler
py3: convert known-int values to bytes using %d...
r36441 add = b'%d' % pos
Pierre-Yves David
progress: move most extension code into a 'mercurial.progress' module...
r25497 elif indicator.startswith('item') and item:
slice = 'end'
if '-' in indicator:
wid = int(indicator.split('-')[1])
elif '+' in indicator:
slice = 'beginning'
wid = int(indicator.split('+')[1])
else:
wid = 20
if slice == 'end':
add = encoding.trim(item, wid, leftside=True)
else:
add = encoding.trim(item, wid)
add += (wid - encoding.colwidth(add)) * ' '
elif indicator == 'bar':
add = ''
needprogress = True
elif indicator == 'unit' and unit:
add = unit
elif indicator == 'estimate':
add = self.estimate(topic, pos, total, now)
elif indicator == 'speed':
add = self.speed(topic, pos, unit, now)
if not needprogress:
head = spacejoin(head, add)
else:
tail = spacejoin(tail, add)
if needprogress:
used = 0
if head:
used += encoding.colwidth(head) + 1
if tail:
used += encoding.colwidth(tail) + 1
progwidth = termwidth - used - 3
if total and pos <= total:
amt = pos * progwidth // total
bar = '=' * (amt - 1)
if amt > 0:
bar += '>'
bar += ' ' * (progwidth - amt)
else:
progwidth -= 3
self.indetcount += 1
# mod the count by twice the width so we can make the
# cursor bounce between the right and left sides
amt = self.indetcount % (2 * progwidth)
amt -= progwidth
bar = (' ' * int(progwidth - abs(amt)) + '<=>' +
' ' * int(abs(amt)))
Alex Gaynor
style: never use a space before a colon or comma...
r34487 prog = ''.join(('[', bar, ']'))
Pierre-Yves David
progress: move most extension code into a 'mercurial.progress' module...
r25497 out = spacejoin(head, prog, tail)
else:
out = spacejoin(head, tail)
Yuya Nishihara
progress: extract stubs to restart ferr.flush() and .write() on EINTR
r32048 self._writeerr('\r' + encoding.trim(out, termwidth))
Pierre-Yves David
progress: move most extension code into a 'mercurial.progress' module...
r25497 self.lasttopic = topic
Yuya Nishihara
progress: extract stubs to restart ferr.flush() and .write() on EINTR
r32048 self._flusherr()
Pierre-Yves David
progress: move most extension code into a 'mercurial.progress' module...
r25497
def clear(self):
Matt Mackall
progress: stop excessive clearing (issue4801)...
r29089 if not self.printed or not self.lastprint or not shouldprint(self.ui):
Pierre-Yves David
progress: move most extension code into a 'mercurial.progress' module...
r25497 return
Yuya Nishihara
progress: extract stubs to restart ferr.flush() and .write() on EINTR
r32048 self._writeerr('\r%s\r' % (' ' * self.width()))
Augie Fackler
progress: force a repaint of a printed progress bar after a clear()...
r26407 if self.printed:
# force immediate re-paint of progress bar
self.lastprint = 0
Pierre-Yves David
progress: move most extension code into a 'mercurial.progress' module...
r25497
def complete(self):
if not shouldprint(self.ui):
return
configitems: register the 'progress.clear-complete' config
r33246 if self.ui.configbool('progress', 'clear-complete'):
Pierre-Yves David
progress: move most extension code into a 'mercurial.progress' module...
r25497 self.clear()
else:
Yuya Nishihara
progress: extract stubs to restart ferr.flush() and .write() on EINTR
r32048 self._writeerr('\n')
self._flusherr()
def _flusherr(self):
Yuya Nishihara
progress: retry ferr.flush() and .write() on EINTR (issue5532)...
r32049 _eintrretry(self.ui.ferr.flush)
Pierre-Yves David
progress: move most extension code into a 'mercurial.progress' module...
r25497
Yuya Nishihara
progress: extract stubs to restart ferr.flush() and .write() on EINTR
r32048 def _writeerr(self, msg):
Yuya Nishihara
progress: retry ferr.flush() and .write() on EINTR (issue5532)...
r32049 _eintrretry(self.ui.ferr.write, msg)
Yuya Nishihara
progress: extract stubs to restart ferr.flush() and .write() on EINTR
r32048
Pierre-Yves David
progress: move most extension code into a 'mercurial.progress' module...
r25497 def width(self):
tw = self.ui.termwidth()
return min(int(self.ui.config('progress', 'width', default=tw)), tw)
def estimate(self, topic, pos, total, now):
if total is None:
return ''
initialpos = self.startvals[topic]
target = total - initialpos
delta = pos - initialpos
if delta > 0:
elapsed = now - self.starttimes[topic]
Jun Wu
progress: remove progress.estimate config...
r34314 seconds = (elapsed * (target - delta)) // delta + 1
return fmtremaining(seconds)
Pierre-Yves David
progress: move most extension code into a 'mercurial.progress' module...
r25497 return ''
def speed(self, topic, pos, unit, now):
initialpos = self.startvals[topic]
delta = pos - initialpos
elapsed = now - self.starttimes[topic]
Jun Wu
progress: remove progress.estimate config...
r34314 if elapsed > 0:
Pierre-Yves David
progress: move most extension code into a 'mercurial.progress' module...
r25497 return _('%d %s/sec') % (delta / elapsed, unit)
return ''
def _oktoprint(self, now):
'''Check if conditions are met to print - e.g. changedelay elapsed'''
if (self.lasttopic is None # first time we printed
# not a topic change
or self.curtopic == self.lasttopic
# it's been long enough we should print anyway
or now - self.lastprint >= self.changedelay):
return True
else:
return False
Jun Wu
progress: make ETA only consider progress made in the last minute...
r34315 def _calibrateestimate(self, topic, now, pos):
'''Adjust starttimes and startvals for topic so ETA works better
If progress is non-linear (ex. get much slower in the last minute),
it's more friendly to only use a recent time span for ETA and speed
calculation.
[======================================> ]
^^^^^^^
estimateinterval, only use this for estimation
'''
interval = self.estimateinterval
if interval <= 0:
return
elapsed = now - self.starttimes[topic]
if elapsed > interval:
delta = pos - self.startvals[topic]
newdelta = delta * interval / elapsed
# If a stall happens temporarily, ETA could change dramatically
# frequently. This is to avoid such dramatical change and make ETA
# smoother.
if newdelta < 0.1:
return
self.startvals[topic] = pos - newdelta
self.starttimes[topic] = now - interval
Pierre-Yves David
progress: move most extension code into a 'mercurial.progress' module...
r25497 def progress(self, topic, pos, item='', unit='', total=None):
Martin von Zweigbergk
progress: extract function for closing topic...
r38437 if pos is None:
self.closetopic(topic)
return
Pierre-Yves David
progress: move most extension code into a 'mercurial.progress' module...
r25497 now = time.time()
Martin von Zweigbergk
progress: use context manager for lock...
r38436 with self._refreshlock:
Martin von Zweigbergk
progress: extract function for closing topic...
r38437 if topic not in self.topics:
self.starttimes[topic] = now
self.startvals[topic] = pos
self.topics.append(topic)
self.topicstates[topic] = pos, item, unit, total
self.curtopic = topic
self._calibrateestimate(topic, now, pos)
if now - self.lastprint >= self.refresh and self.topics:
if self._oktoprint(now):
self.lastprint = now
self.show(now, topic, *self.topicstates[topic])
def closetopic(self, topic):
with self._refreshlock:
self.starttimes.pop(topic, None)
self.startvals.pop(topic, None)
self.topicstates.pop(topic, None)
# reset the progress bar if this is the outermost topic
if self.topics and self.topics[0] == topic and self.printed:
self.complete()
self.resetstate()
# truncate the list of topics assuming all topics within
# this one are also closed
if topic in self.topics:
self.topics = self.topics[:self.topics.index(topic)]
# reset the last topic to the one we just unwound to,
# so that higher-level topics will be stickier than
# lower-level topics
if self.topics:
self.lasttopic = self.topics[-1]
else:
self.lasttopic = None