Kiln » TortoiseHg » TortoiseHg
Clone URL:  
Pushed to one repository · View In Graph Contained in 1.9, 1.9.1, and 1.9.2

guess: nearly functional

Changeset 47fd8085e741

Parent 6875ddceb84d

by Steve Borho

Changes to one file · Browse files at 47fd8085e741 Showing diff from parent 6875ddceb84d Diff from another changeset...

 
30
31
32
 
33
34
35
 
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
 
71
72
73
74
 
75
76
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
77
78
79
80
 
 
 
 
 
 
 
 
 
 
 
 
 
81
82
83
 
104
105
106
107
 
108
109
 
110
111
112
113
114
 
 
 
 
 
 
115
116
 
117
118
119
 
 
 
 
 
 
 
 
 
 
 
120
121
122
123
124
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
125
126
127
128
129
130
131
132
133
134
 
 
 
135
136
137
138
139
140
141
142
143
144
 
 
 
 
145
146
147
148
149
150
151
152
153
154
155
156
157
158
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
159
160
161
 
173
174
175
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
176
177
178
179
 
 
 
180
181
182
 
194
195
196
197
 
198
199
200
 
211
212
213
214
 
215
216
217
218
219
 
220
221
222
 
30
31
32
33
34
35
36
 
40
41
42
 
 
 
 
 
 
 
 
 
 
 
 
 
43
44
45
 
59
60
61
 
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
 
 
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
 
134
135
136
 
137
138
 
139
140
141
 
 
 
142
143
144
145
146
147
148
 
149
150
 
 
151
152
153
154
155
156
157
158
159
160
161
162
 
 
 
 
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
 
 
 
 
 
 
186
187
188
189
190
191
192
193
194
 
 
 
 
195
196
197
198
199
200
201
202
203
 
204
 
 
 
 
 
 
 
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
 
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
 
 
315
316
317
318
319
320
 
332
333
334
 
335
336
337
338
 
349
350
351
 
352
353
354
355
356
 
