##// END OF EJS Templates
hgk: display obsolete changesets in darkgrey
Andrew Shadura -
r24513:b5a0b237 default
parent child Browse files
Show More
@@ -1,4126 +1,4131 b''
1 1 #!/usr/bin/env wish
2 2
3 3 # Copyright (C) 2005 Paul Mackerras. All rights reserved.
4 4 # This program is free software; it may be used, copied, modified
5 5 # and distributed under the terms of the GNU General Public Licence,
6 6 # either version 2, or (at your option) any later version.
7 7 #
8 8 # See hgk.py for extension usage and configuration.
9 9
10 10
11 11 # Modified version of Tip 171:
12 12 # http://www.tcl.tk/cgi-bin/tct/tip/171.html
13 13 #
14 14 # The in_mousewheel global was added to fix strange reentrancy issues.
15 15 # The whole snipped is activated only under windows, mouse wheel
16 16 # bindings working already under MacOSX and Linux.
17 17
18 18 if {[catch {package require Ttk}]} {
19 19 # use a shim
20 20 namespace eval ttk {
21 21 proc style args {}
22 22
23 23 proc entry args {
24 24 eval [linsert $args 0 ::entry] -relief flat
25 25 }
26 26 }
27 27
28 28 interp alias {} ttk::button {} button
29 29 interp alias {} ttk::frame {} frame
30 30 interp alias {} ttk::label {} label
31 31 interp alias {} ttk::scrollbar {} scrollbar
32 32 interp alias {} ttk::optionMenu {} tk_optionMenu
33 33
34 34 proc updatepalette {} {}
35 35 } else {
36 36 proc ::ttk::optionMenu {w varName firstValue args} {
37 37 upvar #0 $varName var
38 38
39 39 if {![info exists var]} {
40 40 set var $firstValue
41 41 }
42 42 ttk::menubutton $w -textvariable $varName -menu $w.menu \
43 43 -direction flush
44 44 menu $w.menu -tearoff 0
45 45 $w.menu add radiobutton -label $firstValue -variable $varName
46 46 foreach i $args {
47 47 $w.menu add radiobutton -label $i -variable $varName
48 48 }
49 49 return $w.menu
50 50 }
51 51 proc updatepalette {} {
52 52 catch {
53 53 tk_setPalette background [ttk::style lookup client -background]
54 54 }
55 55 }
56 56 }
57 57
58 58 if {[tk windowingsystem] eq "win32"} {
59 59
60 60 ttk::style theme use xpnative
61 61
62 62 set mw_classes [list Text Listbox Table TreeCtrl]
63 63 foreach class $mw_classes { bind $class <MouseWheel> {} }
64 64
65 65 set in_mousewheel 0
66 66
67 67 proc ::tk::MouseWheel {wFired X Y D {shifted 0}} {
68 68 global in_mousewheel
69 69 if { $in_mousewheel != 0 } { return }
70 70 # Set event to check based on call
71 71 set evt "<[expr {$shifted?{Shift-}:{}}]MouseWheel>"
72 72 # do not double-fire in case the class already has a binding
73 73 if {[bind [winfo class $wFired] $evt] ne ""} { return }
74 74 # obtain the window the mouse is over
75 75 set w [winfo containing $X $Y]
76 76 # if we are outside the app, try and scroll the focus widget
77 77 if {![winfo exists $w]} { catch {set w [focus]} }
78 78 if {[winfo exists $w]} {
79 79
80 80 if {[bind $w $evt] ne ""} {
81 81 # Awkward ... this widget has a MouseWheel binding, but to
82 82 # trigger successfully in it, we must give it focus.
83 83 catch {focus} old
84 84 if {$w ne $old} { focus $w }
85 85 set in_mousewheel 1
86 86 event generate $w $evt -rootx $X -rooty $Y -delta $D
87 87 set in_mousewheel 0
88 88 if {$w ne $old} { focus $old }
89 89 return
90 90 }
91 91
92 92 # aqua and x11/win32 have different delta handling
93 93 if {[tk windowingsystem] ne "aqua"} {
94 94 set delta [expr {- ($D / 30)}]
95 95 } else {
96 96 set delta [expr {- ($D)}]
97 97 }
98 98 # scrollbars have different call conventions
99 99 if {[string match "*Scrollbar" [winfo class $w]]} {
100 100 catch {tk::ScrollByUnits $w \
101 101 [string index [$w cget -orient] 0] $delta}
102 102 } else {
103 103 set cmd [list $w [expr {$shifted ? "xview" : "yview"}] \
104 104 scroll $delta units]
105 105 # Walking up to find the proper widget (handles cases like
106 106 # embedded widgets in a canvas)
107 107 while {[catch $cmd] && [winfo toplevel $w] ne $w} {
108 108 set w [winfo parent $w]
109 109 }
110 110 }
111 111 }
112 112 }
113 113
114 114 bind all <MouseWheel> [list ::tk::MouseWheel %W %X %Y %D 0]
115 115
116 116 # end of win32 section
117 117 } else {
118 118
119 119 if {[catch {
120 120 set theme [ttk::style theme use]
121 121 }]} {
122 122 set theme $::ttk::currentTheme
123 123 }
124 124 if {$theme eq "default"} {
125 125 ttk::style theme use clam
126 126 }
127 127
128 128 }
129 129
130 130 updatepalette
131 131
132 132 # Unify right mouse button handling.
133 133 # See "mouse buttons on macintosh" thread on comp.lang.tcl
134 134 if {[tk windowingsystem] eq "aqua"} {
135 135 event add <<B3>> <Control-ButtonPress-1>
136 136 event add <<B3>> <Button-2>
137 137 } else {
138 138 event add <<B3>> <Button-3>
139 139 }
140 140
141 141 proc gitdir {} {
142 142 global env
143 143 if {[info exists env(GIT_DIR)]} {
144 144 return $env(GIT_DIR)
145 145 } else {
146 146 return ".hg"
147 147 }
148 148 }
149 149
150 150 proc popupify {w} {
151 151 wm resizable $w 0 0
152 152 wm withdraw $w
153 153 update
154 154 set x [expr {([winfo screenwidth .]-[winfo reqwidth $w])/2}]
155 155 set y [expr {([winfo screenheight .]-[winfo reqheight $w])/2}]
156 156 wm geometry $w +$x+$y
157 157 wm transient $w .
158 158 wm deiconify $w
159 159 wm resizable $w 1 1
160 160 }
161 161
162 162 proc getcommits {rargs} {
163 163 global commits commfd phase canv mainfont env
164 164 global startmsecs nextupdate ncmupdate
165 165 global ctext maincursor textcursor leftover
166 166
167 167 # check that we can find a .git directory somewhere...
168 168 set gitdir [gitdir]
169 169 if {![file isdirectory $gitdir]} {
170 170 error_popup "Cannot find the git directory \"$gitdir\"."
171 171 exit 1
172 172 }
173 173 set commits {}
174 174 set phase getcommits
175 175 set startmsecs [clock clicks -milliseconds]
176 176 set nextupdate [expr $startmsecs + 100]
177 177 set ncmupdate 1
178 178 set limit 0
179 179 set revargs {}
180 180 set showhidden no
181 181 for {set i 0} {$i < [llength $rargs]} {incr i} {
182 182 set opt [lindex $rargs $i]
183 183 switch -- $opt --limit {
184 184 incr i
185 185 set limit [lindex $rargs $i]
186 186 } --hidden {
187 187 set showhidden yes
188 188 } default {
189 189 lappend revargs $opt
190 190 }
191 191 }
192 192 if [catch {
193 193 set parse_args [concat --default HEAD $revargs]
194 194 set parse_temp [eval exec {$env(HG)} --config ui.report_untrusted=false debug-rev-parse $parse_args]
195 195 regsub -all "\r\n" $parse_temp "\n" parse_temp
196 196 set parsed_args [split $parse_temp "\n"]
197 197 } err] {
198 198 # if git-rev-parse failed for some reason...
199 199 if {$rargs == {}} {
200 200 set revargs HEAD
201 201 }
202 202 set parsed_args $revargs
203 203 }
204 204 if {$limit > 0} {
205 205 set parsed_args [concat -n $limit $parsed_args]
206 206 }
207 207 if {$showhidden} {
208 208 append parsed_args --hidden
209 209 }
210 210 if [catch {
211 211 set commfd [open "|{$env(HG)} --config ui.report_untrusted=false debug-rev-list --header --topo-order --parents $parsed_args" r]
212 212 } err] {
213 213 puts stderr "Error executing hg debug-rev-list: $err"
214 214 exit 1
215 215 }
216 216 set leftover {}
217 217 fconfigure $commfd -blocking 0 -translation lf -eofchar {}
218 218 fileevent $commfd readable [list getcommitlines $commfd]
219 219 $canv delete all
220 220 $canv create text 3 3 -anchor nw -text "Reading commits..." \
221 221 -font $mainfont -tags textitems
222 222 . config -cursor watch
223 223 settextcursor watch
224 224 }
225 225
226 226 proc getcommitlines {commfd} {
227 227 global commits parents cdate children
228 228 global commitlisted phase commitinfo nextupdate
229 229 global stopped redisplaying leftover
230 230
231 231 set stuff [read $commfd]
232 232 if {$stuff == {}} {
233 233 if {![eof $commfd]} return
234 234 # set it blocking so we wait for the process to terminate
235 235 fconfigure $commfd -blocking 1
236 236 if {![catch {close $commfd} err]} {
237 237 after idle finishcommits
238 238 return
239 239 }
240 240 if {[string range $err 0 4] == "usage"} {
241 241 set err \
242 242 {Gitk: error reading commits: bad arguments to git-rev-list.
243 243 (Note: arguments to gitk are passed to git-rev-list
244 244 to allow selection of commits to be displayed.)}
245 245 } else {
246 246 set err "Error reading commits: $err"
247 247 }
248 248 error_popup $err
249 249 exit 1
250 250 }
251 251 set start 0
252 252 while 1 {
253 253 set i [string first "\0" $stuff $start]
254 254 if {$i < 0} {
255 255 append leftover [string range $stuff $start end]
256 256 return
257 257 }
258 258 set cmit [string range $stuff $start [expr {$i - 1}]]
259 259 if {$start == 0} {
260 260 set cmit "$leftover$cmit"
261 261 set leftover {}
262 262 }
263 263 set start [expr {$i + 1}]
264 264 regsub -all "\r\n" $cmit "\n" cmit
265 265 set j [string first "\n" $cmit]
266 266 set ok 0
267 267 if {$j >= 0} {
268 268 set ids [string range $cmit 0 [expr {$j - 1}]]
269 269 set ok 1
270 270 foreach id $ids {
271 271 if {![regexp {^[0-9a-f]{12}$} $id]} {
272 272 set ok 0
273 273 break
274 274 }
275 275 }
276 276 }
277 277 if {!$ok} {
278 278 set shortcmit $cmit
279 279 if {[string length $shortcmit] > 80} {
280 280 set shortcmit "[string range $shortcmit 0 80]..."
281 281 }
282 282 error_popup "Can't parse hg debug-rev-list output: {$shortcmit}"
283 283 exit 1
284 284 }
285 285 set id [lindex $ids 0]
286 286 set olds [lrange $ids 1 end]
287 287 set cmit [string range $cmit [expr {$j + 1}] end]
288 288 lappend commits $id
289 289 set commitlisted($id) 1
290 290 parsecommit $id $cmit 1 [lrange $ids 1 end]
291 291 drawcommit $id
292 292 if {[clock clicks -milliseconds] >= $nextupdate} {
293 293 doupdate 1
294 294 }
295 295 while {$redisplaying} {
296 296 set redisplaying 0
297 297 if {$stopped == 1} {
298 298 set stopped 0
299 299 set phase "getcommits"
300 300 foreach id $commits {
301 301 drawcommit $id
302 302 if {$stopped} break
303 303 if {[clock clicks -milliseconds] >= $nextupdate} {
304 304 doupdate 1
305 305 }
306 306 }
307 307 }
308 308 }
309 309 }
310 310 }
311 311
312 312 proc doupdate {reading} {
313 313 global commfd nextupdate numcommits ncmupdate
314 314
315 315 if {$reading} {
316 316 fileevent $commfd readable {}
317 317 }
318 318 update
319 319 set nextupdate [expr {[clock clicks -milliseconds] + 100}]
320 320 if {$numcommits < 100} {
321 321 set ncmupdate [expr {$numcommits + 1}]
322 322 } elseif {$numcommits < 10000} {
323 323 set ncmupdate [expr {$numcommits + 10}]
324 324 } else {
325 325 set ncmupdate [expr {$numcommits + 100}]
326 326 }
327 327 if {$reading} {
328 328 fileevent $commfd readable [list getcommitlines $commfd]
329 329 }
330 330 }
331 331
332 332 proc readcommit {id} {
333 333 global env
334 334 if [catch {set contents [exec $env(HG) --config ui.report_untrusted=false debug-cat-file commit $id]}] return
335 335 parsecommit $id $contents 0 {}
336 336 }
337 337
338 338 proc parsecommit {id contents listed olds} {
339 339 global commitinfo children nchildren parents nparents cdate ncleft
340 global firstparents
340 global firstparents obsolete
341 341
342 342 set inhdr 1
343 343 set comment {}
344 344 set headline {}
345 345 set auname {}
346 346 set audate {}
347 347 set comname {}
348 348 set comdate {}
349 349 set rev {}
350 350 set branch {}
351 351 set bookmark {}
352 352 if {![info exists nchildren($id)]} {
353 353 set children($id) {}
354 354 set nchildren($id) 0
355 355 set ncleft($id) 0
356 356 }
357 357 set parents($id) $olds
358 358 set nparents($id) [llength $olds]
359 359 foreach p $olds {
360 360 if {![info exists nchildren($p)]} {
361 361 set children($p) [list $id]
362 362 set nchildren($p) 1
363 363 set ncleft($p) 1
364 364 } elseif {[lsearch -exact $children($p) $id] < 0} {
365 365 lappend children($p) $id
366 366 incr nchildren($p)
367 367 incr ncleft($p)
368 368 }
369 369 }
370 370 regsub -all "\r\n" $contents "\n" contents
371 371 foreach line [split $contents "\n"] {
372 372 if {$inhdr} {
373 373 set line [split $line]
374 374 if {$line == {}} {
375 375 set inhdr 0
376 376 } else {
377 377 set tag [lindex $line 0]
378 378 if {$tag == "author"} {
379 379 set x [expr {[llength $line] - 2}]
380 380 set audate [lindex $line $x]
381 381 set auname [join [lrange $line 1 [expr {$x - 1}]]]
382 382 } elseif {$tag == "committer"} {
383 383 set x [expr {[llength $line] - 2}]
384 384 set comdate [lindex $line $x]
385 385 set comname [join [lrange $line 1 [expr {$x - 1}]]]
386 386 } elseif {$tag == "revision"} {
387 387 set rev [lindex $line 1]
388 388 } elseif {$tag == "branch"} {
389 389 set branch [join [lrange $line 1 end]]
390 390 } elseif {$tag == "bookmark"} {
391 391 set bookmark [join [lrange $line 1 end]]
392 } elseif {$tag == "obsolete"} {
393 set obsolete($id) ""
392 394 }
393 395 }
394 396 } else {
395 397 if {$comment == {}} {
396 398 set headline [string trim $line]
397 399 } else {
398 400 append comment "\n"
399 401 }
400 402 if {!$listed} {
401 403 # git-rev-list indents the comment by 4 spaces;
402 404 # if we got this via git-cat-file, add the indentation
403 405 append comment " "
404 406 }
405 407 append comment $line
406 408 }
407 409 }
408 410 if {$audate != {}} {
409 411 set audate [clock format $audate]
410 412 }
411 413 if {$comdate != {}} {
412 414 set cdate($id) $comdate
413 415 set comdate [clock format $comdate]
414 416 }
415 417 set commitinfo($id) [list $headline $auname $audate \
416 418 $comname $comdate $comment $rev $branch $bookmark]
417 419
418 420 if {[info exists firstparents]} {
419 421 set i [lsearch $firstparents $id]
420 422 if {$i != -1} {
421 423 # remove the parent from firstparents, possible building
422 424 # an empty list
423 425 set firstparents [concat \
424 426 [lrange $firstparents 0 [expr $i - 1]] \
425 427 [lrange $firstparents [expr $i + 1] end]]
426 428 if {$firstparents eq {}} {
427 429 # we have found all parents of the first changeset
428 430 # which means that we can safely select the first line
429 431 after idle {
430 432 selectline 0 0
431 433 }
432 434 }
433 435 }
434 436 } else {
435 437 # this is the first changeset, save the parents
436 438 set firstparents $olds
437 439 if {$firstparents eq {}} {
438 440 # a repository with a single changeset
439 441 after idle {
440 442 selectline 0 0
441 443 }
442 444 }
443 445 }
444 446 }
445 447
446 448 proc readrefs {} {
447 449 global bookmarkcurrent bookmarkids tagids idtags idbookmarks headids idheads tagcontents env curid
448 450
449 451 set status [catch {exec $env(HG) --config ui.report_untrusted=false id} curid]
450 452 if { $status != 0 } {
451 453 puts $::errorInfo
452 454 if { ![string equal $::errorCode NONE] } {
453 455 exit 2
454 456 }
455 457 }
456 458 regexp -- {[[:xdigit:]]+} $curid curid
457 459
458 460 set status [catch {exec $env(HG) --config ui.report_untrusted=false tags} tags]
459 461 if { $status != 0 } {
460 462 puts $::errorInfo
461 463 if { ![string equal $::errorCode NONE] } {
462 464 exit 2
463 465 }
464 466 }
465 467
466 468 foreach {- tag rev id} [regexp -inline -all -line {^(.+\S)\s+(\d+):(\S+)} $tags] {
467 469 # we use foreach as Tcl8.4 doesn't support lassign
468 470 lappend tagids($tag) $id
469 471 lappend idtags($id) $tag
470 472 }
471 473
472 474 set status [catch {exec $env(HG) --config ui.report_untrusted=false heads} heads]
473 475 if { $status != 0 } {
474 476 puts $::errorInfo
475 477 if { ![string equal $::errorCode NONE] } {
476 478 exit 2
477 479 }
478 480 }
479 481
480 482 set lines [split $heads \r\n]
481 483 foreach f $lines {
482 484 set match ""
483 485 regexp {changeset:\s+(\S+):(\S+)$} $f match id sha
484 486 if {$match != ""} {
485 487 lappend idheads($sha) $id
486 488 }
487 489 }
488 490
489 491 set status [catch {exec $env(HG) --config ui.report_untrusted=false bookmarks} bookmarks]
490 492 if { $status != 0 } {
491 493 puts $::errorInfo
492 494 if { ![string equal $::errorCode NONE] } {
493 495 exit 2
494 496 }
495 497 }
496 498 set lines [split $bookmarks "\n"]
497 499 set bookmarkcurrent 0
498 500 foreach f $lines {
499 501 regexp {(\S+)$} $f full
500 502 regsub {\s+(\S+)$} $f "" direct
501 503 set sha [split $full ':']
502 504 set bookmark [lindex $sha 1]
503 505 set current [string first " * " $direct)]
504 506 regsub {^\s(\*|\s)\s} $direct "" direct
505 507 lappend bookmarkids($direct) $bookmark
506 508 lappend idbookmarks($bookmark) $direct
507 509 if {$current >= 0} {
508 510 set bookmarkcurrent $direct
509 511 }
510 512 }
511 513 }
512 514
513 515 proc readotherrefs {base dname excl} {
514 516 global otherrefids idotherrefs
515 517
516 518 set git [gitdir]
517 519 set files [glob -nocomplain -types f [file join $git $base *]]
518 520 foreach f $files {
519 521 catch {
520 522 set fd [open $f r]
521 523 set line [read $fd 40]
522 524 if {[regexp {^[0-9a-f]{12}} $line id]} {
523 525 set name "$dname[file tail $f]"
524 526 set otherrefids($name) $id
525 527 lappend idotherrefs($id) $name
526 528 }
527 529 close $fd
528 530 }
529 531 }
530 532 set dirs [glob -nocomplain -types d [file join $git $base *]]
531 533 foreach d $dirs {
532 534 set dir [file tail $d]
533 535 if {[lsearch -exact $excl $dir] >= 0} continue
534 536 readotherrefs [file join $base $dir] "$dname$dir/" {}
535 537 }
536 538 }
537 539
538 540 proc allcansmousewheel {delta} {
539 541 set delta [expr -5*(int($delta)/abs($delta))]
540 542 allcanvs yview scroll $delta units
541 543 }
542 544
543 545 proc error_popup msg {
544 546 set w .error
545 547 toplevel $w
546 548 wm transient $w .
547 549 message $w.m -text $msg -justify center -aspect 400
548 550 pack $w.m -side top -fill x -padx 20 -pady 20
549 551 ttk::button $w.ok -text OK -command "destroy $w"
550 552 pack $w.ok -side bottom -fill x
551 553 bind $w <Visibility> "grab $w; focus $w"
552 554 popupify $w
553 555 tkwait window $w
554 556 }
555 557
556 558 proc makewindow {} {
557 559 global canv canv2 canv3 linespc charspc ctext cflist textfont
558 560 global findtype findtypemenu findloc findstring fstring geometry
559 561 global entries sha1entry sha1string sha1but
560 562 global maincursor textcursor curtextcursor
561 563 global rowctxmenu gaudydiff mergemax
562 564 global hgvdiff bgcolor fgcolor diffremcolor diffaddcolor diffmerge1color
563 565 global diffmerge2color hunksepcolor
564 566 global posx posy
565 567
566 568 if {[info exists posx]} {
567 569 wm geometry . +$posx+$posy
568 570 }
569 571
570 572 menu .bar
571 573 .bar add cascade -label "File" -menu .bar.file
572 574 menu .bar.file
573 575 .bar.file add command -label "Reread references" -command rereadrefs
574 576 .bar.file add command -label "Quit" -command doquit
575 577 menu .bar.help
576 578 .bar add cascade -label "Help" -menu .bar.help
577 579 .bar.help add command -label "About hgk" -command about
578 580 . configure -menu .bar
579 581
580 582 if {![info exists geometry(canv1)]} {
581 583 set geometry(canv1) [expr 45 * $charspc]
582 584 set geometry(canv2) [expr 30 * $charspc]
583 585 set geometry(canv3) [expr 15 * $charspc]
584 586 set geometry(canvh) [expr 25 * $linespc + 4]
585 587 set geometry(ctextw) 80
586 588 set geometry(ctexth) 30
587 589 set geometry(cflistw) 30
588 590 }
589 591 panedwindow .ctop -orient vertical
590 592 if {[info exists geometry(width)]} {
591 593 .ctop conf -width $geometry(width) -height $geometry(height)
592 594 set texth [expr {$geometry(height) - $geometry(canvh) - 56}]
593 595 set geometry(ctexth) [expr {($texth - 8) /
594 596 [font metrics $textfont -linespace]}]
595 597 }
596 598 ttk::frame .ctop.top
597 599 ttk::frame .ctop.top.bar
598 600 pack .ctop.top.bar -side bottom -fill x
599 601 set cscroll .ctop.top.csb
600 602 ttk::scrollbar $cscroll -command {allcanvs yview}
601 603 pack $cscroll -side right -fill y
602 604 panedwindow .ctop.top.clist -orient horizontal -sashpad 0 -handlesize 4
603 605 pack .ctop.top.clist -side top -fill both -expand 1
604 606 .ctop add .ctop.top
605 607 set canv .ctop.top.clist.canv
606 608 canvas $canv -height $geometry(canvh) -width $geometry(canv1) \
607 609 -bg $bgcolor -bd 0 \
608 610 -yscrollincr $linespc -yscrollcommand "$cscroll set" -selectbackground "#c0c0c0"
609 611 .ctop.top.clist add $canv
610 612 set canv2 .ctop.top.clist.canv2
611 613 canvas $canv2 -height $geometry(canvh) -width $geometry(canv2) \
612 614 -bg $bgcolor -bd 0 -yscrollincr $linespc -selectbackground "#c0c0c0"
613 615 .ctop.top.clist add $canv2
614 616 set canv3 .ctop.top.clist.canv3
615 617 canvas $canv3 -height $geometry(canvh) -width $geometry(canv3) \
616 618 -bg $bgcolor -bd 0 -yscrollincr $linespc -selectbackground "#c0c0c0"
617 619 .ctop.top.clist add $canv3
618 620 bind .ctop.top.clist <Configure> {resizeclistpanes %W %w}
619 621
620 622 set sha1entry .ctop.top.bar.sha1
621 623 set entries $sha1entry
622 624 set sha1but .ctop.top.bar.sha1label
623 625 button $sha1but -text "SHA1 ID: " -state disabled -relief flat \
624 626 -command gotocommit -width 8
625 627 $sha1but conf -disabledforeground [$sha1but cget -foreground]
626 628 pack .ctop.top.bar.sha1label -side left
627 629 ttk::entry $sha1entry -width 40 -font $textfont -textvariable sha1string
628 630 trace add variable sha1string write sha1change
629 631 pack $sha1entry -side left -pady 2
630 632
631 633 image create bitmap bm-left -data {
632 634 #define left_width 16
633 635 #define left_height 16
634 636 static unsigned char left_bits[] = {
635 637 0x00, 0x00, 0xc0, 0x01, 0xe0, 0x00, 0x70, 0x00, 0x38, 0x00, 0x1c, 0x00,
636 638 0x0e, 0x00, 0xff, 0x7f, 0xff, 0x7f, 0xff, 0x7f, 0x0e, 0x00, 0x1c, 0x00,
637 639 0x38, 0x00, 0x70, 0x00, 0xe0, 0x00, 0xc0, 0x01};
638 640 }
639 641 image create bitmap bm-right -data {
640 642 #define right_width 16
641 643 #define right_height 16
642 644 static unsigned char right_bits[] = {
643 645 0x00, 0x00, 0xc0, 0x01, 0x80, 0x03, 0x00, 0x07, 0x00, 0x0e, 0x00, 0x1c,
644 646 0x00, 0x38, 0xff, 0x7f, 0xff, 0x7f, 0xff, 0x7f, 0x00, 0x38, 0x00, 0x1c,
645 647 0x00, 0x0e, 0x00, 0x07, 0x80, 0x03, 0xc0, 0x01};
646 648 }
647 649 ttk::button .ctop.top.bar.leftbut -image bm-left -command goback \
648 650 -state disabled -width 26
649 651 pack .ctop.top.bar.leftbut -side left -fill y
650 652 ttk::button .ctop.top.bar.rightbut -image bm-right -command goforw \
651 653 -state disabled -width 26
652 654 pack .ctop.top.bar.rightbut -side left -fill y
653 655
654 656 ttk::button .ctop.top.bar.findbut -text "Find" -command dofind
655 657 pack .ctop.top.bar.findbut -side left
656 658 set findstring {}
657 659 set fstring .ctop.top.bar.findstring
658 660 lappend entries $fstring
659 661 ttk::entry $fstring -width 30 -font $textfont -textvariable findstring
660 662 pack $fstring -side left -expand 1 -fill x
661 663 set findtype Exact
662 664 set findtypemenu [ttk::optionMenu .ctop.top.bar.findtype \
663 665 findtype Exact IgnCase Regexp]
664 666 set findloc "All fields"
665 667 ttk::optionMenu .ctop.top.bar.findloc findloc "All fields" Headline \
666 668 Comments Author Files Pickaxe
667 669 pack .ctop.top.bar.findloc -side right
668 670 pack .ctop.top.bar.findtype -side right
669 671 # for making sure type==Exact whenever loc==Pickaxe
670 672 trace add variable findloc write findlocchange
671 673
672 674 panedwindow .ctop.cdet -orient horizontal
673 675 .ctop add .ctop.cdet
674 676 ttk::frame .ctop.cdet.left
675 677 set ctext .ctop.cdet.left.ctext
676 678 text $ctext -fg $fgcolor -bg $bgcolor -state disabled -font $textfont \
677 679 -width $geometry(ctextw) -height $geometry(ctexth) \
678 680 -yscrollcommand ".ctop.cdet.left.sb set" \
679 681 -xscrollcommand ".ctop.cdet.left.hb set" -wrap none
680 682 ttk::scrollbar .ctop.cdet.left.sb -command "$ctext yview"
681 683 ttk::scrollbar .ctop.cdet.left.hb -orient horizontal -command "$ctext xview"
682 684 pack .ctop.cdet.left.sb -side right -fill y
683 685 pack .ctop.cdet.left.hb -side bottom -fill x
684 686 pack $ctext -side left -fill both -expand 1
685 687 .ctop.cdet add .ctop.cdet.left
686 688
687 689 $ctext tag conf filesep -font [concat $textfont bold] -back "#aaaaaa"
688 690 if {$gaudydiff} {
689 691 $ctext tag conf hunksep -back blue -fore white
690 692 $ctext tag conf d0 -back "#ff8080"
691 693 $ctext tag conf d1 -back green
692 694 } else {
693 695 $ctext tag conf hunksep -fore $hunksepcolor
694 696 $ctext tag conf d0 -fore $diffremcolor
695 697 $ctext tag conf d1 -fore $diffaddcolor
696 698
697 699 # The mX colours seem to be used in merge changesets, where m0
698 700 # is first parent, m1 is second parent and so on. Git can have
699 701 # several parents, Hg cannot, so I think the m2..mmax would be
700 702 # unused.
701 703 $ctext tag conf m0 -fore $diffmerge1color
702 704 $ctext tag conf m1 -fore $diffmerge2color
703 705 $ctext tag conf m2 -fore green
704 706 $ctext tag conf m3 -fore purple
705 707 $ctext tag conf m4 -fore brown
706 708 $ctext tag conf mmax -fore darkgrey
707 709 set mergemax 5
708 710 $ctext tag conf mresult -font [concat $textfont bold]
709 711 $ctext tag conf msep -font [concat $textfont bold]
710 712 $ctext tag conf found -back yellow
711 713 }
712 714
713 715 ttk::frame .ctop.cdet.right
714 716 set cflist .ctop.cdet.right.cfiles
715 717 listbox $cflist -fg $fgcolor -bg $bgcolor \
716 718 -selectmode extended -width $geometry(cflistw) \
717 719 -yscrollcommand ".ctop.cdet.right.sb set"
718 720 ttk::scrollbar .ctop.cdet.right.sb -command "$cflist yview"
719 721 pack .ctop.cdet.right.sb -side right -fill y
720 722 pack $cflist -side left -fill both -expand 1
721 723 .ctop.cdet add .ctop.cdet.right
722 724 bind .ctop.cdet <Configure> {resizecdetpanes %W %w}
723 725
724 726 pack .ctop -side top -fill both -expand 1
725 727
726 728 bindall <1> {selcanvline %W %x %y}
727 729 #bindall <B1-Motion> {selcanvline %W %x %y}
728 730 bindall <MouseWheel> "allcansmousewheel %D"
729 731 bindall <ButtonRelease-4> "allcanvs yview scroll -5 units"
730 732 bindall <ButtonRelease-5> "allcanvs yview scroll 5 units"
731 733 bindall <2> "allcanvs scan mark 0 %y"
732 734 bindall <B2-Motion> "allcanvs scan dragto 0 %y"
733 735 bind . <Key-Up> "selnextline -1"
734 736 bind . <Key-Down> "selnextline 1"
735 737 bind . <Key-Prior> "allcanvs yview scroll -1 pages"
736 738 bind . <Key-Next> "allcanvs yview scroll 1 pages"
737 739 bindkey <Key-Delete> "$ctext yview scroll -1 pages"
738 740 bindkey <Key-BackSpace> "$ctext yview scroll -1 pages"
739 741 bindkey <Key-space> "$ctext yview scroll 1 pages"
740 742 bindkey p "selnextline -1"
741 743 bindkey n "selnextline 1"
742 744 bindkey b "$ctext yview scroll -1 pages"
743 745 bindkey d "$ctext yview scroll 18 units"
744 746 bindkey u "$ctext yview scroll -18 units"
745 747 bindkey / {findnext 1}
746 748 bindkey <Key-Return> {findnext 0}
747 749 bindkey ? findprev
748 750 bindkey f nextfile
749 751 bind . <Control-q> doquit
750 752 bind . <Control-w> doquit
751 753 bind . <Control-f> dofind
752 754 bind . <Control-g> {findnext 0}
753 755 bind . <Control-r> findprev
754 756 bind . <Control-equal> {incrfont 1}
755 757 bind . <Control-KP_Add> {incrfont 1}
756 758 bind . <Control-minus> {incrfont -1}
757 759 bind . <Control-KP_Subtract> {incrfont -1}
758 760 bind $cflist <<ListboxSelect>> listboxsel
759 761 bind . <Destroy> {savestuff %W}
760 762 bind . <Button-1> "click %W"
761 763 bind $fstring <Key-Return> dofind
762 764 bind $sha1entry <Key-Return> gotocommit
763 765 bind $sha1entry <<PasteSelection>> clearsha1
764 766
765 767 set maincursor [. cget -cursor]
766 768 set textcursor [$ctext cget -cursor]
767 769 set curtextcursor $textcursor
768 770
769 771 set rowctxmenu .rowctxmenu
770 772 menu $rowctxmenu -tearoff 0
771 773 $rowctxmenu add command -label "Diff this -> selected" \
772 774 -command {diffvssel 0}
773 775 $rowctxmenu add command -label "Diff selected -> this" \
774 776 -command {diffvssel 1}
775 777 $rowctxmenu add command -label "Make patch" -command mkpatch
776 778 $rowctxmenu add command -label "Create tag" -command mktag
777 779 $rowctxmenu add command -label "Write commit to file" -command writecommit
778 780 if { $hgvdiff ne "" } {
779 781 $rowctxmenu add command -label "Visual diff with parent" \
780 782 -command {vdiff 1}
781 783 $rowctxmenu add command -label "Visual diff with selected" \
782 784 -command {vdiff 0}
783 785 }
784 786 }
785 787
786 788 # when we make a key binding for the toplevel, make sure
787 789 # it doesn't get triggered when that key is pressed in the
788 790 # find string entry widget.
789 791 proc bindkey {ev script} {
790 792 global entries
791 793 bind . $ev $script
792 794 set escript [bind Entry $ev]
793 795 if {$escript == {}} {
794 796 set escript [bind Entry <Key>]
795 797 }
796 798 foreach e $entries {
797 799 bind $e $ev "$escript; break"
798 800 }
799 801 }
800 802
801 803 # set the focus back to the toplevel for any click outside
802 804 # the entry widgets
803 805 proc click {w} {
804 806 global ctext entries
805 807 foreach e [concat $entries $ctext] {
806 808 if {$w == $e} return
807 809 }
808 810 focus .
809 811 }
810 812
811 813 proc savestuff {w} {
812 814 global canv canv2 canv3 ctext cflist mainfont textfont
813 815 global stuffsaved findmergefiles gaudydiff maxgraphpct
814 816 global maxwidth authorcolors curidfont bgcolor fgcolor
815 817 global diffremcolor diffaddcolor hunksepcolor
816 818 global diffmerge1color diffmerge2color
817 819
818 820 if {$stuffsaved} return
819 821 if {![winfo viewable .]} return
820 822 catch {
821 823 set f [open "~/.hgk-new" w]
822 824 puts $f [list set mainfont $mainfont]
823 825 puts $f [list set curidfont $curidfont]
824 826 puts $f [list set textfont $textfont]
825 827 puts $f [list set findmergefiles $findmergefiles]
826 828 puts $f [list set gaudydiff $gaudydiff]
827 829 puts $f [list set maxgraphpct $maxgraphpct]
828 830 puts $f [list set maxwidth $maxwidth]
829 831 puts $f "set geometry(width) [winfo width .ctop]"
830 832 puts $f "set geometry(height) [winfo height .ctop]"
831 833 puts $f "set geometry(canv1) [expr [winfo width $canv]-2]"
832 834 puts $f "set geometry(canv2) [expr [winfo width $canv2]-2]"
833 835 puts $f "set geometry(canv3) [expr [winfo width $canv3]-2]"
834 836 puts $f "set geometry(canvh) [expr [winfo height $canv]-2]"
835 837 set wid [expr {([winfo width $ctext] - 8) \
836 838 / [font measure $textfont "0"]}]
837 839 puts $f "set geometry(ctextw) $wid"
838 840 set wid [expr {([winfo width $cflist] - 11) \
839 841 / [font measure [$cflist cget -font] "0"]}]
840 842 puts $f "set geometry(cflistw) $wid"
841 843 puts $f "#"
842 844 puts $f "# main window position:"
843 845 puts $f "set posx [winfo x .]"
844 846 puts $f "set posy [winfo y .]"
845 847 puts $f "#"
846 848 puts $f "# authorcolors format:"
847 849 puts $f "#"
848 850 puts $f "# zero or more sublists of"
849 851 puts $f "#"
850 852 puts $f "# { regex color }"
851 853 puts $f "#"
852 854 puts $f "# followed by a list of colors"
853 855 puts $f "#"
854 856 puts $f "# If the commit author matches a regex in a sublist,"
855 857 puts $f "# the commit will be colored by that color"
856 858 puts $f "# otherwise the next unused entry from the list of colors"
857 859 puts $f "# will be assigned to this commit and also all other commits"
858 860 puts $f "# of the same author. When the list of colors is exhausted,"
859 861 puts $f "# the last entry will be reused."
860 862 puts $f "#"
861 863 puts $f "set authorcolors {$authorcolors}"
862 864 puts $f "#"
863 865 puts $f "# The background color in the text windows"
864 866 puts $f "set bgcolor $bgcolor"
865 867 puts $f "#"
866 868 puts $f "# The text color used in the diff and file list view"
867 869 puts $f "set fgcolor $fgcolor"
868 870 puts $f "#"
869 871 puts $f "# Color to display + lines in diffs"
870 872 puts $f "set diffaddcolor $diffaddcolor"
871 873 puts $f "#"
872 874 puts $f "# Color to display - lines in diffs"
873 875 puts $f "set diffremcolor $diffremcolor"
874 876 puts $f "#"
875 877 puts $f "# Merge diffs: Color to signal lines from first parent"
876 878 puts $f "set diffmerge1color $diffmerge1color"
877 879 puts $f "#"
878 880 puts $f "# Merge diffs: Color to signal lines from second parent"
879 881 puts $f "set diffmerge2color $diffmerge2color"
880 882 puts $f "#"
881 883 puts $f "# Hunkseparator (@@ -lineno,lines +lineno,lines @@) color"
882 884 puts $f "set hunksepcolor $hunksepcolor"
883 885 close $f
884 886 file rename -force "~/.hgk-new" "~/.hgk"
885 887 }
886 888 set stuffsaved 1
887 889 }
888 890
889 891 proc resizeclistpanes {win w} {
890 892 global oldwidth
891 893 if [info exists oldwidth($win)] {
892 894 set s0 [$win sash coord 0]
893 895 set s1 [$win sash coord 1]
894 896 if {$w < 60} {
895 897 set sash0 [expr {int($w/2 - 2)}]
896 898 set sash1 [expr {int($w*5/6 - 2)}]
897 899 } else {
898 900 set factor [expr {1.0 * $w / $oldwidth($win)}]
899 901 set sash0 [expr {int($factor * [lindex $s0 0])}]
900 902 set sash1 [expr {int($factor * [lindex $s1 0])}]
901 903 if {$sash0 < 30} {
902 904 set sash0 30
903 905 }
904 906 if {$sash1 < $sash0 + 20} {
905 907 set sash1 [expr $sash0 + 20]
906 908 }
907 909 if {$sash1 > $w - 10} {
908 910 set sash1 [expr $w - 10]
909 911 if {$sash0 > $sash1 - 20} {
910 912 set sash0 [expr $sash1 - 20]
911 913 }
912 914 }
913 915 }
914 916 $win sash place 0 $sash0 [lindex $s0 1]
915 917 $win sash place 1 $sash1 [lindex $s1 1]
916 918 }
917 919 set oldwidth($win) $w
918 920 }
919 921
920 922 proc resizecdetpanes {win w} {
921 923 global oldwidth
922 924 if [info exists oldwidth($win)] {
923 925 set s0 [$win sash coord 0]
924 926 if {$w < 60} {
925 927 set sash0 [expr {int($w*3/4 - 2)}]
926 928 } else {
927 929 set factor [expr {1.0 * $w / $oldwidth($win)}]
928 930 set sash0 [expr {int($factor * [lindex $s0 0])}]
929 931 if {$sash0 < 45} {
930 932 set sash0 45
931 933 }
932 934 if {$sash0 > $w - 15} {
933 935 set sash0 [expr $w - 15]
934 936 }
935 937 }
936 938 $win sash place 0 $sash0 [lindex $s0 1]
937 939 }
938 940 set oldwidth($win) $w
939 941 }
940 942
941 943 proc allcanvs args {
942 944 global canv canv2 canv3
943 945 eval $canv $args
944 946 eval $canv2 $args
945 947 eval $canv3 $args
946 948 }
947 949
948 950 proc bindall {event action} {
949 951 global canv canv2 canv3
950 952 bind $canv $event $action
951 953 bind $canv2 $event $action
952 954 bind $canv3 $event $action
953 955 }
954 956
955 957 proc about {} {
956 958 set w .about
957 959 if {[winfo exists $w]} {
958 960 raise $w
959 961 return
960 962 }
961 963 toplevel $w
962 964 wm title $w "About hgk"
963 965 message $w.m -text {
964 966 Hgk version 1.2
965 967
966 968 Copyright οΏ½ 2005 Paul Mackerras
967 969
968 970 Use and redistribute under the terms of the GNU General Public License} \
969 971 -justify center -aspect 400
970 972 pack $w.m -side top -fill x -padx 20 -pady 20
971 973 ttk::button $w.ok -text Close -command "destroy $w"
972 974 pack $w.ok -side bottom
973 975 popupify $w
974 976 }
975 977
976 978 set aunextcolor 0
977 979 proc assignauthorcolor {name} {
978 980 global authorcolors aucolormap aunextcolor
979 981 if [info exists aucolormap($name)] return
980 982
981 983 set randomcolors {black}
982 984 for {set i 0} {$i < [llength $authorcolors]} {incr i} {
983 985 set col [lindex $authorcolors $i]
984 986 if {[llength $col] > 1} {
985 987 set re [lindex $col 0]
986 988 set c [lindex $col 1]
987 989 if {[regexp -- $re $name]} {
988 990 set aucolormap($name) $c
989 991 return
990 992 }
991 993 } else {
992 994 set randomcolors [lrange $authorcolors $i end]
993 995 break
994 996 }
995 997 }
996 998
997 999 set ncolors [llength $randomcolors]
998 1000 set c [lindex $randomcolors $aunextcolor]
999 1001 if {[incr aunextcolor] >= $ncolors} {
1000 1002 incr aunextcolor -1
1001 1003 }
1002 1004 set aucolormap($name) $c
1003 1005 }
1004 1006
1005 1007 proc assigncolor {id} {
1006 1008 global commitinfo colormap commcolors colors nextcolor
1007 1009 global parents nparents children nchildren
1008 1010 global cornercrossings crossings
1009 1011
1010 1012 if [info exists colormap($id)] return
1011 1013 set ncolors [llength $colors]
1012 1014 if {$nparents($id) <= 1 && $nchildren($id) == 1} {
1013 1015 set child [lindex $children($id) 0]
1014 1016 if {[info exists colormap($child)]
1015 1017 && $nparents($child) == 1} {
1016 1018 set colormap($id) $colormap($child)
1017 1019 return
1018 1020 }
1019 1021 }
1020 1022 set badcolors {}
1021 1023 if {[info exists cornercrossings($id)]} {
1022 1024 foreach x $cornercrossings($id) {
1023 1025 if {[info exists colormap($x)]
1024 1026 && [lsearch -exact $badcolors $colormap($x)] < 0} {
1025 1027 lappend badcolors $colormap($x)
1026 1028 }
1027 1029 }
1028 1030 if {[llength $badcolors] >= $ncolors} {
1029 1031 set badcolors {}
1030 1032 }
1031 1033 }
1032 1034 set origbad $badcolors
1033 1035 if {[llength $badcolors] < $ncolors - 1} {
1034 1036 if {[info exists crossings($id)]} {
1035 1037 foreach x $crossings($id) {
1036 1038 if {[info exists colormap($x)]
1037 1039 && [lsearch -exact $badcolors $colormap($x)] < 0} {
1038 1040 lappend badcolors $colormap($x)
1039 1041 }
1040 1042 }
1041 1043 if {[llength $badcolors] >= $ncolors} {
1042 1044 set badcolors $origbad
1043 1045 }
1044 1046 }
1045 1047 set origbad $badcolors
1046 1048 }
1047 1049 if {[llength $badcolors] < $ncolors - 1} {
1048 1050 foreach child $children($id) {
1049 1051 if {[info exists colormap($child)]
1050 1052 && [lsearch -exact $badcolors $colormap($child)] < 0} {
1051 1053 lappend badcolors $colormap($child)
1052 1054 }
1053 1055 if {[info exists parents($child)]} {
1054 1056 foreach p $parents($child) {
1055 1057 if {[info exists colormap($p)]
1056 1058 && [lsearch -exact $badcolors $colormap($p)] < 0} {
1057 1059 lappend badcolors $colormap($p)
1058 1060 }
1059 1061 }
1060 1062 }
1061 1063 }
1062 1064 if {[llength $badcolors] >= $ncolors} {
1063 1065 set badcolors $origbad
1064 1066 }
1065 1067 }
1066 1068 for {set i 0} {$i <= $ncolors} {incr i} {
1067 1069 set c [lindex $colors $nextcolor]
1068 1070 if {[incr nextcolor] >= $ncolors} {
1069 1071 set nextcolor 0
1070 1072 }
1071 1073 if {[lsearch -exact $badcolors $c]} break
1072 1074 }
1073 1075 set colormap($id) $c
1074 1076 }
1075 1077
1076 1078 proc initgraph {} {
1077 1079 global canvy canvy0 lineno numcommits nextcolor linespc
1078 1080 global mainline mainlinearrow sidelines
1079 1081 global nchildren ncleft
1080 1082 global displist nhyperspace
1081 1083
1082 1084 allcanvs delete all
1083 1085 set nextcolor 0
1084 1086 set canvy $canvy0
1085 1087 set lineno -1
1086 1088 set numcommits 0
1087 1089 catch {unset mainline}
1088 1090 catch {unset mainlinearrow}
1089 1091 catch {unset sidelines}
1090 1092 foreach id [array names nchildren] {
1091 1093 set ncleft($id) $nchildren($id)
1092 1094 }
1093 1095 set displist {}
1094 1096 set nhyperspace 0
1095 1097 }
1096 1098
1097 1099 proc bindline {t id} {
1098 1100 global canv
1099 1101
1100 1102 $canv bind $t <Enter> "lineenter %x %y $id"
1101 1103 $canv bind $t <Motion> "linemotion %x %y $id"
1102 1104 $canv bind $t <Leave> "lineleave $id"
1103 1105 $canv bind $t <Button-1> "lineclick %x %y $id 1"
1104 1106 }
1105 1107
1106 1108 proc drawlines {id xtra} {
1107 1109 global mainline mainlinearrow sidelines lthickness colormap canv
1108 1110
1109 1111 $canv delete lines.$id
1110 1112 if {[info exists mainline($id)]} {
1111 1113 set t [$canv create line $mainline($id) \
1112 1114 -width [expr {($xtra + 1) * $lthickness}] \
1113 1115 -fill $colormap($id) -tags lines.$id \
1114 1116 -arrow $mainlinearrow($id)]
1115 1117 $canv lower $t
1116 1118 bindline $t $id
1117 1119 }
1118 1120 if {[info exists sidelines($id)]} {
1119 1121 foreach ls $sidelines($id) {
1120 1122 set coords [lindex $ls 0]
1121 1123 set thick [lindex $ls 1]
1122 1124 set arrow [lindex $ls 2]
1123 1125 set t [$canv create line $coords -fill $colormap($id) \
1124 1126 -width [expr {($thick + $xtra) * $lthickness}] \
1125 1127 -arrow $arrow -tags lines.$id]
1126 1128 $canv lower $t
1127 1129 bindline $t $id
1128 1130 }
1129 1131 }
1130 1132 }
1131 1133
1132 1134 # level here is an index in displist
1133 1135 proc drawcommitline {level} {
1134 1136 global parents children nparents displist
1135 1137 global canv canv2 canv3 mainfont namefont canvy linespc
1136 1138 global lineid linehtag linentag linedtag commitinfo
1137 1139 global colormap numcommits currentparents dupparents
1138 1140 global idtags idline idheads idotherrefs idbookmarks
1139 1141 global lineno lthickness mainline mainlinearrow sidelines
1140 1142 global commitlisted rowtextx idpos lastuse displist
1141 1143 global oldnlines olddlevel olddisplist
1142 global aucolormap curid curidfont
1144 global aucolormap curid curidfont obsolete
1143 1145
1144 1146 incr numcommits
1145 1147 incr lineno
1146 1148 set id [lindex $displist $level]
1147 1149 set lastuse($id) $lineno
1148 1150 set lineid($lineno) $id
1149 1151 set idline($id) $lineno
1150 1152 set ofill [expr {[info exists commitlisted($id)]? "blue": "white"}]
1151 1153 if {![info exists commitinfo($id)]} {
1152 1154 readcommit $id
1153 1155 if {![info exists commitinfo($id)]} {
1154 1156 set commitinfo($id) {"No commit information available"}
1155 1157 set nparents($id) 0
1156 1158 }
1157 1159 }
1160 if {[info exists obsolete($id)]} {
1161 set ofill darkgrey
1162 }
1158 1163 assigncolor $id
1159 1164 set currentparents {}
1160 1165 set dupparents {}
1161 1166 if {[info exists commitlisted($id)] && [info exists parents($id)]} {
1162 1167 foreach p $parents($id) {
1163 1168 if {[lsearch -exact $currentparents $p] < 0} {
1164 1169 lappend currentparents $p
1165 1170 } else {
1166 1171 # remember that this parent was listed twice
1167 1172 lappend dupparents $p
1168 1173 }
1169 1174 }
1170 1175 }
1171 1176 set x [xcoord $level $level $lineno]
1172 1177 set y1 $canvy
1173 1178 set canvy [expr $canvy + $linespc]
1174 1179 allcanvs conf -scrollregion \
1175 1180 [list 0 0 0 [expr $y1 + 0.5 * $linespc + 2]]
1176 1181 if {[info exists mainline($id)]} {
1177 1182 lappend mainline($id) $x $y1
1178 1183 if {$mainlinearrow($id) ne "none"} {
1179 1184 set mainline($id) [trimdiagstart $mainline($id)]
1180 1185 }
1181 1186 }
1182 1187 drawlines $id 0
1183 1188 set orad [expr {$linespc / 3}]
1184 1189 set t [$canv create oval [expr $x - $orad] [expr $y1 - $orad] \
1185 1190 [expr $x + $orad - 1] [expr $y1 + $orad - 1] \
1186 1191 -fill $ofill -outline black -width 1]
1187 1192 $canv raise $t
1188 1193 $canv bind $t <1> {selcanvline {} %x %y}
1189 1194 set xt [xcoord [llength $displist] $level $lineno]
1190 1195 if {[llength $currentparents] > 2} {
1191 1196 set xt [expr {$xt + ([llength $currentparents] - 2) * $linespc}]
1192 1197 }
1193 1198 set rowtextx($lineno) $xt
1194 1199 set idpos($id) [list $x $xt $y1]
1195 1200 if {[info exists idtags($id)] || [info exists idheads($id)]
1196 1201 || [info exists idotherrefs($id)] || [info exists idbookmarks($id)]} {
1197 1202 set xt [drawtags $id $x $xt $y1]
1198 1203 }
1199 1204 set headline [lindex $commitinfo($id) 0]
1200 1205 set name [lindex $commitinfo($id) 1]
1201 1206 assignauthorcolor $name
1202 1207 set fg $aucolormap($name)
1203 1208 if {$id == $curid} {
1204 1209 set fn $curidfont
1205 1210 } else {
1206 1211 set fn $mainfont
1207 1212 }
1208 1213
1209 1214 set date [lindex $commitinfo($id) 2]
1210 1215 set linehtag($lineno) [$canv create text $xt $y1 -anchor w \
1211 1216 -text $headline -font $fn \
1212 1217 -fill $fg]
1213 1218 $canv bind $linehtag($lineno) <<B3>> "rowmenu %X %Y $id"
1214 1219 set linentag($lineno) [$canv2 create text 3 $y1 -anchor w \
1215 1220 -text $name -font $namefont \
1216 1221 -fill $fg]
1217 1222 set linedtag($lineno) [$canv3 create text 3 $y1 -anchor w \
1218 1223 -text $date -font $mainfont \
1219 1224 -fill $fg]
1220 1225
1221 1226 set olddlevel $level
1222 1227 set olddisplist $displist
1223 1228 set oldnlines [llength $displist]
1224 1229 }
1225 1230
1226 1231 proc drawtags {id x xt y1} {
1227 1232 global bookmarkcurrent idtags idbookmarks idheads idotherrefs commitinfo
1228 1233 global linespc lthickness
1229 1234 global canv mainfont idline rowtextx
1230 1235
1231 1236 set marks {}
1232 1237 set nbookmarks 0
1233 1238 set ntags 0
1234 1239 set nheads 0
1235 1240 if {[info exists idtags($id)]} {
1236 1241 set marks $idtags($id)
1237 1242 set ntags [llength $marks]
1238 1243 }
1239 1244 if {[info exists idbookmarks($id)]} {
1240 1245 set marks [concat $marks $idbookmarks($id)]
1241 1246 set nbookmarks [llength $idbookmarks($id)]
1242 1247 }
1243 1248 if {[info exists idheads($id)]} {
1244 1249 set headmark [lindex $commitinfo($id) 7]
1245 1250 if {$headmark ne "default"} {
1246 1251 lappend marks $headmark
1247 1252 set nheads 1
1248 1253 }
1249 1254 }
1250 1255 if {$marks eq {}} {
1251 1256 return $xt
1252 1257 }
1253 1258
1254 1259 set delta [expr {int(0.5 * ($linespc - $lthickness))}]
1255 1260 set yt [expr $y1 - 0.5 * $linespc]
1256 1261 set yb [expr $yt + $linespc - 1]
1257 1262 set xvals {}
1258 1263 set wvals {}
1259 1264 foreach tag $marks {
1260 1265 set wid [font measure $mainfont $tag]
1261 1266 lappend xvals $xt
1262 1267 lappend wvals $wid
1263 1268 set xt [expr {$xt + $delta + $wid + $lthickness + $linespc}]
1264 1269 }
1265 1270 set t [$canv create line $x $y1 [lindex $xvals end] $y1 \
1266 1271 -width $lthickness -fill black -tags tag.$id]
1267 1272 $canv lower $t
1268 1273 foreach tag $marks x $xvals wid $wvals {
1269 1274 set xl [expr $x + $delta]
1270 1275 set xr [expr $x + $delta + $wid + $lthickness]
1271 1276 if {[incr ntags -1] >= 0} {
1272 1277 # draw a tag
1273 1278 set t [$canv create polygon $x [expr $yt + $delta] $xl $yt \
1274 1279 $xr $yt $xr $yb $xl $yb $x [expr $yb - $delta] \
1275 1280 -width 1 -outline black -fill yellow -tags tag.$id]
1276 1281 $canv bind $t <1> [list showtag $tag 1]
1277 1282 set rowtextx($idline($id)) [expr {$xr + $linespc}]
1278 1283 } elseif {[incr nbookmarks -1] >= 0} {
1279 1284 # draw a tag
1280 1285 set col "#7f7f7f"
1281 1286 if {[string compare $bookmarkcurrent $tag] == 0} {
1282 1287 set col "#bebebe"
1283 1288 }
1284 1289 set xl [expr $xl - $delta/2]
1285 1290 $canv create polygon $x $yt $xr $yt $xr $yb $x $yb \
1286 1291 -width 1 -outline black -fill $col -tags tag.$id
1287 1292 } else {
1288 1293 # draw a head or other ref
1289 1294 if {[incr nheads -1] >= 0} {
1290 1295 set col "#00ff00"
1291 1296 } else {
1292 1297 set col "#ddddff"
1293 1298 }
1294 1299 set xl [expr $xl - $delta/2]
1295 1300 $canv create polygon $x $yt $xr $yt $xr $yb $x $yb \
1296 1301 -width 1 -outline black -fill $col -tags tag.$id
1297 1302 }
1298 1303 set t [$canv create text $xl $y1 -anchor w -text $tag \
1299 1304 -font $mainfont -tags tag.$id]
1300 1305 if {$ntags >= 0} {
1301 1306 $canv bind $t <1> [list showtag $tag 1]
1302 1307 }
1303 1308 }
1304 1309 return $xt
1305 1310 }
1306 1311
1307 1312 proc notecrossings {id lo hi corner} {
1308 1313 global olddisplist crossings cornercrossings
1309 1314
1310 1315 for {set i $lo} {[incr i] < $hi} {} {
1311 1316 set p [lindex $olddisplist $i]
1312 1317 if {$p == {}} continue
1313 1318 if {$i == $corner} {
1314 1319 if {![info exists cornercrossings($id)]
1315 1320 || [lsearch -exact $cornercrossings($id) $p] < 0} {
1316 1321 lappend cornercrossings($id) $p
1317 1322 }
1318 1323 if {![info exists cornercrossings($p)]
1319 1324 || [lsearch -exact $cornercrossings($p) $id] < 0} {
1320 1325 lappend cornercrossings($p) $id
1321 1326 }
1322 1327 } else {
1323 1328 if {![info exists crossings($id)]
1324 1329 || [lsearch -exact $crossings($id) $p] < 0} {
1325 1330 lappend crossings($id) $p
1326 1331 }
1327 1332 if {![info exists crossings($p)]
1328 1333 || [lsearch -exact $crossings($p) $id] < 0} {
1329 1334 lappend crossings($p) $id
1330 1335 }
1331 1336 }
1332 1337 }
1333 1338 }
1334 1339
1335 1340 proc xcoord {i level ln} {
1336 1341 global canvx0 xspc1 xspc2
1337 1342
1338 1343 set x [expr {$canvx0 + $i * $xspc1($ln)}]
1339 1344 if {$i > 0 && $i == $level} {
1340 1345 set x [expr {$x + 0.5 * ($xspc2 - $xspc1($ln))}]
1341 1346 } elseif {$i > $level} {
1342 1347 set x [expr {$x + $xspc2 - $xspc1($ln)}]
1343 1348 }
1344 1349 return $x
1345 1350 }
1346 1351
1347 1352 # it seems Tk can't draw arrows on the end of diagonal line segments...
1348 1353 proc trimdiagend {line} {
1349 1354 while {[llength $line] > 4} {
1350 1355 set x1 [lindex $line end-3]
1351 1356 set y1 [lindex $line end-2]
1352 1357 set x2 [lindex $line end-1]
1353 1358 set y2 [lindex $line end]
1354 1359 if {($x1 == $x2) != ($y1 == $y2)} break
1355 1360 set line [lreplace $line end-1 end]
1356 1361 }
1357 1362 return $line
1358 1363 }
1359 1364
1360 1365 proc trimdiagstart {line} {
1361 1366 while {[llength $line] > 4} {
1362 1367 set x1 [lindex $line 0]
1363 1368 set y1 [lindex $line 1]
1364 1369 set x2 [lindex $line 2]
1365 1370 set y2 [lindex $line 3]
1366 1371 if {($x1 == $x2) != ($y1 == $y2)} break
1367 1372 set line [lreplace $line 0 1]
1368 1373 }
1369 1374 return $line
1370 1375 }
1371 1376
1372 1377 proc drawslants {id needonscreen nohs} {
1373 1378 global canv mainline mainlinearrow sidelines
1374 1379 global canvx0 canvy xspc1 xspc2 lthickness
1375 1380 global currentparents dupparents
1376 1381 global lthickness linespc canvy colormap lineno geometry
1377 1382 global maxgraphpct maxwidth
1378 1383 global displist onscreen lastuse
1379 1384 global parents commitlisted
1380 1385 global oldnlines olddlevel olddisplist
1381 1386 global nhyperspace numcommits nnewparents
1382 1387
1383 1388 if {$lineno < 0} {
1384 1389 lappend displist $id
1385 1390 set onscreen($id) 1
1386 1391 return 0
1387 1392 }
1388 1393
1389 1394 set y1 [expr {$canvy - $linespc}]
1390 1395 set y2 $canvy
1391 1396
1392 1397 # work out what we need to get back on screen
1393 1398 set reins {}
1394 1399 if {$onscreen($id) < 0} {
1395 1400 # next to do isn't displayed, better get it on screen...
1396 1401 lappend reins [list $id 0]
1397 1402 }
1398 1403 # make sure all the previous commits's parents are on the screen
1399 1404 foreach p $currentparents {
1400 1405 if {$onscreen($p) < 0} {
1401 1406 lappend reins [list $p 0]
1402 1407 }
1403 1408 }
1404 1409 # bring back anything requested by caller
1405 1410 if {$needonscreen ne {}} {
1406 1411 lappend reins $needonscreen
1407 1412 }
1408 1413
1409 1414 # try the shortcut
1410 1415 if {$currentparents == $id && $onscreen($id) == 0 && $reins eq {}} {
1411 1416 set dlevel $olddlevel
1412 1417 set x [xcoord $dlevel $dlevel $lineno]
1413 1418 set mainline($id) [list $x $y1]
1414 1419 set mainlinearrow($id) none
1415 1420 set lastuse($id) $lineno
1416 1421 set displist [lreplace $displist $dlevel $dlevel $id]
1417 1422 set onscreen($id) 1
1418 1423 set xspc1([expr {$lineno + 1}]) $xspc1($lineno)
1419 1424 return $dlevel
1420 1425 }
1421 1426
1422 1427 # update displist
1423 1428 set displist [lreplace $displist $olddlevel $olddlevel]
1424 1429 set j $olddlevel
1425 1430 foreach p $currentparents {
1426 1431 set lastuse($p) $lineno
1427 1432 if {$onscreen($p) == 0} {
1428 1433 set displist [linsert $displist $j $p]
1429 1434 set onscreen($p) 1
1430 1435 incr j
1431 1436 }
1432 1437 }
1433 1438 if {$onscreen($id) == 0} {
1434 1439 lappend displist $id
1435 1440 set onscreen($id) 1
1436 1441 }
1437 1442
1438 1443 # remove the null entry if present
1439 1444 set nullentry [lsearch -exact $displist {}]
1440 1445 if {$nullentry >= 0} {
1441 1446 set displist [lreplace $displist $nullentry $nullentry]
1442 1447 }
1443 1448
1444 1449 # bring back the ones we need now (if we did it earlier
1445 1450 # it would change displist and invalidate olddlevel)
1446 1451 foreach pi $reins {
1447 1452 # test again in case of duplicates in reins
1448 1453 set p [lindex $pi 0]
1449 1454 if {$onscreen($p) < 0} {
1450 1455 set onscreen($p) 1
1451 1456 set lastuse($p) $lineno
1452 1457 set displist [linsert $displist [lindex $pi 1] $p]
1453 1458 incr nhyperspace -1
1454 1459 }
1455 1460 }
1456 1461
1457 1462 set lastuse($id) $lineno
1458 1463
1459 1464 # see if we need to make any lines jump off into hyperspace
1460 1465 set displ [llength $displist]
1461 1466 if {$displ > $maxwidth} {
1462 1467 set ages {}
1463 1468 foreach x $displist {
1464 1469 lappend ages [list $lastuse($x) $x]
1465 1470 }
1466 1471 set ages [lsort -integer -index 0 $ages]
1467 1472 set k 0
1468 1473 while {$displ > $maxwidth} {
1469 1474 set use [lindex $ages $k 0]
1470 1475 set victim [lindex $ages $k 1]
1471 1476 if {$use >= $lineno - 5} break
1472 1477 incr k
1473 1478 if {[lsearch -exact $nohs $victim] >= 0} continue
1474 1479 set i [lsearch -exact $displist $victim]
1475 1480 set displist [lreplace $displist $i $i]
1476 1481 set onscreen($victim) -1
1477 1482 incr nhyperspace
1478 1483 incr displ -1
1479 1484 if {$i < $nullentry} {
1480 1485 incr nullentry -1
1481 1486 }
1482 1487 set x [lindex $mainline($victim) end-1]
1483 1488 lappend mainline($victim) $x $y1
1484 1489 set line [trimdiagend $mainline($victim)]
1485 1490 set arrow "last"
1486 1491 if {$mainlinearrow($victim) ne "none"} {
1487 1492 set line [trimdiagstart $line]
1488 1493 set arrow "both"
1489 1494 }
1490 1495 lappend sidelines($victim) [list $line 1 $arrow]
1491 1496 unset mainline($victim)
1492 1497 }
1493 1498 }
1494 1499
1495 1500 set dlevel [lsearch -exact $displist $id]
1496 1501
1497 1502 # If we are reducing, put in a null entry
1498 1503 if {$displ < $oldnlines} {
1499 1504 # does the next line look like a merge?
1500 1505 # i.e. does it have > 1 new parent?
1501 1506 if {$nnewparents($id) > 1} {
1502 1507 set i [expr {$dlevel + 1}]
1503 1508 } elseif {$nnewparents([lindex $olddisplist $olddlevel]) == 0} {
1504 1509 set i $olddlevel
1505 1510 if {$nullentry >= 0 && $nullentry < $i} {
1506 1511 incr i -1
1507 1512 }
1508 1513 } elseif {$nullentry >= 0} {
1509 1514 set i $nullentry
1510 1515 while {$i < $displ
1511 1516 && [lindex $olddisplist $i] == [lindex $displist $i]} {
1512 1517 incr i
1513 1518 }
1514 1519 } else {
1515 1520 set i $olddlevel
1516 1521 if {$dlevel >= $i} {
1517 1522 incr i
1518 1523 }
1519 1524 }
1520 1525 if {$i < $displ} {
1521 1526 set displist [linsert $displist $i {}]
1522 1527 incr displ
1523 1528 if {$dlevel >= $i} {
1524 1529 incr dlevel
1525 1530 }
1526 1531 }
1527 1532 }
1528 1533
1529 1534 # decide on the line spacing for the next line
1530 1535 set lj [expr {$lineno + 1}]
1531 1536 set maxw [expr {$maxgraphpct * $geometry(canv1) / 100}]
1532 1537 if {$displ <= 1 || $canvx0 + $displ * $xspc2 <= $maxw} {
1533 1538 set xspc1($lj) $xspc2
1534 1539 } else {
1535 1540 set xspc1($lj) [expr {($maxw - $canvx0 - $xspc2) / ($displ - 1)}]
1536 1541 if {$xspc1($lj) < $lthickness} {
1537 1542 set xspc1($lj) $lthickness
1538 1543 }
1539 1544 }
1540 1545
1541 1546 foreach idi $reins {
1542 1547 set id [lindex $idi 0]
1543 1548 set j [lsearch -exact $displist $id]
1544 1549 set xj [xcoord $j $dlevel $lj]
1545 1550 set mainline($id) [list $xj $y2]
1546 1551 set mainlinearrow($id) first
1547 1552 }
1548 1553
1549 1554 set i -1
1550 1555 foreach id $olddisplist {
1551 1556 incr i
1552 1557 if {$id == {}} continue
1553 1558 if {$onscreen($id) <= 0} continue
1554 1559 set xi [xcoord $i $olddlevel $lineno]
1555 1560 if {$i == $olddlevel} {
1556 1561 foreach p $currentparents {
1557 1562 set j [lsearch -exact $displist $p]
1558 1563 set coords [list $xi $y1]
1559 1564 set xj [xcoord $j $dlevel $lj]
1560 1565 if {$xj < $xi - $linespc} {
1561 1566 lappend coords [expr {$xj + $linespc}] $y1
1562 1567 notecrossings $p $j $i [expr {$j + 1}]
1563 1568 } elseif {$xj > $xi + $linespc} {
1564 1569 lappend coords [expr {$xj - $linespc}] $y1
1565 1570 notecrossings $p $i $j [expr {$j - 1}]
1566 1571 }
1567 1572 if {[lsearch -exact $dupparents $p] >= 0} {
1568 1573 # draw a double-width line to indicate the doubled parent
1569 1574 lappend coords $xj $y2
1570 1575 lappend sidelines($p) [list $coords 2 none]
1571 1576 if {![info exists mainline($p)]} {
1572 1577 set mainline($p) [list $xj $y2]
1573 1578 set mainlinearrow($p) none
1574 1579 }
1575 1580 } else {
1576 1581 # normal case, no parent duplicated
1577 1582 set yb $y2
1578 1583 set dx [expr {abs($xi - $xj)}]
1579 1584 if {0 && $dx < $linespc} {
1580 1585 set yb [expr {$y1 + $dx}]
1581 1586 }
1582 1587 if {![info exists mainline($p)]} {
1583 1588 if {$xi != $xj} {
1584 1589 lappend coords $xj $yb
1585 1590 }
1586 1591 set mainline($p) $coords
1587 1592 set mainlinearrow($p) none
1588 1593 } else {
1589 1594 lappend coords $xj $yb
1590 1595 if {$yb < $y2} {
1591 1596 lappend coords $xj $y2
1592 1597 }
1593 1598 lappend sidelines($p) [list $coords 1 none]
1594 1599 }
1595 1600 }
1596 1601 }
1597 1602 } else {
1598 1603 set j $i
1599 1604 if {[lindex $displist $i] != $id} {
1600 1605 set j [lsearch -exact $displist $id]
1601 1606 }
1602 1607 if {$j != $i || $xspc1($lineno) != $xspc1($lj)
1603 1608 || ($olddlevel < $i && $i < $dlevel)
1604 1609 || ($dlevel < $i && $i < $olddlevel)} {
1605 1610 set xj [xcoord $j $dlevel $lj]
1606 1611 lappend mainline($id) $xi $y1 $xj $y2
1607 1612 }
1608 1613 }
1609 1614 }
1610 1615 return $dlevel
1611 1616 }
1612 1617
1613 1618 # search for x in a list of lists
1614 1619 proc llsearch {llist x} {
1615 1620 set i 0
1616 1621 foreach l $llist {
1617 1622 if {$l == $x || [lsearch -exact $l $x] >= 0} {
1618 1623 return $i
1619 1624 }
1620 1625 incr i
1621 1626 }
1622 1627 return -1
1623 1628 }
1624 1629
1625 1630 proc drawmore {reading} {
1626 1631 global displayorder numcommits ncmupdate nextupdate
1627 1632 global stopped nhyperspace parents commitlisted
1628 1633 global maxwidth onscreen displist currentparents olddlevel
1629 1634
1630 1635 set n [llength $displayorder]
1631 1636 while {$numcommits < $n} {
1632 1637 set id [lindex $displayorder $numcommits]
1633 1638 set ctxend [expr {$numcommits + 10}]
1634 1639 if {!$reading && $ctxend > $n} {
1635 1640 set ctxend $n
1636 1641 }
1637 1642 set dlist {}
1638 1643 if {$numcommits > 0} {
1639 1644 set dlist [lreplace $displist $olddlevel $olddlevel]
1640 1645 set i $olddlevel
1641 1646 foreach p $currentparents {
1642 1647 if {$onscreen($p) == 0} {
1643 1648 set dlist [linsert $dlist $i $p]
1644 1649 incr i
1645 1650 }
1646 1651 }
1647 1652 }
1648 1653 set nohs {}
1649 1654 set reins {}
1650 1655 set isfat [expr {[llength $dlist] > $maxwidth}]
1651 1656 if {$nhyperspace > 0 || $isfat} {
1652 1657 if {$ctxend > $n} break
1653 1658 # work out what to bring back and
1654 1659 # what we want to don't want to send into hyperspace
1655 1660 set room 1
1656 1661 for {set k $numcommits} {$k < $ctxend} {incr k} {
1657 1662 set x [lindex $displayorder $k]
1658 1663 set i [llsearch $dlist $x]
1659 1664 if {$i < 0} {
1660 1665 set i [llength $dlist]
1661 1666 lappend dlist $x
1662 1667 }
1663 1668 if {[lsearch -exact $nohs $x] < 0} {
1664 1669 lappend nohs $x
1665 1670 }
1666 1671 if {$reins eq {} && $onscreen($x) < 0 && $room} {
1667 1672 set reins [list $x $i]
1668 1673 }
1669 1674 set newp {}
1670 1675 if {[info exists commitlisted($x)]} {
1671 1676 set right 0
1672 1677 foreach p $parents($x) {
1673 1678 if {[llsearch $dlist $p] < 0} {
1674 1679 lappend newp $p
1675 1680 if {[lsearch -exact $nohs $p] < 0} {
1676 1681 lappend nohs $p
1677 1682 }
1678 1683 if {$reins eq {} && $onscreen($p) < 0 && $room} {
1679 1684 set reins [list $p [expr {$i + $right}]]
1680 1685 }
1681 1686 }
1682 1687 set right 1
1683 1688 }
1684 1689 }
1685 1690 set l [lindex $dlist $i]
1686 1691 if {[llength $l] == 1} {
1687 1692 set l $newp
1688 1693 } else {
1689 1694 set j [lsearch -exact $l $x]
1690 1695 set l [concat [lreplace $l $j $j] $newp]
1691 1696 }
1692 1697 set dlist [lreplace $dlist $i $i $l]
1693 1698 if {$room && $isfat && [llength $newp] <= 1} {
1694 1699 set room 0
1695 1700 }
1696 1701 }
1697 1702 }
1698 1703
1699 1704 set dlevel [drawslants $id $reins $nohs]
1700 1705 drawcommitline $dlevel
1701 1706 if {[clock clicks -milliseconds] >= $nextupdate
1702 1707 && $numcommits >= $ncmupdate} {
1703 1708 doupdate $reading
1704 1709 if {$stopped} break
1705 1710 }
1706 1711 }
1707 1712 }
1708 1713
1709 1714 # level here is an index in todo
1710 1715 proc updatetodo {level noshortcut} {
1711 1716 global ncleft todo nnewparents
1712 1717 global commitlisted parents onscreen
1713 1718
1714 1719 set id [lindex $todo $level]
1715 1720 set olds {}
1716 1721 if {[info exists commitlisted($id)]} {
1717 1722 foreach p $parents($id) {
1718 1723 if {[lsearch -exact $olds $p] < 0} {
1719 1724 lappend olds $p
1720 1725 }
1721 1726 }
1722 1727 }
1723 1728 if {!$noshortcut && [llength $olds] == 1} {
1724 1729 set p [lindex $olds 0]
1725 1730 if {$ncleft($p) == 1 && [lsearch -exact $todo $p] < 0} {
1726 1731 set ncleft($p) 0
1727 1732 set todo [lreplace $todo $level $level $p]
1728 1733 set onscreen($p) 0
1729 1734 set nnewparents($id) 1
1730 1735 return 0
1731 1736 }
1732 1737 }
1733 1738
1734 1739 set todo [lreplace $todo $level $level]
1735 1740 set i $level
1736 1741 set n 0
1737 1742 foreach p $olds {
1738 1743 incr ncleft($p) -1
1739 1744 set k [lsearch -exact $todo $p]
1740 1745 if {$k < 0} {
1741 1746 set todo [linsert $todo $i $p]
1742 1747 set onscreen($p) 0
1743 1748 incr i
1744 1749 incr n
1745 1750 }
1746 1751 }
1747 1752 set nnewparents($id) $n
1748 1753
1749 1754 return 1
1750 1755 }
1751 1756
1752 1757 proc decidenext {{noread 0}} {
1753 1758 global ncleft todo
1754 1759 global datemode cdate
1755 1760 global commitinfo
1756 1761
1757 1762 # choose which one to do next time around
1758 1763 set todol [llength $todo]
1759 1764 set level -1
1760 1765 set latest {}
1761 1766 for {set k $todol} {[incr k -1] >= 0} {} {
1762 1767 set p [lindex $todo $k]
1763 1768 if {$ncleft($p) == 0} {
1764 1769 if {$datemode} {
1765 1770 if {![info exists commitinfo($p)]} {
1766 1771 if {$noread} {
1767 1772 return {}
1768 1773 }
1769 1774 readcommit $p
1770 1775 }
1771 1776 if {$latest == {} || $cdate($p) > $latest} {
1772 1777 set level $k
1773 1778 set latest $cdate($p)
1774 1779 }
1775 1780 } else {
1776 1781 set level $k
1777 1782 break
1778 1783 }
1779 1784 }
1780 1785 }
1781 1786 if {$level < 0} {
1782 1787 if {$todo != {}} {
1783 1788 puts "ERROR: none of the pending commits can be done yet:"
1784 1789 foreach p $todo {
1785 1790 puts " $p ($ncleft($p))"
1786 1791 }
1787 1792 }
1788 1793 return -1
1789 1794 }
1790 1795
1791 1796 return $level
1792 1797 }
1793 1798
1794 1799 proc drawcommit {id} {
1795 1800 global phase todo nchildren datemode nextupdate
1796 1801 global numcommits ncmupdate displayorder todo onscreen
1797 1802
1798 1803 if {$phase != "incrdraw"} {
1799 1804 set phase incrdraw
1800 1805 set displayorder {}
1801 1806 set todo {}
1802 1807 initgraph
1803 1808 }
1804 1809 if {$nchildren($id) == 0} {
1805 1810 lappend todo $id
1806 1811 set onscreen($id) 0
1807 1812 }
1808 1813 set level [decidenext 1]
1809 1814 if {$level == {} || $id != [lindex $todo $level]} {
1810 1815 return
1811 1816 }
1812 1817 while 1 {
1813 1818 lappend displayorder [lindex $todo $level]
1814 1819 if {[updatetodo $level $datemode]} {
1815 1820 set level [decidenext 1]
1816 1821 if {$level == {}} break
1817 1822 }
1818 1823 set id [lindex $todo $level]
1819 1824 if {![info exists commitlisted($id)]} {
1820 1825 break
1821 1826 }
1822 1827 }
1823 1828 drawmore 1
1824 1829 }
1825 1830
1826 1831 proc finishcommits {} {
1827 1832 global phase
1828 1833 global canv mainfont ctext maincursor textcursor
1829 1834
1830 1835 if {$phase != "incrdraw"} {
1831 1836 $canv delete all
1832 1837 $canv create text 3 3 -anchor nw -text "No commits selected" \
1833 1838 -font $mainfont -tags textitems
1834 1839 set phase {}
1835 1840 } else {
1836 1841 drawrest
1837 1842 }
1838 1843 . config -cursor $maincursor
1839 1844 settextcursor $textcursor
1840 1845 }
1841 1846
1842 1847 # Don't change the text pane cursor if it is currently the hand cursor,
1843 1848 # showing that we are over a sha1 ID link.
1844 1849 proc settextcursor {c} {
1845 1850 global ctext curtextcursor
1846 1851
1847 1852 if {[$ctext cget -cursor] == $curtextcursor} {
1848 1853 $ctext config -cursor $c
1849 1854 }
1850 1855 set curtextcursor $c
1851 1856 }
1852 1857
1853 1858 proc drawgraph {} {
1854 1859 global nextupdate startmsecs ncmupdate
1855 1860 global displayorder onscreen
1856 1861
1857 1862 if {$displayorder == {}} return
1858 1863 set startmsecs [clock clicks -milliseconds]
1859 1864 set nextupdate [expr $startmsecs + 100]
1860 1865 set ncmupdate 1
1861 1866 initgraph
1862 1867 foreach id $displayorder {
1863 1868 set onscreen($id) 0
1864 1869 }
1865 1870 drawmore 0
1866 1871 }
1867 1872
1868 1873 proc drawrest {} {
1869 1874 global phase stopped redisplaying selectedline
1870 1875 global datemode todo displayorder
1871 1876 global numcommits ncmupdate
1872 1877 global nextupdate startmsecs
1873 1878
1874 1879 set level [decidenext]
1875 1880 if {$level >= 0} {
1876 1881 set phase drawgraph
1877 1882 while 1 {
1878 1883 lappend displayorder [lindex $todo $level]
1879 1884 set hard [updatetodo $level $datemode]
1880 1885 if {$hard} {
1881 1886 set level [decidenext]
1882 1887 if {$level < 0} break
1883 1888 }
1884 1889 }
1885 1890 drawmore 0
1886 1891 }
1887 1892 set phase {}
1888 1893 set drawmsecs [expr [clock clicks -milliseconds] - $startmsecs]
1889 1894 #puts "overall $drawmsecs ms for $numcommits commits"
1890 1895 if {$redisplaying} {
1891 1896 if {$stopped == 0 && [info exists selectedline]} {
1892 1897 selectline $selectedline 0
1893 1898 }
1894 1899 if {$stopped == 1} {
1895 1900 set stopped 0
1896 1901 after idle drawgraph
1897 1902 } else {
1898 1903 set redisplaying 0
1899 1904 }
1900 1905 }
1901 1906 }
1902 1907
1903 1908 proc findmatches {f} {
1904 1909 global findtype foundstring foundstrlen
1905 1910 if {$findtype == "Regexp"} {
1906 1911 set matches [regexp -indices -all -inline $foundstring $f]
1907 1912 } else {
1908 1913 if {$findtype == "IgnCase"} {
1909 1914 set str [string tolower $f]
1910 1915 } else {
1911 1916 set str $f
1912 1917 }
1913 1918 set matches {}
1914 1919 set i 0
1915 1920 while {[set j [string first $foundstring $str $i]] >= 0} {
1916 1921 lappend matches [list $j [expr $j+$foundstrlen-1]]
1917 1922 set i [expr $j + $foundstrlen]
1918 1923 }
1919 1924 }
1920 1925 return $matches
1921 1926 }
1922 1927
1923 1928 proc dofind {} {
1924 1929 global findtype findloc findstring markedmatches commitinfo
1925 1930 global numcommits lineid linehtag linentag linedtag
1926 1931 global mainfont namefont canv canv2 canv3 selectedline
1927 1932 global matchinglines foundstring foundstrlen
1928 1933
1929 1934 stopfindproc
1930 1935 unmarkmatches
1931 1936 focus .
1932 1937 set matchinglines {}
1933 1938 if {$findloc == "Pickaxe"} {
1934 1939 findpatches
1935 1940 return
1936 1941 }
1937 1942 if {$findtype == "IgnCase"} {
1938 1943 set foundstring [string tolower $findstring]
1939 1944 } else {
1940 1945 set foundstring $findstring
1941 1946 }
1942 1947 set foundstrlen [string length $findstring]
1943 1948 if {$foundstrlen == 0} return
1944 1949 if {$findloc == "Files"} {
1945 1950 findfiles
1946 1951 return
1947 1952 }
1948 1953 if {![info exists selectedline]} {
1949 1954 set oldsel -1
1950 1955 } else {
1951 1956 set oldsel $selectedline
1952 1957 }
1953 1958 set didsel 0
1954 1959 set fldtypes {Headline Author Date CDate Comment}
1955 1960 for {set l 0} {$l < $numcommits} {incr l} {
1956 1961 set id $lineid($l)
1957 1962 set info $commitinfo($id)
1958 1963 set doesmatch 0
1959 1964 foreach f $info ty $fldtypes {
1960 1965 if {$findloc != "All fields" && $findloc != $ty} {
1961 1966 continue
1962 1967 }
1963 1968 set matches [findmatches $f]
1964 1969 if {$matches == {}} continue
1965 1970 set doesmatch 1
1966 1971 if {$ty == "Headline"} {
1967 1972 markmatches $canv $l $f $linehtag($l) $matches $mainfont
1968 1973 } elseif {$ty == "Author"} {
1969 1974 markmatches $canv2 $l $f $linentag($l) $matches $namefont
1970 1975 } elseif {$ty == "Date"} {
1971 1976 markmatches $canv3 $l $f $linedtag($l) $matches $mainfont
1972 1977 }
1973 1978 }
1974 1979 if {$doesmatch} {
1975 1980 lappend matchinglines $l
1976 1981 if {!$didsel && $l > $oldsel} {
1977 1982 findselectline $l
1978 1983 set didsel 1
1979 1984 }
1980 1985 }
1981 1986 }
1982 1987 if {$matchinglines == {}} {
1983 1988 bell
1984 1989 } elseif {!$didsel} {
1985 1990 findselectline [lindex $matchinglines 0]
1986 1991 }
1987 1992 }
1988 1993
1989 1994 proc findselectline {l} {
1990 1995 global findloc commentend ctext
1991 1996 selectline $l 1
1992 1997 if {$findloc == "All fields" || $findloc == "Comments"} {
1993 1998 # highlight the matches in the comments
1994 1999 set f [$ctext get 1.0 $commentend]
1995 2000 set matches [findmatches $f]
1996 2001 foreach match $matches {
1997 2002 set start [lindex $match 0]
1998 2003 set end [expr [lindex $match 1] + 1]
1999 2004 $ctext tag add found "1.0 + $start c" "1.0 + $end c"
2000 2005 }
2001 2006 }
2002 2007 }
2003 2008
2004 2009 proc findnext {restart} {
2005 2010 global matchinglines selectedline
2006 2011 if {![info exists matchinglines]} {
2007 2012 if {$restart} {
2008 2013 dofind
2009 2014 }
2010 2015 return
2011 2016 }
2012 2017 if {![info exists selectedline]} return
2013 2018 foreach l $matchinglines {
2014 2019 if {$l > $selectedline} {
2015 2020 findselectline $l
2016 2021 return
2017 2022 }
2018 2023 }
2019 2024 bell
2020 2025 }
2021 2026
2022 2027 proc findprev {} {
2023 2028 global matchinglines selectedline
2024 2029 if {![info exists matchinglines]} {
2025 2030 dofind
2026 2031 return
2027 2032 }
2028 2033 if {![info exists selectedline]} return
2029 2034 set prev {}
2030 2035 foreach l $matchinglines {
2031 2036 if {$l >= $selectedline} break
2032 2037 set prev $l
2033 2038 }
2034 2039 if {$prev != {}} {
2035 2040 findselectline $prev
2036 2041 } else {
2037 2042 bell
2038 2043 }
2039 2044 }
2040 2045
2041 2046 proc findlocchange {name ix op} {
2042 2047 global findloc findtype findtypemenu
2043 2048 if {$findloc == "Pickaxe"} {
2044 2049 set findtype Exact
2045 2050 set state disabled
2046 2051 } else {
2047 2052 set state normal
2048 2053 }
2049 2054 $findtypemenu entryconf 1 -state $state
2050 2055 $findtypemenu entryconf 2 -state $state
2051 2056 }
2052 2057
2053 2058 proc stopfindproc {{done 0}} {
2054 2059 global findprocpid findprocfile findids
2055 2060 global ctext findoldcursor phase maincursor textcursor
2056 2061 global findinprogress
2057 2062
2058 2063 catch {unset findids}
2059 2064 if {[info exists findprocpid]} {
2060 2065 if {!$done} {
2061 2066 catch {exec kill $findprocpid}
2062 2067 }
2063 2068 catch {close $findprocfile}
2064 2069 unset findprocpid
2065 2070 }
2066 2071 if {[info exists findinprogress]} {
2067 2072 unset findinprogress
2068 2073 if {$phase != "incrdraw"} {
2069 2074 . config -cursor $maincursor
2070 2075 settextcursor $textcursor
2071 2076 }
2072 2077 }
2073 2078 }
2074 2079
2075 2080 proc findpatches {} {
2076 2081 global findstring selectedline numcommits
2077 2082 global findprocpid findprocfile
2078 2083 global finddidsel ctext lineid findinprogress
2079 2084 global findinsertpos
2080 2085 global env
2081 2086
2082 2087 if {$numcommits == 0} return
2083 2088
2084 2089 # make a list of all the ids to search, starting at the one
2085 2090 # after the selected line (if any)
2086 2091 if {[info exists selectedline]} {
2087 2092 set l $selectedline
2088 2093 } else {
2089 2094 set l -1
2090 2095 }
2091 2096 set inputids {}
2092 2097 for {set i 0} {$i < $numcommits} {incr i} {
2093 2098 if {[incr l] >= $numcommits} {
2094 2099 set l 0
2095 2100 }
2096 2101 append inputids $lineid($l) "\n"
2097 2102 }
2098 2103
2099 2104 if {[catch {
2100 2105 set f [open [list | $env(HG) --config ui.report_untrusted=false debug-diff-tree --stdin -s -r -S$findstring << $inputids] r]
2101 2106 } err]} {
2102 2107 error_popup "Error starting search process: $err"
2103 2108 return
2104 2109 }
2105 2110
2106 2111 set findinsertpos end
2107 2112 set findprocfile $f
2108 2113 set findprocpid [pid $f]
2109 2114 fconfigure $f -blocking 0
2110 2115 fileevent $f readable readfindproc
2111 2116 set finddidsel 0
2112 2117 . config -cursor watch
2113 2118 settextcursor watch
2114 2119 set findinprogress 1
2115 2120 }
2116 2121
2117 2122 proc readfindproc {} {
2118 2123 global findprocfile finddidsel
2119 2124 global idline matchinglines findinsertpos
2120 2125
2121 2126 set n [gets $findprocfile line]
2122 2127 if {$n < 0} {
2123 2128 if {[eof $findprocfile]} {
2124 2129 stopfindproc 1
2125 2130 if {!$finddidsel} {
2126 2131 bell
2127 2132 }
2128 2133 }
2129 2134 return
2130 2135 }
2131 2136 if {![regexp {^[0-9a-f]{12}} $line id]} {
2132 2137 error_popup "Can't parse git-diff-tree output: $line"
2133 2138 stopfindproc
2134 2139 return
2135 2140 }
2136 2141 if {![info exists idline($id)]} {
2137 2142 puts stderr "spurious id: $id"
2138 2143 return
2139 2144 }
2140 2145 set l $idline($id)
2141 2146 insertmatch $l $id
2142 2147 }
2143 2148
2144 2149 proc insertmatch {l id} {
2145 2150 global matchinglines findinsertpos finddidsel
2146 2151
2147 2152 if {$findinsertpos == "end"} {
2148 2153 if {$matchinglines != {} && $l < [lindex $matchinglines 0]} {
2149 2154 set matchinglines [linsert $matchinglines 0 $l]
2150 2155 set findinsertpos 1
2151 2156 } else {
2152 2157 lappend matchinglines $l
2153 2158 }
2154 2159 } else {
2155 2160 set matchinglines [linsert $matchinglines $findinsertpos $l]
2156 2161 incr findinsertpos
2157 2162 }
2158 2163 markheadline $l $id
2159 2164 if {!$finddidsel} {
2160 2165 findselectline $l
2161 2166 set finddidsel 1
2162 2167 }
2163 2168 }
2164 2169
2165 2170 proc findfiles {} {
2166 2171 global selectedline numcommits lineid ctext
2167 2172 global ffileline finddidsel parents nparents
2168 2173 global findinprogress findstartline findinsertpos
2169 2174 global treediffs fdiffids fdiffsneeded fdiffpos
2170 2175 global findmergefiles
2171 2176 global env
2172 2177
2173 2178 if {$numcommits == 0} return
2174 2179
2175 2180 if {[info exists selectedline]} {
2176 2181 set l [expr {$selectedline + 1}]
2177 2182 } else {
2178 2183 set l 0
2179 2184 }
2180 2185 set ffileline $l
2181 2186 set findstartline $l
2182 2187 set diffsneeded {}
2183 2188 set fdiffsneeded {}
2184 2189 while 1 {
2185 2190 set id $lineid($l)
2186 2191 if {$findmergefiles || $nparents($id) == 1} {
2187 2192 foreach p $parents($id) {
2188 2193 if {![info exists treediffs([list $id $p])]} {
2189 2194 append diffsneeded "$id $p\n"
2190 2195 lappend fdiffsneeded [list $id $p]
2191 2196 }
2192 2197 }
2193 2198 }
2194 2199 if {[incr l] >= $numcommits} {
2195 2200 set l 0
2196 2201 }
2197 2202 if {$l == $findstartline} break
2198 2203 }
2199 2204
2200 2205 # start off a git-diff-tree process if needed
2201 2206 if {$diffsneeded ne {}} {
2202 2207 if {[catch {
2203 2208 set df [open [list | $env(HG) --config ui.report_untrusted=false debug-diff-tree -r --stdin << $diffsneeded] r]
2204 2209 } err ]} {
2205 2210 error_popup "Error starting search process: $err"
2206 2211 return
2207 2212 }
2208 2213 catch {unset fdiffids}
2209 2214 set fdiffpos 0
2210 2215 fconfigure $df -blocking 0
2211 2216 fileevent $df readable [list readfilediffs $df]
2212 2217 }
2213 2218
2214 2219 set finddidsel 0
2215 2220 set findinsertpos end
2216 2221 set id $lineid($l)
2217 2222 set p [lindex $parents($id) 0]
2218 2223 . config -cursor watch
2219 2224 settextcursor watch
2220 2225 set findinprogress 1
2221 2226 findcont [list $id $p]
2222 2227 update
2223 2228 }
2224 2229
2225 2230 proc readfilediffs {df} {
2226 2231 global findids fdiffids fdiffs
2227 2232
2228 2233 set n [gets $df line]
2229 2234 if {$n < 0} {
2230 2235 if {[eof $df]} {
2231 2236 donefilediff
2232 2237 if {[catch {close $df} err]} {
2233 2238 stopfindproc
2234 2239 bell
2235 2240 error_popup "Error in hg debug-diff-tree: $err"
2236 2241 } elseif {[info exists findids]} {
2237 2242 set ids $findids
2238 2243 stopfindproc
2239 2244 bell
2240 2245 error_popup "Couldn't find diffs for {$ids}"
2241 2246 }
2242 2247 }
2243 2248 return
2244 2249 }
2245 2250 if {[regexp {^([0-9a-f]{12}) \(from ([0-9a-f]{12})\)} $line match id p]} {
2246 2251 # start of a new string of diffs
2247 2252 donefilediff
2248 2253 set fdiffids [list $id $p]
2249 2254 set fdiffs {}
2250 2255 } elseif {[string match ":*" $line]} {
2251 2256 lappend fdiffs [lindex $line 5]
2252 2257 }
2253 2258 }
2254 2259
2255 2260 proc donefilediff {} {
2256 2261 global fdiffids fdiffs treediffs findids
2257 2262 global fdiffsneeded fdiffpos
2258 2263
2259 2264 if {[info exists fdiffids]} {
2260 2265 while {[lindex $fdiffsneeded $fdiffpos] ne $fdiffids
2261 2266 && $fdiffpos < [llength $fdiffsneeded]} {
2262 2267 # git-diff-tree doesn't output anything for a commit
2263 2268 # which doesn't change anything
2264 2269 set nullids [lindex $fdiffsneeded $fdiffpos]
2265 2270 set treediffs($nullids) {}
2266 2271 if {[info exists findids] && $nullids eq $findids} {
2267 2272 unset findids
2268 2273 findcont $nullids
2269 2274 }
2270 2275 incr fdiffpos
2271 2276 }
2272 2277 incr fdiffpos
2273 2278
2274 2279 if {![info exists treediffs($fdiffids)]} {
2275 2280 set treediffs($fdiffids) $fdiffs
2276 2281 }
2277 2282 if {[info exists findids] && $fdiffids eq $findids} {
2278 2283 unset findids
2279 2284 findcont $fdiffids
2280 2285 }
2281 2286 }
2282 2287 }
2283 2288
2284 2289 proc findcont {ids} {
2285 2290 global findids treediffs parents nparents
2286 2291 global ffileline findstartline finddidsel
2287 2292 global lineid numcommits matchinglines findinprogress
2288 2293 global findmergefiles
2289 2294
2290 2295 set id [lindex $ids 0]
2291 2296 set p [lindex $ids 1]
2292 2297 set pi [lsearch -exact $parents($id) $p]
2293 2298 set l $ffileline
2294 2299 while 1 {
2295 2300 if {$findmergefiles || $nparents($id) == 1} {
2296 2301 if {![info exists treediffs($ids)]} {
2297 2302 set findids $ids
2298 2303 set ffileline $l
2299 2304 return
2300 2305 }
2301 2306 set doesmatch 0
2302 2307 foreach f $treediffs($ids) {
2303 2308 set x [findmatches $f]
2304 2309 if {$x != {}} {
2305 2310 set doesmatch 1
2306 2311 break
2307 2312 }
2308 2313 }
2309 2314 if {$doesmatch} {
2310 2315 insertmatch $l $id
2311 2316 set pi $nparents($id)
2312 2317 }
2313 2318 } else {
2314 2319 set pi $nparents($id)
2315 2320 }
2316 2321 if {[incr pi] >= $nparents($id)} {
2317 2322 set pi 0
2318 2323 if {[incr l] >= $numcommits} {
2319 2324 set l 0
2320 2325 }
2321 2326 if {$l == $findstartline} break
2322 2327 set id $lineid($l)
2323 2328 }
2324 2329 set p [lindex $parents($id) $pi]
2325 2330 set ids [list $id $p]
2326 2331 }
2327 2332 stopfindproc
2328 2333 if {!$finddidsel} {
2329 2334 bell
2330 2335 }
2331 2336 }
2332 2337
2333 2338 # mark a commit as matching by putting a yellow background
2334 2339 # behind the headline
2335 2340 proc markheadline {l id} {
2336 2341 global canv mainfont linehtag commitinfo
2337 2342
2338 2343 set bbox [$canv bbox $linehtag($l)]
2339 2344 set t [$canv create rect $bbox -outline {} -tags matches -fill yellow]
2340 2345 $canv lower $t
2341 2346 }
2342 2347
2343 2348 # mark the bits of a headline, author or date that match a find string
2344 2349 proc markmatches {canv l str tag matches font} {
2345 2350 set bbox [$canv bbox $tag]
2346 2351 set x0 [lindex $bbox 0]
2347 2352 set y0 [lindex $bbox 1]
2348 2353 set y1 [lindex $bbox 3]
2349 2354 foreach match $matches {
2350 2355 set start [lindex $match 0]
2351 2356 set end [lindex $match 1]
2352 2357 if {$start > $end} continue
2353 2358 set xoff [font measure $font [string range $str 0 [expr $start-1]]]
2354 2359 set xlen [font measure $font [string range $str 0 [expr $end]]]
2355 2360 set t [$canv create rect [expr $x0+$xoff] $y0 [expr $x0+$xlen+2] $y1 \
2356 2361 -outline {} -tags matches -fill yellow]
2357 2362 $canv lower $t
2358 2363 }
2359 2364 }
2360 2365
2361 2366 proc unmarkmatches {} {
2362 2367 global matchinglines findids
2363 2368 allcanvs delete matches
2364 2369 catch {unset matchinglines}
2365 2370 catch {unset findids}
2366 2371 }
2367 2372
2368 2373 proc selcanvline {w x y} {
2369 2374 global canv canvy0 ctext linespc
2370 2375 global lineid linehtag linentag linedtag rowtextx
2371 2376 set ymax [lindex [$canv cget -scrollregion] 3]
2372 2377 if {$ymax == {}} return
2373 2378 set yfrac [lindex [$canv yview] 0]
2374 2379 set y [expr {$y + $yfrac * $ymax}]
2375 2380 set l [expr {int(($y - $canvy0) / $linespc + 0.5)}]
2376 2381 if {$l < 0} {
2377 2382 set l 0
2378 2383 }
2379 2384 if {$w eq $canv} {
2380 2385 if {![info exists rowtextx($l)] || $x < $rowtextx($l)} return
2381 2386 }
2382 2387 unmarkmatches
2383 2388 selectline $l 1
2384 2389 }
2385 2390
2386 2391 proc commit_descriptor {p} {
2387 2392 global commitinfo
2388 2393 set l "..."
2389 2394 if {[info exists commitinfo($p)]} {
2390 2395 set l [lindex $commitinfo($p) 0]
2391 2396 set r [lindex $commitinfo($p) 6]
2392 2397 }
2393 2398 return "$r:$p ($l)"
2394 2399 }
2395 2400
2396 2401 # append some text to the ctext widget, and make any SHA1 ID
2397 2402 # that we know about be a clickable link.
2398 2403 proc appendwithlinks {text} {
2399 2404 global ctext idline linknum
2400 2405
2401 2406 set start [$ctext index "end - 1c"]
2402 2407 $ctext insert end $text
2403 2408 $ctext insert end "\n"
2404 2409 set links [regexp -indices -all -inline {[0-9a-f]{12}} $text]
2405 2410 foreach l $links {
2406 2411 set s [lindex $l 0]
2407 2412 set e [lindex $l 1]
2408 2413 set linkid [string range $text $s $e]
2409 2414 if {![info exists idline($linkid)]} continue
2410 2415 incr e
2411 2416 $ctext tag add link "$start + $s c" "$start + $e c"
2412 2417 $ctext tag add link$linknum "$start + $s c" "$start + $e c"
2413 2418 $ctext tag bind link$linknum <1> [list selectline $idline($linkid) 1]
2414 2419 incr linknum
2415 2420 }
2416 2421 $ctext tag conf link -foreground blue -underline 1
2417 2422 $ctext tag bind link <Enter> { %W configure -cursor hand2 }
2418 2423 $ctext tag bind link <Leave> { %W configure -cursor $curtextcursor }
2419 2424 }
2420 2425
2421 2426 proc selectline {l isnew} {
2422 2427 global canv canv2 canv3 ctext commitinfo selectedline
2423 2428 global lineid linehtag linentag linedtag
2424 2429 global canvy0 linespc parents nparents children
2425 2430 global cflist currentid sha1entry
2426 2431 global commentend idtags idbookmarks idline linknum
2427 2432
2428 2433 $canv delete hover
2429 2434 normalline
2430 2435 if {![info exists lineid($l)] || ![info exists linehtag($l)]} return
2431 2436 $canv delete secsel
2432 2437 set t [eval $canv create rect [$canv bbox $linehtag($l)] -outline {{}} \
2433 2438 -tags secsel -fill [$canv cget -selectbackground]]
2434 2439 $canv lower $t
2435 2440 $canv2 delete secsel
2436 2441 set t [eval $canv2 create rect [$canv2 bbox $linentag($l)] -outline {{}} \
2437 2442 -tags secsel -fill [$canv2 cget -selectbackground]]
2438 2443 $canv2 lower $t
2439 2444 $canv3 delete secsel
2440 2445 set t [eval $canv3 create rect [$canv3 bbox $linedtag($l)] -outline {{}} \
2441 2446 -tags secsel -fill [$canv3 cget -selectbackground]]
2442 2447 $canv3 lower $t
2443 2448 set y [expr {$canvy0 + $l * $linespc}]
2444 2449 set ymax [lindex [$canv cget -scrollregion] 3]
2445 2450 set ytop [expr {$y - $linespc - 1}]
2446 2451 set ybot [expr {$y + $linespc + 1}]
2447 2452 set wnow [$canv yview]
2448 2453 set wtop [expr [lindex $wnow 0] * $ymax]
2449 2454 set wbot [expr [lindex $wnow 1] * $ymax]
2450 2455 set wh [expr {$wbot - $wtop}]
2451 2456 set newtop $wtop
2452 2457 if {$ytop < $wtop} {
2453 2458 if {$ybot < $wtop} {
2454 2459 set newtop [expr {$y - $wh / 2.0}]
2455 2460 } else {
2456 2461 set newtop $ytop
2457 2462 if {$newtop > $wtop - $linespc} {
2458 2463 set newtop [expr {$wtop - $linespc}]
2459 2464 }
2460 2465 }
2461 2466 } elseif {$ybot > $wbot} {
2462 2467 if {$ytop > $wbot} {
2463 2468 set newtop [expr {$y - $wh / 2.0}]
2464 2469 } else {
2465 2470 set newtop [expr {$ybot - $wh}]
2466 2471 if {$newtop < $wtop + $linespc} {
2467 2472 set newtop [expr {$wtop + $linespc}]
2468 2473 }
2469 2474 }
2470 2475 }
2471 2476 if {$newtop != $wtop} {
2472 2477 if {$newtop < 0} {
2473 2478 set newtop 0
2474 2479 }
2475 2480 allcanvs yview moveto [expr $newtop * 1.0 / $ymax]
2476 2481 }
2477 2482
2478 2483 if {$isnew} {
2479 2484 addtohistory [list selectline $l 0]
2480 2485 }
2481 2486
2482 2487 set selectedline $l
2483 2488
2484 2489 set id $lineid($l)
2485 2490 set currentid $id
2486 2491 $sha1entry delete 0 end
2487 2492 $sha1entry insert 0 $id
2488 2493 $sha1entry selection range 0 end
2489 2494
2490 2495 $ctext conf -state normal
2491 2496 $ctext delete 0.0 end
2492 2497 set linknum 0
2493 2498 $ctext mark set fmark.0 0.0
2494 2499 $ctext mark gravity fmark.0 left
2495 2500 set info $commitinfo($id)
2496 2501 $ctext insert end "Changeset: [lindex $info 6]\n"
2497 2502 if {[llength [lindex $info 7]] > 0} {
2498 2503 $ctext insert end "Branch: [lindex $info 7]\n"
2499 2504 }
2500 2505 $ctext insert end "User: [lindex $info 1]\n"
2501 2506 $ctext insert end "Date: [lindex $info 2]\n"
2502 2507 if {[info exists idbookmarks($id)]} {
2503 2508 $ctext insert end "Bookmarks:"
2504 2509 foreach bookmark $idbookmarks($id) {
2505 2510 $ctext insert end " $bookmark"
2506 2511 }
2507 2512 $ctext insert end "\n"
2508 2513 }
2509 2514
2510 2515 if {[info exists idtags($id)]} {
2511 2516 $ctext insert end "Tags:"
2512 2517 foreach tag $idtags($id) {
2513 2518 $ctext insert end " $tag"
2514 2519 }
2515 2520 $ctext insert end "\n"
2516 2521 }
2517 2522
2518 2523 set comment {}
2519 2524 if {[info exists parents($id)]} {
2520 2525 foreach p $parents($id) {
2521 2526 append comment "Parent: [commit_descriptor $p]\n"
2522 2527 }
2523 2528 }
2524 2529 if {[info exists children($id)]} {
2525 2530 foreach c $children($id) {
2526 2531 append comment "Child: [commit_descriptor $c]\n"
2527 2532 }
2528 2533 }
2529 2534 append comment "\n"
2530 2535 append comment [lindex $info 5]
2531 2536
2532 2537 # make anything that looks like a SHA1 ID be a clickable link
2533 2538 appendwithlinks $comment
2534 2539
2535 2540 $ctext tag delete Comments
2536 2541 $ctext tag remove found 1.0 end
2537 2542 $ctext conf -state disabled
2538 2543 set commentend [$ctext index "end - 1c"]
2539 2544
2540 2545 $cflist delete 0 end
2541 2546 $cflist insert end "Comments"
2542 2547 if {$nparents($id) <= 1} {
2543 2548 set parent "null"
2544 2549 if {$nparents($id) == 1} {
2545 2550 set parent $parents($id)
2546 2551 }
2547 2552 startdiff [concat $id $parent]
2548 2553 } elseif {$nparents($id) > 1} {
2549 2554 mergediff $id
2550 2555 }
2551 2556 }
2552 2557
2553 2558 proc selnextline {dir} {
2554 2559 global selectedline
2555 2560 focus .
2556 2561 if {![info exists selectedline]} return
2557 2562 set l [expr $selectedline + $dir]
2558 2563 unmarkmatches
2559 2564 selectline $l 1
2560 2565 }
2561 2566
2562 2567 proc unselectline {} {
2563 2568 global selectedline
2564 2569
2565 2570 catch {unset selectedline}
2566 2571 allcanvs delete secsel
2567 2572 }
2568 2573
2569 2574 proc addtohistory {cmd} {
2570 2575 global history historyindex
2571 2576
2572 2577 if {$historyindex > 0
2573 2578 && [lindex $history [expr {$historyindex - 1}]] == $cmd} {
2574 2579 return
2575 2580 }
2576 2581
2577 2582 if {$historyindex < [llength $history]} {
2578 2583 set history [lreplace $history $historyindex end $cmd]
2579 2584 } else {
2580 2585 lappend history $cmd
2581 2586 }
2582 2587 incr historyindex
2583 2588 if {$historyindex > 1} {
2584 2589 .ctop.top.bar.leftbut conf -state normal
2585 2590 } else {
2586 2591 .ctop.top.bar.leftbut conf -state disabled
2587 2592 }
2588 2593 .ctop.top.bar.rightbut conf -state disabled
2589 2594 }
2590 2595
2591 2596 proc goback {} {
2592 2597 global history historyindex
2593 2598 focus .
2594 2599
2595 2600 if {$historyindex > 1} {
2596 2601 incr historyindex -1
2597 2602 set cmd [lindex $history [expr {$historyindex - 1}]]
2598 2603 eval $cmd
2599 2604 .ctop.top.bar.rightbut conf -state normal
2600 2605 }
2601 2606 if {$historyindex <= 1} {
2602 2607 .ctop.top.bar.leftbut conf -state disabled
2603 2608 }
2604 2609 }
2605 2610
2606 2611 proc goforw {} {
2607 2612 global history historyindex
2608 2613 focus .
2609 2614
2610 2615 if {$historyindex < [llength $history]} {
2611 2616 set cmd [lindex $history $historyindex]
2612 2617 incr historyindex
2613 2618 eval $cmd
2614 2619 .ctop.top.bar.leftbut conf -state normal
2615 2620 }
2616 2621 if {$historyindex >= [llength $history]} {
2617 2622 .ctop.top.bar.rightbut conf -state disabled
2618 2623 }
2619 2624 }
2620 2625
2621 2626 proc mergediff {id} {
2622 2627 global parents diffmergeid diffmergegca mergefilelist diffpindex
2623 2628
2624 2629 set diffmergeid $id
2625 2630 set diffpindex -1
2626 2631 set diffmergegca [findgca $parents($id)]
2627 2632 if {[info exists mergefilelist($id)]} {
2628 2633 if {$mergefilelist($id) ne {}} {
2629 2634 showmergediff
2630 2635 }
2631 2636 } else {
2632 2637 contmergediff {}
2633 2638 }
2634 2639 }
2635 2640
2636 2641 proc findgca {ids} {
2637 2642 global env
2638 2643 set gca {}
2639 2644 foreach id $ids {
2640 2645 if {$gca eq {}} {
2641 2646 set gca $id
2642 2647 } else {
2643 2648 if {[catch {
2644 2649 set gca [exec $env(HG) --config ui.report_untrusted=false debug-merge-base $gca $id]
2645 2650 } err]} {
2646 2651 return {}
2647 2652 }
2648 2653 }
2649 2654 }
2650 2655 return $gca
2651 2656 }
2652 2657
2653 2658 proc contmergediff {ids} {
2654 2659 global diffmergeid diffpindex parents nparents diffmergegca
2655 2660 global treediffs mergefilelist diffids treepending
2656 2661
2657 2662 # diff the child against each of the parents, and diff
2658 2663 # each of the parents against the GCA.
2659 2664 while 1 {
2660 2665 if {[lindex $ids 0] == $diffmergeid && $diffmergegca ne {}} {
2661 2666 set ids [list [lindex $ids 1] $diffmergegca]
2662 2667 } else {
2663 2668 if {[incr diffpindex] >= $nparents($diffmergeid)} break
2664 2669 set p [lindex $parents($diffmergeid) $diffpindex]
2665 2670 set ids [list $diffmergeid $p]
2666 2671 }
2667 2672 if {![info exists treediffs($ids)]} {
2668 2673 set diffids $ids
2669 2674 if {![info exists treepending]} {
2670 2675 gettreediffs $ids
2671 2676 }
2672 2677 return
2673 2678 }
2674 2679 }
2675 2680
2676 2681 # If a file in some parent is different from the child and also
2677 2682 # different from the GCA, then it's interesting.
2678 2683 # If we don't have a GCA, then a file is interesting if it is
2679 2684 # different from the child in all the parents.
2680 2685 if {$diffmergegca ne {}} {
2681 2686 set files {}
2682 2687 foreach p $parents($diffmergeid) {
2683 2688 set gcadiffs $treediffs([list $p $diffmergegca])
2684 2689 foreach f $treediffs([list $diffmergeid $p]) {
2685 2690 if {[lsearch -exact $files $f] < 0
2686 2691 && [lsearch -exact $gcadiffs $f] >= 0} {
2687 2692 lappend files $f
2688 2693 }
2689 2694 }
2690 2695 }
2691 2696 set files [lsort $files]
2692 2697 } else {
2693 2698 set p [lindex $parents($diffmergeid) 0]
2694 2699 set files $treediffs([list $diffmergeid $p])
2695 2700 for {set i 1} {$i < $nparents($diffmergeid) && $files ne {}} {incr i} {
2696 2701 set p [lindex $parents($diffmergeid) $i]
2697 2702 set df $treediffs([list $diffmergeid $p])
2698 2703 set nf {}
2699 2704 foreach f $files {
2700 2705 if {[lsearch -exact $df $f] >= 0} {
2701 2706 lappend nf $f
2702 2707 }
2703 2708 }
2704 2709 set files $nf
2705 2710 }
2706 2711 }
2707 2712
2708 2713 set mergefilelist($diffmergeid) $files
2709 2714 if {$files ne {}} {
2710 2715 showmergediff
2711 2716 }
2712 2717 }
2713 2718
2714 2719 proc showmergediff {} {
2715 2720 global cflist diffmergeid mergefilelist parents
2716 2721 global diffopts diffinhunk currentfile currenthunk filelines
2717 2722 global diffblocked groupfilelast mergefds groupfilenum grouphunks
2718 2723 global env
2719 2724
2720 2725 set files $mergefilelist($diffmergeid)
2721 2726 foreach f $files {
2722 2727 $cflist insert end $f
2723 2728 }
2724 2729 set env(GIT_DIFF_OPTS) $diffopts
2725 2730 set flist {}
2726 2731 catch {unset currentfile}
2727 2732 catch {unset currenthunk}
2728 2733 catch {unset filelines}
2729 2734 catch {unset groupfilenum}
2730 2735 catch {unset grouphunks}
2731 2736 set groupfilelast -1
2732 2737 foreach p $parents($diffmergeid) {
2733 2738 set cmd [list | $env(HG) --config ui.report_untrusted=false debug-diff-tree -p $p $diffmergeid]
2734 2739 set cmd [concat $cmd $mergefilelist($diffmergeid)]
2735 2740 if {[catch {set f [open $cmd r]} err]} {
2736 2741 error_popup "Error getting diffs: $err"
2737 2742 foreach f $flist {
2738 2743 catch {close $f}
2739 2744 }
2740 2745 return
2741 2746 }
2742 2747 lappend flist $f
2743 2748 set ids [list $diffmergeid $p]
2744 2749 set mergefds($ids) $f
2745 2750 set diffinhunk($ids) 0
2746 2751 set diffblocked($ids) 0
2747 2752 fconfigure $f -blocking 0
2748 2753 fileevent $f readable [list getmergediffline $f $ids $diffmergeid]
2749 2754 }
2750 2755 }
2751 2756
2752 2757 proc getmergediffline {f ids id} {
2753 2758 global diffmergeid diffinhunk diffoldlines diffnewlines
2754 2759 global currentfile currenthunk
2755 2760 global diffoldstart diffnewstart diffoldlno diffnewlno
2756 2761 global diffblocked mergefilelist
2757 2762 global noldlines nnewlines difflcounts filelines
2758 2763
2759 2764 set n [gets $f line]
2760 2765 if {$n < 0} {
2761 2766 if {![eof $f]} return
2762 2767 }
2763 2768
2764 2769 if {!([info exists diffmergeid] && $diffmergeid == $id)} {
2765 2770 if {$n < 0} {
2766 2771 close $f
2767 2772 }
2768 2773 return
2769 2774 }
2770 2775
2771 2776 if {$diffinhunk($ids) != 0} {
2772 2777 set fi $currentfile($ids)
2773 2778 if {$n > 0 && [regexp {^[-+ \\]} $line match]} {
2774 2779 # continuing an existing hunk
2775 2780 set line [string range $line 1 end]
2776 2781 set p [lindex $ids 1]
2777 2782 if {$match eq "-" || $match eq " "} {
2778 2783 set filelines($p,$fi,$diffoldlno($ids)) $line
2779 2784 incr diffoldlno($ids)
2780 2785 }
2781 2786 if {$match eq "+" || $match eq " "} {
2782 2787 set filelines($id,$fi,$diffnewlno($ids)) $line
2783 2788 incr diffnewlno($ids)
2784 2789 }
2785 2790 if {$match eq " "} {
2786 2791 if {$diffinhunk($ids) == 2} {
2787 2792 lappend difflcounts($ids) \
2788 2793 [list $noldlines($ids) $nnewlines($ids)]
2789 2794 set noldlines($ids) 0
2790 2795 set diffinhunk($ids) 1
2791 2796 }
2792 2797 incr noldlines($ids)
2793 2798 } elseif {$match eq "-" || $match eq "+"} {
2794 2799 if {$diffinhunk($ids) == 1} {
2795 2800 lappend difflcounts($ids) [list $noldlines($ids)]
2796 2801 set noldlines($ids) 0
2797 2802 set nnewlines($ids) 0
2798 2803 set diffinhunk($ids) 2
2799 2804 }
2800 2805 if {$match eq "-"} {
2801 2806 incr noldlines($ids)
2802 2807 } else {
2803 2808 incr nnewlines($ids)
2804 2809 }
2805 2810 }
2806 2811 # and if it's \ No newline at end of line, then what?
2807 2812 return
2808 2813 }
2809 2814 # end of a hunk
2810 2815 if {$diffinhunk($ids) == 1 && $noldlines($ids) != 0} {
2811 2816 lappend difflcounts($ids) [list $noldlines($ids)]
2812 2817 } elseif {$diffinhunk($ids) == 2
2813 2818 && ($noldlines($ids) != 0 || $nnewlines($ids) != 0)} {
2814 2819 lappend difflcounts($ids) [list $noldlines($ids) $nnewlines($ids)]
2815 2820 }
2816 2821 set currenthunk($ids) [list $currentfile($ids) \
2817 2822 $diffoldstart($ids) $diffnewstart($ids) \
2818 2823 $diffoldlno($ids) $diffnewlno($ids) \
2819 2824 $difflcounts($ids)]
2820 2825 set diffinhunk($ids) 0
2821 2826 # -1 = need to block, 0 = unblocked, 1 = is blocked
2822 2827 set diffblocked($ids) -1
2823 2828 processhunks
2824 2829 if {$diffblocked($ids) == -1} {
2825 2830 fileevent $f readable {}
2826 2831 set diffblocked($ids) 1
2827 2832 }
2828 2833 }
2829 2834
2830 2835 if {$n < 0} {
2831 2836 # eof
2832 2837 if {!$diffblocked($ids)} {
2833 2838 close $f
2834 2839 set currentfile($ids) [llength $mergefilelist($diffmergeid)]
2835 2840 set currenthunk($ids) [list $currentfile($ids) 0 0 0 0 {}]
2836 2841 processhunks
2837 2842 }
2838 2843 } elseif {[regexp {^diff --git a/(.*) b/} $line match fname]} {
2839 2844 # start of a new file
2840 2845 set currentfile($ids) \
2841 2846 [lsearch -exact $mergefilelist($diffmergeid) $fname]
2842 2847 } elseif {[regexp {^@@ -([0-9]+),([0-9]+) \+([0-9]+),([0-9]+) @@(.*)} \
2843 2848 $line match f1l f1c f2l f2c rest]} {
2844 2849 if {[info exists currentfile($ids)] && $currentfile($ids) >= 0} {
2845 2850 # start of a new hunk
2846 2851 if {$f1l == 0 && $f1c == 0} {
2847 2852 set f1l 1
2848 2853 }
2849 2854 if {$f2l == 0 && $f2c == 0} {
2850 2855 set f2l 1
2851 2856 }
2852 2857 set diffinhunk($ids) 1
2853 2858 set diffoldstart($ids) $f1l
2854 2859 set diffnewstart($ids) $f2l
2855 2860 set diffoldlno($ids) $f1l
2856 2861 set diffnewlno($ids) $f2l
2857 2862 set difflcounts($ids) {}
2858 2863 set noldlines($ids) 0
2859 2864 set nnewlines($ids) 0
2860 2865 }
2861 2866 }
2862 2867 }
2863 2868
2864 2869 proc processhunks {} {
2865 2870 global diffmergeid parents nparents currenthunk
2866 2871 global mergefilelist diffblocked mergefds
2867 2872 global grouphunks grouplinestart grouplineend groupfilenum
2868 2873
2869 2874 set nfiles [llength $mergefilelist($diffmergeid)]
2870 2875 while 1 {
2871 2876 set fi $nfiles
2872 2877 set lno 0
2873 2878 # look for the earliest hunk
2874 2879 foreach p $parents($diffmergeid) {
2875 2880 set ids [list $diffmergeid $p]
2876 2881 if {![info exists currenthunk($ids)]} return
2877 2882 set i [lindex $currenthunk($ids) 0]
2878 2883 set l [lindex $currenthunk($ids) 2]
2879 2884 if {$i < $fi || ($i == $fi && $l < $lno)} {
2880 2885 set fi $i
2881 2886 set lno $l
2882 2887 set pi $p
2883 2888 }
2884 2889 }
2885 2890
2886 2891 if {$fi < $nfiles} {
2887 2892 set ids [list $diffmergeid $pi]
2888 2893 set hunk $currenthunk($ids)
2889 2894 unset currenthunk($ids)
2890 2895 if {$diffblocked($ids) > 0} {
2891 2896 fileevent $mergefds($ids) readable \
2892 2897 [list getmergediffline $mergefds($ids) $ids $diffmergeid]
2893 2898 }
2894 2899 set diffblocked($ids) 0
2895 2900
2896 2901 if {[info exists groupfilenum] && $groupfilenum == $fi
2897 2902 && $lno <= $grouplineend} {
2898 2903 # add this hunk to the pending group
2899 2904 lappend grouphunks($pi) $hunk
2900 2905 set endln [lindex $hunk 4]
2901 2906 if {$endln > $grouplineend} {
2902 2907 set grouplineend $endln
2903 2908 }
2904 2909 continue
2905 2910 }
2906 2911 }
2907 2912
2908 2913 # succeeding stuff doesn't belong in this group, so
2909 2914 # process the group now
2910 2915 if {[info exists groupfilenum]} {
2911 2916 processgroup
2912 2917 unset groupfilenum
2913 2918 unset grouphunks
2914 2919 }
2915 2920
2916 2921 if {$fi >= $nfiles} break
2917 2922
2918 2923 # start a new group
2919 2924 set groupfilenum $fi
2920 2925 set grouphunks($pi) [list $hunk]
2921 2926 set grouplinestart $lno
2922 2927 set grouplineend [lindex $hunk 4]
2923 2928 }
2924 2929 }
2925 2930
2926 2931 proc processgroup {} {
2927 2932 global groupfilelast groupfilenum difffilestart
2928 2933 global mergefilelist diffmergeid ctext filelines
2929 2934 global parents diffmergeid diffoffset
2930 2935 global grouphunks grouplinestart grouplineend nparents
2931 2936 global mergemax
2932 2937
2933 2938 $ctext conf -state normal
2934 2939 set id $diffmergeid
2935 2940 set f $groupfilenum
2936 2941 if {$groupfilelast != $f} {
2937 2942 $ctext insert end "\n"
2938 2943 set here [$ctext index "end - 1c"]
2939 2944 set difffilestart($f) $here
2940 2945 set mark fmark.[expr {$f + 1}]
2941 2946 $ctext mark set $mark $here
2942 2947 $ctext mark gravity $mark left
2943 2948 set header [lindex $mergefilelist($id) $f]
2944 2949 set l [expr {(78 - [string length $header]) / 2}]
2945 2950 set pad [string range "----------------------------------------" 1 $l]
2946 2951 $ctext insert end "$pad $header $pad\n" filesep
2947 2952 set groupfilelast $f
2948 2953 foreach p $parents($id) {
2949 2954 set diffoffset($p) 0
2950 2955 }
2951 2956 }
2952 2957
2953 2958 $ctext insert end "@@" msep
2954 2959 set nlines [expr {$grouplineend - $grouplinestart}]
2955 2960 set events {}
2956 2961 set pnum 0
2957 2962 foreach p $parents($id) {
2958 2963 set startline [expr {$grouplinestart + $diffoffset($p)}]
2959 2964 set ol $startline
2960 2965 set nl $grouplinestart
2961 2966 if {[info exists grouphunks($p)]} {
2962 2967 foreach h $grouphunks($p) {
2963 2968 set l [lindex $h 2]
2964 2969 if {$nl < $l} {
2965 2970 for {} {$nl < $l} {incr nl} {
2966 2971 set filelines($p,$f,$ol) $filelines($id,$f,$nl)
2967 2972 incr ol
2968 2973 }
2969 2974 }
2970 2975 foreach chunk [lindex $h 5] {
2971 2976 if {[llength $chunk] == 2} {
2972 2977 set olc [lindex $chunk 0]
2973 2978 set nlc [lindex $chunk 1]
2974 2979 set nnl [expr {$nl + $nlc}]
2975 2980 lappend events [list $nl $nnl $pnum $olc $nlc]
2976 2981 incr ol $olc
2977 2982 set nl $nnl
2978 2983 } else {
2979 2984 incr ol [lindex $chunk 0]
2980 2985 incr nl [lindex $chunk 0]
2981 2986 }
2982 2987 }
2983 2988 }
2984 2989 }
2985 2990 if {$nl < $grouplineend} {
2986 2991 for {} {$nl < $grouplineend} {incr nl} {
2987 2992 set filelines($p,$f,$ol) $filelines($id,$f,$nl)
2988 2993 incr ol
2989 2994 }
2990 2995 }
2991 2996 set nlines [expr {$ol - $startline}]
2992 2997 $ctext insert end " -$startline,$nlines" msep
2993 2998 incr pnum
2994 2999 }
2995 3000
2996 3001 set nlines [expr {$grouplineend - $grouplinestart}]
2997 3002 $ctext insert end " +$grouplinestart,$nlines @@\n" msep
2998 3003
2999 3004 set events [lsort -integer -index 0 $events]
3000 3005 set nevents [llength $events]
3001 3006 set nmerge $nparents($diffmergeid)
3002 3007 set l $grouplinestart
3003 3008 for {set i 0} {$i < $nevents} {set i $j} {
3004 3009 set nl [lindex $events $i 0]
3005 3010 while {$l < $nl} {
3006 3011 $ctext insert end " $filelines($id,$f,$l)\n"
3007 3012 incr l
3008 3013 }
3009 3014 set e [lindex $events $i]
3010 3015 set enl [lindex $e 1]
3011 3016 set j $i
3012 3017 set active {}
3013 3018 while 1 {
3014 3019 set pnum [lindex $e 2]
3015 3020 set olc [lindex $e 3]
3016 3021 set nlc [lindex $e 4]
3017 3022 if {![info exists delta($pnum)]} {
3018 3023 set delta($pnum) [expr {$olc - $nlc}]
3019 3024 lappend active $pnum
3020 3025 } else {
3021 3026 incr delta($pnum) [expr {$olc - $nlc}]
3022 3027 }
3023 3028 if {[incr j] >= $nevents} break
3024 3029 set e [lindex $events $j]
3025 3030 if {[lindex $e 0] >= $enl} break
3026 3031 if {[lindex $e 1] > $enl} {
3027 3032 set enl [lindex $e 1]
3028 3033 }
3029 3034 }
3030 3035 set nlc [expr {$enl - $l}]
3031 3036 set ncol mresult
3032 3037 set bestpn -1
3033 3038 if {[llength $active] == $nmerge - 1} {
3034 3039 # no diff for one of the parents, i.e. it's identical
3035 3040 for {set pnum 0} {$pnum < $nmerge} {incr pnum} {
3036 3041 if {![info exists delta($pnum)]} {
3037 3042 if {$pnum < $mergemax} {
3038 3043 lappend ncol m$pnum
3039 3044 } else {
3040 3045 lappend ncol mmax
3041 3046 }
3042 3047 break
3043 3048 }
3044 3049 }
3045 3050 } elseif {[llength $active] == $nmerge} {
3046 3051 # all parents are different, see if one is very similar
3047 3052 set bestsim 30
3048 3053 for {set pnum 0} {$pnum < $nmerge} {incr pnum} {
3049 3054 set sim [similarity $pnum $l $nlc $f \
3050 3055 [lrange $events $i [expr {$j-1}]]]
3051 3056 if {$sim > $bestsim} {
3052 3057 set bestsim $sim
3053 3058 set bestpn $pnum
3054 3059 }
3055 3060 }
3056 3061 if {$bestpn >= 0} {
3057 3062 lappend ncol m$bestpn
3058 3063 }
3059 3064 }
3060 3065 set pnum -1
3061 3066 foreach p $parents($id) {
3062 3067 incr pnum
3063 3068 if {![info exists delta($pnum)] || $pnum == $bestpn} continue
3064 3069 set olc [expr {$nlc + $delta($pnum)}]
3065 3070 set ol [expr {$l + $diffoffset($p)}]
3066 3071 incr diffoffset($p) $delta($pnum)
3067 3072 unset delta($pnum)
3068 3073 for {} {$olc > 0} {incr olc -1} {
3069 3074 $ctext insert end "-$filelines($p,$f,$ol)\n" m$pnum
3070 3075 incr ol
3071 3076 }
3072 3077 }
3073 3078 set endl [expr {$l + $nlc}]
3074 3079 if {$bestpn >= 0} {
3075 3080 # show this pretty much as a normal diff
3076 3081 set p [lindex $parents($id) $bestpn]
3077 3082 set ol [expr {$l + $diffoffset($p)}]
3078 3083 incr diffoffset($p) $delta($bestpn)
3079 3084 unset delta($bestpn)
3080 3085 for {set k $i} {$k < $j} {incr k} {
3081 3086 set e [lindex $events $k]
3082 3087 if {[lindex $e 2] != $bestpn} continue
3083 3088 set nl [lindex $e 0]
3084 3089 set ol [expr {$ol + $nl - $l}]
3085 3090 for {} {$l < $nl} {incr l} {
3086 3091 $ctext insert end "+$filelines($id,$f,$l)\n" $ncol
3087 3092 }
3088 3093 set c [lindex $e 3]
3089 3094 for {} {$c > 0} {incr c -1} {
3090 3095 $ctext insert end "-$filelines($p,$f,$ol)\n" m$bestpn
3091 3096 incr ol
3092 3097 }
3093 3098 set nl [lindex $e 1]
3094 3099 for {} {$l < $nl} {incr l} {
3095 3100 $ctext insert end "+$filelines($id,$f,$l)\n" mresult
3096 3101 }
3097 3102 }
3098 3103 }
3099 3104 for {} {$l < $endl} {incr l} {
3100 3105 $ctext insert end "+$filelines($id,$f,$l)\n" $ncol
3101 3106 }
3102 3107 }
3103 3108 while {$l < $grouplineend} {
3104 3109 $ctext insert end " $filelines($id,$f,$l)\n"
3105 3110 incr l
3106 3111 }
3107 3112 $ctext conf -state disabled
3108 3113 }
3109 3114
3110 3115 proc similarity {pnum l nlc f events} {
3111 3116 global diffmergeid parents diffoffset filelines
3112 3117
3113 3118 set id $diffmergeid
3114 3119 set p [lindex $parents($id) $pnum]
3115 3120 set ol [expr {$l + $diffoffset($p)}]
3116 3121 set endl [expr {$l + $nlc}]
3117 3122 set same 0
3118 3123 set diff 0
3119 3124 foreach e $events {
3120 3125 if {[lindex $e 2] != $pnum} continue
3121 3126 set nl [lindex $e 0]
3122 3127 set ol [expr {$ol + $nl - $l}]
3123 3128 for {} {$l < $nl} {incr l} {
3124 3129 incr same [string length $filelines($id,$f,$l)]
3125 3130 incr same
3126 3131 }
3127 3132 set oc [lindex $e 3]
3128 3133 for {} {$oc > 0} {incr oc -1} {
3129 3134 incr diff [string length $filelines($p,$f,$ol)]
3130 3135 incr diff
3131 3136 incr ol
3132 3137 }
3133 3138 set nl [lindex $e 1]
3134 3139 for {} {$l < $nl} {incr l} {
3135 3140 incr diff [string length $filelines($id,$f,$l)]
3136 3141 incr diff
3137 3142 }
3138 3143 }
3139 3144 for {} {$l < $endl} {incr l} {
3140 3145 incr same [string length $filelines($id,$f,$l)]
3141 3146 incr same
3142 3147 }
3143 3148 if {$same == 0} {
3144 3149 return 0
3145 3150 }
3146 3151 return [expr {200 * $same / (2 * $same + $diff)}]
3147 3152 }
3148 3153
3149 3154 proc startdiff {ids} {
3150 3155 global treediffs diffids treepending diffmergeid
3151 3156
3152 3157 set diffids $ids
3153 3158 catch {unset diffmergeid}
3154 3159 if {![info exists treediffs($ids)]} {
3155 3160 if {![info exists treepending]} {
3156 3161 gettreediffs $ids
3157 3162 }
3158 3163 } else {
3159 3164 addtocflist $ids
3160 3165 }
3161 3166 }
3162 3167
3163 3168 proc addtocflist {ids} {
3164 3169 global treediffs cflist
3165 3170 foreach f $treediffs($ids) {
3166 3171 $cflist insert end $f
3167 3172 }
3168 3173 getblobdiffs $ids
3169 3174 }
3170 3175
3171 3176 proc gettreediffs {ids} {
3172 3177 global treediff parents treepending env
3173 3178 set treepending $ids
3174 3179 set treediff {}
3175 3180 set id [lindex $ids 0]
3176 3181 set p [lindex $ids 1]
3177 3182 if [catch {set gdtf [open "|{$env(HG)} --config ui.report_untrusted=false debug-diff-tree -r $p $id" r]}] return
3178 3183 fconfigure $gdtf -blocking 0
3179 3184 fileevent $gdtf readable [list gettreediffline $gdtf $ids]
3180 3185 }
3181 3186
3182 3187 proc gettreediffline {gdtf ids} {
3183 3188 global treediff treediffs treepending diffids diffmergeid
3184 3189
3185 3190 set n [gets $gdtf line]
3186 3191 if {$n < 0} {
3187 3192 if {![eof $gdtf]} return
3188 3193 close $gdtf
3189 3194 set treediffs($ids) $treediff
3190 3195 unset treepending
3191 3196 if {$ids != $diffids} {
3192 3197 gettreediffs $diffids
3193 3198 } else {
3194 3199 if {[info exists diffmergeid]} {
3195 3200 contmergediff $ids
3196 3201 } else {
3197 3202 addtocflist $ids
3198 3203 }
3199 3204 }
3200 3205 return
3201 3206 }
3202 3207 set tab1 [expr [string first "\t" $line] + 1]
3203 3208 set tab2 [expr [string first "\t" $line $tab1] - 1]
3204 3209 set file [string range $line $tab1 $tab2]
3205 3210 lappend treediff $file
3206 3211 }
3207 3212
3208 3213 proc getblobdiffs {ids} {
3209 3214 global diffopts blobdifffd diffids env curdifftag curtagstart
3210 3215 global difffilestart nextupdate diffinhdr treediffs
3211 3216
3212 3217 set id [lindex $ids 0]
3213 3218 set p [lindex $ids 1]
3214 3219 set env(GIT_DIFF_OPTS) $diffopts
3215 3220 set cmd [list | $env(HG) --config ui.report_untrusted=false debug-diff-tree -r -p -C $p $id]
3216 3221 if {[catch {set bdf [open $cmd r]} err]} {
3217 3222 puts "error getting diffs: $err"
3218 3223 return
3219 3224 }
3220 3225 set diffinhdr 0
3221 3226 fconfigure $bdf -blocking 0
3222 3227 set blobdifffd($ids) $bdf
3223 3228 set curdifftag Comments
3224 3229 set curtagstart 0.0
3225 3230 catch {unset difffilestart}
3226 3231 fileevent $bdf readable [list getblobdiffline $bdf $diffids]
3227 3232 set nextupdate [expr {[clock clicks -milliseconds] + 100}]
3228 3233 }
3229 3234
3230 3235 proc getblobdiffline {bdf ids} {
3231 3236 global diffids blobdifffd ctext curdifftag curtagstart
3232 3237 global diffnexthead diffnextnote difffilestart
3233 3238 global nextupdate diffinhdr treediffs
3234 3239 global gaudydiff
3235 3240
3236 3241 set n [gets $bdf line]
3237 3242 if {$n < 0} {
3238 3243 if {[eof $bdf]} {
3239 3244 close $bdf
3240 3245 if {$ids == $diffids && $bdf == $blobdifffd($ids)} {
3241 3246 $ctext tag add $curdifftag $curtagstart end
3242 3247 }
3243 3248 }
3244 3249 return
3245 3250 }
3246 3251 if {$ids != $diffids || $bdf != $blobdifffd($ids)} {
3247 3252 return
3248 3253 }
3249 3254 regsub -all "\r" $line "" line
3250 3255 $ctext conf -state normal
3251 3256 if {[regexp {^diff --git a/(.*) b/(.*)} $line match fname newname]} {
3252 3257 # start of a new file
3253 3258 $ctext insert end "\n"
3254 3259 $ctext tag add $curdifftag $curtagstart end
3255 3260 set curtagstart [$ctext index "end - 1c"]
3256 3261 set header $newname
3257 3262 set here [$ctext index "end - 1c"]
3258 3263 set i [lsearch -exact $treediffs($diffids) $fname]
3259 3264 if {$i >= 0} {
3260 3265 set difffilestart($i) $here
3261 3266 incr i
3262 3267 $ctext mark set fmark.$i $here
3263 3268 $ctext mark gravity fmark.$i left
3264 3269 }
3265 3270 if {$newname != $fname} {
3266 3271 set i [lsearch -exact $treediffs($diffids) $newname]
3267 3272 if {$i >= 0} {
3268 3273 set difffilestart($i) $here
3269 3274 incr i
3270 3275 $ctext mark set fmark.$i $here
3271 3276 $ctext mark gravity fmark.$i left
3272 3277 }
3273 3278 }
3274 3279 set curdifftag "f:$fname"
3275 3280 $ctext tag delete $curdifftag
3276 3281 set l [expr {(78 - [string length $header]) / 2}]
3277 3282 set pad [string range "----------------------------------------" 1 $l]
3278 3283 $ctext insert end "$pad $header $pad\n" filesep
3279 3284 set diffinhdr 1
3280 3285 } elseif {[regexp {^(---|\+\+\+) } $line] && $diffinhdr} {
3281 3286 set diffinhdr 1
3282 3287 } elseif {[regexp {^@@ -([0-9]+),([0-9]+) \+([0-9]+),([0-9]+) @@(.*)} \
3283 3288 $line match f1l f1c f2l f2c rest]} {
3284 3289 if {$gaudydiff} {
3285 3290 $ctext insert end "\t" hunksep
3286 3291 $ctext insert end " $f1l " d0 " $f2l " d1
3287 3292 $ctext insert end " $rest \n" hunksep
3288 3293 } else {
3289 3294 $ctext insert end "$line\n" hunksep
3290 3295 }
3291 3296 set diffinhdr 0
3292 3297 } else {
3293 3298 set x [string range $line 0 0]
3294 3299 if {$x == "-" || $x == "+"} {
3295 3300 set tag [expr {$x == "+"}]
3296 3301 if {$gaudydiff} {
3297 3302 set line [string range $line 1 end]
3298 3303 }
3299 3304 $ctext insert end "$line\n" d$tag
3300 3305 } elseif {$x == " "} {
3301 3306 if {$gaudydiff} {
3302 3307 set line [string range $line 1 end]
3303 3308 }
3304 3309 $ctext insert end "$line\n"
3305 3310 } elseif {$diffinhdr || $x == "\\"} {
3306 3311 # e.g. "\ No newline at end of file"
3307 3312 $ctext insert end "$line\n" filesep
3308 3313 } elseif {$line != ""} {
3309 3314 # Something else we don't recognize
3310 3315 if {$curdifftag != "Comments"} {
3311 3316 $ctext insert end "\n"
3312 3317 $ctext tag add $curdifftag $curtagstart end
3313 3318 set curtagstart [$ctext index "end - 1c"]
3314 3319 set curdifftag Comments
3315 3320 }
3316 3321 $ctext insert end "$line\n" filesep
3317 3322 }
3318 3323 }
3319 3324 $ctext conf -state disabled
3320 3325 if {[clock clicks -milliseconds] >= $nextupdate} {
3321 3326 incr nextupdate 100
3322 3327 fileevent $bdf readable {}
3323 3328 update
3324 3329 fileevent $bdf readable "getblobdiffline $bdf {$ids}"
3325 3330 }
3326 3331 }
3327 3332
3328 3333 proc nextfile {} {
3329 3334 global difffilestart ctext
3330 3335 set here [$ctext index @0,0]
3331 3336 for {set i 0} {[info exists difffilestart($i)]} {incr i} {
3332 3337 if {[$ctext compare $difffilestart($i) > $here]} {
3333 3338 if {![info exists pos]
3334 3339 || [$ctext compare $difffilestart($i) < $pos]} {
3335 3340 set pos $difffilestart($i)
3336 3341 }
3337 3342 }
3338 3343 }
3339 3344 if {[info exists pos]} {
3340 3345 $ctext yview $pos
3341 3346 }
3342 3347 }
3343 3348
3344 3349 proc listboxsel {} {
3345 3350 global ctext cflist currentid
3346 3351 if {![info exists currentid]} return
3347 3352 set sel [lsort [$cflist curselection]]
3348 3353 if {$sel eq {}} return
3349 3354 set first [lindex $sel 0]
3350 3355 catch {$ctext yview fmark.$first}
3351 3356 }
3352 3357
3353 3358 proc setcoords {} {
3354 3359 global linespc charspc canvx0 canvy0 mainfont
3355 3360 global xspc1 xspc2 lthickness
3356 3361
3357 3362 set linespc [font metrics $mainfont -linespace]
3358 3363 set charspc [font measure $mainfont "m"]
3359 3364 set canvy0 [expr 3 + 0.5 * $linespc]
3360 3365 set canvx0 [expr 3 + 0.5 * $linespc]
3361 3366 set lthickness [expr {int($linespc / 9) + 1}]
3362 3367 set xspc1(0) $linespc
3363 3368 set xspc2 $linespc
3364 3369 }
3365 3370
3366 3371 proc redisplay {} {
3367 3372 global stopped redisplaying phase
3368 3373 if {$stopped > 1} return
3369 3374 if {$phase == "getcommits"} return
3370 3375 set redisplaying 1
3371 3376 if {$phase == "drawgraph" || $phase == "incrdraw"} {
3372 3377 set stopped 1
3373 3378 } else {
3374 3379 drawgraph
3375 3380 }
3376 3381 }
3377 3382
3378 3383 proc incrfont {inc} {
3379 3384 global mainfont namefont textfont ctext canv phase
3380 3385 global stopped entries curidfont
3381 3386 unmarkmatches
3382 3387 set mainfont [lreplace $mainfont 1 1 [expr {[lindex $mainfont 1] + $inc}]]
3383 3388 set curidfont [lreplace $curidfont 1 1 [expr {[lindex $curidfont 1] + $inc}]]
3384 3389 set namefont [lreplace $namefont 1 1 [expr {[lindex $namefont 1] + $inc}]]
3385 3390 set textfont [lreplace $textfont 1 1 [expr {[lindex $textfont 1] + $inc}]]
3386 3391 setcoords
3387 3392 $ctext conf -font $textfont
3388 3393 $ctext tag conf filesep -font [concat $textfont bold]
3389 3394 foreach e $entries {
3390 3395 $e conf -font $mainfont
3391 3396 }
3392 3397 if {$phase == "getcommits"} {
3393 3398 $canv itemconf textitems -font $mainfont
3394 3399 }
3395 3400 redisplay
3396 3401 }
3397 3402
3398 3403 proc clearsha1 {} {
3399 3404 global sha1entry sha1string
3400 3405 if {[string length $sha1string] == 40} {
3401 3406 $sha1entry delete 0 end
3402 3407 }
3403 3408 }
3404 3409
3405 3410 proc sha1change {n1 n2 op} {
3406 3411 global sha1string currentid sha1but
3407 3412 if {$sha1string == {}
3408 3413 || ([info exists currentid] && $sha1string == $currentid)} {
3409 3414 set state disabled
3410 3415 } else {
3411 3416 set state normal
3412 3417 }
3413 3418 if {[$sha1but cget -state] == $state} return
3414 3419 if {$state == "normal"} {
3415 3420 $sha1but conf -state normal -relief raised -text "Goto: "
3416 3421 } else {
3417 3422 $sha1but conf -state disabled -relief flat -text "SHA1 ID: "
3418 3423 }
3419 3424 }
3420 3425
3421 3426 proc gotocommit {} {
3422 3427 global sha1string currentid idline tagids
3423 3428 global lineid numcommits
3424 3429
3425 3430 if {$sha1string == {}
3426 3431 || ([info exists currentid] && $sha1string == $currentid)} return
3427 3432 if {[info exists tagids($sha1string)]} {
3428 3433 set id $tagids($sha1string)
3429 3434 } else {
3430 3435 set id [string tolower $sha1string]
3431 3436 if {[regexp {^[0-9a-f]{4,39}$} $id]} {
3432 3437 set matches {}
3433 3438 for {set l 0} {$l < $numcommits} {incr l} {
3434 3439 if {[string match $id* $lineid($l)]} {
3435 3440 lappend matches $lineid($l)
3436 3441 }
3437 3442 }
3438 3443 if {$matches ne {}} {
3439 3444 if {[llength $matches] > 1} {
3440 3445 error_popup "Short SHA1 id $id is ambiguous"
3441 3446 return
3442 3447 }
3443 3448 set id [lindex $matches 0]
3444 3449 }
3445 3450 }
3446 3451 }
3447 3452 if {[info exists idline($id)]} {
3448 3453 selectline $idline($id) 1
3449 3454 return
3450 3455 }
3451 3456 if {[regexp {^[0-9a-fA-F]{4,}$} $sha1string]} {
3452 3457 set type "SHA1 id"
3453 3458 } else {
3454 3459 set type "Tag"
3455 3460 }
3456 3461 error_popup "$type $sha1string is not known"
3457 3462 }
3458 3463
3459 3464 proc lineenter {x y id} {
3460 3465 global hoverx hovery hoverid hovertimer
3461 3466 global commitinfo canv
3462 3467
3463 3468 if {![info exists commitinfo($id)]} return
3464 3469 set hoverx $x
3465 3470 set hovery $y
3466 3471 set hoverid $id
3467 3472 if {[info exists hovertimer]} {
3468 3473 after cancel $hovertimer
3469 3474 }
3470 3475 set hovertimer [after 500 linehover]
3471 3476 $canv delete hover
3472 3477 }
3473 3478
3474 3479 proc linemotion {x y id} {
3475 3480 global hoverx hovery hoverid hovertimer
3476 3481
3477 3482 if {[info exists hoverid] && $id == $hoverid} {
3478 3483 set hoverx $x
3479 3484 set hovery $y
3480 3485 if {[info exists hovertimer]} {
3481 3486 after cancel $hovertimer
3482 3487 }
3483 3488 set hovertimer [after 500 linehover]
3484 3489 }
3485 3490 }
3486 3491
3487 3492 proc lineleave {id} {
3488 3493 global hoverid hovertimer canv
3489 3494
3490 3495 if {[info exists hoverid] && $id == $hoverid} {
3491 3496 $canv delete hover
3492 3497 if {[info exists hovertimer]} {
3493 3498 after cancel $hovertimer
3494 3499 unset hovertimer
3495 3500 }
3496 3501 unset hoverid
3497 3502 }
3498 3503 }
3499 3504
3500 3505 proc linehover {} {
3501 3506 global hoverx hovery hoverid hovertimer
3502 3507 global canv linespc lthickness
3503 3508 global commitinfo mainfont
3504 3509
3505 3510 set text [lindex $commitinfo($hoverid) 0]
3506 3511 set ymax [lindex [$canv cget -scrollregion] 3]
3507 3512 if {$ymax == {}} return
3508 3513 set yfrac [lindex [$canv yview] 0]
3509 3514 set x [expr {$hoverx + 2 * $linespc}]
3510 3515 set y [expr {$hovery + $yfrac * $ymax - $linespc / 2}]
3511 3516 set x0 [expr {$x - 2 * $lthickness}]
3512 3517 set y0 [expr {$y - 2 * $lthickness}]
3513 3518 set x1 [expr {$x + [font measure $mainfont $text] + 2 * $lthickness}]
3514 3519 set y1 [expr {$y + $linespc + 2 * $lthickness}]
3515 3520 set t [$canv create rectangle $x0 $y0 $x1 $y1 \
3516 3521 -fill \#ffff80 -outline black -width 1 -tags hover]
3517 3522 $canv raise $t
3518 3523 set t [$canv create text $x $y -anchor nw -text $text -tags hover]
3519 3524 $canv raise $t
3520 3525 }
3521 3526
3522 3527 proc clickisonarrow {id y} {
3523 3528 global mainline mainlinearrow sidelines lthickness
3524 3529
3525 3530 set thresh [expr {2 * $lthickness + 6}]
3526 3531 if {[info exists mainline($id)]} {
3527 3532 if {$mainlinearrow($id) ne "none"} {
3528 3533 if {abs([lindex $mainline($id) 1] - $y) < $thresh} {
3529 3534 return "up"
3530 3535 }
3531 3536 }
3532 3537 }
3533 3538 if {[info exists sidelines($id)]} {
3534 3539 foreach ls $sidelines($id) {
3535 3540 set coords [lindex $ls 0]
3536 3541 set arrow [lindex $ls 2]
3537 3542 if {$arrow eq "first" || $arrow eq "both"} {
3538 3543 if {abs([lindex $coords 1] - $y) < $thresh} {
3539 3544 return "up"
3540 3545 }
3541 3546 }
3542 3547 if {$arrow eq "last" || $arrow eq "both"} {
3543 3548 if {abs([lindex $coords end] - $y) < $thresh} {
3544 3549 return "down"
3545 3550 }
3546 3551 }
3547 3552 }
3548 3553 }
3549 3554 return {}
3550 3555 }
3551 3556
3552 3557 proc arrowjump {id dirn y} {
3553 3558 global mainline sidelines canv
3554 3559
3555 3560 set yt {}
3556 3561 if {$dirn eq "down"} {
3557 3562 if {[info exists mainline($id)]} {
3558 3563 set y1 [lindex $mainline($id) 1]
3559 3564 if {$y1 > $y} {
3560 3565 set yt $y1
3561 3566 }
3562 3567 }
3563 3568 if {[info exists sidelines($id)]} {
3564 3569 foreach ls $sidelines($id) {
3565 3570 set y1 [lindex $ls 0 1]
3566 3571 if {$y1 > $y && ($yt eq {} || $y1 < $yt)} {
3567 3572 set yt $y1
3568 3573 }
3569 3574 }
3570 3575 }
3571 3576 } else {
3572 3577 if {[info exists sidelines($id)]} {
3573 3578 foreach ls $sidelines($id) {
3574 3579 set y1 [lindex $ls 0 end]
3575 3580 if {$y1 < $y && ($yt eq {} || $y1 > $yt)} {
3576 3581 set yt $y1
3577 3582 }
3578 3583 }
3579 3584 }
3580 3585 }
3581 3586 if {$yt eq {}} return
3582 3587 set ymax [lindex [$canv cget -scrollregion] 3]
3583 3588 if {$ymax eq {} || $ymax <= 0} return
3584 3589 set view [$canv yview]
3585 3590 set yspan [expr {[lindex $view 1] - [lindex $view 0]}]
3586 3591 set yfrac [expr {$yt / $ymax - $yspan / 2}]
3587 3592 if {$yfrac < 0} {
3588 3593 set yfrac 0
3589 3594 }
3590 3595 $canv yview moveto $yfrac
3591 3596 }
3592 3597
3593 3598 proc lineclick {x y id isnew} {
3594 3599 global ctext commitinfo children cflist canv thickerline
3595 3600
3596 3601 unmarkmatches
3597 3602 unselectline
3598 3603 normalline
3599 3604 $canv delete hover
3600 3605 # draw this line thicker than normal
3601 3606 drawlines $id 1
3602 3607 set thickerline $id
3603 3608 if {$isnew} {
3604 3609 set ymax [lindex [$canv cget -scrollregion] 3]
3605 3610 if {$ymax eq {}} return
3606 3611 set yfrac [lindex [$canv yview] 0]
3607 3612 set y [expr {$y + $yfrac * $ymax}]
3608 3613 }
3609 3614 set dirn [clickisonarrow $id $y]
3610 3615 if {$dirn ne {}} {
3611 3616 arrowjump $id $dirn $y
3612 3617 return
3613 3618 }
3614 3619
3615 3620 if {$isnew} {
3616 3621 addtohistory [list lineclick $x $y $id 0]
3617 3622 }
3618 3623 # fill the details pane with info about this line
3619 3624 $ctext conf -state normal
3620 3625 $ctext delete 0.0 end
3621 3626 $ctext tag conf link -foreground blue -underline 1
3622 3627 $ctext tag bind link <Enter> { %W configure -cursor hand2 }
3623 3628 $ctext tag bind link <Leave> { %W configure -cursor $curtextcursor }
3624 3629 $ctext insert end "Parent:\t"
3625 3630 $ctext insert end $id [list link link0]
3626 3631 $ctext tag bind link0 <1> [list selbyid $id]
3627 3632 set info $commitinfo($id)
3628 3633 $ctext insert end "\n\t[lindex $info 0]\n"
3629 3634 $ctext insert end "\tUser:\t[lindex $info 1]\n"
3630 3635 $ctext insert end "\tDate:\t[lindex $info 2]\n"
3631 3636 if {[info exists children($id)]} {
3632 3637 $ctext insert end "\nChildren:"
3633 3638 set i 0
3634 3639 foreach child $children($id) {
3635 3640 incr i
3636 3641 set info $commitinfo($child)
3637 3642 $ctext insert end "\n\t"
3638 3643 $ctext insert end $child [list link link$i]
3639 3644 $ctext tag bind link$i <1> [list selbyid $child]
3640 3645 $ctext insert end "\n\t[lindex $info 0]"
3641 3646 $ctext insert end "\n\tUser:\t[lindex $info 1]"
3642 3647 $ctext insert end "\n\tDate:\t[lindex $info 2]\n"
3643 3648 }
3644 3649 }
3645 3650 $ctext conf -state disabled
3646 3651
3647 3652 $cflist delete 0 end
3648 3653 }
3649 3654
3650 3655 proc normalline {} {
3651 3656 global thickerline
3652 3657 if {[info exists thickerline]} {
3653 3658 drawlines $thickerline 0
3654 3659 unset thickerline
3655 3660 }
3656 3661 }
3657 3662
3658 3663 proc selbyid {id} {
3659 3664 global idline
3660 3665 if {[info exists idline($id)]} {
3661 3666 selectline $idline($id) 1
3662 3667 }
3663 3668 }
3664 3669
3665 3670 proc mstime {} {
3666 3671 global startmstime
3667 3672 if {![info exists startmstime]} {
3668 3673 set startmstime [clock clicks -milliseconds]
3669 3674 }
3670 3675 return [format "%.3f" [expr {([clock click -milliseconds] - $startmstime) / 1000.0}]]
3671 3676 }
3672 3677
3673 3678 proc rowmenu {x y id} {
3674 3679 global rowctxmenu idline selectedline rowmenuid hgvdiff
3675 3680
3676 3681 if {![info exists selectedline] || $idline($id) eq $selectedline} {
3677 3682 set state disabled
3678 3683 } else {
3679 3684 set state normal
3680 3685 }
3681 3686 $rowctxmenu entryconfigure 0 -state $state
3682 3687 $rowctxmenu entryconfigure 1 -state $state
3683 3688 $rowctxmenu entryconfigure 2 -state $state
3684 3689 if { $hgvdiff ne "" } {
3685 3690 $rowctxmenu entryconfigure 6 -state $state
3686 3691 }
3687 3692 set rowmenuid $id
3688 3693 tk_popup $rowctxmenu $x $y
3689 3694 }
3690 3695
3691 3696 proc diffvssel {dirn} {
3692 3697 global rowmenuid selectedline lineid
3693 3698
3694 3699 if {![info exists selectedline]} return
3695 3700 if {$dirn} {
3696 3701 set oldid $lineid($selectedline)
3697 3702 set newid $rowmenuid
3698 3703 } else {
3699 3704 set oldid $rowmenuid
3700 3705 set newid $lineid($selectedline)
3701 3706 }
3702 3707 addtohistory [list doseldiff $oldid $newid]
3703 3708 doseldiff $oldid $newid
3704 3709 }
3705 3710
3706 3711 proc doseldiff {oldid newid} {
3707 3712 global ctext cflist
3708 3713 global commitinfo
3709 3714
3710 3715 $ctext conf -state normal
3711 3716 $ctext delete 0.0 end
3712 3717 $ctext mark set fmark.0 0.0
3713 3718 $ctext mark gravity fmark.0 left
3714 3719 $cflist delete 0 end
3715 3720 $cflist insert end "Top"
3716 3721 $ctext insert end "From "
3717 3722 $ctext tag conf link -foreground blue -underline 1
3718 3723 $ctext tag bind link <Enter> { %W configure -cursor hand2 }
3719 3724 $ctext tag bind link <Leave> { %W configure -cursor $curtextcursor }
3720 3725 $ctext tag bind link0 <1> [list selbyid $oldid]
3721 3726 $ctext insert end $oldid [list link link0]
3722 3727 $ctext insert end "\n "
3723 3728 $ctext insert end [lindex $commitinfo($oldid) 0]
3724 3729 $ctext insert end "\n\nTo "
3725 3730 $ctext tag bind link1 <1> [list selbyid $newid]
3726 3731 $ctext insert end $newid [list link link1]
3727 3732 $ctext insert end "\n "
3728 3733 $ctext insert end [lindex $commitinfo($newid) 0]
3729 3734 $ctext insert end "\n"
3730 3735 $ctext conf -state disabled
3731 3736 $ctext tag delete Comments
3732 3737 $ctext tag remove found 1.0 end
3733 3738 startdiff [list $newid $oldid]
3734 3739 }
3735 3740
3736 3741 proc mkpatch {} {
3737 3742 global rowmenuid currentid commitinfo patchtop patchnum
3738 3743
3739 3744 if {![info exists currentid]} return
3740 3745 set oldid $currentid
3741 3746 set oldhead [lindex $commitinfo($oldid) 0]
3742 3747 set newid $rowmenuid
3743 3748 set newhead [lindex $commitinfo($newid) 0]
3744 3749 set top .patch
3745 3750 set patchtop $top
3746 3751 catch {destroy $top}
3747 3752 toplevel $top
3748 3753 ttk::label $top.from -text "From:"
3749 3754 ttk::entry $top.fromsha1 -width 40
3750 3755 $top.fromsha1 insert 0 $oldid
3751 3756 $top.fromsha1 conf -state readonly
3752 3757 grid $top.from $top.fromsha1 -sticky w -pady {10 0}
3753 3758 ttk::entry $top.fromhead -width 60
3754 3759 $top.fromhead insert 0 $oldhead
3755 3760 $top.fromhead conf -state readonly
3756 3761 grid x $top.fromhead -sticky w
3757 3762 ttk::label $top.to -text "To:"
3758 3763 ttk::entry $top.tosha1 -width 40
3759 3764 $top.tosha1 insert 0 $newid
3760 3765 $top.tosha1 conf -state readonly
3761 3766 grid $top.to $top.tosha1 -sticky w
3762 3767 ttk::entry $top.tohead -width 60
3763 3768 $top.tohead insert 0 $newhead
3764 3769 $top.tohead conf -state readonly
3765 3770 grid x $top.tohead -sticky w
3766 3771 ttk::button $top.rev -text "Reverse" -command mkpatchrev
3767 3772 grid $top.rev x -pady 10
3768 3773 ttk::label $top.flab -text "Output file:"
3769 3774 ttk::entry $top.fname -width 60
3770 3775 $top.fname insert 0 [file normalize "patch$patchnum.patch"]
3771 3776 incr patchnum
3772 3777 grid $top.flab $top.fname -sticky w
3773 3778 ttk::frame $top.buts
3774 3779 ttk::button $top.buts.gen -text "Generate" -command mkpatchgo
3775 3780 ttk::button $top.buts.can -text "Cancel" -command mkpatchcan
3776 3781 grid $top.buts.gen $top.buts.can
3777 3782 grid columnconfigure $top.buts 0 -weight 1 -uniform a
3778 3783 grid columnconfigure $top.buts 1 -weight 1 -uniform a
3779 3784 grid $top.buts - -pady 10 -sticky ew
3780 3785 focus $top.fname
3781 3786 popupify $top
3782 3787 wm title $top "Generate a patch"
3783 3788 }
3784 3789
3785 3790 proc mkpatchrev {} {
3786 3791 global patchtop
3787 3792
3788 3793 set oldid [$patchtop.fromsha1 get]
3789 3794 set oldhead [$patchtop.fromhead get]
3790 3795 set newid [$patchtop.tosha1 get]
3791 3796 set newhead [$patchtop.tohead get]
3792 3797 foreach e [list fromsha1 fromhead tosha1 tohead] \
3793 3798 v [list $newid $newhead $oldid $oldhead] {
3794 3799 $patchtop.$e conf -state normal
3795 3800 $patchtop.$e delete 0 end
3796 3801 $patchtop.$e insert 0 $v
3797 3802 $patchtop.$e conf -state readonly
3798 3803 }
3799 3804 }
3800 3805
3801 3806 proc mkpatchgo {} {
3802 3807 global patchtop env
3803 3808
3804 3809 set oldid [$patchtop.fromsha1 get]
3805 3810 set newid [$patchtop.tosha1 get]
3806 3811 set fname [$patchtop.fname get]
3807 3812 if {[catch {exec $env(HG) --config ui.report_untrusted=false debug-diff-tree -p $oldid $newid >$fname &} err]} {
3808 3813 error_popup "Error creating patch: $err"
3809 3814 }
3810 3815 catch {destroy $patchtop}
3811 3816 unset patchtop
3812 3817 }
3813 3818
3814 3819 proc mkpatchcan {} {
3815 3820 global patchtop
3816 3821
3817 3822 catch {destroy $patchtop}
3818 3823 unset patchtop
3819 3824 }
3820 3825
3821 3826 proc mktag {} {
3822 3827 global rowmenuid mktagtop commitinfo
3823 3828
3824 3829 set top .maketag
3825 3830 set mktagtop $top
3826 3831 catch {destroy $top}
3827 3832 toplevel $top
3828 3833 ttk::label $top.id -text "ID:"
3829 3834 ttk::entry $top.sha1 -width 40
3830 3835 $top.sha1 insert 0 $rowmenuid
3831 3836 $top.sha1 conf -state readonly
3832 3837 grid $top.id $top.sha1 -sticky w -pady {10 0}
3833 3838 ttk::entry $top.head -width 60
3834 3839 $top.head insert 0 [lindex $commitinfo($rowmenuid) 0]
3835 3840 $top.head conf -state readonly
3836 3841 grid x $top.head -sticky w
3837 3842 ttk::label $top.tlab -text "Tag name:"
3838 3843 ttk::entry $top.tag -width 60
3839 3844 grid $top.tlab $top.tag -sticky w
3840 3845 ttk::frame $top.buts
3841 3846 ttk::button $top.buts.gen -text "Create" -command mktaggo
3842 3847 ttk::button $top.buts.can -text "Cancel" -command mktagcan
3843 3848 grid $top.buts.gen $top.buts.can
3844 3849 grid columnconfigure $top.buts 0 -weight 1 -uniform a
3845 3850 grid columnconfigure $top.buts 1 -weight 1 -uniform a
3846 3851 grid $top.buts - -pady 10 -sticky ew
3847 3852 focus $top.tag
3848 3853 popupify $top
3849 3854 wm title $top "Create a tag"
3850 3855 }
3851 3856
3852 3857 proc domktag {} {
3853 3858 global mktagtop env tagids idtags
3854 3859
3855 3860 set id [$mktagtop.sha1 get]
3856 3861 set tag [$mktagtop.tag get]
3857 3862 if {$tag == {}} {
3858 3863 error_popup "No tag name specified"
3859 3864 return
3860 3865 }
3861 3866 if {[info exists tagids($tag)]} {
3862 3867 error_popup "Tag \"$tag\" already exists"
3863 3868 return
3864 3869 }
3865 3870 if {[catch {
3866 3871 set out [exec $env(HG) --config ui.report_untrusted=false tag -r $id $tag]
3867 3872 } err]} {
3868 3873 error_popup "Error creating tag: $err"
3869 3874 return
3870 3875 }
3871 3876
3872 3877 set tagids($tag) $id
3873 3878 lappend idtags($id) $tag
3874 3879 redrawtags $id
3875 3880 }
3876 3881
3877 3882 proc redrawtags {id} {
3878 3883 global canv linehtag idline idpos selectedline
3879 3884
3880 3885 if {![info exists idline($id)]} return
3881 3886 $canv delete tag.$id
3882 3887 set xt [eval drawtags $id $idpos($id)]
3883 3888 $canv coords $linehtag($idline($id)) $xt [lindex $idpos($id) 2]
3884 3889 if {[info exists selectedline] && $selectedline == $idline($id)} {
3885 3890 selectline $selectedline 0
3886 3891 }
3887 3892 }
3888 3893
3889 3894 proc mktagcan {} {
3890 3895 global mktagtop
3891 3896
3892 3897 catch {destroy $mktagtop}
3893 3898 unset mktagtop
3894 3899 }
3895 3900
3896 3901 proc mktaggo {} {
3897 3902 domktag
3898 3903 mktagcan
3899 3904 }
3900 3905
3901 3906 proc writecommit {} {
3902 3907 global rowmenuid wrcomtop commitinfo
3903 3908
3904 3909 set top .writecommit
3905 3910 set wrcomtop $top
3906 3911 catch {destroy $top}
3907 3912 toplevel $top
3908 3913 ttk::label $top.id -text "ID:"
3909 3914 ttk::entry $top.sha1 -width 40
3910 3915 $top.sha1 insert 0 $rowmenuid
3911 3916 $top.sha1 conf -state readonly
3912 3917 grid $top.id $top.sha1 -sticky w -pady {10 0}
3913 3918 ttk::entry $top.head -width 60
3914 3919 $top.head insert 0 [lindex $commitinfo($rowmenuid) 0]
3915 3920 $top.head conf -state readonly
3916 3921 grid x $top.head -sticky w
3917 3922 ttk::label $top.flab -text "Output file:"
3918 3923 ttk::entry $top.fname -width 60
3919 3924 $top.fname insert 0 [file normalize "commit-[string range $rowmenuid 0 6].diff"]
3920 3925 grid $top.flab $top.fname -sticky w
3921 3926 ttk::frame $top.buts
3922 3927 ttk::button $top.buts.gen -text "Write" -command wrcomgo
3923 3928 ttk::button $top.buts.can -text "Cancel" -command wrcomcan
3924 3929 grid $top.buts.gen $top.buts.can
3925 3930 grid columnconfigure $top.buts 0 -weight 1 -uniform a
3926 3931 grid columnconfigure $top.buts 1 -weight 1 -uniform a
3927 3932 grid $top.buts - -pady 10 -sticky ew
3928 3933 focus $top.fname
3929 3934 popupify $top
3930 3935 wm title $top "Write commit to a file"
3931 3936 }
3932 3937
3933 3938 proc wrcomgo {} {
3934 3939 global wrcomtop
3935 3940
3936 3941 set id [$wrcomtop.sha1 get]
3937 3942 set fname [$wrcomtop.fname get]
3938 3943 if {[catch {exec $::env(HG) --config ui.report_untrusted=false export --git -o [string map {% %%} $fname] $id} err]} {
3939 3944 error_popup "Error writing commit: $err"
3940 3945 }
3941 3946 catch {destroy $wrcomtop}
3942 3947 unset wrcomtop
3943 3948 }
3944 3949
3945 3950 proc wrcomcan {} {
3946 3951 global wrcomtop
3947 3952
3948 3953 catch {destroy $wrcomtop}
3949 3954 unset wrcomtop
3950 3955 }
3951 3956
3952 3957 proc listrefs {id} {
3953 3958 global idtags idheads idotherrefs idbookmarks
3954 3959
3955 3960 set w {}
3956 3961 if {[info exists idbookmarks($id)]} {
3957 3962 set w $idbookmarks($id)
3958 3963 }
3959 3964 set x {}
3960 3965 if {[info exists idtags($id)]} {
3961 3966 set x $idtags($id)
3962 3967 }
3963 3968 set y {}
3964 3969 if {[info exists idheads($id)]} {
3965 3970 set y $idheads($id)
3966 3971 }
3967 3972 set z {}
3968 3973 if {[info exists idotherrefs($id)]} {
3969 3974 set z $idotherrefs($id)
3970 3975 }
3971 3976 return [list $w $x $y $z]
3972 3977 }
3973 3978
3974 3979 proc rereadrefs {} {
3975 3980 global idbookmarks idtags idheads idotherrefs
3976 3981 global bookmarkids tagids headids otherrefids
3977 3982
3978 3983 set refids [concat [array names idtags] \
3979 3984 [array names idheads] [array names idotherrefs] \
3980 3985 [array names idbookmarks]]
3981 3986 foreach id $refids {
3982 3987 if {![info exists ref($id)]} {
3983 3988 set ref($id) [listrefs $id]
3984 3989 }
3985 3990 }
3986 3991 foreach v {tagids idtags headids idheads otherrefids idotherrefs \
3987 3992 bookmarkids idbookmarks} {
3988 3993 catch {unset $v}
3989 3994 }
3990 3995 readrefs
3991 3996 set refids [lsort -unique [concat $refids [array names idtags] \
3992 3997 [array names idheads] [array names idotherrefs] \
3993 3998 [array names idbookmarks]]]
3994 3999 foreach id $refids {
3995 4000 set v [listrefs $id]
3996 4001 if {![info exists ref($id)] || $ref($id) != $v} {
3997 4002 redrawtags $id
3998 4003 }
3999 4004 }
4000 4005 }
4001 4006
4002 4007 proc vdiff {withparent} {
4003 4008 global env rowmenuid selectedline lineid hgvdiff
4004 4009
4005 4010 if {![info exists rowmenuid]} return
4006 4011 set curid $rowmenuid
4007 4012
4008 4013 if {$withparent} {
4009 4014 set parents [exec $env(HG) --config ui.report_untrusted=false parents --rev $curid --template "{node}\n"]
4010 4015 set firstparent [lindex [split $parents "\n"] 0]
4011 4016 set otherid $firstparent
4012 4017 } else {
4013 4018 if {![info exists selectedline]} return
4014 4019 set otherid $lineid($selectedline)
4015 4020 }
4016 4021 set range "$otherid:$curid"
4017 4022 if {[catch {exec $env(HG) --config ui.report_untrusted=false $hgvdiff -r $range} err]} {
4018 4023 # Ignore errors, this is just visualization
4019 4024 }
4020 4025 }
4021 4026
4022 4027 proc showtag {tag isnew} {
4023 4028 global ctext cflist tagcontents tagids linknum
4024 4029
4025 4030 if {$isnew} {
4026 4031 addtohistory [list showtag $tag 0]
4027 4032 }
4028 4033 $ctext conf -state normal
4029 4034 $ctext delete 0.0 end
4030 4035 set linknum 0
4031 4036 if {[info exists tagcontents($tag)]} {
4032 4037 set text $tagcontents($tag)
4033 4038 } else {
4034 4039 set text "Tag: $tag\nId: $tagids($tag)"
4035 4040 }
4036 4041 appendwithlinks $text
4037 4042 $ctext conf -state disabled
4038 4043 $cflist delete 0 end
4039 4044 }
4040 4045
4041 4046 proc doquit {} {
4042 4047 global stopped
4043 4048 set stopped 100
4044 4049 destroy .
4045 4050 }
4046 4051
4047 4052 proc getconfig {} {
4048 4053 global env
4049 4054
4050 4055 set lines [exec $env(HG) debug-config]
4051 4056 regsub -all "\r\n" $lines "\n" config
4052 4057 set config {}
4053 4058 foreach line [split $lines "\n"] {
4054 4059 regsub "^(k|v)=" $line "" line
4055 4060 lappend config $line
4056 4061 }
4057 4062 return $config
4058 4063 }
4059 4064
4060 4065 # defaults...
4061 4066 set datemode 0
4062 4067 set boldnames 0
4063 4068 set diffopts "-U 5 -p"
4064 4069
4065 4070 set mainfont {Helvetica 9}
4066 4071 set curidfont {}
4067 4072 set textfont {Courier 9}
4068 4073 set findmergefiles 0
4069 4074 set gaudydiff 0
4070 4075 set maxgraphpct 50
4071 4076 set maxwidth 16
4072 4077
4073 4078 set colors {green red blue magenta darkgrey brown orange}
4074 4079 set authorcolors {
4075 4080 black blue deeppink mediumorchid blue burlywood4 goldenrod slateblue red2 navy dimgrey
4076 4081 }
4077 4082 set bgcolor white
4078 4083
4079 4084 # This color should probably be some system color (provided by tk),
4080 4085 # but as the bgcolor has always been set to white, I choose to ignore
4081 4086 set fgcolor black
4082 4087 set diffaddcolor "#00a000"
4083 4088 set diffremcolor red
4084 4089 set diffmerge1color red
4085 4090 set diffmerge2color blue
4086 4091 set hunksepcolor blue
4087 4092
4088 4093 catch {source ~/.hgk}
4089 4094
4090 4095 if {$curidfont == ""} { # initialize late based on current mainfont
4091 4096 set curidfont "$mainfont bold italic underline"
4092 4097 }
4093 4098
4094 4099 set namefont $mainfont
4095 4100 if {$boldnames} {
4096 4101 lappend namefont bold
4097 4102 }
4098 4103
4099 4104 set revtreeargs {}
4100 4105 foreach arg $argv {
4101 4106 switch -regexp -- $arg {
4102 4107 "^$" { }
4103 4108 "^-b" { set boldnames 1 }
4104 4109 "^-d" { set datemode 1 }
4105 4110 default {
4106 4111 lappend revtreeargs $arg
4107 4112 }
4108 4113 }
4109 4114 }
4110 4115
4111 4116 set history {}
4112 4117 set historyindex 0
4113 4118
4114 4119 set stopped 0
4115 4120 set redisplaying 0
4116 4121 set stuffsaved 0
4117 4122 set patchnum 0
4118 4123
4119 4124 array set config [getconfig]
4120 4125 set hgvdiff $config(vdiff)
4121 4126 setcoords
4122 4127 makewindow
4123 4128 readrefs
4124 4129 set hgroot [exec $env(HG) root]
4125 4130 wm title . "hgk $hgroot"
4126 4131 getcommits $revtreeargs
@@ -1,357 +1,360 b''
1 1 # Minimal support for git commands on an hg repository
2 2 #
3 3 # Copyright 2005, 2006 Chris Mason <mason@suse.com>
4 4 #
5 5 # This software may be used and distributed according to the terms of the
6 6 # GNU General Public License version 2 or any later version.
7 7
8 8 '''browse the repository in a graphical way
9 9
10 10 The hgk extension allows browsing the history of a repository in a
11 11 graphical way. It requires Tcl/Tk version 8.4 or later. (Tcl/Tk is not
12 12 distributed with Mercurial.)
13 13
14 14 hgk consists of two parts: a Tcl script that does the displaying and
15 15 querying of information, and an extension to Mercurial named hgk.py,
16 16 which provides hooks for hgk to get information. hgk can be found in
17 17 the contrib directory, and the extension is shipped in the hgext
18 18 repository, and needs to be enabled.
19 19
20 20 The :hg:`view` command will launch the hgk Tcl script. For this command
21 21 to work, hgk must be in your search path. Alternately, you can specify
22 22 the path to hgk in your configuration file::
23 23
24 24 [hgk]
25 25 path=/location/of/hgk
26 26
27 27 hgk can make use of the extdiff extension to visualize revisions.
28 28 Assuming you had already configured extdiff vdiff command, just add::
29 29
30 30 [hgk]
31 31 vdiff=vdiff
32 32
33 33 Revisions context menu will now display additional entries to fire
34 34 vdiff on hovered and selected revisions.
35 35 '''
36 36
37 37 import os
38 from mercurial import cmdutil, commands, patch, revlog, scmutil
38 from mercurial import cmdutil, commands, patch, revlog, scmutil, obsolete
39 39 from mercurial.node import nullid, nullrev, short
40 40 from mercurial.i18n import _
41 41
42 42 cmdtable = {}
43 43 command = cmdutil.command(cmdtable)
44 44 testedwith = 'internal'
45 45
46 46 @command('debug-diff-tree',
47 47 [('p', 'patch', None, _('generate patch')),
48 48 ('r', 'recursive', None, _('recursive')),
49 49 ('P', 'pretty', None, _('pretty')),
50 50 ('s', 'stdin', None, _('stdin')),
51 51 ('C', 'copy', None, _('detect copies')),
52 52 ('S', 'search', "", _('search'))],
53 53 ('[OPTION]... NODE1 NODE2 [FILE]...'),
54 54 inferrepo=True)
55 55 def difftree(ui, repo, node1=None, node2=None, *files, **opts):
56 56 """diff trees from two commits"""
57 57 def __difftree(repo, node1, node2, files=[]):
58 58 assert node2 is not None
59 59 mmap = repo[node1].manifest()
60 60 mmap2 = repo[node2].manifest()
61 61 m = scmutil.match(repo[node1], files)
62 62 modified, added, removed = repo.status(node1, node2, m)[:3]
63 63 empty = short(nullid)
64 64
65 65 for f in modified:
66 66 # TODO get file permissions
67 67 ui.write(":100664 100664 %s %s M\t%s\t%s\n" %
68 68 (short(mmap[f]), short(mmap2[f]), f, f))
69 69 for f in added:
70 70 ui.write(":000000 100664 %s %s N\t%s\t%s\n" %
71 71 (empty, short(mmap2[f]), f, f))
72 72 for f in removed:
73 73 ui.write(":100664 000000 %s %s D\t%s\t%s\n" %
74 74 (short(mmap[f]), empty, f, f))
75 75 ##
76 76
77 77 while True:
78 78 if opts['stdin']:
79 79 try:
80 80 line = raw_input().split(' ')
81 81 node1 = line[0]
82 82 if len(line) > 1:
83 83 node2 = line[1]
84 84 else:
85 85 node2 = None
86 86 except EOFError:
87 87 break
88 88 node1 = repo.lookup(node1)
89 89 if node2:
90 90 node2 = repo.lookup(node2)
91 91 else:
92 92 node2 = node1
93 93 node1 = repo.changelog.parents(node1)[0]
94 94 if opts['patch']:
95 95 if opts['pretty']:
96 96 catcommit(ui, repo, node2, "")
97 97 m = scmutil.match(repo[node1], files)
98 98 diffopts = patch.difffeatureopts(ui)
99 99 diffopts.git = True
100 100 chunks = patch.diff(repo, node1, node2, match=m,
101 101 opts=diffopts)
102 102 for chunk in chunks:
103 103 ui.write(chunk)
104 104 else:
105 105 __difftree(repo, node1, node2, files=files)
106 106 if not opts['stdin']:
107 107 break
108 108
109 109 def catcommit(ui, repo, n, prefix, ctx=None):
110 110 nlprefix = '\n' + prefix
111 111 if ctx is None:
112 112 ctx = repo[n]
113 113 # use ctx.node() instead ??
114 114 ui.write(("tree %s\n" % short(ctx.changeset()[0])))
115 115 for p in ctx.parents():
116 116 ui.write(("parent %s\n" % p))
117 117
118 118 date = ctx.date()
119 119 description = ctx.description().replace("\0", "")
120 120 lines = description.splitlines()
121 121 if lines and lines[-1].startswith('committer:'):
122 122 committer = lines[-1].split(': ')[1].rstrip()
123 123 else:
124 124 committer = ""
125 125
126 126 ui.write(("author %s %s %s\n" % (ctx.user(), int(date[0]), date[1])))
127 127 if committer != '':
128 128 ui.write(("committer %s %s %s\n" % (committer, int(date[0]), date[1])))
129 129 ui.write(("revision %d\n" % ctx.rev()))
130 130 ui.write(("branch %s\n" % ctx.branch()))
131 if obsolete.isenabled(repo, obsolete.createmarkersopt):
132 if ctx.obsolete():
133 ui.write(("obsolete\n"))
131 134 ui.write(("phase %s\n\n" % ctx.phasestr()))
132 135
133 136 if prefix != "":
134 137 ui.write("%s%s\n" % (prefix,
135 138 description.replace('\n', nlprefix).strip()))
136 139 else:
137 140 ui.write(description + "\n")
138 141 if prefix:
139 142 ui.write('\0')
140 143
141 144 @command('debug-merge-base', [], _('REV REV'))
142 145 def base(ui, repo, node1, node2):
143 146 """output common ancestor information"""
144 147 node1 = repo.lookup(node1)
145 148 node2 = repo.lookup(node2)
146 149 n = repo.changelog.ancestor(node1, node2)
147 150 ui.write(short(n) + "\n")
148 151
149 152 @command('debug-cat-file',
150 153 [('s', 'stdin', None, _('stdin'))],
151 154 _('[OPTION]... TYPE FILE'),
152 155 inferrepo=True)
153 156 def catfile(ui, repo, type=None, r=None, **opts):
154 157 """cat a specific revision"""
155 158 # in stdin mode, every line except the commit is prefixed with two
156 159 # spaces. This way the our caller can find the commit without magic
157 160 # strings
158 161 #
159 162 prefix = ""
160 163 if opts['stdin']:
161 164 try:
162 165 (type, r) = raw_input().split(' ')
163 166 prefix = " "
164 167 except EOFError:
165 168 return
166 169
167 170 else:
168 171 if not type or not r:
169 172 ui.warn(_("cat-file: type or revision not supplied\n"))
170 173 commands.help_(ui, 'cat-file')
171 174
172 175 while r:
173 176 if type != "commit":
174 177 ui.warn(_("aborting hg cat-file only understands commits\n"))
175 178 return 1
176 179 n = repo.lookup(r)
177 180 catcommit(ui, repo, n, prefix)
178 181 if opts['stdin']:
179 182 try:
180 183 (type, r) = raw_input().split(' ')
181 184 except EOFError:
182 185 break
183 186 else:
184 187 break
185 188
186 189 # git rev-tree is a confusing thing. You can supply a number of
187 190 # commit sha1s on the command line, and it walks the commit history
188 191 # telling you which commits are reachable from the supplied ones via
189 192 # a bitmask based on arg position.
190 193 # you can specify a commit to stop at by starting the sha1 with ^
191 194 def revtree(ui, args, repo, full="tree", maxnr=0, parents=False):
192 195 def chlogwalk():
193 196 count = len(repo)
194 197 i = count
195 198 l = [0] * 100
196 199 chunk = 100
197 200 while True:
198 201 if chunk > i:
199 202 chunk = i
200 203 i = 0
201 204 else:
202 205 i -= chunk
203 206
204 207 for x in xrange(chunk):
205 208 if i + x >= count:
206 209 l[chunk - x:] = [0] * (chunk - x)
207 210 break
208 211 if full is not None:
209 212 if (i + x) in repo:
210 213 l[x] = repo[i + x]
211 214 l[x].changeset() # force reading
212 215 else:
213 216 if (i + x) in repo:
214 217 l[x] = 1
215 218 for x in xrange(chunk - 1, -1, -1):
216 219 if l[x] != 0:
217 220 yield (i + x, full is not None and l[x] or None)
218 221 if i == 0:
219 222 break
220 223
221 224 # calculate and return the reachability bitmask for sha
222 225 def is_reachable(ar, reachable, sha):
223 226 if len(ar) == 0:
224 227 return 1
225 228 mask = 0
226 229 for i in xrange(len(ar)):
227 230 if sha in reachable[i]:
228 231 mask |= 1 << i
229 232
230 233 return mask
231 234
232 235 reachable = []
233 236 stop_sha1 = []
234 237 want_sha1 = []
235 238 count = 0
236 239
237 240 # figure out which commits they are asking for and which ones they
238 241 # want us to stop on
239 242 for i, arg in enumerate(args):
240 243 if arg.startswith('^'):
241 244 s = repo.lookup(arg[1:])
242 245 stop_sha1.append(s)
243 246 want_sha1.append(s)
244 247 elif arg != 'HEAD':
245 248 want_sha1.append(repo.lookup(arg))
246 249
247 250 # calculate the graph for the supplied commits
248 251 for i, n in enumerate(want_sha1):
249 252 reachable.append(set())
250 253 visit = [n]
251 254 reachable[i].add(n)
252 255 while visit:
253 256 n = visit.pop(0)
254 257 if n in stop_sha1:
255 258 continue
256 259 for p in repo.changelog.parents(n):
257 260 if p not in reachable[i]:
258 261 reachable[i].add(p)
259 262 visit.append(p)
260 263 if p in stop_sha1:
261 264 continue
262 265
263 266 # walk the repository looking for commits that are in our
264 267 # reachability graph
265 268 for i, ctx in chlogwalk():
266 269 if i not in repo:
267 270 continue
268 271 n = repo.changelog.node(i)
269 272 mask = is_reachable(want_sha1, reachable, n)
270 273 if mask:
271 274 parentstr = ""
272 275 if parents:
273 276 pp = repo.changelog.parents(n)
274 277 if pp[0] != nullid:
275 278 parentstr += " " + short(pp[0])
276 279 if pp[1] != nullid:
277 280 parentstr += " " + short(pp[1])
278 281 if not full:
279 282 ui.write("%s%s\n" % (short(n), parentstr))
280 283 elif full == "commit":
281 284 ui.write("%s%s\n" % (short(n), parentstr))
282 285 catcommit(ui, repo, n, ' ', ctx)
283 286 else:
284 287 (p1, p2) = repo.changelog.parents(n)
285 288 (h, h1, h2) = map(short, (n, p1, p2))
286 289 (i1, i2) = map(repo.changelog.rev, (p1, p2))
287 290
288 291 date = ctx.date()[0]
289 292 ui.write("%s %s:%s" % (date, h, mask))
290 293 mask = is_reachable(want_sha1, reachable, p1)
291 294 if i1 != nullrev and mask > 0:
292 295 ui.write("%s:%s " % (h1, mask)),
293 296 mask = is_reachable(want_sha1, reachable, p2)
294 297 if i2 != nullrev and mask > 0:
295 298 ui.write("%s:%s " % (h2, mask))
296 299 ui.write("\n")
297 300 if maxnr and count >= maxnr:
298 301 break
299 302 count += 1
300 303
301 304 @command('debug-rev-parse',
302 305 [('', 'default', '', _('ignored'))],
303 306 _('REV'))
304 307 def revparse(ui, repo, *revs, **opts):
305 308 """parse given revisions"""
306 309 def revstr(rev):
307 310 if rev == 'HEAD':
308 311 rev = 'tip'
309 312 return revlog.hex(repo.lookup(rev))
310 313
311 314 for r in revs:
312 315 revrange = r.split(':', 1)
313 316 ui.write('%s\n' % revstr(revrange[0]))
314 317 if len(revrange) == 2:
315 318 ui.write('^%s\n' % revstr(revrange[1]))
316 319
317 320 # git rev-list tries to order things by date, and has the ability to stop
318 321 # at a given commit without walking the whole repo. TODO add the stop
319 322 # parameter
320 323 @command('debug-rev-list',
321 324 [('H', 'header', None, _('header')),
322 325 ('t', 'topo-order', None, _('topo-order')),
323 326 ('p', 'parents', None, _('parents')),
324 327 ('n', 'max-count', 0, _('max-count'))],
325 328 ('[OPTION]... REV...'))
326 329 def revlist(ui, repo, *revs, **opts):
327 330 """print revisions"""
328 331 if opts['header']:
329 332 full = "commit"
330 333 else:
331 334 full = None
332 335 copy = [x for x in revs]
333 336 revtree(ui, copy, repo, full, opts['max_count'], opts['parents'])
334 337
335 338 @command('debug-config', [], _('hg debug-config'))
336 339 def config(ui, repo, **opts):
337 340 """print extension options"""
338 341 def writeopt(name, value):
339 342 ui.write(('k=%s\nv=%s\n' % (name, value)))
340 343
341 344 writeopt('vdiff', ui.config('hgk', 'vdiff', ''))
342 345
343 346
344 347 @command('view',
345 348 [('l', 'limit', '',
346 349 _('limit number of changes displayed'), _('NUM'))],
347 350 _('[-l LIMIT] [REVRANGE]'))
348 351 def view(ui, repo, *etc, **opts):
349 352 "start interactive history viewer"
350 353 os.chdir(repo.root)
351 354 optstr = ' '.join(['--%s %s' % (k, v) for k, v in opts.iteritems() if v])
352 355 if repo.filtername is None:
353 356 optstr += '--hidden'
354 357
355 358 cmd = ui.config("hgk", "path", "hgk") + " %s %s" % (optstr, " ".join(etc))
356 359 ui.debug("running %s\n" % cmd)
357 360 ui.system(cmd)
General Comments 0
You need to be logged in to leave comments. Login now