Mercurial > public > mercurial-scm > hg
diff hgext/automv.py @ 28129:7c40b4b7f8f1
automv: new experimental extension
Automatically detect moves and record them at commit time.
This extension was originally developed at
https://bitbucket.org/facebook/hg-experimental
author | Martijn Pieters <mjpieters@fb.com> |
---|---|
date | Mon, 08 Feb 2016 13:52:51 +0000 |
parents | |
children | 28024d0d42dc |
line wrap: on
line diff
--- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/hgext/automv.py Mon Feb 08 13:52:51 2016 +0000 @@ -0,0 +1,83 @@ +# automv.py +# +# Copyright 2013-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. +"""Check for unrecorded moves at commit time (EXPERIMENTAL) + +This extension checks at commit/amend time if any of the committed files +comes from an unrecorded mv. + +The threshold at which a file is considered a move can be set with the +``automv.similarity`` config option; the default value is 1.00. + +""" +from __future__ import absolute_import + +from mercurial import ( + commands, + copies, + extensions, + scmutil, + similar +) +from mercurial.i18n import _ + +def extsetup(ui): + entry = extensions.wrapcommand( + commands.table, 'commit', mvcheck) + entry[1].append( + ('', 'no-automv', None, + _('disable automatic file move detection'))) + +def mvcheck(orig, ui, repo, *pats, **opts): + disabled = opts.pop('no_automv', False) + if not disabled: + threshold = float(ui.config('automv', 'similarity', '1.00')) + if threshold > 0: + match = scmutil.match(repo[None], pats, opts) + added, removed = _interestingfiles(repo, match) + renames = _findrenames(repo, match, added, removed, threshold) + _markchanges(repo, renames) + + # developer config: automv.testmode + if not ui.configbool('automv', 'testmode'): + return orig(ui, repo, *pats, **opts) + +def _interestingfiles(repo, matcher): + stat = repo.status(repo['.'], repo[None], matcher) + added = stat[1] + removed = stat[2] + + copy = copies._forwardcopies(repo['.'], repo[None], matcher) + # remove the copy files for which we already have copy info + added = [f for f in added if f not in copy] + + return added, removed + +def _findrenames(repo, matcher, added, removed, similarity): + """Find renames from removed files of the current commit/amend files + to the added ones""" + renames = {} + if similarity > 0: + for src, dst, score in similar.findrenames( + repo, added, removed, similarity): + if repo.ui.verbose: + repo.ui.status( + _('detected move of %s as %s (%d%% similar)\n') % ( + matcher.rel(src), matcher.rel(dst), score * 100)) + renames[dst] = src + if renames: + repo.ui.status(_('detected move of %d files\n') % len(renames)) + return renames + +def _markchanges(repo, renames): + """Marks the files in renames as copied.""" + wctx = repo[None] + wlock = repo.wlock() + try: + for dst, src in renames.iteritems(): + wctx.copy(src, dst) + finally: + wlock.release()