Kiln » TortoiseHg » TortoiseHg
Clone URL:  
Pushed to one repository · View In Graph Contained in 0.9, 0.9.1, and 0.9.1.1

history: support filtering by commit author exclusively

Changeset 491aa0411e77

Parent f16a874fa811

by Henrik Stuart

Changes to 2 files · Browse files at 491aa0411e77 Showing diff from parent f16a874fa811 Diff from another changeset...

 
313
314
315
 
 
316
317
318
 
464
465
466
467
 
 
468
469
470
 
827
828
829
830
 
 
831
832
833
 
313
314
315
316
317
318
319
320
 
466
467
468
 
469
470
471
472
473
 
830
831
832
 
833
834
835
836
837
@@ -313,6 +313,8 @@
  gdialog.Prompt(_('Invalid date specification'),   str(e), self).run()   return + elif mode == 4: # User + opts['user'] = [w.strip() for w in text.split(',')]   else:   return   self.custombutton.set_active(True) @@ -464,7 +466,8 @@
  opts = {'date': None, 'no_merges':False, 'only_merges':False,   'keyword':[], 'branch':None, 'pats':[], 'filehist':None,   'revrange':[], 'revlist':[], 'noheads':False, - 'branch-view':False, 'rev':[] } + 'branch-view':False, 'rev':[], + 'user':[]}   if self.filteropts is not None: opts = self.filteropts   opts['branch-view'] = self.compactgraph   opts.update(kwopts) @@ -827,7 +830,8 @@
    filtercombo = gtk.combo_box_new_text()   for f in (_('Rev Range'), _('File Patterns'), - _('Keywords'), _('Date')): + _('Keywords'), _('Date'), + _('User')):   filtercombo.append_text(f)   filtercombo.set_active(1)   self.filtercombo = filtercombo
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
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
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
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
226
227
228
229
230
231
232
233
234
235
236
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
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
378
379
380
381
382
383
384
385
386
387
388
389
390
391
392
393
394
395
396
397
398
399
400
401
402
403
404
405
406
407
408
409
410
411
412
413
414
415
416
417
418
419
420
421
422
423
424
425
426
427
428
429
430
431
432
433
434
435
436
437
438
439
440
441
442
443
444
445
446
447
448
449
450
451
452
453
454
455
456
457
458
459
460
461
462
463
464
465
466
467
468
469
470
471
472
473
474
475
476
477
478
479
480
481
482
483
484
485
486
487
488
489
490
491
492
493
494
495
496
497
498
499
500
501
502
503
504
505
506
507
508
509
510
511
512
513
514
515
516
517
518
519
520
521
522
523
524
525
526
527
528
529
530
531
532
533
534
535
536
537
538
539
 
 
 
 
 
 
 
 
 
 
 
