##// END OF EJS Templates
addremove: add -s/--similarity option...
Vadim Gelfer -
r2958:ff3ea21a default
parent child Browse files
Show More
@@ -8,7 +8,7 b''
8 from demandload import demandload
8 from demandload import demandload
9 from node import *
9 from node import *
10 from i18n import gettext as _
10 from i18n import gettext as _
11 demandload(globals(), 'util')
11 demandload(globals(), 'mdiff util')
12 demandload(globals(), 'os sys')
12 demandload(globals(), 'os sys')
13
13
14 def make_filename(repo, pat, node,
14 def make_filename(repo, pat, node,
@@ -93,19 +93,53 b' def walk(repo, pats=[], opts={}, node=No'
93 for r in results:
93 for r in results:
94 yield r
94 yield r
95
95
96 def addremove(repo, pats=[], opts={}, wlock=None, dry_run=None):
96 def findrenames(repo, added=None, removed=None, threshold=0.5):
97 if added is None or removed is None:
98 added, removed = repo.status()[1:3]
99 changes = repo.changelog.read(repo.dirstate.parents()[0])
100 mf = repo.manifest.read(changes[0])
101 for a in added:
102 aa = repo.wread(a)
103 bestscore, bestname = None, None
104 for r in removed:
105 rr = repo.file(r).read(mf[r])
106 delta = mdiff.textdiff(aa, rr)
107 if len(delta) < len(aa):
108 myscore = 1.0 - (float(len(delta)) / len(aa))
109 if bestscore is None or myscore > bestscore:
110 bestscore, bestname = myscore, r
111 if bestname and bestscore >= threshold:
112 yield bestname, a, bestscore
113
114 def addremove(repo, pats=[], opts={}, wlock=None, dry_run=None,
115 similarity=None):
97 if dry_run is None:
116 if dry_run is None:
98 dry_run = opts.get('dry_run')
117 dry_run = opts.get('dry_run')
118 if similarity is None:
119 similarity = float(opts.get('similarity') or 0)
99 add, remove = [], []
120 add, remove = [], []
121 mapping = {}
100 for src, abs, rel, exact in walk(repo, pats, opts):
122 for src, abs, rel, exact in walk(repo, pats, opts):
101 if src == 'f' and repo.dirstate.state(abs) == '?':
123 if src == 'f' and repo.dirstate.state(abs) == '?':
102 add.append(abs)
124 add.append(abs)
125 mapping[abs] = rel, exact
103 if repo.ui.verbose or not exact:
126 if repo.ui.verbose or not exact:
104 repo.ui.status(_('adding %s\n') % ((pats and rel) or abs))
127 repo.ui.status(_('adding %s\n') % ((pats and rel) or abs))
105 if repo.dirstate.state(abs) != 'r' and not os.path.exists(rel):
128 if repo.dirstate.state(abs) != 'r' and not os.path.exists(rel):
106 remove.append(abs)
129 remove.append(abs)
130 mapping[abs] = rel, exact
107 if repo.ui.verbose or not exact:
131 if repo.ui.verbose or not exact:
108 repo.ui.status(_('removing %s\n') % ((pats and rel) or abs))
132 repo.ui.status(_('removing %s\n') % ((pats and rel) or abs))
109 if not dry_run:
133 if not dry_run:
110 repo.add(add, wlock=wlock)
134 repo.add(add, wlock=wlock)
111 repo.remove(remove, wlock=wlock)
135 repo.remove(remove, wlock=wlock)
136 if similarity > 0:
137 for old, new, score in findrenames(repo, add, remove, similarity):
138 oldrel, oldexact = mapping[old]
139 newrel, newexact = mapping[new]
140 if repo.ui.verbose or not oldexact or not newexact:
141 repo.ui.status(_('recording removal of %s as rename to %s '
142 '(%d%% similar)\n') %
143 (oldrel, newrel, score * 100))
144 if not dry_run:
145 repo.copy(old, new, wlock=wlock)
@@ -658,8 +658,17 b' def addremove(ui, repo, *pats, **opts):'
658
658
659 New files are ignored if they match any of the patterns in .hgignore. As
659 New files are ignored if they match any of the patterns in .hgignore. As
660 with add, these changes take effect at the next commit.
660 with add, these changes take effect at the next commit.
661
662 Use the -s option to detect renamed files. With a parameter > 0,
663 this compares every removed file with every added file and records
664 those similar enough as renames. This option takes a percentage
665 between 0 (disabled) and 100 (files must be identical) as its
666 parameter. Detecting renamed files this way can be expensive.
661 """
667 """
662 return cmdutil.addremove(repo, pats, opts)
668 sim = float(opts.get('similarity') or 0)
669 if sim < 0 or sim > 100:
670 raise util.Abort(_('similarity must be between 0 and 100'))
671 return cmdutil.addremove(repo, pats, opts, similarity=sim/100.)
663
672
664 def annotate(ui, repo, *pats, **opts):
673 def annotate(ui, repo, *pats, **opts):
665 """show changeset information per file line
674 """show changeset information per file line
@@ -2747,7 +2756,10 b' table = {'
2747 (addremove,
2756 (addremove,
2748 [('I', 'include', [], _('include names matching the given patterns')),
2757 [('I', 'include', [], _('include names matching the given patterns')),
2749 ('X', 'exclude', [], _('exclude names matching the given patterns')),
2758 ('X', 'exclude', [], _('exclude names matching the given patterns')),
2750 ('n', 'dry-run', None, _('do not perform actions, just print output'))],
2759 ('n', 'dry-run', None,
2760 _('do not perform actions, just print output')),
2761 ('s', 'similarity', '',
2762 _('guess renamed files by similarity (0<=s<=1)'))],
2751 _('hg addremove [OPTION]... [FILE]...')),
2763 _('hg addremove [OPTION]... [FILE]...')),
2752 "^annotate":
2764 "^annotate":
2753 (annotate,
2765 (annotate,
@@ -10,3 +10,17 b' cd dir/'
10 touch ../foo_2 bar_2
10 touch ../foo_2 bar_2
11 hg -v addremove
11 hg -v addremove
12 hg -v commit -m "add 2" -d "1000000 0"
12 hg -v commit -m "add 2" -d "1000000 0"
13
14 cd ..
15 hg init sim
16 cd sim
17 echo a > a
18 echo a >> a
19 echo a >> a
20 echo c > c
21 hg commit -Ama
22 mv a b
23 rm c
24 echo d > d
25 hg addremove -s 0.5
26 hg commit -mb
@@ -6,3 +6,10 b' adding dir/bar_2'
6 adding foo_2
6 adding foo_2
7 dir/bar_2
7 dir/bar_2
8 foo_2
8 foo_2
9 adding a
10 adding c
11 adding b
12 adding d
13 removing a
14 removing c
15 recording removal of a as rename to b (100% similar)
General Comments 0
You need to be logged in to leave comments. Login now