357
358
359
360
@@ -30,6 +30,7 @@
    repo = hg.repository(ui.ui(), path=paths.find_root(root))   self.repo = repo + self.thread = None     reponame = hglib.get_reponame(repo)   self.setWindowTitle(_('Detect Copies/Renames in %s') % reponame) @@ -39,19 +40,6 @@
  layout = QVBoxLayout()   self.setLayout(layout)   - tophbox = QHBoxLayout() - layout.addLayout(tophbox) - - lbl = QLabel(_('Minimum Simularity Percentage')) - tophbox.addWidget(lbl) - - slider = QSlider(Qt.Horizontal) - slider.setRange(0, 100) - slider.setTickInterval(10) - slider.setTickPosition(QSlider.TicksBelow) - tophbox.addWidget(slider) - lbl.setBuddy(slider) -   # vsplit for top & diff   vsplit = QSplitter(Qt.Horizontal)   vsplit.restoreState(s.value('guess/vsplit-state').toByteArray()) @@ -71,13 +59,55 @@
  utlbl = QLabel(_('<b>Unrevisioned Files</b>'))   utvbox.addWidget(utlbl)   self.unrevlist = QListWidget() - self.unrevlist.setSelectionMode(QAbstractItemView.MultiSelection) + self.unrevlist.setSelectionMode(QAbstractItemView.ExtendedSelection)   utvbox.addWidget(self.unrevlist)   + simhbox = QHBoxLayout() + utvbox.addLayout(simhbox) + lbl = QLabel() + slider = QSlider(Qt.Horizontal) + slider.setRange(0, 100) + slider.setTickInterval(10) + slider.setPageStep(10) + slider.setTickPosition(QSlider.TicksBelow) + slider.changefunc = lambda v: lbl.setText( + _('Min Simularity: %d%%') % v) + slider.valueChanged.connect(slider.changefunc) + slider.setValue(100) + self.simslider = slider + lbl.setBuddy(slider) + simhbox.addWidget(lbl) + simhbox.addWidget(slider, 1) + + buthbox = QHBoxLayout() + utvbox.addLayout(buthbox) + copycheck = QCheckBox(_('Only consider deleted files')) + copycheck.setToolTip(_('Uncheck to consider all revisioned files' + ' for copy sources')) + copycheck.setChecked(True) + findrenames = QPushButton(_('Find Rename')) + findrenames.setToolTip(_('Find copy and/or rename sources')) + findrenames.clicked.connect(self.findRenames) + buthbox.addWidget(copycheck) + buthbox.addStretch(1) + buthbox.addWidget(findrenames) + self.findbtn, self.copycheck = findrenames, copycheck +   matchlbl = QLabel(_('<b>Candidate Matches</b>'))   matchvbox.addWidget(matchlbl) - token = QListWidget() - matchvbox.addWidget(token) + self.matchlv = QTreeView() + self.matchlv.setItemsExpandable(False) + self.matchlv.setRootIsDecorated(False) + self.matchlv.setModel(MatchModel()) + buthbox = QHBoxLayout() + matchbtn = QPushButton(_('Accept Selected Matches')) + matchbtn.clicked.connect(self.acceptMatch) + matchbtn.setEnabled(False) + self.matchbtn = matchbtn + buthbox.addStretch(1) + buthbox.addWidget(matchbtn) + matchvbox.addWidget(self.matchlv) + matchvbox.addLayout(buthbox)     diffframe = QFrame(hsplit)   diffvbox = QVBoxLayout() @@ -104,58 +134,92 @@
  wctx = self.repo[None]   wctx.status(unknown=True)   self.unrevlist.clear() - self.unrevlcl = [] + dests = []   for u in wctx.unknown(): - self.unrevlcl.append(u) + dests.append(u)   for a in wctx.added():   if not wctx[a].renamed(): - self.unrevlcl.append(a) - for x in self.unrevlcl: - self.unrevlist.addItem(hglib.tounicode(x)) + dests.append(a) + for x in dests: + item = QListWidgetItem(hglib.tounicode(x)) + item.orig = x + self.unrevlist.addItem(item) + self.difftb.clear()   - def findRenames(self, copy=False): + def findRenames(self):   'User pressed "find renames" button' - # get selected rows from self.unrevlist - # pass to search thread + if self.thread and self.thread.isRunning(): + QMessageBox.information(self, _('Search already in progress'), + _('Cannot start a new search')) + return + ulist = [] + for item in self.unrevlist.selectedItems(): + ulist.append(item.orig) + if not ulist: + QMessageBox.information(self, _('No rows selected'), + _('Select one or more rows for search')) + return   - def findCopies(self): - 'User pressed "find copies" button' - # call rename function with simularity = 100% - self.findRenames(copy=True) + def done(): + for col in xrange(3): + self.matchlv.resizeColumnToContents(col) + self.findbtn.setEnabled(True) + self.matchbtn.setDisabled(model.isEmpty()) + + pct = self.simslider.value() / 100 + copies = not self.copycheck.isChecked() + model = self.matchlv.model() + model.clear() + self.findbtn.setEnabled(False) + self.matchbtn.setEnabled(False) + + self.thread = RenameSearchThread(self.repo, ulist, pct, copies) + self.thread.match.connect(model.appendRow) + #self.thread.error.connect(print) + #self.thread.progress.connect(print) + self.thread.searchComplete.connect(done) + self.thread.start()     def acceptMatch(self):   'User pressed "accept match" button'   hglib.invalidaterepo(self.repo) - canmodel, upaths = self.cantree.get_selection().get_selected_rows() - for path in upaths: - row = canmodel[path] - src, usrc, dest, udest, percent, sensitive = row - if not sensitive: - continue + sel = self.matchlv.selectionModel() + for index in sel.selectedIndexes(): + src, dest, percent = self.matchlv.model().getRow(index)   if not os.path.exists(self.repo.wjoin(src)):   # Mark missing rename source as removed   self.repo.remove([src])   self.repo.copy(src, dest)   shlib.shell_notify([self.repo.wjoin(src), self.repo.wjoin(dest)])   # Mark all rows with this target file as non-sensitive - for row in canmodel: - if row[2] == dest: - row[5] = False - # emit matchAccepted() + #for row in self.matchlv.model().getRows(): + # if row[1] == dest: + # row[5] = False + self.matchAccepted.emit()   self.refresh()     def showDiff(self):   'User selected a row in the candidate tree'   hglib.invalidaterepo(self.repo) - src, usrc, dest, udest, percent, sensitive = row   ctx = self.repo['.'] - aa = self.repo.wread(dest) - rr = ctx.filectx(src).data() - opts = mdiff.defaultopts - difftext = mdiff.unidiff(rr, '', aa, '', src, dest, None, opts=opts) - if not difftext: - l = _('== %s and %s have identical contents ==\n\n') % (src, dest) - # pass through qtlib.difflabel and htmlui + hu = htmlui.htmlui() + for index in self.matchlv.selectedIndexes(): + row = self.matchlv.model().getRow(index) + src, dest, percent = self.matchlv.model().getRow(index) + aa = self.repo.wread(dest) + rr = ctx.filectx(src).data() + opts = mdiff.defaultopts + difftext = mdiff.unidiff(rr, '', aa, '', src, + dest, None, opts=opts) + if not difftext: + t = _('%s and %s have identical contents\n\n') % (usrc, udest) + hu.write(t, label='ui.error') + else: + for t, l in qtlib.difflabel(difftext.splitlines, True): + hu.write(t, label=l) + # for now, only show one at a time + break + self.difftb.setHtml(hu.getdata()[0])     def accept(self):   s = QSettings() @@ -173,10 +237,84 @@
  QDialog.reject(self)     +class MatchModel(QAbstractTableModel): + def __init__(self, parent=None): + QAbstractTableModel.__init__(self, parent) + self.rows = [] + self.headers = (_('Source'), _('Dest'), _('% Match')) + + def rowCount(self, parent): + return len(self.rows) + + def columnCount(self, parent): + return len(self.headers) + + def data(self, index, role): + if not index.isValid(): + return QVariant() + if role == Qt.DisplayRole: + s = self.rows[index.row()][index.column()] + return QVariant(hglib.tounicode(s)) + ''' + elif role == Qt.TextColorRole: + src, dst, pct = self.rows[index.row()] + if pct == 1.0: + return QColor('green') + else: + return QColor('black') + elif role == Qt.ToolTipRole: + # explain what row means? + ''' + return QVariant() + + def headerData(self, col, orientation, role): + if role != Qt.DisplayRole or orientation != Qt.Horizontal: + return QVariant() + else: + return QVariant(self.headers[col]) + + def flags(self, index): + return Qt.ItemIsSelectable | Qt.ItemIsEnabled + + # Custom methods + + def getRow(self, index): + assert index.isValid() + return self.rows[index.row()] + + def appendRow(self, *args): + self.beginInsertRows(QModelIndex(), len(self.rows), len(self.rows)) + vals = [str(a) for a in args] # PyQt is upgrading to QString + self.rows.append(vals) + self.endInsertRows() + self.emit(SIGNAL("dataChanged()")) + + def clear(self): + self.beginRemoveRows(QModelIndex(), 0, len(self.rows)-1) + self.rows = [] + self.endRemoveRows() + self.emit(SIGNAL("dataChanged()")) + + def sort(self, col, order): + self.emit(SIGNAL("layoutAboutToBeChanged()")) + if col == COL_PATH: + c = self.checked + self.rows.sort(lambda x, y: cmp(c[x[col]], c[y[col]])) + else: + self.rows.sort(lambda x, y: cmp(x[col], y[col])) + if order == Qt.DescendingOrder: + self.rows.reverse() + self.emit(SIGNAL("layoutChanged()")) + self.reset() + + def isEmpty(self): + return not bool(self.rows) +  class RenameSearchThread(QThread):   '''Background thread for searching repository history''' - match = pyqtSignal() - error = pyqtSignal() + match = pyqtSignal(str, str, str) + error = pyqtSignal(QString) + progress = pyqtSignal()   searchComplete = pyqtSignal()     def __init__(self, repo, ufiles, minpct, copies): @@ -194,7 +332,7 @@
  print e   self.searchComplete.emit()   - def search(repo): + def search(self, repo):   hglib.invalidaterepo(repo)   wctx = repo[None]   pctx = repo['.'] @@ -211,12 +349,12 @@
  removed = sorted([fctx for fctx in removed if fctx.size() > 0])   for o, n in similar._findexactmatches(repo, added, removed):   old, new = o.path(), n.path() - self.match.emit( [old, new, '100%'] ) + self.match.emit(old, new, '100%')   if self.minpct < 1.0:   for o, n, s in similar._findsimilarmatches(repo, added, removed,   self.minpct):   old, new = o.path(), n.path() - self.match.emit( [old, new, '%d%%' % (s*100)] ) + self.match.emit(old, new, '%d%%' % (s*100))    def run(ui, *pats, **opts):   return DetectRenameDialog()