6 # of the GNU General Public License, incorporated herein by reference. |
6 # of the GNU General Public License, incorporated herein by reference. |
7 |
7 |
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, |
15 total=None, seqno=None, revwidth=None, pathname=None): |
15 total=None, seqno=None, revwidth=None, pathname=None): |
16 node_expander = { |
16 node_expander = { |
91 def walk(repo, pats=[], opts={}, node=None, head='', badmatch=None): |
91 def walk(repo, pats=[], opts={}, node=None, head='', badmatch=None): |
92 files, matchfn, results = makewalk(repo, pats, opts, node, head, badmatch) |
92 files, matchfn, results = makewalk(repo, pats, opts, node, head, badmatch) |
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) |