##// END OF EJS Templates
copies: calculate mergecopies() based on pathcopies()...
copies: calculate mergecopies() based on pathcopies() When copies are stored in changesets, we need a changeset-centric version of mergecopies() just like we have a changeset-centric version of pathcopies(). I think the natural way of thinking about mergecopies() is in terms of pathcopies() from the base to each of the commits. So if we can rewrite mergecopies() based on two such pathcopies() calls, we'll get the changeset-centric version for free. That's what this patch does. A nice bonus is that it ends up being a lot simpler. mergecopies() has accumulated a lot of technical debt over time. One good example is the code for dealing with grafts (the "partial/incomplete/dirty" stuff). Since pathcopies() already deals with backwards renames and ping-pong renames, we get that for free. I've run tests with hard-coded debug logging for "fullcopy" and while I haven't looked at every difference it produces, all the ones I have looked at seemed reasonable to me. I'm a little surprised that no more tests fail when run with '--extra-config-opt experimental.copies.read-from=compatibility' compared to before this patch. This patch also fixes the broken cases in test-annotate.t and test-fastannotate.t. It also enables the part of test-copies.t that was previously disabled exactly because mergecopies() needed to get a changeset-centric version. One drawback of the rewritten code is that we may now make remotefilelog prefetch more files. We used to prefetch files that were unique to either side of the merge compared to the other. We now prefetch files that are unique to either side of the merge compared to the base. This means that if you added the same file to each side, we would not prefetch it before, but we would now. Such cases are probably quite rare, but one likely scenario where they happen is when moving from a commit to its successor (or the other way around). The user will probably already have the files in the cache in such cases, so it's probably not a big deal. Some timings for calculating mergecopies between two revisions (revisions shown on each line, all using the common ancestor as base): In the hg repo: 4.8 4.9: 0.21s -> 0.21s 4.0 4.8: 0.35s -> 0.63s In and old copy of the mozilla-unified repo: FIREFOX_BETA_60_BASE^ FIREFOX_BETA_60_BASE: 0.82s -> 0.82s FIREFOX_NIGHTLY_59_END FIREFOX_BETA_60_BASE: 2.5s -> 2.6s FIREFOX_BETA_59_END FIREFOX_BETA_60_BASE: 3.9s -> 4.1s FIREFOX_AURORA_50_BASE FIREFOX_BETA_60_BASE: 31s -> 33s So it's measurably slower in most cases. The most significant difference is in the hg repo between revisions 4.0 and 4.8. In that case it seems to come from the fact that pathcopies() uses fctx.isintroducedafter() (in _tracefile), while the old mergecopies() used fctx.linkrev() (in _checkcopies()). That results in a single call to filectx._adjustlinkrev(), which is responsible for the entire difference in time (in my repo). So we pay a performance penalty but we get more correct code (see change in test-mv-cp-st-diff.t). Deleting the "== f.filenode()" in _tracefile() recovers the lost performance in the hg repo. There were are few other optimizations in _checkcopies() that I could not measure any impact from. One was from the "seen" set. Another was from a "continue" when the file was not in the destination manifest (corresponding to "am" in _tracefile). Also note that merge copies are not calculated when updating with a clean working copy, which is probably the most common case. I therefore think the much simpler code is worth the slowdown. Differential Revision: https://phab.mercurial-scm.org/D6255

File last commit:

