Show More
@@ -8,7 +8,7 b'' | |||
|
8 | 8 | from demandload import demandload |
|
9 | 9 | from node import * |
|
10 | 10 | from i18n import gettext as _ |
|
11 | demandload(globals(), 'util') | |
|
11 | demandload(globals(), 'mdiff util') | |
|
12 | 12 | demandload(globals(), 'os sys') |
|
13 | 13 | |
|
14 | 14 | def make_filename(repo, pat, node, |
@@ -93,19 +93,53 b' def walk(repo, pats=[], opts={}, node=No' | |||
|
93 | 93 | for r in results: |
|
94 | 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 | 116 | if dry_run is None: |
|
98 | 117 | dry_run = opts.get('dry_run') |
|
118 | if similarity is None: | |
|
119 | similarity = float(opts.get('similarity') or 0) | |
|
99 | 120 | add, remove = [], [] |
|
121 | mapping = {} | |
|
100 | 122 | for src, abs, rel, exact in walk(repo, pats, opts): |
|
101 | 123 | if src == 'f' and repo.dirstate.state(abs) == '?': |
|
102 | 124 | add.append(abs) |
|
125 | mapping[abs] = rel, exact | |
|
103 | 126 | if repo.ui.verbose or not exact: |
|
104 | 127 | repo.ui.status(_('adding %s\n') % ((pats and rel) or abs)) |
|
105 | 128 | if repo.dirstate.state(abs) != 'r' and not os.path.exists(rel): |
|
106 | 129 | remove.append(abs) |
|
130 | mapping[abs] = rel, exact | |
|
107 | 131 | if repo.ui.verbose or not exact: |
|
108 | 132 | repo.ui.status(_('removing %s\n') % ((pats and rel) or abs)) |
|
109 | 133 | if not dry_run: |
|
110 | 134 | repo.add(add, wlock=wlock) |
|
111 | 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 | 659 | New files are ignored if they match any of the patterns in .hgignore. As |
|
660 | 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 | 673 | def annotate(ui, repo, *pats, **opts): |
|
665 | 674 | """show changeset information per file line |
@@ -2747,7 +2756,10 b' table = {' | |||
|
2747 | 2756 | (addremove, |
|
2748 | 2757 | [('I', 'include', [], _('include names matching the given patterns')), |
|
2749 | 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 | 2763 | _('hg addremove [OPTION]... [FILE]...')), |
|
2752 | 2764 | "^annotate": |
|
2753 | 2765 | (annotate, |
@@ -10,3 +10,17 b' cd dir/' | |||
|
10 | 10 | touch ../foo_2 bar_2 |
|
11 | 11 | hg -v addremove |
|
12 | 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 |
General Comments 0
You need to be logged in to leave comments.
Login now