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