r41673:bd3f03d8 default
r42408:57203e02 default
Show More
blackbox.py
197 lines | 5.7 KiB | text/x-python | PythonLexer
# blackbox.py - log repository events to a file for post-mortem debugging
#
# Copyright 2010 Nicolas Dumazet
# Copyright 2013 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.
"""log repository events to a blackbox for debugging
Logs event information to .hg/blackbox.log to help debug and diagnose problems.
The events that get logged can be configured via the blackbox.track config key.
Examples::
[blackbox]
track = *
# dirty is *EXPENSIVE* (slow);
# each log entry indicates `+` if the repository is dirty, like :hg:`id`.
dirty = True
# record the source of log messages
logsource = True
[blackbox]
track = command, commandfinish, commandexception, exthook, pythonhook
[blackbox]
track = incoming
[blackbox]
# limit the size of a log file
maxsize = 1.5 MB
# rotate up to N log files when the current one gets too big
maxfiles = 3
[blackbox]
# Include nanoseconds in log entries with %f (see Python function
# datetime.datetime.strftime)
date-format = '%Y-%m-%d @ %H:%M:%S.%f'
"""
from __future__ import absolute_import
import re
from mercurial.i18n import _
from mercurial.node import hex
from mercurial import (
encoding,
loggingutil,
registrar,
)
from mercurial.utils import (
dateutil,
procutil,
)
# Note for extension authors: ONLY specify testedwith = 'ships-with-hg-core' for
# extensions which SHIP WITH MERCURIAL. Non-mainline extensions should
# be specifying the version(s) of Mercurial they are tested with, or
# leave the attribute unspecified.
testedwith = 'ships-with-hg-core'
cmdtable = {}
command = registrar.command(cmdtable)
configtable = {}
configitem = registrar.configitem(configtable)
configitem('blackbox', 'dirty',
default=False,
)
configitem('blackbox', 'maxsize',
default='1 MB',
)
configitem('blackbox', 'logsource',
default=False,
)
configitem('blackbox', 'maxfiles',
default=7,
)
configitem('blackbox', 'track',
default=lambda: ['*'],
)
configitem('blackbox', 'date-format',
default='%Y/%m/%d %H:%M:%S',
)
_lastlogger = loggingutil.proxylogger()
class blackboxlogger(object):
def __init__(self, ui, repo):
self._repo = repo
self._trackedevents = set(ui.configlist('blackbox', 'track'))
self._maxfiles = ui.configint('blackbox', 'maxfiles')
self._maxsize = ui.configbytes('blackbox', 'maxsize')
self._inlog = False
def tracked(self, event):
return b'*' in self._trackedevents or event in self._trackedevents
def log(self, ui, event, msg, opts):
# self._log() -> ctx.dirty() may create new subrepo instance, which
# ui is derived from baseui. So the recursion guard in ui.log()
# doesn't work as it's local to the ui instance.
if self._inlog:
return
self._inlog = True
try:
self._log(ui, event, msg, opts)
finally:
self._inlog = False
def _log(self, ui, event, msg, opts):
default = ui.configdate('devel', 'default-date')
date = dateutil.datestr(default, ui.config('blackbox', 'date-format'))
user = procutil.getuser()
pid = '%d' % procutil.getpid()
changed = ''
ctx = self._repo[None]
parents = ctx.parents()
rev = ('+'.join([hex(p.node()) for p in parents]))
if (ui.configbool('blackbox', 'dirty') and
ctx.dirty(missing=True, merge=False, branch=False)):
changed = '+'
if ui.configbool('blackbox', 'logsource'):
src = ' [%s]' % event
else:
src = ''
try:
fmt = '%s %s @%s%s (%s)%s> %s'
args = (date, user, rev, changed, pid, src, msg)
with loggingutil.openlogfile(
ui, self._repo.vfs, name='blackbox.log',
maxfiles=self._maxfiles, maxsize=self._maxsize) as fp:
fp.write(fmt % args)
except (IOError, OSError) as err:
# deactivate this to avoid failed logging again
self._trackedevents.clear()
ui.debug('warning: cannot write to blackbox.log: %s\n' %
encoding.strtolocal(err.strerror))
return
_lastlogger.logger = self
def uipopulate(ui):
ui.setlogger(b'blackbox', _lastlogger)
def reposetup(ui, repo):
# During 'hg pull' a httppeer repo is created to represent the remote repo.
# It doesn't have a .hg directory to put a blackbox in, so we don't do
# the blackbox setup for it.
if not repo.local():
return
# Since blackbox.log is stored in the repo directory, the logger should be
# instantiated per repository.
logger = blackboxlogger(ui, repo)
ui.setlogger(b'blackbox', logger)
# Set _lastlogger even if ui.log is not called. This gives blackbox a
# fallback place to log
if _lastlogger.logger is None:
_lastlogger.logger = logger
repo._wlockfreeprefix.add('blackbox.log')
@command('blackbox',
[('l', 'limit', 10, _('the number of events to show')),
],
_('hg blackbox [OPTION]...'),
helpcategory=command.CATEGORY_MAINTENANCE,
helpbasic=True)
def blackbox(ui, repo, *revs, **opts):
'''view the recent repository events
'''
if not repo.vfs.exists('blackbox.log'):
return
limit = opts.get(r'limit')
fp = repo.vfs('blackbox.log', 'r')
lines = fp.read().split('\n')
count = 0
output = []
for line in reversed(lines):
if count >= limit:
break
# count the commands by matching lines like: 2013/01/23 19:13:36 root>
if re.match(br'^\d{4}/\d{2}/\d{2} \d{2}:\d{2}:\d{2} .*> .*', line):
count += 1
output.append(line)
ui.status('\n'.join(reversed(output)))