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