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