540
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
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
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
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
226
227
228
229
230
231
232
233
234
235
236
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
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
378
379
380
381
382
383
384
385
386
387
388
389
390
391
392
393
394
395
396
397
398
399
400
401
402
403
404
405
406
407
408
409
410
411
412
413
414
415
416
417
418
419
420
421
422
423
424
425
426
427
428
429
430
431
432
433
434
435
436
437
438
439
440
441
442
443
444
445
446
447
448
449
450
451
452
453
454
455
456
457
458
459
460
461
462
463
464
465
466
467
468
469
470
471
472
473
474
475
476
477
478
479
480
481
482
483
484
485
486
487
488
489
490
491
492
493
494
495
496
497
498
499
500
501
502
503
504
505
506
507
508
509
510
511
512
513
514
515
516
517
518
519
520
521
522
523
524
525
526
527
528
529
530
531
532
533
534
535
536
537
538
539
540
541
542
543
544
545
546
547
548
549
550
551
 """Directed graph production.    This module contains the code to produce an ordered directed graph of a  Mercurial repository, such as we display in the tree view at the top of the  history window. Original code was from graphlog extension.    The generator functions walks through the revision history and for the  selected revision emits tuples with the following elements:     (curr_rev, node, lines, parents)     - Current revision.   - node; defined as tuple (rev_column, rev_color)   - lines; a list of (col, next_col, color, type) indicating the edges between   the current row and the next row   - parent revisions of current revision    The node tuple has the following elements:   - rev_column: Column for the node "circle"   - rev_color: Color used to render the circle    The lines tuple has the following elements:   - col: Column for the upper end of the line.   - nextcol: Column for the lower end of the line.   - color: Colour used for line   - type: 0 for plain line, 1 for loose upper end, 2 for loose lower end   loose ends are rendered as dashed lines. They indicate that the   edge of the graph does not really end here. This is used to render   a branch view where the focus is on branches instead of revisions.    The data is used in treeview.populate with the following signature   (rev, node, lines, parents) = self.grapher.next()  and stored in treeview.graphdata    The treeview.model is an instance of treemodel which references  treeview.graphdata    treemodel stores it in self.line_graph_data, and extracts it  in on_get_value, where it is mapped to several columns.   REVID, NODE, LINES, PARENTS, LAST_LINES  LAST_LINES is a copy of LINES from the previous row    treeview maps columns   treemodel.NODE, treemodel.LAST_LINES, treemodel.LINES  to CellRendererGraph attributes   "node", "in-lines", "out-lines"  which stores it in varables   node, in_lines, out_lines  """    __copyright__ = "Copyright 2007 Joel Rosdahl, 2008 Steve Borho"  __author__ = "Joel Rosdahl <joel@rosdahl.net>, Steve Borho <steve@borho.org>"    import re    from mercurial.node import nullrev  from mercurial import cmdutil, util    from tortoisehg.util import hglib    def __get_parents(repo, rev):   return [x for x in repo.changelog.parentrevs(rev) if x != nullrev]    known_branch_colors = {} # cache for repository colour configuration    def _get_known_branch_colors(repo):   global known_branch_colors     repo_setting = repo.ui.config('tortoisehg', 'branchcolors')     if known_branch_colors:   branch_colors, setting = known_branch_colors   if repo_setting == setting:   return branch_colors     branchcolors = repo_setting   if not branchcolors:   return {}     branchcolors = hglib.tounicode(branchcolors)   branchcolors = [x for x in re.split(r'(?:(?<=\\\\)|(?<!\\)) ', branchcolors) if x]   values = {}   for branchcolor in branchcolors:   parts = re.split(r'(?:(?<=\\\\)|(?<!\\)):', branchcolor)   if len(parts) != 2:   continue # ignore badly formed entry     # Mercurial branch names are encoded in utf-8 so we must   # make sure to encode back to that after having unescaped   # the string.   branch_name = hglib.toutf(parts[0].replace('\\:', ':').replace('\\ ', ' ').decode('unicode_escape'))   values[branch_name] = hglib.toutf(parts[1])     known_branch_colors = values, repo_setting   return values    def _color_of_branch(repo, rev):   branch = repo[rev].branch()     candidates = _get_known_branch_colors(repo)     if branch in candidates:   return candidates[branch]     if branch == 'default':   color = 0   else:   color = sum([ord(c) for c in branch])   return color    def _color_of(repo, rev, nextcolor, preferredcolor, branch_color=False):   if not branch_color:   if preferredcolor[0]:   rv = preferredcolor[0]   preferredcolor[0] = None   else:   rv = nextcolor[0]   nextcolor[0] = nextcolor[0] + 1   return rv   else:   return _color_of_branch(repo, rev)    type_PLAIN = 0  type_LOOSE_LOW = 1  type_LOOSE_HIGH = 2    def revision_grapher(repo, start_rev, stop_rev, branch=None, noheads=False, branch_color=False):   """incremental revision grapher     This grapher generates a full graph where every edge is visible.   This means that repeated merges between two branches may make   the graph very wide.     if branch is set to the name of a branch, only this branch is shown.   if noheads is True, other heads than start_rev is hidden. Use this   to show ancestors of a revision.   if branch_color is True, the branch colour is determined by a hash   of the branch tip, and will thus always be the same.   """     assert start_rev >= stop_rev   curr_rev = start_rev   revs = []   rev_color = {}   nextcolor = [0]   while curr_rev >= stop_rev:   # Compute revs and next_revs.   if curr_rev not in revs:   if noheads and curr_rev != start_rev:   curr_rev -= 1   continue   if branch:   ctx = repo.changectx(curr_rev)   if ctx.branch() != branch:   curr_rev -= 1   continue   # New head.   revs.append(curr_rev)   rev_color[curr_rev] = _color_of(repo, curr_rev, nextcolor, [None], branch_color)   r = __get_parents(repo, curr_rev)   while r:   r0 = r[0]   if r0 < stop_rev: break   if r0 in rev_color: break   if not branch_color:   rev_color[r0] = rev_color[curr_rev]   else:   rev_color[r0] = _color_of(repo, r0, None, None, True)   r = __get_parents(repo, r0)   rev_index = revs.index(curr_rev)   next_revs = revs[:]     # Add parents to next_revs.   parents = __get_parents(repo, curr_rev)   parents_to_add = []   preferred_color = [rev_color[curr_rev]]   for parent in parents:   if parent not in next_revs:   parents_to_add.append(parent)   if parent not in rev_color:   rev_color[parent] = _color_of(repo, parent, nextcolor, preferred_color, branch_color)   next_revs[rev_index:rev_index + 1] = parents_to_add     lines = []   for i, rev in enumerate(revs):   if rev in next_revs:   color = rev_color[rev]   lines.append( (i, next_revs.index(rev), color, type_PLAIN) )   elif rev == curr_rev:   for parent in parents:   color = rev_color[parent]   lines.append( (i, next_revs.index(parent), color, type_PLAIN) )     yield (curr_rev, (rev_index, rev_color[curr_rev]), lines, None)   revs = next_revs   curr_rev -= 1      class BranchGrapher:   """Incremental branch grapher     This generator function produces a graph that uses loose ends to keep   focus on branches. All revisions in the range are shown, but not all edges.   The function identifies branches and may use loose ends if an edge links   two branches.   """     def __init__(self, repo, start_rev, stop_rev, branch_filter, branch_color):   '''   start_rev - first (newest) changeset to cover   stop_rev - last (oldest) changeset to cover   branch_filter - if not None, show this branch and all its ancestors   branch_color - true if branch name determines colours   '''   assert start_rev >= stop_rev   self.repo = repo     #   # Iterator content   # Variables that keep track of the iterator   #     # The current revision to process   self.curr_rev = start_rev     # Process all revs from start_rev to and including stop_rev   self.stop_rev = stop_rev     # List current branches. For each branch the next rev is listed   # The order of branches determine their columns   self.curr_branches = [start_rev]     # For each branch, tell the next version that will show up   self.next_in_branch = {}     #   # Graph variables   # These hold information related to the graph. Most are computed lazily.   # The graph is split into a number of "branches", defined as paths within   # a named branch.   #     # Map rev to next rev in branch = parent with same branch name   # If two parents, use first that has same branch name.   # The parent of last rev in a branch is undefined,   # even if the revsion has a parent rev.   self.parent_of = {}     # Map rev to newest rev in branch. This identifies the branch that the rev   # is part of   self.branch4rev = {}     # Last revision in branch   self.branch_tail = {}     # Map branch-id (head-rev of branch) to color   self.color4branch = {}     # Next colour used. for branches   self.nextcolor = 0     # If set, show only this branch and all descendants.   self.branch_filter = branch_filter     # Flag to indicate if coloring is done pr micro-branch or pr named branch   self.branch_color = branch_color     def _get_parents(self, rev):   return [x for x in self.repo.changelog.parentrevs(rev) if x != nullrev]     def _branch_name(self, rev):   return self.repo[rev].branch()     def _covered_rev(self, rev):   """True if rev is inside the revision range for the iterator"""   return self.stop_rev <= rev     def _new_branch(self, branch_head):   """Mark all unknown revisions in range that are direct ancestors   of branch_head as part of the same branch. Stops when stop_rev   is passed or a known revision is found"""   assert not branch_head in self.branch4rev   if self.branch_color:   self.color4branch[branch_head] = \   _color_of_branch(self.repo, branch_head)   else:   self.color4branch[branch_head] = self.nextcolor   self.nextcolor += 1   self.next_in_branch[branch_head] = branch_head   branch_name = self._branch_name(branch_head)   rev = branch_head   while not rev in self.branch4rev:   # Add rev to branch   self.branch4rev[rev] = branch_head   self.branch_tail[branch_head] = rev   # Exit if rev is outside visible range   if not self._covered_rev(rev):   # rev is outside visible range, so we don't know tail location   self.branch_tail[branch_head] = 0 # Prev revs wasn't tail   return   # Find next revision in branch   self.parent_of[rev] = None   for parent in self._get_parents(rev):   if self._branch_name(parent) == branch_name:   self.parent_of[rev] = parent   break   if self.parent_of[rev] is None:   return   rev = self.parent_of[rev]     def _get_rev_branch(self, rev):   """Find revision branch or create a new branch"""   branch = self.branch4rev.get(rev)   if branch is None:   branch = rev   self._new_branch(branch)   assert rev in self.branch4rev   assert branch in self.branch_tail   return branch     def _compute_next_branches(self):   """Compute next row of branches"""   next_branches = self.curr_branches[:]   # Find branch used by current revision   curr_branch = self._get_rev_branch(self.curr_rev)   self.next_in_branch[curr_branch] = self.parent_of[self.curr_rev]   branch_index = self.curr_branches.index(curr_branch)   # Insert branches if parents point to new branches   new_parents = 0   for parent in self._get_parents(self.curr_rev):   branch = self._get_rev_branch(parent)   if not branch in next_branches:   new_parents += 1   next_branches.insert(branch_index + new_parents, branch)   # Delete branch if last revision   if self.curr_rev == self.branch_tail[curr_branch]:   del next_branches[branch_index]   # Return result   return next_branches     def _rev_color(self, rev):   """Map a revision to a color"""   return self.color4branch[self.branch4rev[rev]]     def _compute_lines(self, parents, next_branches):   # Compute lines (from CUR to NEXT branch row)   lines = []   curr_rev_branch = self.branch4rev[self.curr_rev]   for curr_column, branch in enumerate(self.curr_branches):   if branch == curr_rev_branch:   # Add lines from current branch to parents   for par_rev in parents:   par_branch = self._get_rev_branch(par_rev)   color = self.color4branch[par_branch]   next_column = next_branches.index(par_branch)   line_type = type_PLAIN   if par_rev != self.next_in_branch.get(par_branch):   line_type = type_LOOSE_LOW   lines.append( (curr_column, next_column, color, line_type) )   else:   # Continue unrelated branch   color = self.color4branch[branch]   next_column = next_branches.index(branch)   lines.append( (curr_column, next_column, color, type_PLAIN) )   return lines     def more(self):   return self.curr_rev >= self.stop_rev     def next(self):   """Perform one iteration of the branch grapher"""     # Compute revision (on CUR branch row)   while self.more():   rev = self.curr_rev   rev_branch = self._get_rev_branch(rev)   if rev_branch in self.curr_branches:   # Follow parent from known child   break   elif self.branch_filter is None:   # New head - no branch name filter   self.curr_branches.append(rev_branch)   break   elif self._branch_name(rev) == self.branch_filter:   # New head - matches branch name filter   self.curr_branches.append(rev_branch)   break   else:   # Skip this revision   self.curr_rev -= 1     # Compute parents (indicates the branches on NEXT branch row that curr_rev links to)   parents = self._get_parents(rev)   # BUG: __get_parents is not defined - why?     # Compute lines (from CUR to NEXT branch row)   next_branches = self._compute_next_branches()   lines = self._compute_lines(parents, next_branches)     # Compute node info (for CUR branch row)   rev_column = self.curr_branches.index(rev_branch)   rev_color = self.color4branch[rev_branch]   node = (rev_column, rev_color)     # Next loop   self.curr_branches = next_branches   self.curr_rev -= 1     # Return result   return (rev, node, lines, None)    def branch_grapher(repo, start_rev, stop_rev, branch=None, branch_color=False):   grapher = BranchGrapher(repo, start_rev, stop_rev, branch, branch_color)   while grapher.more():   yield grapher.next()      def filelog_grapher(repo, path):   '''   Graph the ancestry of a single file (log). Deletions show   up as breaks in the graph.   '''   filerev = len(repo.file(path)) - 1   revs = []   rev_color = {}   nextcolor = 0   type = type_PLAIN   while filerev >= 0:   fctx = repo.filectx(path, fileid=filerev)     # Compute revs and next_revs.   if filerev not in revs:   revs.append(filerev)   rev_color[filerev] = nextcolor ; nextcolor += 1   curcolor = rev_color[filerev]   index = revs.index(filerev)   next_revs = revs[:]     # Add parents to next_revs.   parents = [f.filerev() for f in fctx.parents() if f.path() == path]   parents_to_add = []   for parent in parents:   if parent not in next_revs:   parents_to_add.append(parent)   if len(parents) > 1:   rev_color[parent] = nextcolor ; nextcolor += 1   else:   rev_color[parent] = curcolor   parents_to_add.sort()   next_revs[index:index + 1] = parents_to_add     lines = []   for i, rev in enumerate(revs):   if rev in next_revs:   color = rev_color[rev]   lines.append( (i, next_revs.index(rev), color, type) )   elif rev == filerev:   for parent in parents:   color = rev_color[parent]   lines.append( (i, next_revs.index(parent), color, type) )     ret = fctx.renamed()   if ret:   nextpath, fnode = ret   flog = repo.file(nextpath)   filerev = flog.rev(fnode)   next_revs = [filerev]   rev_color[filerev] = nextcolor   lines = [ (0, 0, nextcolor, type_LOOSE_LOW) ]   nextcolor += 1   else:   nextpath = path   filerev -= 1     yield (fctx.rev(), (index, curcolor), lines, path)   path = nextpath   revs = next_revs        def dumb_log_generator(repo, revs):   for revname in revs:   node = repo.lookup(revname)   rev = repo.changelog.rev(node)   yield (rev, (0,0), [], None)    def filtered_log_generator(repo, pats, opts):   '''Fill view model iteratively   repo - Mercurial repository object   pats - list of file names or patterns   opts - command line options for log command   '''     only_branch = opts.get('branch', None)     # Log searches: pattern, keyword, date, etc   df = False   if opts['date']:   df = util.matchdate(opts['date'])     stack = []   get = util.cachefunc(lambda r: repo[r])   changeiter, matchfn = cmdutil.walkchangerevs(repo.ui, repo, pats, get, opts)   for st, rev, fns in changeiter:   if st == 'iter':   if stack:   yield stack.pop()   continue   if st != 'add':   continue     ctx = get(rev)     if only_branch:   if ctx.branch() != only_branch:   continue     parents = __get_parents(repo, rev)   if opts['no_merges'] and len(parents) == 2:   continue   if opts['only_merges'] and len(parents) != 2:   continue     if df:   changes = get(rev)   if not df(changes[2][0]):   continue     # TODO: add copies/renames later   if opts['keyword']:   miss = 0   for k in [kw.lower() for kw in opts['keyword']]:   if not (k in ctx.user().lower() or   k in ctx.description().lower() or   k in " ".join(ctx.files()).lower()):   miss = 1   break   if miss:   continue + + if opts['user']: + miss = 0 + for u in [u.lower() for u in opts['user']]: + if u not in ctx.user().lower(): + print 'missing' + miss = 1 + break + if miss: + continue +   stack.append((rev, (0,0), [], None))