##// END OF EJS Templates
automv: use lowercase for docstring title...
Jun Wu -
r31599:e4aefdb5 default
parent child Browse files
Show More
@@ -1,100 +1,100
1 1 # automv.py
2 2 #
3 3 # Copyright 2013-2016 Facebook, Inc.
4 4 #
5 5 # This software may be used and distributed according to the terms of the
6 6 # GNU General Public License version 2 or any later version.
7 """Check for unrecorded moves at commit time (EXPERIMENTAL)
7 """check for unrecorded moves at commit time (EXPERIMENTAL)
8 8
9 9 This extension checks at commit/amend time if any of the committed files
10 10 comes from an unrecorded mv.
11 11
12 12 The threshold at which a file is considered a move can be set with the
13 13 ``automv.similarity`` config option. This option takes a percentage between 0
14 14 (disabled) and 100 (files must be identical), the default is 95.
15 15
16 16 """
17 17
18 18 # Using 95 as a default similarity is based on an analysis of the mercurial
19 19 # repositories of the cpython, mozilla-central & mercurial repositories, as
20 20 # well as 2 very large facebook repositories. At 95 50% of all potential
21 21 # missed moves would be caught, as well as correspond with 87% of all
22 22 # explicitly marked moves. Together, 80% of moved files are 95% similar or
23 23 # more.
24 24 #
25 25 # See http://markmail.org/thread/5pxnljesvufvom57 for context.
26 26
27 27 from __future__ import absolute_import
28 28
29 29 from mercurial.i18n import _
30 30 from mercurial import (
31 31 commands,
32 32 copies,
33 33 error,
34 34 extensions,
35 35 scmutil,
36 36 similar
37 37 )
38 38
39 39 def extsetup(ui):
40 40 entry = extensions.wrapcommand(
41 41 commands.table, 'commit', mvcheck)
42 42 entry[1].append(
43 43 ('', 'no-automv', None,
44 44 _('disable automatic file move detection')))
45 45
46 46 def mvcheck(orig, ui, repo, *pats, **opts):
47 47 """Hook to check for moves at commit time"""
48 48 renames = None
49 49 disabled = opts.pop('no_automv', False)
50 50 if not disabled:
51 51 threshold = ui.configint('automv', 'similarity', 95)
52 52 if not 0 <= threshold <= 100:
53 53 raise error.Abort(_('automv.similarity must be between 0 and 100'))
54 54 if threshold > 0:
55 55 match = scmutil.match(repo[None], pats, opts)
56 56 added, removed = _interestingfiles(repo, match)
57 57 renames = _findrenames(repo, match, added, removed,
58 58 threshold / 100.0)
59 59
60 60 with repo.wlock():
61 61 if renames is not None:
62 62 scmutil._markchanges(repo, (), (), renames)
63 63 return orig(ui, repo, *pats, **opts)
64 64
65 65 def _interestingfiles(repo, matcher):
66 66 """Find what files were added or removed in this commit.
67 67
68 68 Returns a tuple of two lists: (added, removed). Only files not *already*
69 69 marked as moved are included in the added list.
70 70
71 71 """
72 72 stat = repo.status(match=matcher)
73 73 added = stat[1]
74 74 removed = stat[2]
75 75
76 76 copy = copies._forwardcopies(repo['.'], repo[None], matcher)
77 77 # remove the copy files for which we already have copy info
78 78 added = [f for f in added if f not in copy]
79 79
80 80 return added, removed
81 81
82 82 def _findrenames(repo, matcher, added, removed, similarity):
83 83 """Find what files in added are really moved files.
84 84
85 85 Any file named in removed that is at least similarity% similar to a file
86 86 in added is seen as a rename.
87 87
88 88 """
89 89 renames = {}
90 90 if similarity > 0:
91 91 for src, dst, score in similar.findrenames(
92 92 repo, added, removed, similarity):
93 93 if repo.ui.verbose:
94 94 repo.ui.status(
95 95 _('detected move of %s as %s (%d%% similar)\n') % (
96 96 matcher.rel(src), matcher.rel(dst), score * 100))
97 97 renames[dst] = src
98 98 if renames:
99 99 repo.ui.status(_('detected move of %d files\n') % len(renames))
100 100 return renames
General Comments 0
You need to be logged in to leave comments. Login now