1#!/bin/sh
2# Tcl ignores the next line -*- tcl -*- \
3exec wish "$0" -- "$@"
4
5# Copyright (C) 2005-2006 Paul Mackerras. All rights reserved.
6# This program is free software; it may be used, copied, modified
7# and distributed under the terms of the GNU General Public Licence,
8# either version 2, or (at your option) any later version.
9
10proc gitdir {} {
11 global env
12 if {[info exists env(GIT_DIR)]} {
13 return $env(GIT_DIR)
14 } else {
15 return [exec git rev-parse --git-dir]
16 }
17}
18
19# A simple scheduler for compute-intensive stuff.
20# The aim is to make sure that event handlers for GUI actions can
21# run at least every 50-100 ms. Unfortunately fileevent handlers are
22# run before X event handlers, so reading from a fast source can
23# make the GUI completely unresponsive.
24proc run args {
25 global isonrunq runq
26
27 set script $args
28 if {[info exists isonrunq($script)]} return
29 if {$runq eq {}} {
30 after idle dorunq
31 }
32 lappend runq [list {} $script]
33 set isonrunq($script) 1
34}
35
36proc filerun {fd script} {
37 fileevent $fd readable [list filereadable $fd $script]
38}
39
40proc filereadable {fd script} {
41 global runq
42
43 fileevent $fd readable {}
44 if {$runq eq {}} {
45 after idle dorunq
46 }
47 lappend runq [list $fd $script]
48}
49
50proc dorunq {} {
51 global isonrunq runq
52
53 set tstart [clock clicks -milliseconds]
54 set t0 $tstart
55 while {$runq ne {}} {
56 set fd [lindex $runq 0 0]
57 set script [lindex $runq 0 1]
58 set repeat [eval $script]
59 set t1 [clock clicks -milliseconds]
60 set t [expr {$t1 - $t0}]
61 set runq [lrange $runq 1 end]
62 if {$repeat ne {} && $repeat} {
63 if {$fd eq {} || $repeat == 2} {
64 # script returns 1 if it wants to be readded
65 # file readers return 2 if they could do more straight away
66 lappend runq [list $fd $script]
67 } else {
68 fileevent $fd readable [list filereadable $fd $script]
69 }
70 } elseif {$fd eq {}} {
71 unset isonrunq($script)
72 }
73 set t0 $t1
74 if {$t1 - $tstart >= 80} break
75 }
76 if {$runq ne {}} {
77 after idle dorunq
78 }
79}
80
81# Start off a git rev-list process and arrange to read its output
82proc start_rev_list {view} {
83 global startmsecs
84 global commfd leftover tclencoding datemode
85 global viewargs viewfiles commitidx vnextroot
86 global lookingforhead showlocalchanges
87
88 set startmsecs [clock clicks -milliseconds]
89 set commitidx($view) 0
90 set vnextroot($view) 0
91 set order "--topo-order"
92 if {$datemode} {
93 set order "--date-order"
94 }
95 if {[catch {
96 set fd [open [concat | git log -z --pretty=raw $order --parents \
97 --boundary $viewargs($view) "--" $viewfiles($view)] r]
98 } err]} {
99 error_popup "Error executing git rev-list: $err"
100 exit 1
101 }
102 set commfd($view) $fd
103 set leftover($view) {}
104 set lookingforhead $showlocalchanges
105 fconfigure $fd -blocking 0 -translation lf -eofchar {}
106 if {$tclencoding != {}} {
107 fconfigure $fd -encoding $tclencoding
108 }
109 filerun $fd [list getcommitlines $fd $view]
110 nowbusy $view
111}
112
113proc stop_rev_list {} {
114 global commfd curview
115
116 if {![info exists commfd($curview)]} return
117 set fd $commfd($curview)
118 catch {
119 set pid [pid $fd]
120 exec kill $pid
121 }
122 catch {close $fd}
123 unset commfd($curview)
124}
125
126proc getcommits {} {
127 global phase canv mainfont curview
128
129 set phase getcommits
130 initlayout
131 start_rev_list $curview
132 show_status "Reading commits..."
133}
134
135# This makes a string representation of a positive integer which
136# sorts as a string in numerical order
137proc strrep {n} {
138 if {$n < 16} {
139 return [format "%x" $n]
140 } elseif {$n < 256} {
141 return [format "x%.2x" $n]
142 } elseif {$n < 65536} {
143 return [format "y%.4x" $n]
144 }
145 return [format "z%.8x" $n]
146}
147
148proc getcommitlines {fd view} {
149 global commitlisted
150 global leftover commfd
151 global displayorder commitidx commitrow commitdata
152 global parentlist children curview hlview
153 global vparentlist vdisporder vcmitlisted
154 global ordertok vnextroot idpending
155
156 set stuff [read $fd 500000]
157 # git log doesn't terminate the last commit with a null...
158 if {$stuff == {} && $leftover($view) ne {} && [eof $fd]} {
159 set stuff "\0"
160 }
161 if {$stuff == {}} {
162 if {![eof $fd]} {
163 return 1
164 }
165 # Check if we have seen any ids listed as parents that haven't
166 # appeared in the list
167 foreach vid [array names idpending "$view,*"] {
168 # should only get here if git log is buggy
169 set id [lindex [split $vid ","] 1]
170 set commitrow($vid) $commitidx($view)
171 incr commitidx($view)
172 if {$view == $curview} {
173 lappend parentlist {}
174 lappend displayorder $id
175 lappend commitlisted 0
176 } else {
177 lappend vparentlist($view) {}
178 lappend vdisporder($view) $id
179 lappend vcmitlisted($view) 0
180 }
181 }
182 global viewname
183 unset commfd($view)
184 notbusy $view
185 # set it blocking so we wait for the process to terminate
186 fconfigure $fd -blocking 1
187 if {[catch {close $fd} err]} {
188 set fv {}
189 if {$view != $curview} {
190 set fv " for the \"$viewname($view)\" view"
191 }
192 if {[string range $err 0 4] == "usage"} {
193 set err "Gitk: error reading commits$fv:\
194 bad arguments to git rev-list."
195 if {$viewname($view) eq "Command line"} {
196 append err \
197 " (Note: arguments to gitk are passed to git rev-list\
198 to allow selection of commits to be displayed.)"
199 }
200 } else {
201 set err "Error reading commits$fv: $err"
202 }
203 error_popup $err
204 }
205 if {$view == $curview} {
206 run chewcommits $view
207 }
208 return 0
209 }
210 set start 0
211 set gotsome 0
212 while 1 {
213 set i [string first "\0" $stuff $start]
214 if {$i < 0} {
215 append leftover($view) [string range $stuff $start end]
216 break
217 }
218 if {$start == 0} {
219 set cmit $leftover($view)
220 append cmit [string range $stuff 0 [expr {$i - 1}]]
221 set leftover($view) {}
222 } else {
223 set cmit [string range $stuff $start [expr {$i - 1}]]
224 }
225 set start [expr {$i + 1}]
226 set j [string first "\n" $cmit]
227 set ok 0
228 set listed 1
229 if {$j >= 0 && [string match "commit *" $cmit]} {
230 set ids [string range $cmit 7 [expr {$j - 1}]]
231 if {[string match {[-<>]*} $ids]} {
232 switch -- [string index $ids 0] {
233 "-" {set listed 0}
234 "<" {set listed 2}
235 ">" {set listed 3}
236 }
237 set ids [string range $ids 1 end]
238 }
239 set ok 1
240 foreach id $ids {
241 if {[string length $id] != 40} {
242 set ok 0
243 break
244 }
245 }
246 }
247 if {!$ok} {
248 set shortcmit $cmit
249 if {[string length $shortcmit] > 80} {
250 set shortcmit "[string range $shortcmit 0 80]..."
251 }
252 error_popup "Can't parse git log output: {$shortcmit}"
253 exit 1
254 }
255 set id [lindex $ids 0]
256 if {![info exists ordertok($view,$id)]} {
257 set otok "o[strrep $vnextroot($view)]"
258 incr vnextroot($view)
259 set ordertok($view,$id) $otok
260 } else {
261 set otok $ordertok($view,$id)
262 unset idpending($view,$id)
263 }
264 if {$listed} {
265 set olds [lrange $ids 1 end]
266 if {[llength $olds] == 1} {
267 set p [lindex $olds 0]
268 lappend children($view,$p) $id
269 if {![info exists ordertok($view,$p)]} {
270 set ordertok($view,$p) $ordertok($view,$id)
271 set idpending($view,$p) 1
272 }
273 } else {
274 set i 0
275 foreach p $olds {
276 if {$i == 0 || [lsearch -exact $olds $p] >= $i} {
277 lappend children($view,$p) $id
278 }
279 if {![info exists ordertok($view,$p)]} {
280 set ordertok($view,$p) "$otok[strrep $i]]"
281 set idpending($view,$p) 1
282 }
283 incr i
284 }
285 }
286 } else {
287 set olds {}
288 }
289 if {![info exists children($view,$id)]} {
290 set children($view,$id) {}
291 }
292 set commitdata($id) [string range $cmit [expr {$j + 1}] end]
293 set commitrow($view,$id) $commitidx($view)
294 incr commitidx($view)
295 if {$view == $curview} {
296 lappend parentlist $olds
297 lappend displayorder $id
298 lappend commitlisted $listed
299 } else {
300 lappend vparentlist($view) $olds
301 lappend vdisporder($view) $id
302 lappend vcmitlisted($view) $listed
303 }
304 set gotsome 1
305 }
306 if {$gotsome} {
307 run chewcommits $view
308 }
309 return 2
310}
311
312proc chewcommits {view} {
313 global curview hlview commfd
314 global selectedline pending_select
315
316 set more 0
317 if {$view == $curview} {
318 set allread [expr {![info exists commfd($view)]}]
319 set tlimit [expr {[clock clicks -milliseconds] + 50}]
320 set more [layoutmore $tlimit $allread]
321 if {$allread && !$more} {
322 global displayorder commitidx phase
323 global numcommits startmsecs
324
325 if {[info exists pending_select]} {
326 set row [first_real_row]
327 selectline $row 1
328 }
329 if {$commitidx($curview) > 0} {
330 #set ms [expr {[clock clicks -milliseconds] - $startmsecs}]
331 #puts "overall $ms ms for $numcommits commits"
332 } else {
333 show_status "No commits selected"
334 }
335 notbusy layout
336 set phase {}
337 }
338 }
339 if {[info exists hlview] && $view == $hlview} {
340 vhighlightmore
341 }
342 return $more
343}
344
345proc readcommit {id} {
346 if {[catch {set contents [exec git cat-file commit $id]}]} return
347 parsecommit $id $contents 0
348}
349
350proc updatecommits {} {
351 global viewdata curview phase displayorder ordertok idpending
352 global children commitrow selectedline thickerline showneartags
353
354 if {$phase ne {}} {
355 stop_rev_list
356 set phase {}
357 }
358 set n $curview
359 foreach id $displayorder {
360 catch {unset children($n,$id)}
361 catch {unset commitrow($n,$id)}
362 catch {unset ordertok($n,$id)}
363 }
364 foreach vid [array names idpending "$n,*"] {
365 unset idpending($vid)
366 }
367 set curview -1
368 catch {unset selectedline}
369 catch {unset thickerline}
370 catch {unset viewdata($n)}
371 readrefs
372 changedrefs
373 if {$showneartags} {
374 getallcommits
375 }
376 showview $n
377}
378
379proc parsecommit {id contents listed} {
380 global commitinfo cdate
381
382 set inhdr 1
383 set comment {}
384 set headline {}
385 set auname {}
386 set audate {}
387 set comname {}
388 set comdate {}
389 set hdrend [string first "\n\n" $contents]
390 if {$hdrend < 0} {
391 # should never happen...
392 set hdrend [string length $contents]
393 }
394 set header [string range $contents 0 [expr {$hdrend - 1}]]
395 set comment [string range $contents [expr {$hdrend + 2}] end]
396 foreach line [split $header "\n"] {
397 set tag [lindex $line 0]
398 if {$tag == "author"} {
399 set audate [lindex $line end-1]
400 set auname [lrange $line 1 end-2]
401 } elseif {$tag == "committer"} {
402 set comdate [lindex $line end-1]
403 set comname [lrange $line 1 end-2]
404 }
405 }
406 set headline {}
407 # take the first non-blank line of the comment as the headline
408 set headline [string trimleft $comment]
409 set i [string first "\n" $headline]
410 if {$i >= 0} {
411 set headline [string range $headline 0 $i]
412 }
413 set headline [string trimright $headline]
414 set i [string first "\r" $headline]
415 if {$i >= 0} {
416 set headline [string trimright [string range $headline 0 $i]]
417 }
418 if {!$listed} {
419 # git rev-list indents the comment by 4 spaces;
420 # if we got this via git cat-file, add the indentation
421 set newcomment {}
422 foreach line [split $comment "\n"] {
423 append newcomment " "
424 append newcomment $line
425 append newcomment "\n"
426 }
427 set comment $newcomment
428 }
429 if {$comdate != {}} {
430 set cdate($id) $comdate
431 }
432 set commitinfo($id) [list $headline $auname $audate \
433 $comname $comdate $comment]
434}
435
436proc getcommit {id} {
437 global commitdata commitinfo
438
439 if {[info exists commitdata($id)]} {
440 parsecommit $id $commitdata($id) 1
441 } else {
442 readcommit $id
443 if {![info exists commitinfo($id)]} {
444 set commitinfo($id) {"No commit information available"}
445 }
446 }
447 return 1
448}
449
450proc readrefs {} {
451 global tagids idtags headids idheads tagobjid
452 global otherrefids idotherrefs mainhead mainheadid
453
454 foreach v {tagids idtags headids idheads otherrefids idotherrefs} {
455 catch {unset $v}
456 }
457 set refd [open [list | git show-ref -d] r]
458 while {[gets $refd line] >= 0} {
459 if {[string index $line 40] ne " "} continue
460 set id [string range $line 0 39]
461 set ref [string range $line 41 end]
462 if {![string match "refs/*" $ref]} continue
463 set name [string range $ref 5 end]
464 if {[string match "remotes/*" $name]} {
465 if {![string match "*/HEAD" $name]} {
466 set headids($name) $id
467 lappend idheads($id) $name
468 }
469 } elseif {[string match "heads/*" $name]} {
470 set name [string range $name 6 end]
471 set headids($name) $id
472 lappend idheads($id) $name
473 } elseif {[string match "tags/*" $name]} {
474 # this lets refs/tags/foo^{} overwrite refs/tags/foo,
475 # which is what we want since the former is the commit ID
476 set name [string range $name 5 end]
477 if {[string match "*^{}" $name]} {
478 set name [string range $name 0 end-3]
479 } else {
480 set tagobjid($name) $id
481 }
482 set tagids($name) $id
483 lappend idtags($id) $name
484 } else {
485 set otherrefids($name) $id
486 lappend idotherrefs($id) $name
487 }
488 }
489 catch {close $refd}
490 set mainhead {}
491 set mainheadid {}
492 catch {
493 set thehead [exec git symbolic-ref HEAD]
494 if {[string match "refs/heads/*" $thehead]} {
495 set mainhead [string range $thehead 11 end]
496 if {[info exists headids($mainhead)]} {
497 set mainheadid $headids($mainhead)
498 }
499 }
500 }
501}
502
503# skip over fake commits
504proc first_real_row {} {
505 global nullid nullid2 displayorder numcommits
506
507 for {set row 0} {$row < $numcommits} {incr row} {
508 set id [lindex $displayorder $row]
509 if {$id ne $nullid && $id ne $nullid2} {
510 break
511 }
512 }
513 return $row
514}
515
516# update things for a head moved to a child of its previous location
517proc movehead {id name} {
518 global headids idheads
519
520 removehead $headids($name) $name
521 set headids($name) $id
522 lappend idheads($id) $name
523}
524
525# update things when a head has been removed
526proc removehead {id name} {
527 global headids idheads
528
529 if {$idheads($id) eq $name} {
530 unset idheads($id)
531 } else {
532 set i [lsearch -exact $idheads($id) $name]
533 if {$i >= 0} {
534 set idheads($id) [lreplace $idheads($id) $i $i]
535 }
536 }
537 unset headids($name)
538}
539
540proc show_error {w top msg} {
541 message $w.m -text $msg -justify center -aspect 400
542 pack $w.m -side top -fill x -padx 20 -pady 20
543 button $w.ok -text OK -command "destroy $top"
544 pack $w.ok -side bottom -fill x
545 bind $top <Visibility> "grab $top; focus $top"
546 bind $top <Key-Return> "destroy $top"
547 tkwait window $top
548}
549
550proc error_popup msg {
551 set w .error
552 toplevel $w
553 wm transient $w .
554 show_error $w $w $msg
555}
556
557proc confirm_popup msg {
558 global confirm_ok
559 set confirm_ok 0
560 set w .confirm
561 toplevel $w
562 wm transient $w .
563 message $w.m -text $msg -justify center -aspect 400
564 pack $w.m -side top -fill x -padx 20 -pady 20
565 button $w.ok -text OK -command "set confirm_ok 1; destroy $w"
566 pack $w.ok -side left -fill x
567 button $w.cancel -text Cancel -command "destroy $w"
568 pack $w.cancel -side right -fill x
569 bind $w <Visibility> "grab $w; focus $w"
570 tkwait window $w
571 return $confirm_ok
572}
573
574proc makewindow {} {
575 global canv canv2 canv3 linespc charspc ctext cflist
576 global textfont mainfont uifont tabstop
577 global findtype findtypemenu findloc findstring fstring geometry
578 global entries sha1entry sha1string sha1but
579 global diffcontextstring diffcontext
580 global maincursor textcursor curtextcursor
581 global rowctxmenu fakerowmenu mergemax wrapcomment
582 global highlight_files gdttype
583 global searchstring sstring
584 global bgcolor fgcolor bglist fglist diffcolors selectbgcolor
585 global headctxmenu
586
587 menu .bar
588 .bar add cascade -label "File" -menu .bar.file
589 .bar configure -font $uifont
590 menu .bar.file
591 .bar.file add command -label "Update" -command updatecommits
592 .bar.file add command -label "Reread references" -command rereadrefs
593 .bar.file add command -label "List references" -command showrefs
594 .bar.file add command -label "Quit" -command doquit
595 .bar.file configure -font $uifont
596 menu .bar.edit
597 .bar add cascade -label "Edit" -menu .bar.edit
598 .bar.edit add command -label "Preferences" -command doprefs
599 .bar.edit configure -font $uifont
600
601 menu .bar.view -font $uifont
602 .bar add cascade -label "View" -menu .bar.view
603 .bar.view add command -label "New view..." -command {newview 0}
604 .bar.view add command -label "Edit view..." -command editview \
605 -state disabled
606 .bar.view add command -label "Delete view" -command delview -state disabled
607 .bar.view add separator
608 .bar.view add radiobutton -label "All files" -command {showview 0} \
609 -variable selectedview -value 0
610
611 menu .bar.help
612 .bar add cascade -label "Help" -menu .bar.help
613 .bar.help add command -label "About gitk" -command about
614 .bar.help add command -label "Key bindings" -command keys
615 .bar.help configure -font $uifont
616 . configure -menu .bar
617
618 # the gui has upper and lower half, parts of a paned window.
619 panedwindow .ctop -orient vertical
620
621 # possibly use assumed geometry
622 if {![info exists geometry(pwsash0)]} {
623 set geometry(topheight) [expr {15 * $linespc}]
624 set geometry(topwidth) [expr {80 * $charspc}]
625 set geometry(botheight) [expr {15 * $linespc}]
626 set geometry(botwidth) [expr {50 * $charspc}]
627 set geometry(pwsash0) "[expr {40 * $charspc}] 2"
628 set geometry(pwsash1) "[expr {60 * $charspc}] 2"
629 }
630
631 # the upper half will have a paned window, a scroll bar to the right, and some stuff below
632 frame .tf -height $geometry(topheight) -width $geometry(topwidth)
633 frame .tf.histframe
634 panedwindow .tf.histframe.pwclist -orient horizontal -sashpad 0 -handlesize 4
635
636 # create three canvases
637 set cscroll .tf.histframe.csb
638 set canv .tf.histframe.pwclist.canv
639 canvas $canv \
640 -selectbackground $selectbgcolor \
641 -background $bgcolor -bd 0 \
642 -yscrollincr $linespc -yscrollcommand "scrollcanv $cscroll"
643 .tf.histframe.pwclist add $canv
644 set canv2 .tf.histframe.pwclist.canv2
645 canvas $canv2 \
646 -selectbackground $selectbgcolor \
647 -background $bgcolor -bd 0 -yscrollincr $linespc
648 .tf.histframe.pwclist add $canv2
649 set canv3 .tf.histframe.pwclist.canv3
650 canvas $canv3 \
651 -selectbackground $selectbgcolor \
652 -background $bgcolor -bd 0 -yscrollincr $linespc
653 .tf.histframe.pwclist add $canv3
654 eval .tf.histframe.pwclist sash place 0 $geometry(pwsash0)
655 eval .tf.histframe.pwclist sash place 1 $geometry(pwsash1)
656
657 # a scroll bar to rule them
658 scrollbar $cscroll -command {allcanvs yview} -highlightthickness 0
659 pack $cscroll -side right -fill y
660 bind .tf.histframe.pwclist <Configure> {resizeclistpanes %W %w}
661 lappend bglist $canv $canv2 $canv3
662 pack .tf.histframe.pwclist -fill both -expand 1 -side left
663
664 # we have two button bars at bottom of top frame. Bar 1
665 frame .tf.bar
666 frame .tf.lbar -height 15
667
668 set sha1entry .tf.bar.sha1
669 set entries $sha1entry
670 set sha1but .tf.bar.sha1label
671 button $sha1but -text "SHA1 ID: " -state disabled -relief flat \
672 -command gotocommit -width 8 -font $uifont
673 $sha1but conf -disabledforeground [$sha1but cget -foreground]
674 pack .tf.bar.sha1label -side left
675 entry $sha1entry -width 40 -font $textfont -textvariable sha1string
676 trace add variable sha1string write sha1change
677 pack $sha1entry -side left -pady 2
678
679 image create bitmap bm-left -data {
680 #define left_width 16
681 #define left_height 16
682 static unsigned char left_bits[] = {
683 0x00, 0x00, 0xc0, 0x01, 0xe0, 0x00, 0x70, 0x00, 0x38, 0x00, 0x1c, 0x00,
684 0x0e, 0x00, 0xff, 0x7f, 0xff, 0x7f, 0xff, 0x7f, 0x0e, 0x00, 0x1c, 0x00,
685 0x38, 0x00, 0x70, 0x00, 0xe0, 0x00, 0xc0, 0x01};
686 }
687 image create bitmap bm-right -data {
688 #define right_width 16
689 #define right_height 16
690 static unsigned char right_bits[] = {
691 0x00, 0x00, 0xc0, 0x01, 0x80, 0x03, 0x00, 0x07, 0x00, 0x0e, 0x00, 0x1c,
692 0x00, 0x38, 0xff, 0x7f, 0xff, 0x7f, 0xff, 0x7f, 0x00, 0x38, 0x00, 0x1c,
693 0x00, 0x0e, 0x00, 0x07, 0x80, 0x03, 0xc0, 0x01};
694 }
695 button .tf.bar.leftbut -image bm-left -command goback \
696 -state disabled -width 26
697 pack .tf.bar.leftbut -side left -fill y
698 button .tf.bar.rightbut -image bm-right -command goforw \
699 -state disabled -width 26
700 pack .tf.bar.rightbut -side left -fill y
701
702 button .tf.bar.findbut -text "Find" -command dofind -font $uifont
703 pack .tf.bar.findbut -side left
704 set findstring {}
705 set fstring .tf.bar.findstring
706 lappend entries $fstring
707 entry $fstring -width 30 -font $textfont -textvariable findstring
708 trace add variable findstring write find_change
709 pack $fstring -side left -expand 1 -fill x -in .tf.bar
710 set findtype Exact
711 set findtypemenu [tk_optionMenu .tf.bar.findtype \
712 findtype Exact IgnCase Regexp]
713 trace add variable findtype write find_change
714 .tf.bar.findtype configure -font $uifont
715 .tf.bar.findtype.menu configure -font $uifont
716 set findloc "All fields"
717 tk_optionMenu .tf.bar.findloc findloc "All fields" Headline \
718 Comments Author Committer
719 trace add variable findloc write find_change
720 .tf.bar.findloc configure -font $uifont
721 .tf.bar.findloc.menu configure -font $uifont
722 pack .tf.bar.findloc -side right
723 pack .tf.bar.findtype -side right
724
725 # build up the bottom bar of upper window
726 label .tf.lbar.flabel -text "Highlight: Commits " \
727 -font $uifont
728 pack .tf.lbar.flabel -side left -fill y
729 set gdttype "touching paths:"
730 set gm [tk_optionMenu .tf.lbar.gdttype gdttype "touching paths:" \
731 "adding/removing string:"]
732 trace add variable gdttype write hfiles_change
733 $gm conf -font $uifont
734 .tf.lbar.gdttype conf -font $uifont
735 pack .tf.lbar.gdttype -side left -fill y
736 entry .tf.lbar.fent -width 25 -font $textfont \
737 -textvariable highlight_files
738 trace add variable highlight_files write hfiles_change
739 lappend entries .tf.lbar.fent
740 pack .tf.lbar.fent -side left -fill x -expand 1
741 label .tf.lbar.vlabel -text " OR in view" -font $uifont
742 pack .tf.lbar.vlabel -side left -fill y
743 global viewhlmenu selectedhlview
744 set viewhlmenu [tk_optionMenu .tf.lbar.vhl selectedhlview None]
745 $viewhlmenu entryconf None -command delvhighlight
746 $viewhlmenu conf -font $uifont
747 .tf.lbar.vhl conf -font $uifont
748 pack .tf.lbar.vhl -side left -fill y
749 label .tf.lbar.rlabel -text " OR " -font $uifont
750 pack .tf.lbar.rlabel -side left -fill y
751 global highlight_related
752 set m [tk_optionMenu .tf.lbar.relm highlight_related None \
753 "Descendent" "Not descendent" "Ancestor" "Not ancestor"]
754 $m conf -font $uifont
755 .tf.lbar.relm conf -font $uifont
756 trace add variable highlight_related write vrel_change
757 pack .tf.lbar.relm -side left -fill y
758
759 # Finish putting the upper half of the viewer together
760 pack .tf.lbar -in .tf -side bottom -fill x
761 pack .tf.bar -in .tf -side bottom -fill x
762 pack .tf.histframe -fill both -side top -expand 1
763 .ctop add .tf
764 .ctop paneconfigure .tf -height $geometry(topheight)
765 .ctop paneconfigure .tf -width $geometry(topwidth)
766
767 # now build up the bottom
768 panedwindow .pwbottom -orient horizontal
769
770 # lower left, a text box over search bar, scroll bar to the right
771 # if we know window height, then that will set the lower text height, otherwise
772 # we set lower text height which will drive window height
773 if {[info exists geometry(main)]} {
774 frame .bleft -width $geometry(botwidth)
775 } else {
776 frame .bleft -width $geometry(botwidth) -height $geometry(botheight)
777 }
778 frame .bleft.top
779 frame .bleft.mid
780
781 button .bleft.top.search -text "Search" -command dosearch \
782 -font $uifont
783 pack .bleft.top.search -side left -padx 5
784 set sstring .bleft.top.sstring
785 entry $sstring -width 20 -font $textfont -textvariable searchstring
786 lappend entries $sstring
787 trace add variable searchstring write incrsearch
788 pack $sstring -side left -expand 1 -fill x
789 radiobutton .bleft.mid.diff -text "Diff" \
790 -command changediffdisp -variable diffelide -value {0 0}
791 radiobutton .bleft.mid.old -text "Old version" \
792 -command changediffdisp -variable diffelide -value {0 1}
793 radiobutton .bleft.mid.new -text "New version" \
794 -command changediffdisp -variable diffelide -value {1 0}
795 label .bleft.mid.labeldiffcontext -text " Lines of context: " \
796 -font $uifont
797 pack .bleft.mid.diff .bleft.mid.old .bleft.mid.new -side left
798 spinbox .bleft.mid.diffcontext -width 5 -font $textfont \
799 -from 1 -increment 1 -to 10000000 \
800 -validate all -validatecommand "diffcontextvalidate %P" \
801 -textvariable diffcontextstring
802 .bleft.mid.diffcontext set $diffcontext
803 trace add variable diffcontextstring write diffcontextchange
804 lappend entries .bleft.mid.diffcontext
805 pack .bleft.mid.labeldiffcontext .bleft.mid.diffcontext -side left
806 set ctext .bleft.ctext
807 text $ctext -background $bgcolor -foreground $fgcolor \
808 -tabs "[expr {$tabstop * $charspc}]" \
809 -state disabled -font $textfont \
810 -yscrollcommand scrolltext -wrap none
811 scrollbar .bleft.sb -command "$ctext yview"
812 pack .bleft.top -side top -fill x
813 pack .bleft.mid -side top -fill x
814 pack .bleft.sb -side right -fill y
815 pack $ctext -side left -fill both -expand 1
816 lappend bglist $ctext
817 lappend fglist $ctext
818
819 $ctext tag conf comment -wrap $wrapcomment
820 $ctext tag conf filesep -font [concat $textfont bold] -back "#aaaaaa"
821 $ctext tag conf hunksep -fore [lindex $diffcolors 2]
822 $ctext tag conf d0 -fore [lindex $diffcolors 0]
823 $ctext tag conf d1 -fore [lindex $diffcolors 1]
824 $ctext tag conf m0 -fore red
825 $ctext tag conf m1 -fore blue
826 $ctext tag conf m2 -fore green
827 $ctext tag conf m3 -fore purple
828 $ctext tag conf m4 -fore brown
829 $ctext tag conf m5 -fore "#009090"
830 $ctext tag conf m6 -fore magenta
831 $ctext tag conf m7 -fore "#808000"
832 $ctext tag conf m8 -fore "#009000"
833 $ctext tag conf m9 -fore "#ff0080"
834 $ctext tag conf m10 -fore cyan
835 $ctext tag conf m11 -fore "#b07070"
836 $ctext tag conf m12 -fore "#70b0f0"
837 $ctext tag conf m13 -fore "#70f0b0"
838 $ctext tag conf m14 -fore "#f0b070"
839 $ctext tag conf m15 -fore "#ff70b0"
840 $ctext tag conf mmax -fore darkgrey
841 set mergemax 16
842 $ctext tag conf mresult -font [concat $textfont bold]
843 $ctext tag conf msep -font [concat $textfont bold]
844 $ctext tag conf found -back yellow
845
846 .pwbottom add .bleft
847 .pwbottom paneconfigure .bleft -width $geometry(botwidth)
848
849 # lower right
850 frame .bright
851 frame .bright.mode
852 radiobutton .bright.mode.patch -text "Patch" \
853 -command reselectline -variable cmitmode -value "patch"
854 .bright.mode.patch configure -font $uifont
855 radiobutton .bright.mode.tree -text "Tree" \
856 -command reselectline -variable cmitmode -value "tree"
857 .bright.mode.tree configure -font $uifont
858 grid .bright.mode.patch .bright.mode.tree -sticky ew
859 pack .bright.mode -side top -fill x
860 set cflist .bright.cfiles
861 set indent [font measure $mainfont "nn"]
862 text $cflist \
863 -selectbackground $selectbgcolor \
864 -background $bgcolor -foreground $fgcolor \
865 -font $mainfont \
866 -tabs [list $indent [expr {2 * $indent}]] \
867 -yscrollcommand ".bright.sb set" \
868 -cursor [. cget -cursor] \
869 -spacing1 1 -spacing3 1
870 lappend bglist $cflist
871 lappend fglist $cflist
872 scrollbar .bright.sb -command "$cflist yview"
873 pack .bright.sb -side right -fill y
874 pack $cflist -side left -fill both -expand 1
875 $cflist tag configure highlight \
876 -background [$cflist cget -selectbackground]
877 $cflist tag configure bold -font [concat $mainfont bold]
878
879 .pwbottom add .bright
880 .ctop add .pwbottom
881
882 # restore window position if known
883 if {[info exists geometry(main)]} {
884 wm geometry . "$geometry(main)"
885 }
886
887 if {[tk windowingsystem] eq {aqua}} {
888 set M1B M1
889 } else {
890 set M1B Control
891 }
892
893 bind .pwbottom <Configure> {resizecdetpanes %W %w}
894 pack .ctop -fill both -expand 1
895 bindall <1> {selcanvline %W %x %y}
896 #bindall <B1-Motion> {selcanvline %W %x %y}
897 if {[tk windowingsystem] == "win32"} {
898 bind . <MouseWheel> { windows_mousewheel_redirector %W %X %Y %D }
899 bind $ctext <MouseWheel> { windows_mousewheel_redirector %W %X %Y %D ; break }
900 } else {
901 bindall <ButtonRelease-4> "allcanvs yview scroll -5 units"
902 bindall <ButtonRelease-5> "allcanvs yview scroll 5 units"
903 }
904 bindall <2> "canvscan mark %W %x %y"
905 bindall <B2-Motion> "canvscan dragto %W %x %y"
906 bindkey <Home> selfirstline
907 bindkey <End> sellastline
908 bind . <Key-Up> "selnextline -1"
909 bind . <Key-Down> "selnextline 1"
910 bind . <Shift-Key-Up> "next_highlight -1"
911 bind . <Shift-Key-Down> "next_highlight 1"
912 bindkey <Key-Right> "goforw"
913 bindkey <Key-Left> "goback"
914 bind . <Key-Prior> "selnextpage -1"
915 bind . <Key-Next> "selnextpage 1"
916 bind . <$M1B-Home> "allcanvs yview moveto 0.0"
917 bind . <$M1B-End> "allcanvs yview moveto 1.0"
918 bind . <$M1B-Key-Up> "allcanvs yview scroll -1 units"
919 bind . <$M1B-Key-Down> "allcanvs yview scroll 1 units"
920 bind . <$M1B-Key-Prior> "allcanvs yview scroll -1 pages"
921 bind . <$M1B-Key-Next> "allcanvs yview scroll 1 pages"
922 bindkey <Key-Delete> "$ctext yview scroll -1 pages"
923 bindkey <Key-BackSpace> "$ctext yview scroll -1 pages"
924 bindkey <Key-space> "$ctext yview scroll 1 pages"
925 bindkey p "selnextline -1"
926 bindkey n "selnextline 1"
927 bindkey z "goback"
928 bindkey x "goforw"
929 bindkey i "selnextline -1"
930 bindkey k "selnextline 1"
931 bindkey j "goback"
932 bindkey l "goforw"
933 bindkey b "$ctext yview scroll -1 pages"
934 bindkey d "$ctext yview scroll 18 units"
935 bindkey u "$ctext yview scroll -18 units"
936 bindkey / {findnext 1}
937 bindkey <Key-Return> {findnext 0}
938 bindkey ? findprev
939 bindkey f nextfile
940 bindkey <F5> updatecommits
941 bind . <$M1B-q> doquit
942 bind . <$M1B-f> dofind
943 bind . <$M1B-g> {findnext 0}
944 bind . <$M1B-r> dosearchback
945 bind . <$M1B-s> dosearch
946 bind . <$M1B-equal> {incrfont 1}
947 bind . <$M1B-KP_Add> {incrfont 1}
948 bind . <$M1B-minus> {incrfont -1}
949 bind . <$M1B-KP_Subtract> {incrfont -1}
950 wm protocol . WM_DELETE_WINDOW doquit
951 bind . <Button-1> "click %W"
952 bind $fstring <Key-Return> dofind
953 bind $sha1entry <Key-Return> gotocommit
954 bind $sha1entry <<PasteSelection>> clearsha1
955 bind $cflist <1> {sel_flist %W %x %y; break}
956 bind $cflist <B1-Motion> {sel_flist %W %x %y; break}
957 bind $cflist <ButtonRelease-1> {treeclick %W %x %y}
958 bind $cflist <Button-3> {pop_flist_menu %W %X %Y %x %y}
959
960 set maincursor [. cget -cursor]
961 set textcursor [$ctext cget -cursor]
962 set curtextcursor $textcursor
963
964 set rowctxmenu .rowctxmenu
965 menu $rowctxmenu -tearoff 0
966 $rowctxmenu add command -label "Diff this -> selected" \
967 -command {diffvssel 0}
968 $rowctxmenu add command -label "Diff selected -> this" \
969 -command {diffvssel 1}
970 $rowctxmenu add command -label "Make patch" -command mkpatch
971 $rowctxmenu add command -label "Create tag" -command mktag
972 $rowctxmenu add command -label "Write commit to file" -command writecommit
973 $rowctxmenu add command -label "Create new branch" -command mkbranch
974 $rowctxmenu add command -label "Cherry-pick this commit" \
975 -command cherrypick
976 $rowctxmenu add command -label "Reset HEAD branch to here" \
977 -command resethead
978
979 set fakerowmenu .fakerowmenu
980 menu $fakerowmenu -tearoff 0
981 $fakerowmenu add command -label "Diff this -> selected" \
982 -command {diffvssel 0}
983 $fakerowmenu add command -label "Diff selected -> this" \
984 -command {diffvssel 1}
985 $fakerowmenu add command -label "Make patch" -command mkpatch
986# $fakerowmenu add command -label "Commit" -command {mkcommit 0}
987# $fakerowmenu add command -label "Commit all" -command {mkcommit 1}
988# $fakerowmenu add command -label "Revert local changes" -command revertlocal
989
990 set headctxmenu .headctxmenu
991 menu $headctxmenu -tearoff 0
992 $headctxmenu add command -label "Check out this branch" \
993 -command cobranch
994 $headctxmenu add command -label "Remove this branch" \
995 -command rmbranch
996
997 global flist_menu
998 set flist_menu .flistctxmenu
999 menu $flist_menu -tearoff 0
1000 $flist_menu add command -label "Highlight this too" \
1001 -command {flist_hl 0}
1002 $flist_menu add command -label "Highlight this only" \
1003 -command {flist_hl 1}
1004}
1005
1006# Windows sends all mouse wheel events to the current focused window, not
1007# the one where the mouse hovers, so bind those events here and redirect
1008# to the correct window
1009proc windows_mousewheel_redirector {W X Y D} {
1010 global canv canv2 canv3
1011 set w [winfo containing -displayof $W $X $Y]
1012 if {$w ne ""} {
1013 set u [expr {$D < 0 ? 5 : -5}]
1014 if {$w == $canv || $w == $canv2 || $w == $canv3} {
1015 allcanvs yview scroll $u units
1016 } else {
1017 catch {
1018 $w yview scroll $u units
1019 }
1020 }
1021 }
1022}
1023
1024# mouse-2 makes all windows scan vertically, but only the one
1025# the cursor is in scans horizontally
1026proc canvscan {op w x y} {
1027 global canv canv2 canv3
1028 foreach c [list $canv $canv2 $canv3] {
1029 if {$c == $w} {
1030 $c scan $op $x $y
1031 } else {
1032 $c scan $op 0 $y
1033 }
1034 }
1035}
1036
1037proc scrollcanv {cscroll f0 f1} {
1038 $cscroll set $f0 $f1
1039 drawfrac $f0 $f1
1040 flushhighlights
1041}
1042
1043# when we make a key binding for the toplevel, make sure
1044# it doesn't get triggered when that key is pressed in the
1045# find string entry widget.
1046proc bindkey {ev script} {
1047 global entries
1048 bind . $ev $script
1049 set escript [bind Entry $ev]
1050 if {$escript == {}} {
1051 set escript [bind Entry <Key>]
1052 }
1053 foreach e $entries {
1054 bind $e $ev "$escript; break"
1055 }
1056}
1057
1058# set the focus back to the toplevel for any click outside
1059# the entry widgets
1060proc click {w} {
1061 global ctext entries
1062 foreach e [concat $entries $ctext] {
1063 if {$w == $e} return
1064 }
1065 focus .
1066}
1067
1068proc savestuff {w} {
1069 global canv canv2 canv3 ctext cflist mainfont textfont uifont tabstop
1070 global stuffsaved findmergefiles maxgraphpct
1071 global maxwidth showneartags showlocalchanges
1072 global viewname viewfiles viewargs viewperm nextviewnum
1073 global cmitmode wrapcomment datetimeformat
1074 global colors bgcolor fgcolor diffcolors diffcontext selectbgcolor
1075
1076 if {$stuffsaved} return
1077 if {![winfo viewable .]} return
1078 catch {
1079 set f [open "~/.gitk-new" w]
1080 puts $f [list set mainfont $mainfont]
1081 puts $f [list set textfont $textfont]
1082 puts $f [list set uifont $uifont]
1083 puts $f [list set tabstop $tabstop]
1084 puts $f [list set findmergefiles $findmergefiles]
1085 puts $f [list set maxgraphpct $maxgraphpct]
1086 puts $f [list set maxwidth $maxwidth]
1087 puts $f [list set cmitmode $cmitmode]
1088 puts $f [list set wrapcomment $wrapcomment]
1089 puts $f [list set showneartags $showneartags]
1090 puts $f [list set showlocalchanges $showlocalchanges]
1091 puts $f [list set datetimeformat $datetimeformat]
1092 puts $f [list set bgcolor $bgcolor]
1093 puts $f [list set fgcolor $fgcolor]
1094 puts $f [list set colors $colors]
1095 puts $f [list set diffcolors $diffcolors]
1096 puts $f [list set diffcontext $diffcontext]
1097 puts $f [list set selectbgcolor $selectbgcolor]
1098
1099 puts $f "set geometry(main) [wm geometry .]"
1100 puts $f "set geometry(topwidth) [winfo width .tf]"
1101 puts $f "set geometry(topheight) [winfo height .tf]"
1102 puts $f "set geometry(pwsash0) \"[.tf.histframe.pwclist sash coord 0]\""
1103 puts $f "set geometry(pwsash1) \"[.tf.histframe.pwclist sash coord 1]\""
1104 puts $f "set geometry(botwidth) [winfo width .bleft]"
1105 puts $f "set geometry(botheight) [winfo height .bleft]"
1106
1107 puts -nonewline $f "set permviews {"
1108 for {set v 0} {$v < $nextviewnum} {incr v} {
1109 if {$viewperm($v)} {
1110 puts $f "{[list $viewname($v) $viewfiles($v) $viewargs($v)]}"
1111 }
1112 }
1113 puts $f "}"
1114 close $f
1115 file rename -force "~/.gitk-new" "~/.gitk"
1116 }
1117 set stuffsaved 1
1118}
1119
1120proc resizeclistpanes {win w} {
1121 global oldwidth
1122 if {[info exists oldwidth($win)]} {
1123 set s0 [$win sash coord 0]
1124 set s1 [$win sash coord 1]
1125 if {$w < 60} {
1126 set sash0 [expr {int($w/2 - 2)}]
1127 set sash1 [expr {int($w*5/6 - 2)}]
1128 } else {
1129 set factor [expr {1.0 * $w / $oldwidth($win)}]
1130 set sash0 [expr {int($factor * [lindex $s0 0])}]
1131 set sash1 [expr {int($factor * [lindex $s1 0])}]
1132 if {$sash0 < 30} {
1133 set sash0 30
1134 }
1135 if {$sash1 < $sash0 + 20} {
1136 set sash1 [expr {$sash0 + 20}]
1137 }
1138 if {$sash1 > $w - 10} {
1139 set sash1 [expr {$w - 10}]
1140 if {$sash0 > $sash1 - 20} {
1141 set sash0 [expr {$sash1 - 20}]
1142 }
1143 }
1144 }
1145 $win sash place 0 $sash0 [lindex $s0 1]
1146 $win sash place 1 $sash1 [lindex $s1 1]
1147 }
1148 set oldwidth($win) $w
1149}
1150
1151proc resizecdetpanes {win w} {
1152 global oldwidth
1153 if {[info exists oldwidth($win)]} {
1154 set s0 [$win sash coord 0]
1155 if {$w < 60} {
1156 set sash0 [expr {int($w*3/4 - 2)}]
1157 } else {
1158 set factor [expr {1.0 * $w / $oldwidth($win)}]
1159 set sash0 [expr {int($factor * [lindex $s0 0])}]
1160 if {$sash0 < 45} {
1161 set sash0 45
1162 }
1163 if {$sash0 > $w - 15} {
1164 set sash0 [expr {$w - 15}]
1165 }
1166 }
1167 $win sash place 0 $sash0 [lindex $s0 1]
1168 }
1169 set oldwidth($win) $w
1170}
1171
1172proc allcanvs args {
1173 global canv canv2 canv3
1174 eval $canv $args
1175 eval $canv2 $args
1176 eval $canv3 $args
1177}
1178
1179proc bindall {event action} {
1180 global canv canv2 canv3
1181 bind $canv $event $action
1182 bind $canv2 $event $action
1183 bind $canv3 $event $action
1184}
1185
1186proc about {} {
1187 global uifont
1188 set w .about
1189 if {[winfo exists $w]} {
1190 raise $w
1191 return
1192 }
1193 toplevel $w
1194 wm title $w "About gitk"
1195 message $w.m -text {
1196Gitk - a commit viewer for git
1197
1198Copyright © 2005-2006 Paul Mackerras
1199
1200Use and redistribute under the terms of the GNU General Public License} \
1201 -justify center -aspect 400 -border 2 -bg white -relief groove
1202 pack $w.m -side top -fill x -padx 2 -pady 2
1203 $w.m configure -font $uifont
1204 button $w.ok -text Close -command "destroy $w" -default active
1205 pack $w.ok -side bottom
1206 $w.ok configure -font $uifont
1207 bind $w <Visibility> "focus $w.ok"
1208 bind $w <Key-Escape> "destroy $w"
1209 bind $w <Key-Return> "destroy $w"
1210}
1211
1212proc keys {} {
1213 global uifont
1214 set w .keys
1215 if {[winfo exists $w]} {
1216 raise $w
1217 return
1218 }
1219 if {[tk windowingsystem] eq {aqua}} {
1220 set M1T Cmd
1221 } else {
1222 set M1T Ctrl
1223 }
1224 toplevel $w
1225 wm title $w "Gitk key bindings"
1226 message $w.m -text "
1227Gitk key bindings:
1228
1229<$M1T-Q> Quit
1230<Home> Move to first commit
1231<End> Move to last commit
1232<Up>, p, i Move up one commit
1233<Down>, n, k Move down one commit
1234<Left>, z, j Go back in history list
1235<Right>, x, l Go forward in history list
1236<PageUp> Move up one page in commit list
1237<PageDown> Move down one page in commit list
1238<$M1T-Home> Scroll to top of commit list
1239<$M1T-End> Scroll to bottom of commit list
1240<$M1T-Up> Scroll commit list up one line
1241<$M1T-Down> Scroll commit list down one line
1242<$M1T-PageUp> Scroll commit list up one page
1243<$M1T-PageDown> Scroll commit list down one page
1244<Shift-Up> Move to previous highlighted line
1245<Shift-Down> Move to next highlighted line
1246<Delete>, b Scroll diff view up one page
1247<Backspace> Scroll diff view up one page
1248<Space> Scroll diff view down one page
1249u Scroll diff view up 18 lines
1250d Scroll diff view down 18 lines
1251<$M1T-F> Find
1252<$M1T-G> Move to next find hit
1253<Return> Move to next find hit
1254/ Move to next find hit, or redo find
1255? Move to previous find hit
1256f Scroll diff view to next file
1257<$M1T-S> Search for next hit in diff view
1258<$M1T-R> Search for previous hit in diff view
1259<$M1T-KP+> Increase font size
1260<$M1T-plus> Increase font size
1261<$M1T-KP-> Decrease font size
1262<$M1T-minus> Decrease font size
1263<F5> Update
1264" \
1265 -justify left -bg white -border 2 -relief groove
1266 pack $w.m -side top -fill both -padx 2 -pady 2
1267 $w.m configure -font $uifont
1268 button $w.ok -text Close -command "destroy $w" -default active
1269 pack $w.ok -side bottom
1270 $w.ok configure -font $uifont
1271 bind $w <Visibility> "focus $w.ok"
1272 bind $w <Key-Escape> "destroy $w"
1273 bind $w <Key-Return> "destroy $w"
1274}
1275
1276# Procedures for manipulating the file list window at the
1277# bottom right of the overall window.
1278
1279proc treeview {w l openlevs} {
1280 global treecontents treediropen treeheight treeparent treeindex
1281
1282 set ix 0
1283 set treeindex() 0
1284 set lev 0
1285 set prefix {}
1286 set prefixend -1
1287 set prefendstack {}
1288 set htstack {}
1289 set ht 0
1290 set treecontents() {}
1291 $w conf -state normal
1292 foreach f $l {
1293 while {[string range $f 0 $prefixend] ne $prefix} {
1294 if {$lev <= $openlevs} {
1295 $w mark set e:$treeindex($prefix) "end -1c"
1296 $w mark gravity e:$treeindex($prefix) left
1297 }
1298 set treeheight($prefix) $ht
1299 incr ht [lindex $htstack end]
1300 set htstack [lreplace $htstack end end]
1301 set prefixend [lindex $prefendstack end]
1302 set prefendstack [lreplace $prefendstack end end]
1303 set prefix [string range $prefix 0 $prefixend]
1304 incr lev -1
1305 }
1306 set tail [string range $f [expr {$prefixend+1}] end]
1307 while {[set slash [string first "/" $tail]] >= 0} {
1308 lappend htstack $ht
1309 set ht 0
1310 lappend prefendstack $prefixend
1311 incr prefixend [expr {$slash + 1}]
1312 set d [string range $tail 0 $slash]
1313 lappend treecontents($prefix) $d
1314 set oldprefix $prefix
1315 append prefix $d
1316 set treecontents($prefix) {}
1317 set treeindex($prefix) [incr ix]
1318 set treeparent($prefix) $oldprefix
1319 set tail [string range $tail [expr {$slash+1}] end]
1320 if {$lev <= $openlevs} {
1321 set ht 1
1322 set treediropen($prefix) [expr {$lev < $openlevs}]
1323 set bm [expr {$lev == $openlevs? "tri-rt": "tri-dn"}]
1324 $w mark set d:$ix "end -1c"
1325 $w mark gravity d:$ix left
1326 set str "\n"
1327 for {set i 0} {$i < $lev} {incr i} {append str "\t"}
1328 $w insert end $str
1329 $w image create end -align center -image $bm -padx 1 \
1330 -name a:$ix
1331 $w insert end $d [highlight_tag $prefix]
1332 $w mark set s:$ix "end -1c"
1333 $w mark gravity s:$ix left
1334 }
1335 incr lev
1336 }
1337 if {$tail ne {}} {
1338 if {$lev <= $openlevs} {
1339 incr ht
1340 set str "\n"
1341 for {set i 0} {$i < $lev} {incr i} {append str "\t"}
1342 $w insert end $str
1343 $w insert end $tail [highlight_tag $f]
1344 }
1345 lappend treecontents($prefix) $tail
1346 }
1347 }
1348 while {$htstack ne {}} {
1349 set treeheight($prefix) $ht
1350 incr ht [lindex $htstack end]
1351 set htstack [lreplace $htstack end end]
1352 set prefixend [lindex $prefendstack end]
1353 set prefendstack [lreplace $prefendstack end end]
1354 set prefix [string range $prefix 0 $prefixend]
1355 }
1356 $w conf -state disabled
1357}
1358
1359proc linetoelt {l} {
1360 global treeheight treecontents
1361
1362 set y 2
1363 set prefix {}
1364 while {1} {
1365 foreach e $treecontents($prefix) {
1366 if {$y == $l} {
1367 return "$prefix$e"
1368 }
1369 set n 1
1370 if {[string index $e end] eq "/"} {
1371 set n $treeheight($prefix$e)
1372 if {$y + $n > $l} {
1373 append prefix $e
1374 incr y
1375 break
1376 }
1377 }
1378 incr y $n
1379 }
1380 }
1381}
1382
1383proc highlight_tree {y prefix} {
1384 global treeheight treecontents cflist
1385
1386 foreach e $treecontents($prefix) {
1387 set path $prefix$e
1388 if {[highlight_tag $path] ne {}} {
1389 $cflist tag add bold $y.0 "$y.0 lineend"
1390 }
1391 incr y
1392 if {[string index $e end] eq "/" && $treeheight($path) > 1} {
1393 set y [highlight_tree $y $path]
1394 }
1395 }
1396 return $y
1397}
1398
1399proc treeclosedir {w dir} {
1400 global treediropen treeheight treeparent treeindex
1401
1402 set ix $treeindex($dir)
1403 $w conf -state normal
1404 $w delete s:$ix e:$ix
1405 set treediropen($dir) 0
1406 $w image configure a:$ix -image tri-rt
1407 $w conf -state disabled
1408 set n [expr {1 - $treeheight($dir)}]
1409 while {$dir ne {}} {
1410 incr treeheight($dir) $n
1411 set dir $treeparent($dir)
1412 }
1413}
1414
1415proc treeopendir {w dir} {
1416 global treediropen treeheight treeparent treecontents treeindex
1417
1418 set ix $treeindex($dir)
1419 $w conf -state normal
1420 $w image configure a:$ix -image tri-dn
1421 $w mark set e:$ix s:$ix
1422 $w mark gravity e:$ix right
1423 set lev 0
1424 set str "\n"
1425 set n [llength $treecontents($dir)]
1426 for {set x $dir} {$x ne {}} {set x $treeparent($x)} {
1427 incr lev
1428 append str "\t"
1429 incr treeheight($x) $n
1430 }
1431 foreach e $treecontents($dir) {
1432 set de $dir$e
1433 if {[string index $e end] eq "/"} {
1434 set iy $treeindex($de)
1435 $w mark set d:$iy e:$ix
1436 $w mark gravity d:$iy left
1437 $w insert e:$ix $str
1438 set treediropen($de) 0
1439 $w image create e:$ix -align center -image tri-rt -padx 1 \
1440 -name a:$iy
1441 $w insert e:$ix $e [highlight_tag $de]
1442 $w mark set s:$iy e:$ix
1443 $w mark gravity s:$iy left
1444 set treeheight($de) 1
1445 } else {
1446 $w insert e:$ix $str
1447 $w insert e:$ix $e [highlight_tag $de]
1448 }
1449 }
1450 $w mark gravity e:$ix left
1451 $w conf -state disabled
1452 set treediropen($dir) 1
1453 set top [lindex [split [$w index @0,0] .] 0]
1454 set ht [$w cget -height]
1455 set l [lindex [split [$w index s:$ix] .] 0]
1456 if {$l < $top} {
1457 $w yview $l.0
1458 } elseif {$l + $n + 1 > $top + $ht} {
1459 set top [expr {$l + $n + 2 - $ht}]
1460 if {$l < $top} {
1461 set top $l
1462 }
1463 $w yview $top.0
1464 }
1465}
1466
1467proc treeclick {w x y} {
1468 global treediropen cmitmode ctext cflist cflist_top
1469
1470 if {$cmitmode ne "tree"} return
1471 if {![info exists cflist_top]} return
1472 set l [lindex [split [$w index "@$x,$y"] "."] 0]
1473 $cflist tag remove highlight $cflist_top.0 "$cflist_top.0 lineend"
1474 $cflist tag add highlight $l.0 "$l.0 lineend"
1475 set cflist_top $l
1476 if {$l == 1} {
1477 $ctext yview 1.0
1478 return
1479 }
1480 set e [linetoelt $l]
1481 if {[string index $e end] ne "/"} {
1482 showfile $e
1483 } elseif {$treediropen($e)} {
1484 treeclosedir $w $e
1485 } else {
1486 treeopendir $w $e
1487 }
1488}
1489
1490proc setfilelist {id} {
1491 global treefilelist cflist
1492
1493 treeview $cflist $treefilelist($id) 0
1494}
1495
1496image create bitmap tri-rt -background black -foreground blue -data {
1497 #define tri-rt_width 13
1498 #define tri-rt_height 13
1499 static unsigned char tri-rt_bits[] = {
1500 0x00, 0x00, 0x00, 0x00, 0x10, 0x00, 0x30, 0x00, 0x70, 0x00, 0xf0, 0x00,
1501 0xf0, 0x01, 0xf0, 0x00, 0x70, 0x00, 0x30, 0x00, 0x10, 0x00, 0x00, 0x00,
1502 0x00, 0x00};
1503} -maskdata {
1504 #define tri-rt-mask_width 13
1505 #define tri-rt-mask_height 13
1506 static unsigned char tri-rt-mask_bits[] = {
1507 0x08, 0x00, 0x18, 0x00, 0x38, 0x00, 0x78, 0x00, 0xf8, 0x00, 0xf8, 0x01,
1508 0xf8, 0x03, 0xf8, 0x01, 0xf8, 0x00, 0x78, 0x00, 0x38, 0x00, 0x18, 0x00,
1509 0x08, 0x00};
1510}
1511image create bitmap tri-dn -background black -foreground blue -data {
1512 #define tri-dn_width 13
1513 #define tri-dn_height 13
1514 static unsigned char tri-dn_bits[] = {
1515 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0xfc, 0x07, 0xf8, 0x03,
1516 0xf0, 0x01, 0xe0, 0x00, 0x40, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
1517 0x00, 0x00};
1518} -maskdata {
1519 #define tri-dn-mask_width 13
1520 #define tri-dn-mask_height 13
1521 static unsigned char tri-dn-mask_bits[] = {
1522 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0xff, 0x1f, 0xfe, 0x0f, 0xfc, 0x07,
1523 0xf8, 0x03, 0xf0, 0x01, 0xe0, 0x00, 0x40, 0x00, 0x00, 0x00, 0x00, 0x00,
1524 0x00, 0x00};
1525}
1526
1527image create bitmap reficon-T -background black -foreground yellow -data {
1528 #define tagicon_width 13
1529 #define tagicon_height 9
1530 static unsigned char tagicon_bits[] = {
1531 0x00, 0x00, 0x00, 0x00, 0xf0, 0x07, 0xf8, 0x07,
1532 0xfc, 0x07, 0xf8, 0x07, 0xf0, 0x07, 0x00, 0x00, 0x00, 0x00};
1533} -maskdata {
1534 #define tagicon-mask_width 13
1535 #define tagicon-mask_height 9
1536 static unsigned char tagicon-mask_bits[] = {
1537 0x00, 0x00, 0xf0, 0x0f, 0xf8, 0x0f, 0xfc, 0x0f,
1538 0xfe, 0x0f, 0xfc, 0x0f, 0xf8, 0x0f, 0xf0, 0x0f, 0x00, 0x00};
1539}
1540set rectdata {
1541 #define headicon_width 13
1542 #define headicon_height 9
1543 static unsigned char headicon_bits[] = {
1544 0x00, 0x00, 0x00, 0x00, 0xf8, 0x07, 0xf8, 0x07,
1545 0xf8, 0x07, 0xf8, 0x07, 0xf8, 0x07, 0x00, 0x00, 0x00, 0x00};
1546}
1547set rectmask {
1548 #define headicon-mask_width 13
1549 #define headicon-mask_height 9
1550 static unsigned char headicon-mask_bits[] = {
1551 0x00, 0x00, 0xfc, 0x0f, 0xfc, 0x0f, 0xfc, 0x0f,
1552 0xfc, 0x0f, 0xfc, 0x0f, 0xfc, 0x0f, 0xfc, 0x0f, 0x00, 0x00};
1553}
1554image create bitmap reficon-H -background black -foreground green \
1555 -data $rectdata -maskdata $rectmask
1556image create bitmap reficon-o -background black -foreground "#ddddff" \
1557 -data $rectdata -maskdata $rectmask
1558
1559proc init_flist {first} {
1560 global cflist cflist_top selectedline difffilestart
1561
1562 $cflist conf -state normal
1563 $cflist delete 0.0 end
1564 if {$first ne {}} {
1565 $cflist insert end $first
1566 set cflist_top 1
1567 $cflist tag add highlight 1.0 "1.0 lineend"
1568 } else {
1569 catch {unset cflist_top}
1570 }
1571 $cflist conf -state disabled
1572 set difffilestart {}
1573}
1574
1575proc highlight_tag {f} {
1576 global highlight_paths
1577
1578 foreach p $highlight_paths {
1579 if {[string match $p $f]} {
1580 return "bold"
1581 }
1582 }
1583 return {}
1584}
1585
1586proc highlight_filelist {} {
1587 global cmitmode cflist
1588
1589 $cflist conf -state normal
1590 if {$cmitmode ne "tree"} {
1591 set end [lindex [split [$cflist index end] .] 0]
1592 for {set l 2} {$l < $end} {incr l} {
1593 set line [$cflist get $l.0 "$l.0 lineend"]
1594 if {[highlight_tag $line] ne {}} {
1595 $cflist tag add bold $l.0 "$l.0 lineend"
1596 }
1597 }
1598 } else {
1599 highlight_tree 2 {}
1600 }
1601 $cflist conf -state disabled
1602}
1603
1604proc unhighlight_filelist {} {
1605 global cflist
1606
1607 $cflist conf -state normal
1608 $cflist tag remove bold 1.0 end
1609 $cflist conf -state disabled
1610}
1611
1612proc add_flist {fl} {
1613 global cflist
1614
1615 $cflist conf -state normal
1616 foreach f $fl {
1617 $cflist insert end "\n"
1618 $cflist insert end $f [highlight_tag $f]
1619 }
1620 $cflist conf -state disabled
1621}
1622
1623proc sel_flist {w x y} {
1624 global ctext difffilestart cflist cflist_top cmitmode
1625
1626 if {$cmitmode eq "tree"} return
1627 if {![info exists cflist_top]} return
1628 set l [lindex [split [$w index "@$x,$y"] "."] 0]
1629 $cflist tag remove highlight $cflist_top.0 "$cflist_top.0 lineend"
1630 $cflist tag add highlight $l.0 "$l.0 lineend"
1631 set cflist_top $l
1632 if {$l == 1} {
1633 $ctext yview 1.0
1634 } else {
1635 catch {$ctext yview [lindex $difffilestart [expr {$l - 2}]]}
1636 }
1637}
1638
1639proc pop_flist_menu {w X Y x y} {
1640 global ctext cflist cmitmode flist_menu flist_menu_file
1641 global treediffs diffids
1642
1643 set l [lindex [split [$w index "@$x,$y"] "."] 0]
1644 if {$l <= 1} return
1645 if {$cmitmode eq "tree"} {
1646 set e [linetoelt $l]
1647 if {[string index $e end] eq "/"} return
1648 } else {
1649 set e [lindex $treediffs($diffids) [expr {$l-2}]]
1650 }
1651 set flist_menu_file $e
1652 tk_popup $flist_menu $X $Y
1653}
1654
1655proc flist_hl {only} {
1656 global flist_menu_file highlight_files
1657
1658 set x [shellquote $flist_menu_file]
1659 if {$only || $highlight_files eq {}} {
1660 set highlight_files $x
1661 } else {
1662 append highlight_files " " $x
1663 }
1664}
1665
1666# Functions for adding and removing shell-type quoting
1667
1668proc shellquote {str} {
1669 if {![string match "*\['\"\\ \t]*" $str]} {
1670 return $str
1671 }
1672 if {![string match "*\['\"\\]*" $str]} {
1673 return "\"$str\""
1674 }
1675 if {![string match "*'*" $str]} {
1676 return "'$str'"
1677 }
1678 return "\"[string map {\" \\\" \\ \\\\} $str]\""
1679}
1680
1681proc shellarglist {l} {
1682 set str {}
1683 foreach a $l {
1684 if {$str ne {}} {
1685 append str " "
1686 }
1687 append str [shellquote $a]
1688 }
1689 return $str
1690}
1691
1692proc shelldequote {str} {
1693 set ret {}
1694 set used -1
1695 while {1} {
1696 incr used
1697 if {![regexp -start $used -indices "\['\"\\\\ \t]" $str first]} {
1698 append ret [string range $str $used end]
1699 set used [string length $str]
1700 break
1701 }
1702 set first [lindex $first 0]
1703 set ch [string index $str $first]
1704 if {$first > $used} {
1705 append ret [string range $str $used [expr {$first - 1}]]
1706 set used $first
1707 }
1708 if {$ch eq " " || $ch eq "\t"} break
1709 incr used
1710 if {$ch eq "'"} {
1711 set first [string first "'" $str $used]
1712 if {$first < 0} {
1713 error "unmatched single-quote"
1714 }
1715 append ret [string range $str $used [expr {$first - 1}]]
1716 set used $first
1717 continue
1718 }
1719 if {$ch eq "\\"} {
1720 if {$used >= [string length $str]} {
1721 error "trailing backslash"
1722 }
1723 append ret [string index $str $used]
1724 continue
1725 }
1726 # here ch == "\""
1727 while {1} {
1728 if {![regexp -start $used -indices "\[\"\\\\]" $str first]} {
1729 error "unmatched double-quote"
1730 }
1731 set first [lindex $first 0]
1732 set ch [string index $str $first]
1733 if {$first > $used} {
1734 append ret [string range $str $used [expr {$first - 1}]]
1735 set used $first
1736 }
1737 if {$ch eq "\""} break
1738 incr used
1739 append ret [string index $str $used]
1740 incr used
1741 }
1742 }
1743 return [list $used $ret]
1744}
1745
1746proc shellsplit {str} {
1747 set l {}
1748 while {1} {
1749 set str [string trimleft $str]
1750 if {$str eq {}} break
1751 set dq [shelldequote $str]
1752 set n [lindex $dq 0]
1753 set word [lindex $dq 1]
1754 set str [string range $str $n end]
1755 lappend l $word
1756 }
1757 return $l
1758}
1759
1760# Code to implement multiple views
1761
1762proc newview {ishighlight} {
1763 global nextviewnum newviewname newviewperm uifont newishighlight
1764 global newviewargs revtreeargs
1765
1766 set newishighlight $ishighlight
1767 set top .gitkview
1768 if {[winfo exists $top]} {
1769 raise $top
1770 return
1771 }
1772 set newviewname($nextviewnum) "View $nextviewnum"
1773 set newviewperm($nextviewnum) 0
1774 set newviewargs($nextviewnum) [shellarglist $revtreeargs]
1775 vieweditor $top $nextviewnum "Gitk view definition"
1776}
1777
1778proc editview {} {
1779 global curview
1780 global viewname viewperm newviewname newviewperm
1781 global viewargs newviewargs
1782
1783 set top .gitkvedit-$curview
1784 if {[winfo exists $top]} {
1785 raise $top
1786 return
1787 }
1788 set newviewname($curview) $viewname($curview)
1789 set newviewperm($curview) $viewperm($curview)
1790 set newviewargs($curview) [shellarglist $viewargs($curview)]
1791 vieweditor $top $curview "Gitk: edit view $viewname($curview)"
1792}
1793
1794proc vieweditor {top n title} {
1795 global newviewname newviewperm viewfiles
1796 global uifont
1797
1798 toplevel $top
1799 wm title $top $title
1800 label $top.nl -text "Name" -font $uifont
1801 entry $top.name -width 20 -textvariable newviewname($n) -font $uifont
1802 grid $top.nl $top.name -sticky w -pady 5
1803 checkbutton $top.perm -text "Remember this view" -variable newviewperm($n) \
1804 -font $uifont
1805 grid $top.perm - -pady 5 -sticky w
1806 message $top.al -aspect 1000 -font $uifont \
1807 -text "Commits to include (arguments to git rev-list):"
1808 grid $top.al - -sticky w -pady 5
1809 entry $top.args -width 50 -textvariable newviewargs($n) \
1810 -background white -font $uifont
1811 grid $top.args - -sticky ew -padx 5
1812 message $top.l -aspect 1000 -font $uifont \
1813 -text "Enter files and directories to include, one per line:"
1814 grid $top.l - -sticky w
1815 text $top.t -width 40 -height 10 -background white -font $uifont
1816 if {[info exists viewfiles($n)]} {
1817 foreach f $viewfiles($n) {
1818 $top.t insert end $f
1819 $top.t insert end "\n"
1820 }
1821 $top.t delete {end - 1c} end
1822 $top.t mark set insert 0.0
1823 }
1824 grid $top.t - -sticky ew -padx 5
1825 frame $top.buts
1826 button $top.buts.ok -text "OK" -command [list newviewok $top $n] \
1827 -font $uifont
1828 button $top.buts.can -text "Cancel" -command [list destroy $top] \
1829 -font $uifont
1830 grid $top.buts.ok $top.buts.can
1831 grid columnconfigure $top.buts 0 -weight 1 -uniform a
1832 grid columnconfigure $top.buts 1 -weight 1 -uniform a
1833 grid $top.buts - -pady 10 -sticky ew
1834 focus $top.t
1835}
1836
1837proc doviewmenu {m first cmd op argv} {
1838 set nmenu [$m index end]
1839 for {set i $first} {$i <= $nmenu} {incr i} {
1840 if {[$m entrycget $i -command] eq $cmd} {
1841 eval $m $op $i $argv
1842 break
1843 }
1844 }
1845}
1846
1847proc allviewmenus {n op args} {
1848 global viewhlmenu
1849
1850 doviewmenu .bar.view 5 [list showview $n] $op $args
1851 doviewmenu $viewhlmenu 1 [list addvhighlight $n] $op $args
1852}
1853
1854proc newviewok {top n} {
1855 global nextviewnum newviewperm newviewname newishighlight
1856 global viewname viewfiles viewperm selectedview curview
1857 global viewargs newviewargs viewhlmenu
1858
1859 if {[catch {
1860 set newargs [shellsplit $newviewargs($n)]
1861 } err]} {
1862 error_popup "Error in commit selection arguments: $err"
1863 wm raise $top
1864 focus $top
1865 return
1866 }
1867 set files {}
1868 foreach f [split [$top.t get 0.0 end] "\n"] {
1869 set ft [string trim $f]
1870 if {$ft ne {}} {
1871 lappend files $ft
1872 }
1873 }
1874 if {![info exists viewfiles($n)]} {
1875 # creating a new view
1876 incr nextviewnum
1877 set viewname($n) $newviewname($n)
1878 set viewperm($n) $newviewperm($n)
1879 set viewfiles($n) $files
1880 set viewargs($n) $newargs
1881 addviewmenu $n
1882 if {!$newishighlight} {
1883 run showview $n
1884 } else {
1885 run addvhighlight $n
1886 }
1887 } else {
1888 # editing an existing view
1889 set viewperm($n) $newviewperm($n)
1890 if {$newviewname($n) ne $viewname($n)} {
1891 set viewname($n) $newviewname($n)
1892 doviewmenu .bar.view 5 [list showview $n] \
1893 entryconf [list -label $viewname($n)]
1894 doviewmenu $viewhlmenu 1 [list addvhighlight $n] \
1895 entryconf [list -label $viewname($n) -value $viewname($n)]
1896 }
1897 if {$files ne $viewfiles($n) || $newargs ne $viewargs($n)} {
1898 set viewfiles($n) $files
1899 set viewargs($n) $newargs
1900 if {$curview == $n} {
1901 run updatecommits
1902 }
1903 }
1904 }
1905 catch {destroy $top}
1906}
1907
1908proc delview {} {
1909 global curview viewdata viewperm hlview selectedhlview
1910
1911 if {$curview == 0} return
1912 if {[info exists hlview] && $hlview == $curview} {
1913 set selectedhlview None
1914 unset hlview
1915 }
1916 allviewmenus $curview delete
1917 set viewdata($curview) {}
1918 set viewperm($curview) 0
1919 showview 0
1920}
1921
1922proc addviewmenu {n} {
1923 global viewname viewhlmenu
1924
1925 .bar.view add radiobutton -label $viewname($n) \
1926 -command [list showview $n] -variable selectedview -value $n
1927 $viewhlmenu add radiobutton -label $viewname($n) \
1928 -command [list addvhighlight $n] -variable selectedhlview
1929}
1930
1931proc flatten {var} {
1932 global $var
1933
1934 set ret {}
1935 foreach i [array names $var] {
1936 lappend ret $i [set $var\($i\)]
1937 }
1938 return $ret
1939}
1940
1941proc unflatten {var l} {
1942 global $var
1943
1944 catch {unset $var}
1945 foreach {i v} $l {
1946 set $var\($i\) $v
1947 }
1948}
1949
1950proc showview {n} {
1951 global curview viewdata viewfiles
1952 global displayorder parentlist rowidlist
1953 global colormap rowtextx commitrow nextcolor canvxmax
1954 global numcommits commitlisted rowchk
1955 global selectedline currentid canv canvy0
1956 global treediffs
1957 global pending_select phase
1958 global commitidx rowlaidout rowoptim
1959 global commfd
1960 global selectedview selectfirst
1961 global vparentlist vdisporder vcmitlisted
1962 global hlview selectedhlview
1963
1964 if {$n == $curview} return
1965 set selid {}
1966 if {[info exists selectedline]} {
1967 set selid $currentid
1968 set y [yc $selectedline]
1969 set ymax [lindex [$canv cget -scrollregion] 3]
1970 set span [$canv yview]
1971 set ytop [expr {[lindex $span 0] * $ymax}]
1972 set ybot [expr {[lindex $span 1] * $ymax}]
1973 if {$ytop < $y && $y < $ybot} {
1974 set yscreen [expr {$y - $ytop}]
1975 } else {
1976 set yscreen [expr {($ybot - $ytop) / 2}]
1977 }
1978 } elseif {[info exists pending_select]} {
1979 set selid $pending_select
1980 unset pending_select
1981 }
1982 unselectline
1983 normalline
1984 if {$curview >= 0} {
1985 set vparentlist($curview) $parentlist
1986 set vdisporder($curview) $displayorder
1987 set vcmitlisted($curview) $commitlisted
1988 if {$phase ne {}} {
1989 set viewdata($curview) \
1990 [list $phase $rowidlist $rowlaidout $rowoptim $numcommits]
1991 } elseif {![info exists viewdata($curview)]
1992 || [lindex $viewdata($curview) 0] ne {}} {
1993 set viewdata($curview) \
1994 [list {} $rowidlist]
1995 }
1996 }
1997 catch {unset treediffs}
1998 clear_display
1999 if {[info exists hlview] && $hlview == $n} {
2000 unset hlview
2001 set selectedhlview None
2002 }
2003
2004 set curview $n
2005 set selectedview $n
2006 .bar.view entryconf Edit* -state [expr {$n == 0? "disabled": "normal"}]
2007 .bar.view entryconf Delete* -state [expr {$n == 0? "disabled": "normal"}]
2008
2009 if {![info exists viewdata($n)]} {
2010 if {$selid ne {}} {
2011 set pending_select $selid
2012 }
2013 getcommits
2014 return
2015 }
2016
2017 set v $viewdata($n)
2018 set phase [lindex $v 0]
2019 set displayorder $vdisporder($n)
2020 set parentlist $vparentlist($n)
2021 set commitlisted $vcmitlisted($n)
2022 set rowidlist [lindex $v 1]
2023 if {$phase eq {}} {
2024 set numcommits [llength $displayorder]
2025 } else {
2026 set rowlaidout [lindex $v 2]
2027 set rowoptim [lindex $v 3]
2028 set numcommits [lindex $v 4]
2029 catch {unset rowchk}
2030 }
2031
2032 catch {unset colormap}
2033 catch {unset rowtextx}
2034 set nextcolor 0
2035 set canvxmax [$canv cget -width]
2036 set curview $n
2037 set row 0
2038 setcanvscroll
2039 set yf 0
2040 set row {}
2041 set selectfirst 0
2042 if {$selid ne {} && [info exists commitrow($n,$selid)]} {
2043 set row $commitrow($n,$selid)
2044 # try to get the selected row in the same position on the screen
2045 set ymax [lindex [$canv cget -scrollregion] 3]
2046 set ytop [expr {[yc $row] - $yscreen}]
2047 if {$ytop < 0} {
2048 set ytop 0
2049 }
2050 set yf [expr {$ytop * 1.0 / $ymax}]
2051 }
2052 allcanvs yview moveto $yf
2053 drawvisible
2054 if {$row ne {}} {
2055 selectline $row 0
2056 } elseif {$selid ne {}} {
2057 set pending_select $selid
2058 } else {
2059 set row [first_real_row]
2060 if {$row < $numcommits} {
2061 selectline $row 0
2062 } else {
2063 set selectfirst 1
2064 }
2065 }
2066 if {$phase ne {}} {
2067 if {$phase eq "getcommits"} {
2068 show_status "Reading commits..."
2069 }
2070 run chewcommits $n
2071 } elseif {$numcommits == 0} {
2072 show_status "No commits selected"
2073 }
2074 run refill_reflist
2075}
2076
2077# Stuff relating to the highlighting facility
2078
2079proc ishighlighted {row} {
2080 global vhighlights fhighlights nhighlights rhighlights
2081
2082 if {[info exists nhighlights($row)] && $nhighlights($row) > 0} {
2083 return $nhighlights($row)
2084 }
2085 if {[info exists vhighlights($row)] && $vhighlights($row) > 0} {
2086 return $vhighlights($row)
2087 }
2088 if {[info exists fhighlights($row)] && $fhighlights($row) > 0} {
2089 return $fhighlights($row)
2090 }
2091 if {[info exists rhighlights($row)] && $rhighlights($row) > 0} {
2092 return $rhighlights($row)
2093 }
2094 return 0
2095}
2096
2097proc bolden {row font} {
2098 global canv linehtag selectedline boldrows
2099
2100 lappend boldrows $row
2101 $canv itemconf $linehtag($row) -font $font
2102 if {[info exists selectedline] && $row == $selectedline} {
2103 $canv delete secsel
2104 set t [eval $canv create rect [$canv bbox $linehtag($row)] \
2105 -outline {{}} -tags secsel \
2106 -fill [$canv cget -selectbackground]]
2107 $canv lower $t
2108 }
2109}
2110
2111proc bolden_name {row font} {
2112 global canv2 linentag selectedline boldnamerows
2113
2114 lappend boldnamerows $row
2115 $canv2 itemconf $linentag($row) -font $font
2116 if {[info exists selectedline] && $row == $selectedline} {
2117 $canv2 delete secsel
2118 set t [eval $canv2 create rect [$canv2 bbox $linentag($row)] \
2119 -outline {{}} -tags secsel \
2120 -fill [$canv2 cget -selectbackground]]
2121 $canv2 lower $t
2122 }
2123}
2124
2125proc unbolden {} {
2126 global mainfont boldrows
2127
2128 set stillbold {}
2129 foreach row $boldrows {
2130 if {![ishighlighted $row]} {
2131 bolden $row $mainfont
2132 } else {
2133 lappend stillbold $row
2134 }
2135 }
2136 set boldrows $stillbold
2137}
2138
2139proc addvhighlight {n} {
2140 global hlview curview viewdata vhl_done vhighlights commitidx
2141
2142 if {[info exists hlview]} {
2143 delvhighlight
2144 }
2145 set hlview $n
2146 if {$n != $curview && ![info exists viewdata($n)]} {
2147 set viewdata($n) [list getcommits {{}} 0 0 0]
2148 set vparentlist($n) {}
2149 set vdisporder($n) {}
2150 set vcmitlisted($n) {}
2151 start_rev_list $n
2152 }
2153 set vhl_done $commitidx($hlview)
2154 if {$vhl_done > 0} {
2155 drawvisible
2156 }
2157}
2158
2159proc delvhighlight {} {
2160 global hlview vhighlights
2161
2162 if {![info exists hlview]} return
2163 unset hlview
2164 catch {unset vhighlights}
2165 unbolden
2166}
2167
2168proc vhighlightmore {} {
2169 global hlview vhl_done commitidx vhighlights
2170 global displayorder vdisporder curview mainfont
2171
2172 set font [concat $mainfont bold]
2173 set max $commitidx($hlview)
2174 if {$hlview == $curview} {
2175 set disp $displayorder
2176 } else {
2177 set disp $vdisporder($hlview)
2178 }
2179 set vr [visiblerows]
2180 set r0 [lindex $vr 0]
2181 set r1 [lindex $vr 1]
2182 for {set i $vhl_done} {$i < $max} {incr i} {
2183 set id [lindex $disp $i]
2184 if {[info exists commitrow($curview,$id)]} {
2185 set row $commitrow($curview,$id)
2186 if {$r0 <= $row && $row <= $r1} {
2187 if {![highlighted $row]} {
2188 bolden $row $font
2189 }
2190 set vhighlights($row) 1
2191 }
2192 }
2193 }
2194 set vhl_done $max
2195}
2196
2197proc askvhighlight {row id} {
2198 global hlview vhighlights commitrow iddrawn mainfont
2199
2200 if {[info exists commitrow($hlview,$id)]} {
2201 if {[info exists iddrawn($id)] && ![ishighlighted $row]} {
2202 bolden $row [concat $mainfont bold]
2203 }
2204 set vhighlights($row) 1
2205 } else {
2206 set vhighlights($row) 0
2207 }
2208}
2209
2210proc hfiles_change {name ix op} {
2211 global highlight_files filehighlight fhighlights fh_serial
2212 global mainfont highlight_paths
2213
2214 if {[info exists filehighlight]} {
2215 # delete previous highlights
2216 catch {close $filehighlight}
2217 unset filehighlight
2218 catch {unset fhighlights}
2219 unbolden
2220 unhighlight_filelist
2221 }
2222 set highlight_paths {}
2223 after cancel do_file_hl $fh_serial
2224 incr fh_serial
2225 if {$highlight_files ne {}} {
2226 after 300 do_file_hl $fh_serial
2227 }
2228}
2229
2230proc makepatterns {l} {
2231 set ret {}
2232 foreach e $l {
2233 set ee [string map {"*" "\\*" "?" "\\?" "\[" "\\\[" "\\" "\\\\"} $e]
2234 if {[string index $ee end] eq "/"} {
2235 lappend ret "$ee*"
2236 } else {
2237 lappend ret $ee
2238 lappend ret "$ee/*"
2239 }
2240 }
2241 return $ret
2242}
2243
2244proc do_file_hl {serial} {
2245 global highlight_files filehighlight highlight_paths gdttype fhl_list
2246
2247 if {$gdttype eq "touching paths:"} {
2248 if {[catch {set paths [shellsplit $highlight_files]}]} return
2249 set highlight_paths [makepatterns $paths]
2250 highlight_filelist
2251 set gdtargs [concat -- $paths]
2252 } else {
2253 set gdtargs [list "-S$highlight_files"]
2254 }
2255 set cmd [concat | git diff-tree -r -s --stdin $gdtargs]
2256 set filehighlight [open $cmd r+]
2257 fconfigure $filehighlight -blocking 0
2258 filerun $filehighlight readfhighlight
2259 set fhl_list {}
2260 drawvisible
2261 flushhighlights
2262}
2263
2264proc flushhighlights {} {
2265 global filehighlight fhl_list
2266
2267 if {[info exists filehighlight]} {
2268 lappend fhl_list {}
2269 puts $filehighlight ""
2270 flush $filehighlight
2271 }
2272}
2273
2274proc askfilehighlight {row id} {
2275 global filehighlight fhighlights fhl_list
2276
2277 lappend fhl_list $id
2278 set fhighlights($row) -1
2279 puts $filehighlight $id
2280}
2281
2282proc readfhighlight {} {
2283 global filehighlight fhighlights commitrow curview mainfont iddrawn
2284 global fhl_list
2285
2286 if {![info exists filehighlight]} {
2287 return 0
2288 }
2289 set nr 0
2290 while {[incr nr] <= 100 && [gets $filehighlight line] >= 0} {
2291 set line [string trim $line]
2292 set i [lsearch -exact $fhl_list $line]
2293 if {$i < 0} continue
2294 for {set j 0} {$j < $i} {incr j} {
2295 set id [lindex $fhl_list $j]
2296 if {[info exists commitrow($curview,$id)]} {
2297 set fhighlights($commitrow($curview,$id)) 0
2298 }
2299 }
2300 set fhl_list [lrange $fhl_list [expr {$i+1}] end]
2301 if {$line eq {}} continue
2302 if {![info exists commitrow($curview,$line)]} continue
2303 set row $commitrow($curview,$line)
2304 if {[info exists iddrawn($line)] && ![ishighlighted $row]} {
2305 bolden $row [concat $mainfont bold]
2306 }
2307 set fhighlights($row) 1
2308 }
2309 if {[eof $filehighlight]} {
2310 # strange...
2311 puts "oops, git diff-tree died"
2312 catch {close $filehighlight}
2313 unset filehighlight
2314 return 0
2315 }
2316 next_hlcont
2317 return 1
2318}
2319
2320proc find_change {name ix op} {
2321 global nhighlights mainfont boldnamerows
2322 global findstring findpattern findtype
2323
2324 # delete previous highlights, if any
2325 foreach row $boldnamerows {
2326 bolden_name $row $mainfont
2327 }
2328 set boldnamerows {}
2329 catch {unset nhighlights}
2330 unbolden
2331 unmarkmatches
2332 if {$findtype ne "Regexp"} {
2333 set e [string map {"*" "\\*" "?" "\\?" "\[" "\\\[" "\\" "\\\\"} \
2334 $findstring]
2335 set findpattern "*$e*"
2336 }
2337 drawvisible
2338}
2339
2340proc doesmatch {f} {
2341 global findtype findstring findpattern
2342
2343 if {$findtype eq "Regexp"} {
2344 return [regexp $findstring $f]
2345 } elseif {$findtype eq "IgnCase"} {
2346 return [string match -nocase $findpattern $f]
2347 } else {
2348 return [string match $findpattern $f]
2349 }
2350}
2351
2352proc askfindhighlight {row id} {
2353 global nhighlights commitinfo iddrawn mainfont
2354 global findloc
2355 global markingmatches
2356
2357 if {![info exists commitinfo($id)]} {
2358 getcommit $id
2359 }
2360 set info $commitinfo($id)
2361 set isbold 0
2362 set fldtypes {Headline Author Date Committer CDate Comments}
2363 foreach f $info ty $fldtypes {
2364 if {($findloc eq "All fields" || $findloc eq $ty) &&
2365 [doesmatch $f]} {
2366 if {$ty eq "Author"} {
2367 set isbold 2
2368 break
2369 }
2370 set isbold 1
2371 }
2372 }
2373 if {$isbold && [info exists iddrawn($id)]} {
2374 set f [concat $mainfont bold]
2375 if {![ishighlighted $row]} {
2376 bolden $row $f
2377 if {$isbold > 1} {
2378 bolden_name $row $f
2379 }
2380 }
2381 if {$markingmatches} {
2382 markrowmatches $row $id
2383 }
2384 }
2385 set nhighlights($row) $isbold
2386}
2387
2388proc markrowmatches {row id} {
2389 global canv canv2 linehtag linentag commitinfo findloc
2390
2391 set headline [lindex $commitinfo($id) 0]
2392 set author [lindex $commitinfo($id) 1]
2393 $canv delete match$row
2394 $canv2 delete match$row
2395 if {$findloc eq "All fields" || $findloc eq "Headline"} {
2396 set m [findmatches $headline]
2397 if {$m ne {}} {
2398 markmatches $canv $row $headline $linehtag($row) $m \
2399 [$canv itemcget $linehtag($row) -font] $row
2400 }
2401 }
2402 if {$findloc eq "All fields" || $findloc eq "Author"} {
2403 set m [findmatches $author]
2404 if {$m ne {}} {
2405 markmatches $canv2 $row $author $linentag($row) $m \
2406 [$canv2 itemcget $linentag($row) -font] $row
2407 }
2408 }
2409}
2410
2411proc vrel_change {name ix op} {
2412 global highlight_related
2413
2414 rhighlight_none
2415 if {$highlight_related ne "None"} {
2416 run drawvisible
2417 }
2418}
2419
2420# prepare for testing whether commits are descendents or ancestors of a
2421proc rhighlight_sel {a} {
2422 global descendent desc_todo ancestor anc_todo
2423 global highlight_related rhighlights
2424
2425 catch {unset descendent}
2426 set desc_todo [list $a]
2427 catch {unset ancestor}
2428 set anc_todo [list $a]
2429 if {$highlight_related ne "None"} {
2430 rhighlight_none
2431 run drawvisible
2432 }
2433}
2434
2435proc rhighlight_none {} {
2436 global rhighlights
2437
2438 catch {unset rhighlights}
2439 unbolden
2440}
2441
2442proc is_descendent {a} {
2443 global curview children commitrow descendent desc_todo
2444
2445 set v $curview
2446 set la $commitrow($v,$a)
2447 set todo $desc_todo
2448 set leftover {}
2449 set done 0
2450 for {set i 0} {$i < [llength $todo]} {incr i} {
2451 set do [lindex $todo $i]
2452 if {$commitrow($v,$do) < $la} {
2453 lappend leftover $do
2454 continue
2455 }
2456 foreach nk $children($v,$do) {
2457 if {![info exists descendent($nk)]} {
2458 set descendent($nk) 1
2459 lappend todo $nk
2460 if {$nk eq $a} {
2461 set done 1
2462 }
2463 }
2464 }
2465 if {$done} {
2466 set desc_todo [concat $leftover [lrange $todo [expr {$i+1}] end]]
2467 return
2468 }
2469 }
2470 set descendent($a) 0
2471 set desc_todo $leftover
2472}
2473
2474proc is_ancestor {a} {
2475 global curview parentlist commitrow ancestor anc_todo
2476
2477 set v $curview
2478 set la $commitrow($v,$a)
2479 set todo $anc_todo
2480 set leftover {}
2481 set done 0
2482 for {set i 0} {$i < [llength $todo]} {incr i} {
2483 set do [lindex $todo $i]
2484 if {![info exists commitrow($v,$do)] || $commitrow($v,$do) > $la} {
2485 lappend leftover $do
2486 continue
2487 }
2488 foreach np [lindex $parentlist $commitrow($v,$do)] {
2489 if {![info exists ancestor($np)]} {
2490 set ancestor($np) 1
2491 lappend todo $np
2492 if {$np eq $a} {
2493 set done 1
2494 }
2495 }
2496 }
2497 if {$done} {
2498 set anc_todo [concat $leftover [lrange $todo [expr {$i+1}] end]]
2499 return
2500 }
2501 }
2502 set ancestor($a) 0
2503 set anc_todo $leftover
2504}
2505
2506proc askrelhighlight {row id} {
2507 global descendent highlight_related iddrawn mainfont rhighlights
2508 global selectedline ancestor
2509
2510 if {![info exists selectedline]} return
2511 set isbold 0
2512 if {$highlight_related eq "Descendent" ||
2513 $highlight_related eq "Not descendent"} {
2514 if {![info exists descendent($id)]} {
2515 is_descendent $id
2516 }
2517 if {$descendent($id) == ($highlight_related eq "Descendent")} {
2518 set isbold 1
2519 }
2520 } elseif {$highlight_related eq "Ancestor" ||
2521 $highlight_related eq "Not ancestor"} {
2522 if {![info exists ancestor($id)]} {
2523 is_ancestor $id
2524 }
2525 if {$ancestor($id) == ($highlight_related eq "Ancestor")} {
2526 set isbold 1
2527 }
2528 }
2529 if {[info exists iddrawn($id)]} {
2530 if {$isbold && ![ishighlighted $row]} {
2531 bolden $row [concat $mainfont bold]
2532 }
2533 }
2534 set rhighlights($row) $isbold
2535}
2536
2537proc next_hlcont {} {
2538 global fhl_row fhl_dirn displayorder numcommits
2539 global vhighlights fhighlights nhighlights rhighlights
2540 global hlview filehighlight findstring highlight_related
2541
2542 if {![info exists fhl_dirn] || $fhl_dirn == 0} return
2543 set row $fhl_row
2544 while {1} {
2545 if {$row < 0 || $row >= $numcommits} {
2546 bell
2547 set fhl_dirn 0
2548 return
2549 }
2550 set id [lindex $displayorder $row]
2551 if {[info exists hlview]} {
2552 if {![info exists vhighlights($row)]} {
2553 askvhighlight $row $id
2554 }
2555 if {$vhighlights($row) > 0} break
2556 }
2557 if {$findstring ne {}} {
2558 if {![info exists nhighlights($row)]} {
2559 askfindhighlight $row $id
2560 }
2561 if {$nhighlights($row) > 0} break
2562 }
2563 if {$highlight_related ne "None"} {
2564 if {![info exists rhighlights($row)]} {
2565 askrelhighlight $row $id
2566 }
2567 if {$rhighlights($row) > 0} break
2568 }
2569 if {[info exists filehighlight]} {
2570 if {![info exists fhighlights($row)]} {
2571 # ask for a few more while we're at it...
2572 set r $row
2573 for {set n 0} {$n < 100} {incr n} {
2574 if {![info exists fhighlights($r)]} {
2575 askfilehighlight $r [lindex $displayorder $r]
2576 }
2577 incr r $fhl_dirn
2578 if {$r < 0 || $r >= $numcommits} break
2579 }
2580 flushhighlights
2581 }
2582 if {$fhighlights($row) < 0} {
2583 set fhl_row $row
2584 return
2585 }
2586 if {$fhighlights($row) > 0} break
2587 }
2588 incr row $fhl_dirn
2589 }
2590 set fhl_dirn 0
2591 selectline $row 1
2592}
2593
2594proc next_highlight {dirn} {
2595 global selectedline fhl_row fhl_dirn
2596 global hlview filehighlight findstring highlight_related
2597
2598 if {![info exists selectedline]} return
2599 if {!([info exists hlview] || $findstring ne {} ||
2600 $highlight_related ne "None" || [info exists filehighlight])} return
2601 set fhl_row [expr {$selectedline + $dirn}]
2602 set fhl_dirn $dirn
2603 next_hlcont
2604}
2605
2606proc cancel_next_highlight {} {
2607 global fhl_dirn
2608
2609 set fhl_dirn 0
2610}
2611
2612# Graph layout functions
2613
2614proc shortids {ids} {
2615 set res {}
2616 foreach id $ids {
2617 if {[llength $id] > 1} {
2618 lappend res [shortids $id]
2619 } elseif {[regexp {^[0-9a-f]{40}$} $id]} {
2620 lappend res [string range $id 0 7]
2621 } else {
2622 lappend res $id
2623 }
2624 }
2625 return $res
2626}
2627
2628proc incrange {l x o} {
2629 set n [llength $l]
2630 while {$x < $n} {
2631 set e [lindex $l $x]
2632 if {$e ne {}} {
2633 lset l $x [expr {$e + $o}]
2634 }
2635 incr x
2636 }
2637 return $l
2638}
2639
2640proc ntimes {n o} {
2641 set ret {}
2642 for {} {$n > 0} {incr n -1} {
2643 lappend ret $o
2644 }
2645 return $ret
2646}
2647
2648proc usedinrange {id l1 l2} {
2649 global children commitrow curview
2650
2651 if {[info exists commitrow($curview,$id)]} {
2652 set r $commitrow($curview,$id)
2653 if {$l1 <= $r && $r <= $l2} {
2654 return [expr {$r - $l1 + 1}]
2655 }
2656 }
2657 set kids $children($curview,$id)
2658 foreach c $kids {
2659 if {[info exists commitrow($curview,$c)]} {
2660 set r $commitrow($curview,$c)
2661 if {$l1 <= $r && $r <= $l2} {
2662 return [expr {$r - $l1 + 1}]
2663 }
2664 }
2665 }
2666 return 0
2667}
2668
2669# Work out where id should go in idlist so that order-token
2670# values increase from left to right
2671proc idcol {idlist id {i 0}} {
2672 global ordertok curview
2673
2674 set t $ordertok($curview,$id)
2675 if {$i >= [llength $idlist] ||
2676 $t < $ordertok($curview,[lindex $idlist $i])} {
2677 if {$i > [llength $idlist]} {
2678 set i [llength $idlist]
2679 }
2680 while {[incr i -1] >= 0 &&
2681 $t < $ordertok($curview,[lindex $idlist $i])} {}
2682 incr i
2683 } else {
2684 if {$t > $ordertok($curview,[lindex $idlist $i])} {
2685 while {[incr i] < [llength $idlist] &&
2686 $t >= $ordertok($curview,[lindex $idlist $i])} {}
2687 }
2688 }
2689 return $i
2690}
2691
2692proc makeuparrow {oid y x} {
2693 global rowidlist uparrowlen displayorder
2694
2695 for {set i 0} {$i < $uparrowlen && $y > 1} {incr i} {
2696 incr y -1
2697 set idl [lindex $rowidlist $y]
2698 set x [idcol $idl $oid $x]
2699 lset rowidlist $y [linsert $idl $x $oid]
2700 }
2701}
2702
2703proc initlayout {} {
2704 global rowidlist displayorder commitlisted
2705 global rowlaidout rowoptim
2706 global rowchk
2707 global numcommits canvxmax canv
2708 global nextcolor
2709 global parentlist
2710 global colormap rowtextx
2711 global selectfirst
2712
2713 set numcommits 0
2714 set displayorder {}
2715 set commitlisted {}
2716 set parentlist {}
2717 set nextcolor 0
2718 set rowidlist {{}}
2719 catch {unset rowchk}
2720 set rowlaidout 0
2721 set rowoptim 0
2722 set canvxmax [$canv cget -width]
2723 catch {unset colormap}
2724 catch {unset rowtextx}
2725 set selectfirst 1
2726}
2727
2728proc setcanvscroll {} {
2729 global canv canv2 canv3 numcommits linespc canvxmax canvy0
2730
2731 set ymax [expr {$canvy0 + ($numcommits - 0.5) * $linespc + 2}]
2732 $canv conf -scrollregion [list 0 0 $canvxmax $ymax]
2733 $canv2 conf -scrollregion [list 0 0 0 $ymax]
2734 $canv3 conf -scrollregion [list 0 0 0 $ymax]
2735}
2736
2737proc visiblerows {} {
2738 global canv numcommits linespc
2739
2740 set ymax [lindex [$canv cget -scrollregion] 3]
2741 if {$ymax eq {} || $ymax == 0} return
2742 set f [$canv yview]
2743 set y0 [expr {int([lindex $f 0] * $ymax)}]
2744 set r0 [expr {int(($y0 - 3) / $linespc) - 1}]
2745 if {$r0 < 0} {
2746 set r0 0
2747 }
2748 set y1 [expr {int([lindex $f 1] * $ymax)}]
2749 set r1 [expr {int(($y1 - 3) / $linespc) + 1}]
2750 if {$r1 >= $numcommits} {
2751 set r1 [expr {$numcommits - 1}]
2752 }
2753 return [list $r0 $r1]
2754}
2755
2756proc layoutmore {tmax allread} {
2757 global rowlaidout rowoptim commitidx numcommits optim_delay
2758 global uparrowlen curview rowidlist
2759
2760 set showlast 0
2761 set showdelay $optim_delay
2762 set optdelay [expr {$uparrowlen + 1}]
2763 while {1} {
2764 if {$rowoptim - $showdelay > $numcommits} {
2765 showstuff [expr {$rowoptim - $showdelay}] $showlast
2766 } elseif {$rowlaidout - $optdelay > $rowoptim} {
2767 set nr [expr {$rowlaidout - $optdelay - $rowoptim}]
2768 if {$nr > 100} {
2769 set nr 100
2770 }
2771 optimize_rows $rowoptim 0 [expr {$rowoptim + $nr}]
2772 incr rowoptim $nr
2773 } elseif {$commitidx($curview) > $rowlaidout} {
2774 set nr [expr {$commitidx($curview) - $rowlaidout}]
2775 # may need to increase this threshold if uparrowlen or
2776 # mingaplen are increased...
2777 if {$nr > 200} {
2778 set nr 200
2779 }
2780 set row $rowlaidout
2781 set rowlaidout [layoutrows $row [expr {$row + $nr}] $allread]
2782 if {$rowlaidout == $row} {
2783 return 0
2784 }
2785 } elseif {$allread} {
2786 set optdelay 0
2787 set nrows $commitidx($curview)
2788 if {[lindex $rowidlist $nrows] ne {}} {
2789 layouttail
2790 set rowlaidout $commitidx($curview)
2791 } elseif {$rowoptim == $nrows} {
2792 set showdelay 0
2793 set showlast 1
2794 if {$numcommits == $nrows} {
2795 return 0
2796 }
2797 }
2798 } else {
2799 return 0
2800 }
2801 if {$tmax ne {} && [clock clicks -milliseconds] >= $tmax} {
2802 return 1
2803 }
2804 }
2805}
2806
2807proc showstuff {canshow last} {
2808 global numcommits commitrow pending_select selectedline curview
2809 global lookingforhead mainheadid displayorder selectfirst
2810 global lastscrollset commitinterest
2811
2812 if {$numcommits == 0} {
2813 global phase
2814 set phase "incrdraw"
2815 allcanvs delete all
2816 }
2817 for {set l $numcommits} {$l < $canshow} {incr l} {
2818 set id [lindex $displayorder $l]
2819 if {[info exists commitinterest($id)]} {
2820 foreach script $commitinterest($id) {
2821 eval [string map [list "%I" $id] $script]
2822 }
2823 unset commitinterest($id)
2824 }
2825 }
2826 set r0 $numcommits
2827 set prev $numcommits
2828 set numcommits $canshow
2829 set t [clock clicks -milliseconds]
2830 if {$prev < 100 || $last || $t - $lastscrollset > 500} {
2831 set lastscrollset $t
2832 setcanvscroll
2833 }
2834 set rows [visiblerows]
2835 set r1 [lindex $rows 1]
2836 if {$r1 >= $canshow} {
2837 set r1 [expr {$canshow - 1}]
2838 }
2839 if {$r0 <= $r1} {
2840 drawcommits $r0 $r1
2841 }
2842 if {[info exists pending_select] &&
2843 [info exists commitrow($curview,$pending_select)] &&
2844 $commitrow($curview,$pending_select) < $numcommits} {
2845 selectline $commitrow($curview,$pending_select) 1
2846 }
2847 if {$selectfirst} {
2848 if {[info exists selectedline] || [info exists pending_select]} {
2849 set selectfirst 0
2850 } else {
2851 set l [first_real_row]
2852 selectline $l 1
2853 set selectfirst 0
2854 }
2855 }
2856 if {$lookingforhead && [info exists commitrow($curview,$mainheadid)]
2857 && ($last || $commitrow($curview,$mainheadid) < $numcommits - 1)} {
2858 set lookingforhead 0
2859 dodiffindex
2860 }
2861}
2862
2863proc doshowlocalchanges {} {
2864 global lookingforhead curview mainheadid phase commitrow
2865
2866 if {[info exists commitrow($curview,$mainheadid)] &&
2867 ($phase eq {} || $commitrow($curview,$mainheadid) < $numcommits - 1)} {
2868 dodiffindex
2869 } elseif {$phase ne {}} {
2870 set lookingforhead 1
2871 }
2872}
2873
2874proc dohidelocalchanges {} {
2875 global lookingforhead localfrow localirow lserial
2876
2877 set lookingforhead 0
2878 if {$localfrow >= 0} {
2879 removerow $localfrow
2880 set localfrow -1
2881 if {$localirow > 0} {
2882 incr localirow -1
2883 }
2884 }
2885 if {$localirow >= 0} {
2886 removerow $localirow
2887 set localirow -1
2888 }
2889 incr lserial
2890}
2891
2892# spawn off a process to do git diff-index --cached HEAD
2893proc dodiffindex {} {
2894 global localirow localfrow lserial
2895
2896 incr lserial
2897 set localfrow -1
2898 set localirow -1
2899 set fd [open "|git diff-index --cached HEAD" r]
2900 fconfigure $fd -blocking 0
2901 filerun $fd [list readdiffindex $fd $lserial]
2902}
2903
2904proc readdiffindex {fd serial} {
2905 global localirow commitrow mainheadid nullid2 curview
2906 global commitinfo commitdata lserial
2907
2908 set isdiff 1
2909 if {[gets $fd line] < 0} {
2910 if {![eof $fd]} {
2911 return 1
2912 }
2913 set isdiff 0
2914 }
2915 # we only need to see one line and we don't really care what it says...
2916 close $fd
2917
2918 # now see if there are any local changes not checked in to the index
2919 if {$serial == $lserial} {
2920 set fd [open "|git diff-files" r]
2921 fconfigure $fd -blocking 0
2922 filerun $fd [list readdifffiles $fd $serial]
2923 }
2924
2925 if {$isdiff && $serial == $lserial && $localirow == -1} {
2926 # add the line for the changes in the index to the graph
2927 set localirow $commitrow($curview,$mainheadid)
2928 set hl "Local changes checked in to index but not committed"
2929 set commitinfo($nullid2) [list $hl {} {} {} {} " $hl\n"]
2930 set commitdata($nullid2) "\n $hl\n"
2931 insertrow $localirow $nullid2
2932 }
2933 return 0
2934}
2935
2936proc readdifffiles {fd serial} {
2937 global localirow localfrow commitrow mainheadid nullid curview
2938 global commitinfo commitdata lserial
2939
2940 set isdiff 1
2941 if {[gets $fd line] < 0} {
2942 if {![eof $fd]} {
2943 return 1
2944 }
2945 set isdiff 0
2946 }
2947 # we only need to see one line and we don't really care what it says...
2948 close $fd
2949
2950 if {$isdiff && $serial == $lserial && $localfrow == -1} {
2951 # add the line for the local diff to the graph
2952 if {$localirow >= 0} {
2953 set localfrow $localirow
2954 incr localirow
2955 } else {
2956 set localfrow $commitrow($curview,$mainheadid)
2957 }
2958 set hl "Local uncommitted changes, not checked in to index"
2959 set commitinfo($nullid) [list $hl {} {} {} {} " $hl\n"]
2960 set commitdata($nullid) "\n $hl\n"
2961 insertrow $localfrow $nullid
2962 }
2963 return 0
2964}
2965
2966proc layoutrows {row endrow last} {
2967 global rowidlist displayorder
2968 global uparrowlen downarrowlen maxwidth mingaplen
2969 global children parentlist
2970 global commitidx curview
2971 global rowchk
2972
2973 set idlist [lindex $rowidlist $row]
2974 while {$row < $endrow} {
2975 set id [lindex $displayorder $row]
2976 if {1} {
2977 if {!$last &&
2978 $row + $uparrowlen + $mingaplen >= $commitidx($curview)} break
2979 for {set x [llength $idlist]} {[incr x -1] >= 0} {} {
2980 set i [lindex $idlist $x]
2981 if {![info exists rowchk($i)] || $row >= $rowchk($i)} {
2982 set r [usedinrange $i [expr {$row - $downarrowlen}] \
2983 [expr {$row + $uparrowlen + $mingaplen}]]
2984 if {$r == 0} {
2985 set idlist [lreplace $idlist $x $x]
2986 continue
2987 }
2988 set rowchk($i) [expr {$row + $r}]
2989 }
2990 }
2991 lset rowidlist $row $idlist
2992 }
2993 set oldolds {}
2994 set newolds {}
2995 foreach p [lindex $parentlist $row] {
2996 # is id the first child of this parent?
2997 if {$id eq [lindex $children($curview,$p) 0]} {
2998 lappend newolds $p
2999 } elseif {[lsearch -exact $idlist $p] < 0} {
3000 lappend oldolds $p
3001 }
3002 }
3003 set col [lsearch -exact $idlist $id]
3004 if {$col < 0} {
3005 set col [idcol $idlist $id]
3006 set idlist [linsert $idlist $col $id]
3007 lset rowidlist $row $idlist
3008 if {$children($curview,$id) ne {}} {
3009 makeuparrow $id $row $col
3010 }
3011 }
3012 incr row
3013 set idlist [lreplace $idlist $col $col]
3014 set x $col
3015 foreach i $newolds {
3016 set x [idcol $idlist $i $x]
3017 set idlist [linsert $idlist $x $i]
3018 }
3019 foreach oid $oldolds {
3020 set x [idcol $idlist $oid $x]
3021 set idlist [linsert $idlist $x $oid]
3022 makeuparrow $oid $row $x
3023 }
3024 lappend rowidlist $idlist
3025 }
3026 return $row
3027}
3028
3029proc addextraid {id row} {
3030 global displayorder commitrow commitinfo
3031 global commitidx commitlisted
3032 global parentlist children curview
3033
3034 incr commitidx($curview)
3035 lappend displayorder $id
3036 lappend commitlisted 0
3037 lappend parentlist {}
3038 set commitrow($curview,$id) $row
3039 readcommit $id
3040 if {![info exists commitinfo($id)]} {
3041 set commitinfo($id) {"No commit information available"}
3042 }
3043 if {![info exists children($curview,$id)]} {
3044 set children($curview,$id) {}
3045 }
3046}
3047
3048proc layouttail {} {
3049 global rowidlist commitidx curview
3050
3051 set row $commitidx($curview)
3052 set idlist [lindex $rowidlist $row]
3053 while {$idlist ne {}} {
3054 set col [expr {[llength $idlist] - 1}]
3055 set id [lindex $idlist $col]
3056 addextraid $id $row
3057 incr row
3058 set idlist [lreplace $idlist $col $col]
3059 lappend rowidlist $idlist
3060 }
3061}
3062
3063proc insert_pad {row col npad} {
3064 global rowidlist
3065
3066 set pad [ntimes $npad {}]
3067 set idlist [lindex $rowidlist $row]
3068 set bef [lrange $idlist 0 [expr {$col - 1}]]
3069 set aft [lrange $idlist $col end]
3070 set i [lsearch -exact $aft {}]
3071 if {$i > 0} {
3072 set aft [lreplace $aft $i $i]
3073 }
3074 lset rowidlist $row [concat $bef $pad $aft]
3075}
3076
3077proc optimize_rows {row col endrow} {
3078 global rowidlist displayorder curview children
3079
3080 if {$row < 1} {
3081 set row 1
3082 }
3083 set idlist [lindex $rowidlist [expr {$row - 1}]]
3084 if {$row >= 2} {
3085 set previdlist [lindex $rowidlist [expr {$row - 2}]]
3086 } else {
3087 set previdlist {}
3088 }
3089 for {} {$row < $endrow} {incr row} {
3090 set pprevidlist $previdlist
3091 set previdlist $idlist
3092 set idlist [lindex $rowidlist $row]
3093 set haspad 0
3094 set y0 [expr {$row - 1}]
3095 set ym [expr {$row - 2}]
3096 set x0 -1
3097 set xm -1
3098 for {} {$col < [llength $idlist]} {incr col} {
3099 set id [lindex $idlist $col]
3100 if {[lindex $previdlist $col] eq $id} continue
3101 if {$id eq {}} {
3102 set haspad 1
3103 continue
3104 }
3105 set x0 [lsearch -exact $previdlist $id]
3106 if {$x0 < 0} continue
3107 set z [expr {$x0 - $col}]
3108 set isarrow 0
3109 set z0 {}
3110 if {$ym >= 0} {
3111 set xm [lsearch -exact $pprevidlist $id]
3112 if {$xm >= 0} {
3113 set z0 [expr {$xm - $x0}]
3114 }
3115 }
3116 if {$z0 eq {}} {
3117 # if row y0 is the first child of $id then it's not an arrow
3118 if {[lindex $children($curview,$id) 0] ne
3119 [lindex $displayorder $y0]} {
3120 set isarrow 1
3121 }
3122 }
3123 if {!$isarrow && $id ne [lindex $displayorder $row] &&
3124 [lsearch -exact [lindex $rowidlist [expr {$row+1}]] $id] < 0} {
3125 set isarrow 1
3126 }
3127 # Looking at lines from this row to the previous row,
3128 # make them go straight up if they end in an arrow on
3129 # the previous row; otherwise make them go straight up
3130 # or at 45 degrees.
3131 if {$z < -1 || ($z < 0 && $isarrow)} {
3132 # Line currently goes left too much;
3133 # insert pads in the previous row, then optimize it
3134 set npad [expr {-1 - $z + $isarrow}]
3135 insert_pad $y0 $x0 $npad
3136 if {$y0 > 0} {
3137 optimize_rows $y0 $x0 $row
3138 }
3139 set previdlist [lindex $rowidlist $y0]
3140 set x0 [lsearch -exact $previdlist $id]
3141 set z [expr {$x0 - $col}]
3142 if {$z0 ne {}} {
3143 set pprevidlist [lindex $rowidlist $ym]
3144 set xm [lsearch -exact $pprevidlist $id]
3145 set z0 [expr {$xm - $x0}]
3146 }
3147 } elseif {$z > 1 || ($z > 0 && $isarrow)} {
3148 # Line currently goes right too much;
3149 # insert pads in this line
3150 set npad [expr {$z - 1 + $isarrow}]
3151 insert_pad $row $col $npad
3152 set idlist [lindex $rowidlist $row]
3153 incr col $npad
3154 set z [expr {$x0 - $col}]
3155 set haspad 1
3156 }
3157 if {$z0 eq {} && !$isarrow && $ym >= 0} {
3158 # this line links to its first child on row $row-2
3159 set id [lindex $displayorder $ym]
3160 set xc [lsearch -exact $pprevidlist $id]
3161 if {$xc >= 0} {
3162 set z0 [expr {$xc - $x0}]
3163 }
3164 }
3165 # avoid lines jigging left then immediately right
3166 if {$z0 ne {} && $z < 0 && $z0 > 0} {
3167 insert_pad $y0 $x0 1
3168 incr x0
3169 optimize_rows $y0 $x0 $row
3170 set previdlist [lindex $rowidlist $y0]
3171 set pprevidlist [lindex $rowidlist $ym]
3172 }
3173 }
3174 if {!$haspad} {
3175 # Find the first column that doesn't have a line going right
3176 for {set col [llength $idlist]} {[incr col -1] >= 0} {} {
3177 set id [lindex $idlist $col]
3178 if {$id eq {}} break
3179 set x0 [lsearch -exact $previdlist $id]
3180 if {$x0 < 0} {
3181 # check if this is the link to the first child
3182 set kid [lindex $displayorder $y0]
3183 if {[lindex $children($curview,$id) 0] eq $kid} {
3184 # it is, work out offset to child
3185 set x0 [lsearch -exact $previdlist $kid]
3186 }
3187 }
3188 if {$x0 <= $col} break
3189 }
3190 # Insert a pad at that column as long as it has a line and
3191 # isn't the last column
3192 if {$x0 >= 0 && [incr col] < [llength $idlist]} {
3193 set idlist [linsert $idlist $col {}]
3194 }
3195 }
3196 lset rowidlist $row $idlist
3197 set col 0
3198 }
3199}
3200
3201proc xc {row col} {
3202 global canvx0 linespc
3203 return [expr {$canvx0 + $col * $linespc}]
3204}
3205
3206proc yc {row} {
3207 global canvy0 linespc
3208 return [expr {$canvy0 + $row * $linespc}]
3209}
3210
3211proc linewidth {id} {
3212 global thickerline lthickness
3213
3214 set wid $lthickness
3215 if {[info exists thickerline] && $id eq $thickerline} {
3216 set wid [expr {2 * $lthickness}]
3217 }
3218 return $wid
3219}
3220
3221proc rowranges {id} {
3222 global commitrow curview children uparrowlen downarrowlen
3223 global rowidlist
3224
3225 set kids $children($curview,$id)
3226 if {$kids eq {}} {
3227 return {}
3228 }
3229 set ret {}
3230 lappend kids $id
3231 foreach child $kids {
3232 if {![info exists commitrow($curview,$child)]} break
3233 set row $commitrow($curview,$child)
3234 if {![info exists prev]} {
3235 lappend ret [expr {$row + 1}]
3236 } else {
3237 if {$row <= $prevrow} {
3238 puts "oops children out of order [shortids $id] $row < [shortids $prev] $prevrow"
3239 }
3240 # see if the line extends the whole way from prevrow to row
3241 if {$row > $prevrow + $uparrowlen + $downarrowlen &&
3242 [lsearch -exact [lindex $rowidlist \
3243 [expr {int(($row + $prevrow) / 2)}]] $id] < 0} {
3244 # it doesn't, see where it ends
3245 set r [expr {$prevrow + $downarrowlen}]
3246 if {[lsearch -exact [lindex $rowidlist $r] $id] < 0} {
3247 while {[incr r -1] > $prevrow &&
3248 [lsearch -exact [lindex $rowidlist $r] $id] < 0} {}
3249 } else {
3250 while {[incr r] <= $row &&
3251 [lsearch -exact [lindex $rowidlist $r] $id] >= 0} {}
3252 incr r -1
3253 }
3254 lappend ret $r
3255 # see where it starts up again
3256 set r [expr {$row - $uparrowlen}]
3257 if {[lsearch -exact [lindex $rowidlist $r] $id] < 0} {
3258 while {[incr r] < $row &&
3259 [lsearch -exact [lindex $rowidlist $r] $id] < 0} {}
3260 } else {
3261 while {[incr r -1] >= $prevrow &&
3262 [lsearch -exact [lindex $rowidlist $r] $id] >= 0} {}
3263 incr r
3264 }
3265 lappend ret $r
3266 }
3267 }
3268 if {$child eq $id} {
3269 lappend ret $row
3270 }
3271 set prev $id
3272 set prevrow $row
3273 }
3274 return $ret
3275}
3276
3277proc drawlineseg {id row endrow arrowlow} {
3278 global rowidlist displayorder iddrawn linesegs
3279 global canv colormap linespc curview maxlinelen parentlist
3280
3281 set cols [list [lsearch -exact [lindex $rowidlist $row] $id]]
3282 set le [expr {$row + 1}]
3283 set arrowhigh 1
3284 while {1} {
3285 set c [lsearch -exact [lindex $rowidlist $le] $id]
3286 if {$c < 0} {
3287 incr le -1
3288 break
3289 }
3290 lappend cols $c
3291 set x [lindex $displayorder $le]
3292 if {$x eq $id} {
3293 set arrowhigh 0
3294 break
3295 }
3296 if {[info exists iddrawn($x)] || $le == $endrow} {
3297 set c [lsearch -exact [lindex $rowidlist [expr {$le+1}]] $id]
3298 if {$c >= 0} {
3299 lappend cols $c
3300 set arrowhigh 0
3301 }
3302 break
3303 }
3304 incr le
3305 }
3306 if {$le <= $row} {
3307 return $row
3308 }
3309
3310 set lines {}
3311 set i 0
3312 set joinhigh 0
3313 if {[info exists linesegs($id)]} {
3314 set lines $linesegs($id)
3315 foreach li $lines {
3316 set r0 [lindex $li 0]
3317 if {$r0 > $row} {
3318 if {$r0 == $le && [lindex $li 1] - $row <= $maxlinelen} {
3319 set joinhigh 1
3320 }
3321 break
3322 }
3323 incr i
3324 }
3325 }
3326 set joinlow 0
3327 if {$i > 0} {
3328 set li [lindex $lines [expr {$i-1}]]
3329 set r1 [lindex $li 1]
3330 if {$r1 == $row && $le - [lindex $li 0] <= $maxlinelen} {
3331 set joinlow 1
3332 }
3333 }
3334
3335 set x [lindex $cols [expr {$le - $row}]]
3336 set xp [lindex $cols [expr {$le - 1 - $row}]]
3337 set dir [expr {$xp - $x}]
3338 if {$joinhigh} {
3339 set ith [lindex $lines $i 2]
3340 set coords [$canv coords $ith]
3341 set ah [$canv itemcget $ith -arrow]
3342 set arrowhigh [expr {$ah eq "first" || $ah eq "both"}]
3343 set x2 [lindex $cols [expr {$le + 1 - $row}]]
3344 if {$x2 ne {} && $x - $x2 == $dir} {
3345 set coords [lrange $coords 0 end-2]
3346 }
3347 } else {
3348 set coords [list [xc $le $x] [yc $le]]
3349 }
3350 if {$joinlow} {
3351 set itl [lindex $lines [expr {$i-1}] 2]
3352 set al [$canv itemcget $itl -arrow]
3353 set arrowlow [expr {$al eq "last" || $al eq "both"}]
3354 } elseif {$arrowlow} {
3355 if {[lsearch -exact [lindex $rowidlist [expr {$row-1}]] $id] >= 0 ||
3356 [lsearch -exact [lindex $parentlist [expr {$row-1}]] $id] >= 0} {
3357 set arrowlow 0
3358 }
3359 }
3360 set arrow [lindex {none first last both} [expr {$arrowhigh + 2*$arrowlow}]]
3361 for {set y $le} {[incr y -1] > $row} {} {
3362 set x $xp
3363 set xp [lindex $cols [expr {$y - 1 - $row}]]
3364 set ndir [expr {$xp - $x}]
3365 if {$dir != $ndir || $xp < 0} {
3366 lappend coords [xc $y $x] [yc $y]
3367 }
3368 set dir $ndir
3369 }
3370 if {!$joinlow} {
3371 if {$xp < 0} {
3372 # join parent line to first child
3373 set ch [lindex $displayorder $row]
3374 set xc [lsearch -exact [lindex $rowidlist $row] $ch]
3375 if {$xc < 0} {
3376 puts "oops: drawlineseg: child $ch not on row $row"
3377 } elseif {$xc != $x} {
3378 if {($arrowhigh && $le == $row + 1) || $dir == 0} {
3379 set d [expr {int(0.5 * $linespc)}]
3380 set x1 [xc $row $x]
3381 if {$xc < $x} {
3382 set x2 [expr {$x1 - $d}]
3383 } else {
3384 set x2 [expr {$x1 + $d}]
3385 }
3386 set y2 [yc $row]
3387 set y1 [expr {$y2 + $d}]
3388 lappend coords $x1 $y1 $x2 $y2
3389 } elseif {$xc < $x - 1} {
3390 lappend coords [xc $row [expr {$x-1}]] [yc $row]
3391 } elseif {$xc > $x + 1} {
3392 lappend coords [xc $row [expr {$x+1}]] [yc $row]
3393 }
3394 set x $xc
3395 }
3396 lappend coords [xc $row $x] [yc $row]
3397 } else {
3398 set xn [xc $row $xp]
3399 set yn [yc $row]
3400 lappend coords $xn $yn
3401 }
3402 if {!$joinhigh} {
3403 assigncolor $id
3404 set t [$canv create line $coords -width [linewidth $id] \
3405 -fill $colormap($id) -tags lines.$id -arrow $arrow]
3406 $canv lower $t
3407 bindline $t $id
3408 set lines [linsert $lines $i [list $row $le $t]]
3409 } else {
3410 $canv coords $ith $coords
3411 if {$arrow ne $ah} {
3412 $canv itemconf $ith -arrow $arrow
3413 }
3414 lset lines $i 0 $row
3415 }
3416 } else {
3417 set xo [lsearch -exact [lindex $rowidlist [expr {$row - 1}]] $id]
3418 set ndir [expr {$xo - $xp}]
3419 set clow [$canv coords $itl]
3420 if {$dir == $ndir} {
3421 set clow [lrange $clow 2 end]
3422 }
3423 set coords [concat $coords $clow]
3424 if {!$joinhigh} {
3425 lset lines [expr {$i-1}] 1 $le
3426 } else {
3427 # coalesce two pieces
3428 $canv delete $ith
3429 set b [lindex $lines [expr {$i-1}] 0]
3430 set e [lindex $lines $i 1]
3431 set lines [lreplace $lines [expr {$i-1}] $i [list $b $e $itl]]
3432 }
3433 $canv coords $itl $coords
3434 if {$arrow ne $al} {
3435 $canv itemconf $itl -arrow $arrow
3436 }
3437 }
3438
3439 set linesegs($id) $lines
3440 return $le
3441}
3442
3443proc drawparentlinks {id row} {
3444 global rowidlist canv colormap curview parentlist
3445 global idpos linespc
3446
3447 set rowids [lindex $rowidlist $row]
3448 set col [lsearch -exact $rowids $id]
3449 if {$col < 0} return
3450 set olds [lindex $parentlist $row]
3451 set row2 [expr {$row + 1}]
3452 set x [xc $row $col]
3453 set y [yc $row]
3454 set y2 [yc $row2]
3455 set d [expr {int(0.5 * $linespc)}]
3456 set ymid [expr {$y + $d}]
3457 set ids [lindex $rowidlist $row2]
3458 # rmx = right-most X coord used
3459 set rmx 0
3460 foreach p $olds {
3461 set i [lsearch -exact $ids $p]
3462 if {$i < 0} {
3463 puts "oops, parent $p of $id not in list"
3464 continue
3465 }
3466 set x2 [xc $row2 $i]
3467 if {$x2 > $rmx} {
3468 set rmx $x2
3469 }
3470 set j [lsearch -exact $rowids $p]
3471 if {$j < 0} {
3472 # drawlineseg will do this one for us
3473 continue
3474 }
3475 assigncolor $p
3476 # should handle duplicated parents here...
3477 set coords [list $x $y]
3478 if {$i != $col} {
3479 # if attaching to a vertical segment, draw a smaller
3480 # slant for visual distinctness
3481 if {$i == $j} {
3482 if {$i < $col} {
3483 lappend coords [expr {$x2 + $d}] $y $x2 $ymid
3484 } else {
3485 lappend coords [expr {$x2 - $d}] $y $x2 $ymid
3486 }
3487 } elseif {$i < $col && $i < $j} {
3488 # segment slants towards us already
3489 lappend coords [xc $row $j] $y
3490 } else {
3491 if {$i < $col - 1} {
3492 lappend coords [expr {$x2 + $linespc}] $y
3493 } elseif {$i > $col + 1} {
3494 lappend coords [expr {$x2 - $linespc}] $y
3495 }
3496 lappend coords $x2 $y2
3497 }
3498 } else {
3499 lappend coords $x2 $y2
3500 }
3501 set t [$canv create line $coords -width [linewidth $p] \
3502 -fill $colormap($p) -tags lines.$p]
3503 $canv lower $t
3504 bindline $t $p
3505 }
3506 if {$rmx > [lindex $idpos($id) 1]} {
3507 lset idpos($id) 1 $rmx
3508 redrawtags $id
3509 }
3510}
3511
3512proc drawlines {id} {
3513 global canv
3514
3515 $canv itemconf lines.$id -width [linewidth $id]
3516}
3517
3518proc drawcmittext {id row col} {
3519 global linespc canv canv2 canv3 canvy0 fgcolor curview
3520 global commitlisted commitinfo rowidlist parentlist
3521 global rowtextx idpos idtags idheads idotherrefs
3522 global linehtag linentag linedtag
3523 global mainfont canvxmax boldrows boldnamerows fgcolor nullid nullid2
3524
3525 # listed is 0 for boundary, 1 for normal, 2 for left, 3 for right
3526 set listed [lindex $commitlisted $row]
3527 if {$id eq $nullid} {
3528 set ofill red
3529 } elseif {$id eq $nullid2} {
3530 set ofill green
3531 } else {
3532 set ofill [expr {$listed != 0? "blue": "white"}]
3533 }
3534 set x [xc $row $col]
3535 set y [yc $row]
3536 set orad [expr {$linespc / 3}]
3537 if {$listed <= 1} {
3538 set t [$canv create oval [expr {$x - $orad}] [expr {$y - $orad}] \
3539 [expr {$x + $orad - 1}] [expr {$y + $orad - 1}] \
3540 -fill $ofill -outline $fgcolor -width 1 -tags circle]
3541 } elseif {$listed == 2} {
3542 # triangle pointing left for left-side commits
3543 set t [$canv create polygon \
3544 [expr {$x - $orad}] $y \
3545 [expr {$x + $orad - 1}] [expr {$y - $orad}] \
3546 [expr {$x + $orad - 1}] [expr {$y + $orad - 1}] \
3547 -fill $ofill -outline $fgcolor -width 1 -tags circle]
3548 } else {
3549 # triangle pointing right for right-side commits
3550 set t [$canv create polygon \
3551 [expr {$x + $orad - 1}] $y \
3552 [expr {$x - $orad}] [expr {$y - $orad}] \
3553 [expr {$x - $orad}] [expr {$y + $orad - 1}] \
3554 -fill $ofill -outline $fgcolor -width 1 -tags circle]
3555 }
3556 $canv raise $t
3557 $canv bind $t <1> {selcanvline {} %x %y}
3558 set rmx [llength [lindex $rowidlist $row]]
3559 set olds [lindex $parentlist $row]
3560 if {$olds ne {}} {
3561 set nextids [lindex $rowidlist [expr {$row + 1}]]
3562 foreach p $olds {
3563 set i [lsearch -exact $nextids $p]
3564 if {$i > $rmx} {
3565 set rmx $i
3566 }
3567 }
3568 }
3569 set xt [xc $row $rmx]
3570 set rowtextx($row) $xt
3571 set idpos($id) [list $x $xt $y]
3572 if {[info exists idtags($id)] || [info exists idheads($id)]
3573 || [info exists idotherrefs($id)]} {
3574 set xt [drawtags $id $x $xt $y]
3575 }
3576 set headline [lindex $commitinfo($id) 0]
3577 set name [lindex $commitinfo($id) 1]
3578 set date [lindex $commitinfo($id) 2]
3579 set date [formatdate $date]
3580 set font $mainfont
3581 set nfont $mainfont
3582 set isbold [ishighlighted $row]
3583 if {$isbold > 0} {
3584 lappend boldrows $row
3585 lappend font bold
3586 if {$isbold > 1} {
3587 lappend boldnamerows $row
3588 lappend nfont bold
3589 }
3590 }
3591 set linehtag($row) [$canv create text $xt $y -anchor w -fill $fgcolor \
3592 -text $headline -font $font -tags text]
3593 $canv bind $linehtag($row) <Button-3> "rowmenu %X %Y $id"
3594 set linentag($row) [$canv2 create text 3 $y -anchor w -fill $fgcolor \
3595 -text $name -font $nfont -tags text]
3596 set linedtag($row) [$canv3 create text 3 $y -anchor w -fill $fgcolor \
3597 -text $date -font $mainfont -tags text]
3598 set xr [expr {$xt + [font measure $mainfont $headline]}]
3599 if {$xr > $canvxmax} {
3600 set canvxmax $xr
3601 setcanvscroll
3602 }
3603}
3604
3605proc drawcmitrow {row} {
3606 global displayorder rowidlist
3607 global iddrawn markingmatches
3608 global commitinfo parentlist numcommits
3609 global filehighlight fhighlights findstring nhighlights
3610 global hlview vhighlights
3611 global highlight_related rhighlights
3612
3613 if {$row >= $numcommits} return
3614
3615 set id [lindex $displayorder $row]
3616 if {[info exists hlview] && ![info exists vhighlights($row)]} {
3617 askvhighlight $row $id
3618 }
3619 if {[info exists filehighlight] && ![info exists fhighlights($row)]} {
3620 askfilehighlight $row $id
3621 }
3622 if {$findstring ne {} && ![info exists nhighlights($row)]} {
3623 askfindhighlight $row $id
3624 }
3625 if {$highlight_related ne "None" && ![info exists rhighlights($row)]} {
3626 askrelhighlight $row $id
3627 }
3628 if {![info exists iddrawn($id)]} {
3629 set col [lsearch -exact [lindex $rowidlist $row] $id]
3630 if {$col < 0} {
3631 puts "oops, row $row id $id not in list"
3632 return
3633 }
3634 if {![info exists commitinfo($id)]} {
3635 getcommit $id
3636 }
3637 assigncolor $id
3638 drawcmittext $id $row $col
3639 set iddrawn($id) 1
3640 }
3641 if {$markingmatches} {
3642 markrowmatches $row $id
3643 }
3644}
3645
3646proc drawcommits {row {endrow {}}} {
3647 global numcommits iddrawn displayorder curview
3648 global parentlist rowidlist
3649
3650 if {$row < 0} {
3651 set row 0
3652 }
3653 if {$endrow eq {}} {
3654 set endrow $row
3655 }
3656 if {$endrow >= $numcommits} {
3657 set endrow [expr {$numcommits - 1}]
3658 }
3659
3660 # make the lines join to already-drawn rows either side
3661 set r [expr {$row - 1}]
3662 if {$r < 0 || ![info exists iddrawn([lindex $displayorder $r])]} {
3663 set r $row
3664 }
3665 set er [expr {$endrow + 1}]
3666 if {$er >= $numcommits ||
3667 ![info exists iddrawn([lindex $displayorder $er])]} {
3668 set er $endrow
3669 }
3670 for {} {$r <= $er} {incr r} {
3671 set id [lindex $displayorder $r]
3672 set wasdrawn [info exists iddrawn($id)]
3673 drawcmitrow $r
3674 if {$r == $er} break
3675 set nextid [lindex $displayorder [expr {$r + 1}]]
3676 if {$wasdrawn && [info exists iddrawn($nextid)]} {
3677 catch {unset prevlines}
3678 continue
3679 }
3680 drawparentlinks $id $r
3681
3682 if {[info exists lineends($r)]} {
3683 foreach lid $lineends($r) {
3684 unset prevlines($lid)
3685 }
3686 }
3687 set rowids [lindex $rowidlist $r]
3688 foreach lid $rowids {
3689 if {$lid eq {}} continue
3690 if {$lid eq $id} {
3691 # see if this is the first child of any of its parents
3692 foreach p [lindex $parentlist $r] {
3693 if {[lsearch -exact $rowids $p] < 0} {
3694 # make this line extend up to the child
3695 set le [drawlineseg $p $r $er 0]
3696 lappend lineends($le) $p
3697 set prevlines($p) 1
3698 }
3699 }
3700 } elseif {![info exists prevlines($lid)]} {
3701 set le [drawlineseg $lid $r $er 1]
3702 lappend lineends($le) $lid
3703 set prevlines($lid) 1
3704 }
3705 }
3706 }
3707}
3708
3709proc drawfrac {f0 f1} {
3710 global canv linespc
3711
3712 set ymax [lindex [$canv cget -scrollregion] 3]
3713 if {$ymax eq {} || $ymax == 0} return
3714 set y0 [expr {int($f0 * $ymax)}]
3715 set row [expr {int(($y0 - 3) / $linespc) - 1}]
3716 set y1 [expr {int($f1 * $ymax)}]
3717 set endrow [expr {int(($y1 - 3) / $linespc) + 1}]
3718 drawcommits $row $endrow
3719}
3720
3721proc drawvisible {} {
3722 global canv
3723 eval drawfrac [$canv yview]
3724}
3725
3726proc clear_display {} {
3727 global iddrawn linesegs
3728 global vhighlights fhighlights nhighlights rhighlights
3729
3730 allcanvs delete all
3731 catch {unset iddrawn}
3732 catch {unset linesegs}
3733 catch {unset vhighlights}
3734 catch {unset fhighlights}
3735 catch {unset nhighlights}
3736 catch {unset rhighlights}
3737}
3738
3739proc findcrossings {id} {
3740 global rowidlist parentlist numcommits displayorder
3741
3742 set cross {}
3743 set ccross {}
3744 foreach {s e} [rowranges $id] {
3745 if {$e >= $numcommits} {
3746 set e [expr {$numcommits - 1}]
3747 }
3748 if {$e <= $s} continue
3749 for {set row $e} {[incr row -1] >= $s} {} {
3750 set x [lsearch -exact [lindex $rowidlist $row] $id]
3751 if {$x < 0} break
3752 set olds [lindex $parentlist $row]
3753 set kid [lindex $displayorder $row]
3754 set kidx [lsearch -exact [lindex $rowidlist $row] $kid]
3755 if {$kidx < 0} continue
3756 set nextrow [lindex $rowidlist [expr {$row + 1}]]
3757 foreach p $olds {
3758 set px [lsearch -exact $nextrow $p]
3759 if {$px < 0} continue
3760 if {($kidx < $x && $x < $px) || ($px < $x && $x < $kidx)} {
3761 if {[lsearch -exact $ccross $p] >= 0} continue
3762 if {$x == $px + ($kidx < $px? -1: 1)} {
3763 lappend ccross $p
3764 } elseif {[lsearch -exact $cross $p] < 0} {
3765 lappend cross $p
3766 }
3767 }
3768 }
3769 }
3770 }
3771 return [concat $ccross {{}} $cross]
3772}
3773
3774proc assigncolor {id} {
3775 global colormap colors nextcolor
3776 global commitrow parentlist children children curview
3777
3778 if {[info exists colormap($id)]} return
3779 set ncolors [llength $colors]
3780 if {[info exists children($curview,$id)]} {
3781 set kids $children($curview,$id)
3782 } else {
3783 set kids {}
3784 }
3785 if {[llength $kids] == 1} {
3786 set child [lindex $kids 0]
3787 if {[info exists colormap($child)]
3788 && [llength [lindex $parentlist $commitrow($curview,$child)]] == 1} {
3789 set colormap($id) $colormap($child)
3790 return
3791 }
3792 }
3793 set badcolors {}
3794 set origbad {}
3795 foreach x [findcrossings $id] {
3796 if {$x eq {}} {
3797 # delimiter between corner crossings and other crossings
3798 if {[llength $badcolors] >= $ncolors - 1} break
3799 set origbad $badcolors
3800 }
3801 if {[info exists colormap($x)]
3802 && [lsearch -exact $badcolors $colormap($x)] < 0} {
3803 lappend badcolors $colormap($x)
3804 }
3805 }
3806 if {[llength $badcolors] >= $ncolors} {
3807 set badcolors $origbad
3808 }
3809 set origbad $badcolors
3810 if {[llength $badcolors] < $ncolors - 1} {
3811 foreach child $kids {
3812 if {[info exists colormap($child)]
3813 && [lsearch -exact $badcolors $colormap($child)] < 0} {
3814 lappend badcolors $colormap($child)
3815 }
3816 foreach p [lindex $parentlist $commitrow($curview,$child)] {
3817 if {[info exists colormap($p)]
3818 && [lsearch -exact $badcolors $colormap($p)] < 0} {
3819 lappend badcolors $colormap($p)
3820 }
3821 }
3822 }
3823 if {[llength $badcolors] >= $ncolors} {
3824 set badcolors $origbad
3825 }
3826 }
3827 for {set i 0} {$i <= $ncolors} {incr i} {
3828 set c [lindex $colors $nextcolor]
3829 if {[incr nextcolor] >= $ncolors} {
3830 set nextcolor 0
3831 }
3832 if {[lsearch -exact $badcolors $c]} break
3833 }
3834 set colormap($id) $c
3835}
3836
3837proc bindline {t id} {
3838 global canv
3839
3840 $canv bind $t <Enter> "lineenter %x %y $id"
3841 $canv bind $t <Motion> "linemotion %x %y $id"
3842 $canv bind $t <Leave> "lineleave $id"
3843 $canv bind $t <Button-1> "lineclick %x %y $id 1"
3844}
3845
3846proc drawtags {id x xt y1} {
3847 global idtags idheads idotherrefs mainhead
3848 global linespc lthickness
3849 global canv mainfont commitrow rowtextx curview fgcolor bgcolor
3850
3851 set marks {}
3852 set ntags 0
3853 set nheads 0
3854 if {[info exists idtags($id)]} {
3855 set marks $idtags($id)
3856 set ntags [llength $marks]
3857 }
3858 if {[info exists idheads($id)]} {
3859 set marks [concat $marks $idheads($id)]
3860 set nheads [llength $idheads($id)]
3861 }
3862 if {[info exists idotherrefs($id)]} {
3863 set marks [concat $marks $idotherrefs($id)]
3864 }
3865 if {$marks eq {}} {
3866 return $xt
3867 }
3868
3869 set delta [expr {int(0.5 * ($linespc - $lthickness))}]
3870 set yt [expr {$y1 - 0.5 * $linespc}]
3871 set yb [expr {$yt + $linespc - 1}]
3872 set xvals {}
3873 set wvals {}
3874 set i -1
3875 foreach tag $marks {
3876 incr i
3877 if {$i >= $ntags && $i < $ntags + $nheads && $tag eq $mainhead} {
3878 set wid [font measure [concat $mainfont bold] $tag]
3879 } else {
3880 set wid [font measure $mainfont $tag]
3881 }
3882 lappend xvals $xt
3883 lappend wvals $wid
3884 set xt [expr {$xt + $delta + $wid + $lthickness + $linespc}]
3885 }
3886 set t [$canv create line $x $y1 [lindex $xvals end] $y1 \
3887 -width $lthickness -fill black -tags tag.$id]
3888 $canv lower $t
3889 foreach tag $marks x $xvals wid $wvals {
3890 set xl [expr {$x + $delta}]
3891 set xr [expr {$x + $delta + $wid + $lthickness}]
3892 set font $mainfont
3893 if {[incr ntags -1] >= 0} {
3894 # draw a tag
3895 set t [$canv create polygon $x [expr {$yt + $delta}] $xl $yt \
3896 $xr $yt $xr $yb $xl $yb $x [expr {$yb - $delta}] \
3897 -width 1 -outline black -fill yellow -tags tag.$id]
3898 $canv bind $t <1> [list showtag $tag 1]
3899 set rowtextx($commitrow($curview,$id)) [expr {$xr + $linespc}]
3900 } else {
3901 # draw a head or other ref
3902 if {[incr nheads -1] >= 0} {
3903 set col green
3904 if {$tag eq $mainhead} {
3905 lappend font bold
3906 }
3907 } else {
3908 set col "#ddddff"
3909 }
3910 set xl [expr {$xl - $delta/2}]
3911 $canv create polygon $x $yt $xr $yt $xr $yb $x $yb \
3912 -width 1 -outline black -fill $col -tags tag.$id
3913 if {[regexp {^(remotes/.*/|remotes/)} $tag match remoteprefix]} {
3914 set rwid [font measure $mainfont $remoteprefix]
3915 set xi [expr {$x + 1}]
3916 set yti [expr {$yt + 1}]
3917 set xri [expr {$x + $rwid}]
3918 $canv create polygon $xi $yti $xri $yti $xri $yb $xi $yb \
3919 -width 0 -fill "#ffddaa" -tags tag.$id
3920 }
3921 }
3922 set t [$canv create text $xl $y1 -anchor w -text $tag -fill $fgcolor \
3923 -font $font -tags [list tag.$id text]]
3924 if {$ntags >= 0} {
3925 $canv bind $t <1> [list showtag $tag 1]
3926 } elseif {$nheads >= 0} {
3927 $canv bind $t <Button-3> [list headmenu %X %Y $id $tag]
3928 }
3929 }
3930 return $xt
3931}
3932
3933proc xcoord {i level ln} {
3934 global canvx0 xspc1 xspc2
3935
3936 set x [expr {$canvx0 + $i * $xspc1($ln)}]
3937 if {$i > 0 && $i == $level} {
3938 set x [expr {$x + 0.5 * ($xspc2 - $xspc1($ln))}]
3939 } elseif {$i > $level} {
3940 set x [expr {$x + $xspc2 - $xspc1($ln)}]
3941 }
3942 return $x
3943}
3944
3945proc show_status {msg} {
3946 global canv mainfont fgcolor
3947
3948 clear_display
3949 $canv create text 3 3 -anchor nw -text $msg -font $mainfont \
3950 -tags text -fill $fgcolor
3951}
3952
3953# Insert a new commit as the child of the commit on row $row.
3954# The new commit will be displayed on row $row and the commits
3955# on that row and below will move down one row.
3956proc insertrow {row newcmit} {
3957 global displayorder parentlist commitlisted children
3958 global commitrow curview rowidlist numcommits
3959 global rowlaidout rowoptim numcommits
3960 global selectedline rowchk commitidx
3961
3962 if {$row >= $numcommits} {
3963 puts "oops, inserting new row $row but only have $numcommits rows"
3964 return
3965 }
3966 set p [lindex $displayorder $row]
3967 set displayorder [linsert $displayorder $row $newcmit]
3968 set parentlist [linsert $parentlist $row $p]
3969 set kids $children($curview,$p)
3970 lappend kids $newcmit
3971 set children($curview,$p) $kids
3972 set children($curview,$newcmit) {}
3973 set commitlisted [linsert $commitlisted $row 1]
3974 set l [llength $displayorder]
3975 for {set r $row} {$r < $l} {incr r} {
3976 set id [lindex $displayorder $r]
3977 set commitrow($curview,$id) $r
3978 }
3979 incr commitidx($curview)
3980
3981 set idlist [lindex $rowidlist $row]
3982 if {[llength $kids] == 1} {
3983 set col [lsearch -exact $idlist $p]
3984 lset idlist $col $newcmit
3985 } else {
3986 set col [llength $idlist]
3987 lappend idlist $newcmit
3988 }
3989 set rowidlist [linsert $rowidlist $row $idlist]
3990
3991 catch {unset rowchk}
3992
3993 incr rowlaidout
3994 incr rowoptim
3995 incr numcommits
3996
3997 if {[info exists selectedline] && $selectedline >= $row} {
3998 incr selectedline
3999 }
4000 redisplay
4001}
4002
4003# Remove a commit that was inserted with insertrow on row $row.
4004proc removerow {row} {
4005 global displayorder parentlist commitlisted children
4006 global commitrow curview rowidlist numcommits
4007 global rowlaidout rowoptim numcommits
4008 global linesegends selectedline rowchk commitidx
4009
4010 if {$row >= $numcommits} {
4011 puts "oops, removing row $row but only have $numcommits rows"
4012 return
4013 }
4014 set rp1 [expr {$row + 1}]
4015 set id [lindex $displayorder $row]
4016 set p [lindex $parentlist $row]
4017 set displayorder [lreplace $displayorder $row $row]
4018 set parentlist [lreplace $parentlist $row $row]
4019 set commitlisted [lreplace $commitlisted $row $row]
4020 set kids $children($curview,$p)
4021 set i [lsearch -exact $kids $id]
4022 if {$i >= 0} {
4023 set kids [lreplace $kids $i $i]
4024 set children($curview,$p) $kids
4025 }
4026 set l [llength $displayorder]
4027 for {set r $row} {$r < $l} {incr r} {
4028 set id [lindex $displayorder $r]
4029 set commitrow($curview,$id) $r
4030 }
4031 incr commitidx($curview) -1
4032
4033 set rowidlist [lreplace $rowidlist $row $row]
4034
4035 catch {unset rowchk}
4036
4037 incr rowlaidout -1
4038 incr rowoptim -1
4039 incr numcommits -1
4040
4041 if {[info exists selectedline] && $selectedline > $row} {
4042 incr selectedline -1
4043 }
4044 redisplay
4045}
4046
4047# Don't change the text pane cursor if it is currently the hand cursor,
4048# showing that we are over a sha1 ID link.
4049proc settextcursor {c} {
4050 global ctext curtextcursor
4051
4052 if {[$ctext cget -cursor] == $curtextcursor} {
4053 $ctext config -cursor $c
4054 }
4055 set curtextcursor $c
4056}
4057
4058proc nowbusy {what} {
4059 global isbusy
4060
4061 if {[array names isbusy] eq {}} {
4062 . config -cursor watch
4063 settextcursor watch
4064 }
4065 set isbusy($what) 1
4066}
4067
4068proc notbusy {what} {
4069 global isbusy maincursor textcursor
4070
4071 catch {unset isbusy($what)}
4072 if {[array names isbusy] eq {}} {
4073 . config -cursor $maincursor
4074 settextcursor $textcursor
4075 }
4076}
4077
4078proc findmatches {f} {
4079 global findtype findstring
4080 if {$findtype == "Regexp"} {
4081 set matches [regexp -indices -all -inline $findstring $f]
4082 } else {
4083 set fs $findstring
4084 if {$findtype == "IgnCase"} {
4085 set f [string tolower $f]
4086 set fs [string tolower $fs]
4087 }
4088 set matches {}
4089 set i 0
4090 set l [string length $fs]
4091 while {[set j [string first $fs $f $i]] >= 0} {
4092 lappend matches [list $j [expr {$j+$l-1}]]
4093 set i [expr {$j + $l}]
4094 }
4095 }
4096 return $matches
4097}
4098
4099proc dofind {{rev 0}} {
4100 global findstring findstartline findcurline selectedline numcommits
4101
4102 unmarkmatches
4103 cancel_next_highlight
4104 focus .
4105 if {$findstring eq {} || $numcommits == 0} return
4106 if {![info exists selectedline]} {
4107 set findstartline [lindex [visiblerows] $rev]
4108 } else {
4109 set findstartline $selectedline
4110 }
4111 set findcurline $findstartline
4112 nowbusy finding
4113 if {!$rev} {
4114 run findmore
4115 } else {
4116 if {$findcurline == 0} {
4117 set findcurline $numcommits
4118 }
4119 incr findcurline -1
4120 run findmorerev
4121 }
4122}
4123
4124proc findnext {restart} {
4125 global findcurline
4126 if {![info exists findcurline]} {
4127 if {$restart} {
4128 dofind
4129 } else {
4130 bell
4131 }
4132 } else {
4133 run findmore
4134 nowbusy finding
4135 }
4136}
4137
4138proc findprev {} {
4139 global findcurline
4140 if {![info exists findcurline]} {
4141 dofind 1
4142 } else {
4143 run findmorerev
4144 nowbusy finding
4145 }
4146}
4147
4148proc findmore {} {
4149 global commitdata commitinfo numcommits findstring findpattern findloc
4150 global findstartline findcurline displayorder
4151
4152 set fldtypes {Headline Author Date Committer CDate Comments}
4153 set l [expr {$findcurline + 1}]
4154 if {$l >= $numcommits} {
4155 set l 0
4156 }
4157 if {$l <= $findstartline} {
4158 set lim [expr {$findstartline + 1}]
4159 } else {
4160 set lim $numcommits
4161 }
4162 if {$lim - $l > 500} {
4163 set lim [expr {$l + 500}]
4164 }
4165 set last 0
4166 for {} {$l < $lim} {incr l} {
4167 set id [lindex $displayorder $l]
4168 # shouldn't happen unless git log doesn't give all the commits...
4169 if {![info exists commitdata($id)]} continue
4170 if {![doesmatch $commitdata($id)]} continue
4171 if {![info exists commitinfo($id)]} {
4172 getcommit $id
4173 }
4174 set info $commitinfo($id)
4175 foreach f $info ty $fldtypes {
4176 if {($findloc eq "All fields" || $findloc eq $ty) &&
4177 [doesmatch $f]} {
4178 findselectline $l
4179 notbusy finding
4180 return 0
4181 }
4182 }
4183 }
4184 if {$l == $findstartline + 1} {
4185 bell
4186 unset findcurline
4187 notbusy finding
4188 return 0
4189 }
4190 set findcurline [expr {$l - 1}]
4191 return 1
4192}
4193
4194proc findmorerev {} {
4195 global commitdata commitinfo numcommits findstring findpattern findloc
4196 global findstartline findcurline displayorder
4197
4198 set fldtypes {Headline Author Date Committer CDate Comments}
4199 set l $findcurline
4200 if {$l == 0} {
4201 set l $numcommits
4202 }
4203 incr l -1
4204 if {$l >= $findstartline} {
4205 set lim [expr {$findstartline - 1}]
4206 } else {
4207 set lim -1
4208 }
4209 if {$l - $lim > 500} {
4210 set lim [expr {$l - 500}]
4211 }
4212 set last 0
4213 for {} {$l > $lim} {incr l -1} {
4214 set id [lindex $displayorder $l]
4215 if {![info exists commitdata($id)]} continue
4216 if {![doesmatch $commitdata($id)]} continue
4217 if {![info exists commitinfo($id)]} {
4218 getcommit $id
4219 }
4220 set info $commitinfo($id)
4221 foreach f $info ty $fldtypes {
4222 if {($findloc eq "All fields" || $findloc eq $ty) &&
4223 [doesmatch $f]} {
4224 findselectline $l
4225 notbusy finding
4226 return 0
4227 }
4228 }
4229 }
4230 if {$l == -1} {
4231 bell
4232 unset findcurline
4233 notbusy finding
4234 return 0
4235 }
4236 set findcurline [expr {$l + 1}]
4237 return 1
4238}
4239
4240proc findselectline {l} {
4241 global findloc commentend ctext findcurline markingmatches
4242
4243 set markingmatches 1
4244 set findcurline $l
4245 selectline $l 1
4246 if {$findloc == "All fields" || $findloc == "Comments"} {
4247 # highlight the matches in the comments
4248 set f [$ctext get 1.0 $commentend]
4249 set matches [findmatches $f]
4250 foreach match $matches {
4251 set start [lindex $match 0]
4252 set end [expr {[lindex $match 1] + 1}]
4253 $ctext tag add found "1.0 + $start c" "1.0 + $end c"
4254 }
4255 }
4256 drawvisible
4257}
4258
4259# mark the bits of a headline or author that match a find string
4260proc markmatches {canv l str tag matches font row} {
4261 global selectedline
4262
4263 set bbox [$canv bbox $tag]
4264 set x0 [lindex $bbox 0]
4265 set y0 [lindex $bbox 1]
4266 set y1 [lindex $bbox 3]
4267 foreach match $matches {
4268 set start [lindex $match 0]
4269 set end [lindex $match 1]
4270 if {$start > $end} continue
4271 set xoff [font measure $font [string range $str 0 [expr {$start-1}]]]
4272 set xlen [font measure $font [string range $str 0 [expr {$end}]]]
4273 set t [$canv create rect [expr {$x0+$xoff}] $y0 \
4274 [expr {$x0+$xlen+2}] $y1 \
4275 -outline {} -tags [list match$l matches] -fill yellow]
4276 $canv lower $t
4277 if {[info exists selectedline] && $row == $selectedline} {
4278 $canv raise $t secsel
4279 }
4280 }
4281}
4282
4283proc unmarkmatches {} {
4284 global findids markingmatches findcurline
4285
4286 allcanvs delete matches
4287 catch {unset findids}
4288 set markingmatches 0
4289 catch {unset findcurline}
4290}
4291
4292proc selcanvline {w x y} {
4293 global canv canvy0 ctext linespc
4294 global rowtextx
4295 set ymax [lindex [$canv cget -scrollregion] 3]
4296 if {$ymax == {}} return
4297 set yfrac [lindex [$canv yview] 0]
4298 set y [expr {$y + $yfrac * $ymax}]
4299 set l [expr {int(($y - $canvy0) / $linespc + 0.5)}]
4300 if {$l < 0} {
4301 set l 0
4302 }
4303 if {$w eq $canv} {
4304 if {![info exists rowtextx($l)] || $x < $rowtextx($l)} return
4305 }
4306 unmarkmatches
4307 selectline $l 1
4308}
4309
4310proc commit_descriptor {p} {
4311 global commitinfo
4312 if {![info exists commitinfo($p)]} {
4313 getcommit $p
4314 }
4315 set l "..."
4316 if {[llength $commitinfo($p)] > 1} {
4317 set l [lindex $commitinfo($p) 0]
4318 }
4319 return "$p ($l)\n"
4320}
4321
4322# append some text to the ctext widget, and make any SHA1 ID
4323# that we know about be a clickable link.
4324proc appendwithlinks {text tags} {
4325 global ctext commitrow linknum curview
4326
4327 set start [$ctext index "end - 1c"]
4328 $ctext insert end $text $tags
4329 set links [regexp -indices -all -inline {[0-9a-f]{40}} $text]
4330 foreach l $links {
4331 set s [lindex $l 0]
4332 set e [lindex $l 1]
4333 set linkid [string range $text $s $e]
4334 if {![info exists commitrow($curview,$linkid)]} continue
4335 incr e
4336 $ctext tag add link "$start + $s c" "$start + $e c"
4337 $ctext tag add link$linknum "$start + $s c" "$start + $e c"
4338 $ctext tag bind link$linknum <1> \
4339 [list selectline $commitrow($curview,$linkid) 1]
4340 incr linknum
4341 }
4342 $ctext tag conf link -foreground blue -underline 1
4343 $ctext tag bind link <Enter> { %W configure -cursor hand2 }
4344 $ctext tag bind link <Leave> { %W configure -cursor $curtextcursor }
4345}
4346
4347proc viewnextline {dir} {
4348 global canv linespc
4349
4350 $canv delete hover
4351 set ymax [lindex [$canv cget -scrollregion] 3]
4352 set wnow [$canv yview]
4353 set wtop [expr {[lindex $wnow 0] * $ymax}]
4354 set newtop [expr {$wtop + $dir * $linespc}]
4355 if {$newtop < 0} {
4356 set newtop 0
4357 } elseif {$newtop > $ymax} {
4358 set newtop $ymax
4359 }
4360 allcanvs yview moveto [expr {$newtop * 1.0 / $ymax}]
4361}
4362
4363# add a list of tag or branch names at position pos
4364# returns the number of names inserted
4365proc appendrefs {pos ids var} {
4366 global ctext commitrow linknum curview $var maxrefs
4367
4368 if {[catch {$ctext index $pos}]} {
4369 return 0
4370 }
4371 $ctext conf -state normal
4372 $ctext delete $pos "$pos lineend"
4373 set tags {}
4374 foreach id $ids {
4375 foreach tag [set $var\($id\)] {
4376 lappend tags [list $tag $id]
4377 }
4378 }
4379 if {[llength $tags] > $maxrefs} {
4380 $ctext insert $pos "many ([llength $tags])"
4381 } else {
4382 set tags [lsort -index 0 -decreasing $tags]
4383 set sep {}
4384 foreach ti $tags {
4385 set id [lindex $ti 1]
4386 set lk link$linknum
4387 incr linknum
4388 $ctext tag delete $lk
4389 $ctext insert $pos $sep
4390 $ctext insert $pos [lindex $ti 0] $lk
4391 if {[info exists commitrow($curview,$id)]} {
4392 $ctext tag conf $lk -foreground blue
4393 $ctext tag bind $lk <1> \
4394 [list selectline $commitrow($curview,$id) 1]
4395 $ctext tag conf $lk -underline 1
4396 $ctext tag bind $lk <Enter> { %W configure -cursor hand2 }
4397 $ctext tag bind $lk <Leave> \
4398 { %W configure -cursor $curtextcursor }
4399 }
4400 set sep ", "
4401 }
4402 }
4403 $ctext conf -state disabled
4404 return [llength $tags]
4405}
4406
4407# called when we have finished computing the nearby tags
4408proc dispneartags {delay} {
4409 global selectedline currentid showneartags tagphase
4410
4411 if {![info exists selectedline] || !$showneartags} return
4412 after cancel dispnexttag
4413 if {$delay} {
4414 after 200 dispnexttag
4415 set tagphase -1
4416 } else {
4417 after idle dispnexttag
4418 set tagphase 0
4419 }
4420}
4421
4422proc dispnexttag {} {
4423 global selectedline currentid showneartags tagphase ctext
4424
4425 if {![info exists selectedline] || !$showneartags} return
4426 switch -- $tagphase {
4427 0 {
4428 set dtags [desctags $currentid]
4429 if {$dtags ne {}} {
4430 appendrefs precedes $dtags idtags
4431 }
4432 }
4433 1 {
4434 set atags [anctags $currentid]
4435 if {$atags ne {}} {
4436 appendrefs follows $atags idtags
4437 }
4438 }
4439 2 {
4440 set dheads [descheads $currentid]
4441 if {$dheads ne {}} {
4442 if {[appendrefs branch $dheads idheads] > 1
4443 && [$ctext get "branch -3c"] eq "h"} {
4444 # turn "Branch" into "Branches"
4445 $ctext conf -state normal
4446 $ctext insert "branch -2c" "es"
4447 $ctext conf -state disabled
4448 }
4449 }
4450 }
4451 }
4452 if {[incr tagphase] <= 2} {
4453 after idle dispnexttag
4454 }
4455}
4456
4457proc selectline {l isnew} {
4458 global canv canv2 canv3 ctext commitinfo selectedline
4459 global displayorder linehtag linentag linedtag
4460 global canvy0 linespc parentlist children curview
4461 global currentid sha1entry
4462 global commentend idtags linknum
4463 global mergemax numcommits pending_select
4464 global cmitmode showneartags allcommits
4465
4466 catch {unset pending_select}
4467 $canv delete hover
4468 normalline
4469 cancel_next_highlight
4470 unsel_reflist
4471 if {$l < 0 || $l >= $numcommits} return
4472 set y [expr {$canvy0 + $l * $linespc}]
4473 set ymax [lindex [$canv cget -scrollregion] 3]
4474 set ytop [expr {$y - $linespc - 1}]
4475 set ybot [expr {$y + $linespc + 1}]
4476 set wnow [$canv yview]
4477 set wtop [expr {[lindex $wnow 0] * $ymax}]
4478 set wbot [expr {[lindex $wnow 1] * $ymax}]
4479 set wh [expr {$wbot - $wtop}]
4480 set newtop $wtop
4481 if {$ytop < $wtop} {
4482 if {$ybot < $wtop} {
4483 set newtop [expr {$y - $wh / 2.0}]
4484 } else {
4485 set newtop $ytop
4486 if {$newtop > $wtop - $linespc} {
4487 set newtop [expr {$wtop - $linespc}]
4488 }
4489 }
4490 } elseif {$ybot > $wbot} {
4491 if {$ytop > $wbot} {
4492 set newtop [expr {$y - $wh / 2.0}]
4493 } else {
4494 set newtop [expr {$ybot - $wh}]
4495 if {$newtop < $wtop + $linespc} {
4496 set newtop [expr {$wtop + $linespc}]
4497 }
4498 }
4499 }
4500 if {$newtop != $wtop} {
4501 if {$newtop < 0} {
4502 set newtop 0
4503 }
4504 allcanvs yview moveto [expr {$newtop * 1.0 / $ymax}]
4505 drawvisible
4506 }
4507
4508 if {![info exists linehtag($l)]} return
4509 $canv delete secsel
4510 set t [eval $canv create rect [$canv bbox $linehtag($l)] -outline {{}} \
4511 -tags secsel -fill [$canv cget -selectbackground]]
4512 $canv lower $t
4513 $canv2 delete secsel
4514 set t [eval $canv2 create rect [$canv2 bbox $linentag($l)] -outline {{}} \
4515 -tags secsel -fill [$canv2 cget -selectbackground]]
4516 $canv2 lower $t
4517 $canv3 delete secsel
4518 set t [eval $canv3 create rect [$canv3 bbox $linedtag($l)] -outline {{}} \
4519 -tags secsel -fill [$canv3 cget -selectbackground]]
4520 $canv3 lower $t
4521
4522 if {$isnew} {
4523 addtohistory [list selectline $l 0]
4524 }
4525
4526 set selectedline $l
4527
4528 set id [lindex $displayorder $l]
4529 set currentid $id
4530 $sha1entry delete 0 end
4531 $sha1entry insert 0 $id
4532 $sha1entry selection from 0
4533 $sha1entry selection to end
4534 rhighlight_sel $id
4535
4536 $ctext conf -state normal
4537 clear_ctext
4538 set linknum 0
4539 set info $commitinfo($id)
4540 set date [formatdate [lindex $info 2]]
4541 $ctext insert end "Author: [lindex $info 1] $date\n"
4542 set date [formatdate [lindex $info 4]]
4543 $ctext insert end "Committer: [lindex $info 3] $date\n"
4544 if {[info exists idtags($id)]} {
4545 $ctext insert end "Tags:"
4546 foreach tag $idtags($id) {
4547 $ctext insert end " $tag"
4548 }
4549 $ctext insert end "\n"
4550 }
4551
4552 set headers {}
4553 set olds [lindex $parentlist $l]
4554 if {[llength $olds] > 1} {
4555 set np 0
4556 foreach p $olds {
4557 if {$np >= $mergemax} {
4558 set tag mmax
4559 } else {
4560 set tag m$np
4561 }
4562 $ctext insert end "Parent: " $tag
4563 appendwithlinks [commit_descriptor $p] {}
4564 incr np
4565 }
4566 } else {
4567 foreach p $olds {
4568 append headers "Parent: [commit_descriptor $p]"
4569 }
4570 }
4571
4572 foreach c $children($curview,$id) {
4573 append headers "Child: [commit_descriptor $c]"
4574 }
4575
4576 # make anything that looks like a SHA1 ID be a clickable link
4577 appendwithlinks $headers {}
4578 if {$showneartags} {
4579 if {![info exists allcommits]} {
4580 getallcommits
4581 }
4582 $ctext insert end "Branch: "
4583 $ctext mark set branch "end -1c"
4584 $ctext mark gravity branch left
4585 $ctext insert end "\nFollows: "
4586 $ctext mark set follows "end -1c"
4587 $ctext mark gravity follows left
4588 $ctext insert end "\nPrecedes: "
4589 $ctext mark set precedes "end -1c"
4590 $ctext mark gravity precedes left
4591 $ctext insert end "\n"
4592 dispneartags 1
4593 }
4594 $ctext insert end "\n"
4595 set comment [lindex $info 5]
4596 if {[string first "\r" $comment] >= 0} {
4597 set comment [string map {"\r" "\n "} $comment]
4598 }
4599 appendwithlinks $comment {comment}
4600
4601 $ctext tag remove found 1.0 end
4602 $ctext conf -state disabled
4603 set commentend [$ctext index "end - 1c"]
4604
4605 init_flist "Comments"
4606 if {$cmitmode eq "tree"} {
4607 gettree $id
4608 } elseif {[llength $olds] <= 1} {
4609 startdiff $id
4610 } else {
4611 mergediff $id $l
4612 }
4613}
4614
4615proc selfirstline {} {
4616 unmarkmatches
4617 selectline 0 1
4618}
4619
4620proc sellastline {} {
4621 global numcommits
4622 unmarkmatches
4623 set l [expr {$numcommits - 1}]
4624 selectline $l 1
4625}
4626
4627proc selnextline {dir} {
4628 global selectedline
4629 focus .
4630 if {![info exists selectedline]} return
4631 set l [expr {$selectedline + $dir}]
4632 unmarkmatches
4633 selectline $l 1
4634}
4635
4636proc selnextpage {dir} {
4637 global canv linespc selectedline numcommits
4638
4639 set lpp [expr {([winfo height $canv] - 2) / $linespc}]
4640 if {$lpp < 1} {
4641 set lpp 1
4642 }
4643 allcanvs yview scroll [expr {$dir * $lpp}] units
4644 drawvisible
4645 if {![info exists selectedline]} return
4646 set l [expr {$selectedline + $dir * $lpp}]
4647 if {$l < 0} {
4648 set l 0
4649 } elseif {$l >= $numcommits} {
4650 set l [expr $numcommits - 1]
4651 }
4652 unmarkmatches
4653 selectline $l 1
4654}
4655
4656proc unselectline {} {
4657 global selectedline currentid
4658
4659 catch {unset selectedline}
4660 catch {unset currentid}
4661 allcanvs delete secsel
4662 rhighlight_none
4663 cancel_next_highlight
4664}
4665
4666proc reselectline {} {
4667 global selectedline
4668
4669 if {[info exists selectedline]} {
4670 selectline $selectedline 0
4671 }
4672}
4673
4674proc addtohistory {cmd} {
4675 global history historyindex curview
4676
4677 set elt [list $curview $cmd]
4678 if {$historyindex > 0
4679 && [lindex $history [expr {$historyindex - 1}]] == $elt} {
4680 return
4681 }
4682
4683 if {$historyindex < [llength $history]} {
4684 set history [lreplace $history $historyindex end $elt]
4685 } else {
4686 lappend history $elt
4687 }
4688 incr historyindex
4689 if {$historyindex > 1} {
4690 .tf.bar.leftbut conf -state normal
4691 } else {
4692 .tf.bar.leftbut conf -state disabled
4693 }
4694 .tf.bar.rightbut conf -state disabled
4695}
4696
4697proc godo {elt} {
4698 global curview
4699
4700 set view [lindex $elt 0]
4701 set cmd [lindex $elt 1]
4702 if {$curview != $view} {
4703 showview $view
4704 }
4705 eval $cmd
4706}
4707
4708proc goback {} {
4709 global history historyindex
4710 focus .
4711
4712 if {$historyindex > 1} {
4713 incr historyindex -1
4714 godo [lindex $history [expr {$historyindex - 1}]]
4715 .tf.bar.rightbut conf -state normal
4716 }
4717 if {$historyindex <= 1} {
4718 .tf.bar.leftbut conf -state disabled
4719 }
4720}
4721
4722proc goforw {} {
4723 global history historyindex
4724 focus .
4725
4726 if {$historyindex < [llength $history]} {
4727 set cmd [lindex $history $historyindex]
4728 incr historyindex
4729 godo $cmd
4730 .tf.bar.leftbut conf -state normal
4731 }
4732 if {$historyindex >= [llength $history]} {
4733 .tf.bar.rightbut conf -state disabled
4734 }
4735}
4736
4737proc gettree {id} {
4738 global treefilelist treeidlist diffids diffmergeid treepending
4739 global nullid nullid2
4740
4741 set diffids $id
4742 catch {unset diffmergeid}
4743 if {![info exists treefilelist($id)]} {
4744 if {![info exists treepending]} {
4745 if {$id eq $nullid} {
4746 set cmd [list | git ls-files]
4747 } elseif {$id eq $nullid2} {
4748 set cmd [list | git ls-files --stage -t]
4749 } else {
4750 set cmd [list | git ls-tree -r $id]
4751 }
4752 if {[catch {set gtf [open $cmd r]}]} {
4753 return
4754 }
4755 set treepending $id
4756 set treefilelist($id) {}
4757 set treeidlist($id) {}
4758 fconfigure $gtf -blocking 0
4759 filerun $gtf [list gettreeline $gtf $id]
4760 }
4761 } else {
4762 setfilelist $id
4763 }
4764}
4765
4766proc gettreeline {gtf id} {
4767 global treefilelist treeidlist treepending cmitmode diffids nullid nullid2
4768
4769 set nl 0
4770 while {[incr nl] <= 1000 && [gets $gtf line] >= 0} {
4771 if {$diffids eq $nullid} {
4772 set fname $line
4773 } else {
4774 if {$diffids ne $nullid2 && [lindex $line 1] ne "blob"} continue
4775 set i [string first "\t" $line]
4776 if {$i < 0} continue
4777 set sha1 [lindex $line 2]
4778 set fname [string range $line [expr {$i+1}] end]
4779 if {[string index $fname 0] eq "\""} {
4780 set fname [lindex $fname 0]
4781 }
4782 lappend treeidlist($id) $sha1
4783 }
4784 lappend treefilelist($id) $fname
4785 }
4786 if {![eof $gtf]} {
4787 return [expr {$nl >= 1000? 2: 1}]
4788 }
4789 close $gtf
4790 unset treepending
4791 if {$cmitmode ne "tree"} {
4792 if {![info exists diffmergeid]} {
4793 gettreediffs $diffids
4794 }
4795 } elseif {$id ne $diffids} {
4796 gettree $diffids
4797 } else {
4798 setfilelist $id
4799 }
4800 return 0
4801}
4802
4803proc showfile {f} {
4804 global treefilelist treeidlist diffids nullid nullid2
4805 global ctext commentend
4806
4807 set i [lsearch -exact $treefilelist($diffids) $f]
4808 if {$i < 0} {
4809 puts "oops, $f not in list for id $diffids"
4810 return
4811 }
4812 if {$diffids eq $nullid} {
4813 if {[catch {set bf [open $f r]} err]} {
4814 puts "oops, can't read $f: $err"
4815 return
4816 }
4817 } else {
4818 set blob [lindex $treeidlist($diffids) $i]
4819 if {[catch {set bf [open [concat | git cat-file blob $blob] r]} err]} {
4820 puts "oops, error reading blob $blob: $err"
4821 return
4822 }
4823 }
4824 fconfigure $bf -blocking 0
4825 filerun $bf [list getblobline $bf $diffids]
4826 $ctext config -state normal
4827 clear_ctext $commentend
4828 $ctext insert end "\n"
4829 $ctext insert end "$f\n" filesep
4830 $ctext config -state disabled
4831 $ctext yview $commentend
4832}
4833
4834proc getblobline {bf id} {
4835 global diffids cmitmode ctext
4836
4837 if {$id ne $diffids || $cmitmode ne "tree"} {
4838 catch {close $bf}
4839 return 0
4840 }
4841 $ctext config -state normal
4842 set nl 0
4843 while {[incr nl] <= 1000 && [gets $bf line] >= 0} {
4844 $ctext insert end "$line\n"
4845 }
4846 if {[eof $bf]} {
4847 # delete last newline
4848 $ctext delete "end - 2c" "end - 1c"
4849 close $bf
4850 return 0
4851 }
4852 $ctext config -state disabled
4853 return [expr {$nl >= 1000? 2: 1}]
4854}
4855
4856proc mergediff {id l} {
4857 global diffmergeid diffopts mdifffd
4858 global diffids
4859 global parentlist
4860
4861 set diffmergeid $id
4862 set diffids $id
4863 # this doesn't seem to actually affect anything...
4864 set env(GIT_DIFF_OPTS) $diffopts
4865 set cmd [concat | git diff-tree --no-commit-id --cc $id]
4866 if {[catch {set mdf [open $cmd r]} err]} {
4867 error_popup "Error getting merge diffs: $err"
4868 return
4869 }
4870 fconfigure $mdf -blocking 0
4871 set mdifffd($id) $mdf
4872 set np [llength [lindex $parentlist $l]]
4873 filerun $mdf [list getmergediffline $mdf $id $np]
4874}
4875
4876proc getmergediffline {mdf id np} {
4877 global diffmergeid ctext cflist mergemax
4878 global difffilestart mdifffd
4879
4880 $ctext conf -state normal
4881 set nr 0
4882 while {[incr nr] <= 1000 && [gets $mdf line] >= 0} {
4883 if {![info exists diffmergeid] || $id != $diffmergeid
4884 || $mdf != $mdifffd($id)} {
4885 close $mdf
4886 return 0
4887 }
4888 if {[regexp {^diff --cc (.*)} $line match fname]} {
4889 # start of a new file
4890 $ctext insert end "\n"
4891 set here [$ctext index "end - 1c"]
4892 lappend difffilestart $here
4893 add_flist [list $fname]
4894 set l [expr {(78 - [string length $fname]) / 2}]
4895 set pad [string range "----------------------------------------" 1 $l]
4896 $ctext insert end "$pad $fname $pad\n" filesep
4897 } elseif {[regexp {^@@} $line]} {
4898 $ctext insert end "$line\n" hunksep
4899 } elseif {[regexp {^[0-9a-f]{40}$} $line] || [regexp {^index} $line]} {
4900 # do nothing
4901 } else {
4902 # parse the prefix - one ' ', '-' or '+' for each parent
4903 set spaces {}
4904 set minuses {}
4905 set pluses {}
4906 set isbad 0
4907 for {set j 0} {$j < $np} {incr j} {
4908 set c [string range $line $j $j]
4909 if {$c == " "} {
4910 lappend spaces $j
4911 } elseif {$c == "-"} {
4912 lappend minuses $j
4913 } elseif {$c == "+"} {
4914 lappend pluses $j
4915 } else {
4916 set isbad 1
4917 break
4918 }
4919 }
4920 set tags {}
4921 set num {}
4922 if {!$isbad && $minuses ne {} && $pluses eq {}} {
4923 # line doesn't appear in result, parents in $minuses have the line
4924 set num [lindex $minuses 0]
4925 } elseif {!$isbad && $pluses ne {} && $minuses eq {}} {
4926 # line appears in result, parents in $pluses don't have the line
4927 lappend tags mresult
4928 set num [lindex $spaces 0]
4929 }
4930 if {$num ne {}} {
4931 if {$num >= $mergemax} {
4932 set num "max"
4933 }
4934 lappend tags m$num
4935 }
4936 $ctext insert end "$line\n" $tags
4937 }
4938 }
4939 $ctext conf -state disabled
4940 if {[eof $mdf]} {
4941 close $mdf
4942 return 0
4943 }
4944 return [expr {$nr >= 1000? 2: 1}]
4945}
4946
4947proc startdiff {ids} {
4948 global treediffs diffids treepending diffmergeid nullid nullid2
4949
4950 set diffids $ids
4951 catch {unset diffmergeid}
4952 if {![info exists treediffs($ids)] ||
4953 [lsearch -exact $ids $nullid] >= 0 ||
4954 [lsearch -exact $ids $nullid2] >= 0} {
4955 if {![info exists treepending]} {
4956 gettreediffs $ids
4957 }
4958 } else {
4959 addtocflist $ids
4960 }
4961}
4962
4963proc addtocflist {ids} {
4964 global treediffs cflist
4965 add_flist $treediffs($ids)
4966 getblobdiffs $ids
4967}
4968
4969proc diffcmd {ids flags} {
4970 global nullid nullid2
4971
4972 set i [lsearch -exact $ids $nullid]
4973 set j [lsearch -exact $ids $nullid2]
4974 if {$i >= 0} {
4975 if {[llength $ids] > 1 && $j < 0} {
4976 # comparing working directory with some specific revision
4977 set cmd [concat | git diff-index $flags]
4978 if {$i == 0} {
4979 lappend cmd -R [lindex $ids 1]
4980 } else {
4981 lappend cmd [lindex $ids 0]
4982 }
4983 } else {
4984 # comparing working directory with index
4985 set cmd [concat | git diff-files $flags]
4986 if {$j == 1} {
4987 lappend cmd -R
4988 }
4989 }
4990 } elseif {$j >= 0} {
4991 set cmd [concat | git diff-index --cached $flags]
4992 if {[llength $ids] > 1} {
4993 # comparing index with specific revision
4994 if {$i == 0} {
4995 lappend cmd -R [lindex $ids 1]
4996 } else {
4997 lappend cmd [lindex $ids 0]
4998 }
4999 } else {
5000 # comparing index with HEAD
5001 lappend cmd HEAD
5002 }
5003 } else {
5004 set cmd [concat | git diff-tree -r $flags $ids]
5005 }
5006 return $cmd
5007}
5008
5009proc gettreediffs {ids} {
5010 global treediff treepending
5011
5012 set treepending $ids
5013 set treediff {}
5014 if {[catch {set gdtf [open [diffcmd $ids {--no-commit-id}] r]}]} return
5015 fconfigure $gdtf -blocking 0
5016 filerun $gdtf [list gettreediffline $gdtf $ids]
5017}
5018
5019proc gettreediffline {gdtf ids} {
5020 global treediff treediffs treepending diffids diffmergeid
5021 global cmitmode
5022
5023 set nr 0
5024 while {[incr nr] <= 1000 && [gets $gdtf line] >= 0} {
5025 set i [string first "\t" $line]
5026 if {$i >= 0} {
5027 set file [string range $line [expr {$i+1}] end]
5028 if {[string index $file 0] eq "\""} {
5029 set file [lindex $file 0]
5030 }
5031 lappend treediff $file
5032 }
5033 }
5034 if {![eof $gdtf]} {
5035 return [expr {$nr >= 1000? 2: 1}]
5036 }
5037 close $gdtf
5038 set treediffs($ids) $treediff
5039 unset treepending
5040 if {$cmitmode eq "tree"} {
5041 gettree $diffids
5042 } elseif {$ids != $diffids} {
5043 if {![info exists diffmergeid]} {
5044 gettreediffs $diffids
5045 }
5046 } else {
5047 addtocflist $ids
5048 }
5049 return 0
5050}
5051
5052# empty string or positive integer
5053proc diffcontextvalidate {v} {
5054 return [regexp {^(|[1-9][0-9]*)$} $v]
5055}
5056
5057proc diffcontextchange {n1 n2 op} {
5058 global diffcontextstring diffcontext
5059
5060 if {[string is integer -strict $diffcontextstring]} {
5061 if {$diffcontextstring > 0} {
5062 set diffcontext $diffcontextstring
5063 reselectline
5064 }
5065 }
5066}
5067
5068proc getblobdiffs {ids} {
5069 global diffopts blobdifffd diffids env
5070 global diffinhdr treediffs
5071 global diffcontext
5072
5073 set env(GIT_DIFF_OPTS) $diffopts
5074 if {[catch {set bdf [open [diffcmd $ids "-p -C --no-commit-id -U$diffcontext"] r]} err]} {
5075 puts "error getting diffs: $err"
5076 return
5077 }
5078 set diffinhdr 0
5079 fconfigure $bdf -blocking 0
5080 set blobdifffd($ids) $bdf
5081 filerun $bdf [list getblobdiffline $bdf $diffids]
5082}
5083
5084proc setinlist {var i val} {
5085 global $var
5086
5087 while {[llength [set $var]] < $i} {
5088 lappend $var {}
5089 }
5090 if {[llength [set $var]] == $i} {
5091 lappend $var $val
5092 } else {
5093 lset $var $i $val
5094 }
5095}
5096
5097proc makediffhdr {fname ids} {
5098 global ctext curdiffstart treediffs
5099
5100 set i [lsearch -exact $treediffs($ids) $fname]
5101 if {$i >= 0} {
5102 setinlist difffilestart $i $curdiffstart
5103 }
5104 set l [expr {(78 - [string length $fname]) / 2}]
5105 set pad [string range "----------------------------------------" 1 $l]
5106 $ctext insert $curdiffstart "$pad $fname $pad" filesep
5107}
5108
5109proc getblobdiffline {bdf ids} {
5110 global diffids blobdifffd ctext curdiffstart
5111 global diffnexthead diffnextnote difffilestart
5112 global diffinhdr treediffs
5113
5114 set nr 0
5115 $ctext conf -state normal
5116 while {[incr nr] <= 1000 && [gets $bdf line] >= 0} {
5117 if {$ids != $diffids || $bdf != $blobdifffd($ids)} {
5118 close $bdf
5119 return 0
5120 }
5121 if {![string compare -length 11 "diff --git " $line]} {
5122 # trim off "diff --git "
5123 set line [string range $line 11 end]
5124 set diffinhdr 1
5125 # start of a new file
5126 $ctext insert end "\n"
5127 set curdiffstart [$ctext index "end - 1c"]
5128 $ctext insert end "\n" filesep
5129 # If the name hasn't changed the length will be odd,
5130 # the middle char will be a space, and the two bits either
5131 # side will be a/name and b/name, or "a/name" and "b/name".
5132 # If the name has changed we'll get "rename from" and
5133 # "rename to" or "copy from" and "copy to" lines following this,
5134 # and we'll use them to get the filenames.
5135 # This complexity is necessary because spaces in the filename(s)
5136 # don't get escaped.
5137 set l [string length $line]
5138 set i [expr {$l / 2}]
5139 if {!(($l & 1) && [string index $line $i] eq " " &&
5140 [string range $line 2 [expr {$i - 1}]] eq \
5141 [string range $line [expr {$i + 3}] end])} {
5142 continue
5143 }
5144 # unescape if quoted and chop off the a/ from the front
5145 if {[string index $line 0] eq "\""} {
5146 set fname [string range [lindex $line 0] 2 end]
5147 } else {
5148 set fname [string range $line 2 [expr {$i - 1}]]
5149 }
5150 makediffhdr $fname $ids
5151
5152 } elseif {[regexp {^@@ -([0-9]+)(,[0-9]+)? \+([0-9]+)(,[0-9]+)? @@(.*)} \
5153 $line match f1l f1c f2l f2c rest]} {
5154 $ctext insert end "$line\n" hunksep
5155 set diffinhdr 0
5156
5157 } elseif {$diffinhdr} {
5158 if {![string compare -length 12 "rename from " $line] ||
5159 ![string compare -length 10 "copy from " $line]} {
5160 set fname [string range $line [expr 6 + [string first " from " $line] ] end]
5161 if {[string index $fname 0] eq "\""} {
5162 set fname [lindex $fname 0]
5163 }
5164 set i [lsearch -exact $treediffs($ids) $fname]
5165 if {$i >= 0} {
5166 setinlist difffilestart $i $curdiffstart
5167 }
5168 } elseif {![string compare -length 10 $line "rename to "] ||
5169 ![string compare -length 8 $line "copy to "]} {
5170 set fname [string range $line [expr 4 + [string first " to " $line] ] end]
5171 if {[string index $fname 0] eq "\""} {
5172 set fname [lindex $fname 0]
5173 }
5174 makediffhdr $fname $ids
5175 } elseif {[string compare -length 3 $line "---"] == 0} {
5176 # do nothing
5177 continue
5178 } elseif {[string compare -length 3 $line "+++"] == 0} {
5179 set diffinhdr 0
5180 continue
5181 }
5182 $ctext insert end "$line\n" filesep
5183
5184 } else {
5185 set x [string range $line 0 0]
5186 if {$x == "-" || $x == "+"} {
5187 set tag [expr {$x == "+"}]
5188 $ctext insert end "$line\n" d$tag
5189 } elseif {$x == " "} {
5190 $ctext insert end "$line\n"
5191 } else {
5192 # "\ No newline at end of file",
5193 # or something else we don't recognize
5194 $ctext insert end "$line\n" hunksep
5195 }
5196 }
5197 }
5198 $ctext conf -state disabled
5199 if {[eof $bdf]} {
5200 close $bdf
5201 return 0
5202 }
5203 return [expr {$nr >= 1000? 2: 1}]
5204}
5205
5206proc changediffdisp {} {
5207 global ctext diffelide
5208
5209 $ctext tag conf d0 -elide [lindex $diffelide 0]
5210 $ctext tag conf d1 -elide [lindex $diffelide 1]
5211}
5212
5213proc prevfile {} {
5214 global difffilestart ctext
5215 set prev [lindex $difffilestart 0]
5216 set here [$ctext index @0,0]
5217 foreach loc $difffilestart {
5218 if {[$ctext compare $loc >= $here]} {
5219 $ctext yview $prev
5220 return
5221 }
5222 set prev $loc
5223 }
5224 $ctext yview $prev
5225}
5226
5227proc nextfile {} {
5228 global difffilestart ctext
5229 set here [$ctext index @0,0]
5230 foreach loc $difffilestart {
5231 if {[$ctext compare $loc > $here]} {
5232 $ctext yview $loc
5233 return
5234 }
5235 }
5236}
5237
5238proc clear_ctext {{first 1.0}} {
5239 global ctext smarktop smarkbot
5240
5241 set l [lindex [split $first .] 0]
5242 if {![info exists smarktop] || [$ctext compare $first < $smarktop.0]} {
5243 set smarktop $l
5244 }
5245 if {![info exists smarkbot] || [$ctext compare $first < $smarkbot.0]} {
5246 set smarkbot $l
5247 }
5248 $ctext delete $first end
5249}
5250
5251proc incrsearch {name ix op} {
5252 global ctext searchstring searchdirn
5253
5254 $ctext tag remove found 1.0 end
5255 if {[catch {$ctext index anchor}]} {
5256 # no anchor set, use start of selection, or of visible area
5257 set sel [$ctext tag ranges sel]
5258 if {$sel ne {}} {
5259 $ctext mark set anchor [lindex $sel 0]
5260 } elseif {$searchdirn eq "-forwards"} {
5261 $ctext mark set anchor @0,0
5262 } else {
5263 $ctext mark set anchor @0,[winfo height $ctext]
5264 }
5265 }
5266 if {$searchstring ne {}} {
5267 set here [$ctext search $searchdirn -- $searchstring anchor]
5268 if {$here ne {}} {
5269 $ctext see $here
5270 }
5271 searchmarkvisible 1
5272 }
5273}
5274
5275proc dosearch {} {
5276 global sstring ctext searchstring searchdirn
5277
5278 focus $sstring
5279 $sstring icursor end
5280 set searchdirn -forwards
5281 if {$searchstring ne {}} {
5282 set sel [$ctext tag ranges sel]
5283 if {$sel ne {}} {
5284 set start "[lindex $sel 0] + 1c"
5285 } elseif {[catch {set start [$ctext index anchor]}]} {
5286 set start "@0,0"
5287 }
5288 set match [$ctext search -count mlen -- $searchstring $start]
5289 $ctext tag remove sel 1.0 end
5290 if {$match eq {}} {
5291 bell
5292 return
5293 }
5294 $ctext see $match
5295 set mend "$match + $mlen c"
5296 $ctext tag add sel $match $mend
5297 $ctext mark unset anchor
5298 }
5299}
5300
5301proc dosearchback {} {
5302 global sstring ctext searchstring searchdirn
5303
5304 focus $sstring
5305 $sstring icursor end
5306 set searchdirn -backwards
5307 if {$searchstring ne {}} {
5308 set sel [$ctext tag ranges sel]
5309 if {$sel ne {}} {
5310 set start [lindex $sel 0]
5311 } elseif {[catch {set start [$ctext index anchor]}]} {
5312 set start @0,[winfo height $ctext]
5313 }
5314 set match [$ctext search -backwards -count ml -- $searchstring $start]
5315 $ctext tag remove sel 1.0 end
5316 if {$match eq {}} {
5317 bell
5318 return
5319 }
5320 $ctext see $match
5321 set mend "$match + $ml c"
5322 $ctext tag add sel $match $mend
5323 $ctext mark unset anchor
5324 }
5325}
5326
5327proc searchmark {first last} {
5328 global ctext searchstring
5329
5330 set mend $first.0
5331 while {1} {
5332 set match [$ctext search -count mlen -- $searchstring $mend $last.end]
5333 if {$match eq {}} break
5334 set mend "$match + $mlen c"
5335 $ctext tag add found $match $mend
5336 }
5337}
5338
5339proc searchmarkvisible {doall} {
5340 global ctext smarktop smarkbot
5341
5342 set topline [lindex [split [$ctext index @0,0] .] 0]
5343 set botline [lindex [split [$ctext index @0,[winfo height $ctext]] .] 0]
5344 if {$doall || $botline < $smarktop || $topline > $smarkbot} {
5345 # no overlap with previous
5346 searchmark $topline $botline
5347 set smarktop $topline
5348 set smarkbot $botline
5349 } else {
5350 if {$topline < $smarktop} {
5351 searchmark $topline [expr {$smarktop-1}]
5352 set smarktop $topline
5353 }
5354 if {$botline > $smarkbot} {
5355 searchmark [expr {$smarkbot+1}] $botline
5356 set smarkbot $botline
5357 }
5358 }
5359}
5360
5361proc scrolltext {f0 f1} {
5362 global searchstring
5363
5364 .bleft.sb set $f0 $f1
5365 if {$searchstring ne {}} {
5366 searchmarkvisible 0
5367 }
5368}
5369
5370proc setcoords {} {
5371 global linespc charspc canvx0 canvy0 mainfont
5372 global xspc1 xspc2 lthickness
5373
5374 set linespc [font metrics $mainfont -linespace]
5375 set charspc [font measure $mainfont "m"]
5376 set canvy0 [expr {int(3 + 0.5 * $linespc)}]
5377 set canvx0 [expr {int(3 + 0.5 * $linespc)}]
5378 set lthickness [expr {int($linespc / 9) + 1}]
5379 set xspc1(0) $linespc
5380 set xspc2 $linespc
5381}
5382
5383proc redisplay {} {
5384 global canv
5385 global selectedline
5386
5387 set ymax [lindex [$canv cget -scrollregion] 3]
5388 if {$ymax eq {} || $ymax == 0} return
5389 set span [$canv yview]
5390 clear_display
5391 setcanvscroll
5392 allcanvs yview moveto [lindex $span 0]
5393 drawvisible
5394 if {[info exists selectedline]} {
5395 selectline $selectedline 0
5396 allcanvs yview moveto [lindex $span 0]
5397 }
5398}
5399
5400proc incrfont {inc} {
5401 global mainfont textfont ctext canv phase cflist showrefstop
5402 global charspc tabstop
5403 global stopped entries
5404 unmarkmatches
5405 set mainfont [lreplace $mainfont 1 1 [expr {[lindex $mainfont 1] + $inc}]]
5406 set textfont [lreplace $textfont 1 1 [expr {[lindex $textfont 1] + $inc}]]
5407 setcoords
5408 $ctext conf -font $textfont -tabs "[expr {$tabstop * $charspc}]"
5409 $cflist conf -font $textfont
5410 $ctext tag conf filesep -font [concat $textfont bold]
5411 foreach e $entries {
5412 $e conf -font $mainfont
5413 }
5414 if {$phase eq "getcommits"} {
5415 $canv itemconf textitems -font $mainfont
5416 }
5417 if {[info exists showrefstop] && [winfo exists $showrefstop]} {
5418 $showrefstop.list conf -font $mainfont
5419 }
5420 redisplay
5421}
5422
5423proc clearsha1 {} {
5424 global sha1entry sha1string
5425 if {[string length $sha1string] == 40} {
5426 $sha1entry delete 0 end
5427 }
5428}
5429
5430proc sha1change {n1 n2 op} {
5431 global sha1string currentid sha1but
5432 if {$sha1string == {}
5433 || ([info exists currentid] && $sha1string == $currentid)} {
5434 set state disabled
5435 } else {
5436 set state normal
5437 }
5438 if {[$sha1but cget -state] == $state} return
5439 if {$state == "normal"} {
5440 $sha1but conf -state normal -relief raised -text "Goto: "
5441 } else {
5442 $sha1but conf -state disabled -relief flat -text "SHA1 ID: "
5443 }
5444}
5445
5446proc gotocommit {} {
5447 global sha1string currentid commitrow tagids headids
5448 global displayorder numcommits curview
5449
5450 if {$sha1string == {}
5451 || ([info exists currentid] && $sha1string == $currentid)} return
5452 if {[info exists tagids($sha1string)]} {
5453 set id $tagids($sha1string)
5454 } elseif {[info exists headids($sha1string)]} {
5455 set id $headids($sha1string)
5456 } else {
5457 set id [string tolower $sha1string]
5458 if {[regexp {^[0-9a-f]{4,39}$} $id]} {
5459 set matches {}
5460 foreach i $displayorder {
5461 if {[string match $id* $i]} {
5462 lappend matches $i
5463 }
5464 }
5465 if {$matches ne {}} {
5466 if {[llength $matches] > 1} {
5467 error_popup "Short SHA1 id $id is ambiguous"
5468 return
5469 }
5470 set id [lindex $matches 0]
5471 }
5472 }
5473 }
5474 if {[info exists commitrow($curview,$id)]} {
5475 selectline $commitrow($curview,$id) 1
5476 return
5477 }
5478 if {[regexp {^[0-9a-fA-F]{4,}$} $sha1string]} {
5479 set type "SHA1 id"
5480 } else {
5481 set type "Tag/Head"
5482 }
5483 error_popup "$type $sha1string is not known"
5484}
5485
5486proc lineenter {x y id} {
5487 global hoverx hovery hoverid hovertimer
5488 global commitinfo canv
5489
5490 if {![info exists commitinfo($id)] && ![getcommit $id]} return
5491 set hoverx $x
5492 set hovery $y
5493 set hoverid $id
5494 if {[info exists hovertimer]} {
5495 after cancel $hovertimer
5496 }
5497 set hovertimer [after 500 linehover]
5498 $canv delete hover
5499}
5500
5501proc linemotion {x y id} {
5502 global hoverx hovery hoverid hovertimer
5503
5504 if {[info exists hoverid] && $id == $hoverid} {
5505 set hoverx $x
5506 set hovery $y
5507 if {[info exists hovertimer]} {
5508 after cancel $hovertimer
5509 }
5510 set hovertimer [after 500 linehover]
5511 }
5512}
5513
5514proc lineleave {id} {
5515 global hoverid hovertimer canv
5516
5517 if {[info exists hoverid] && $id == $hoverid} {
5518 $canv delete hover
5519 if {[info exists hovertimer]} {
5520 after cancel $hovertimer
5521 unset hovertimer
5522 }
5523 unset hoverid
5524 }
5525}
5526
5527proc linehover {} {
5528 global hoverx hovery hoverid hovertimer
5529 global canv linespc lthickness
5530 global commitinfo mainfont
5531
5532 set text [lindex $commitinfo($hoverid) 0]
5533 set ymax [lindex [$canv cget -scrollregion] 3]
5534 if {$ymax == {}} return
5535 set yfrac [lindex [$canv yview] 0]
5536 set x [expr {$hoverx + 2 * $linespc}]
5537 set y [expr {$hovery + $yfrac * $ymax - $linespc / 2}]
5538 set x0 [expr {$x - 2 * $lthickness}]
5539 set y0 [expr {$y - 2 * $lthickness}]
5540 set x1 [expr {$x + [font measure $mainfont $text] + 2 * $lthickness}]
5541 set y1 [expr {$y + $linespc + 2 * $lthickness}]
5542 set t [$canv create rectangle $x0 $y0 $x1 $y1 \
5543 -fill \#ffff80 -outline black -width 1 -tags hover]
5544 $canv raise $t
5545 set t [$canv create text $x $y -anchor nw -text $text -tags hover \
5546 -font $mainfont]
5547 $canv raise $t
5548}
5549
5550proc clickisonarrow {id y} {
5551 global lthickness
5552
5553 set ranges [rowranges $id]
5554 set thresh [expr {2 * $lthickness + 6}]
5555 set n [expr {[llength $ranges] - 1}]
5556 for {set i 1} {$i < $n} {incr i} {
5557 set row [lindex $ranges $i]
5558 if {abs([yc $row] - $y) < $thresh} {
5559 return $i
5560 }
5561 }
5562 return {}
5563}
5564
5565proc arrowjump {id n y} {
5566 global canv
5567
5568 # 1 <-> 2, 3 <-> 4, etc...
5569 set n [expr {(($n - 1) ^ 1) + 1}]
5570 set row [lindex [rowranges $id] $n]
5571 set yt [yc $row]
5572 set ymax [lindex [$canv cget -scrollregion] 3]
5573 if {$ymax eq {} || $ymax <= 0} return
5574 set view [$canv yview]
5575 set yspan [expr {[lindex $view 1] - [lindex $view 0]}]
5576 set yfrac [expr {$yt / $ymax - $yspan / 2}]
5577 if {$yfrac < 0} {
5578 set yfrac 0
5579 }
5580 allcanvs yview moveto $yfrac
5581}
5582
5583proc lineclick {x y id isnew} {
5584 global ctext commitinfo children canv thickerline curview
5585
5586 if {![info exists commitinfo($id)] && ![getcommit $id]} return
5587 unmarkmatches
5588 unselectline
5589 normalline
5590 $canv delete hover
5591 # draw this line thicker than normal
5592 set thickerline $id
5593 drawlines $id
5594 if {$isnew} {
5595 set ymax [lindex [$canv cget -scrollregion] 3]
5596 if {$ymax eq {}} return
5597 set yfrac [lindex [$canv yview] 0]
5598 set y [expr {$y + $yfrac * $ymax}]
5599 }
5600 set dirn [clickisonarrow $id $y]
5601 if {$dirn ne {}} {
5602 arrowjump $id $dirn $y
5603 return
5604 }
5605
5606 if {$isnew} {
5607 addtohistory [list lineclick $x $y $id 0]
5608 }
5609 # fill the details pane with info about this line
5610 $ctext conf -state normal
5611 clear_ctext
5612 $ctext tag conf link -foreground blue -underline 1
5613 $ctext tag bind link <Enter> { %W configure -cursor hand2 }
5614 $ctext tag bind link <Leave> { %W configure -cursor $curtextcursor }
5615 $ctext insert end "Parent:\t"
5616 $ctext insert end $id [list link link0]
5617 $ctext tag bind link0 <1> [list selbyid $id]
5618 set info $commitinfo($id)
5619 $ctext insert end "\n\t[lindex $info 0]\n"
5620 $ctext insert end "\tAuthor:\t[lindex $info 1]\n"
5621 set date [formatdate [lindex $info 2]]
5622 $ctext insert end "\tDate:\t$date\n"
5623 set kids $children($curview,$id)
5624 if {$kids ne {}} {
5625 $ctext insert end "\nChildren:"
5626 set i 0
5627 foreach child $kids {
5628 incr i
5629 if {![info exists commitinfo($child)] && ![getcommit $child]} continue
5630 set info $commitinfo($child)
5631 $ctext insert end "\n\t"
5632 $ctext insert end $child [list link link$i]
5633 $ctext tag bind link$i <1> [list selbyid $child]
5634 $ctext insert end "\n\t[lindex $info 0]"
5635 $ctext insert end "\n\tAuthor:\t[lindex $info 1]"
5636 set date [formatdate [lindex $info 2]]
5637 $ctext insert end "\n\tDate:\t$date\n"
5638 }
5639 }
5640 $ctext conf -state disabled
5641 init_flist {}
5642}
5643
5644proc normalline {} {
5645 global thickerline
5646 if {[info exists thickerline]} {
5647 set id $thickerline
5648 unset thickerline
5649 drawlines $id
5650 }
5651}
5652
5653proc selbyid {id} {
5654 global commitrow curview
5655 if {[info exists commitrow($curview,$id)]} {
5656 selectline $commitrow($curview,$id) 1
5657 }
5658}
5659
5660proc mstime {} {
5661 global startmstime
5662 if {![info exists startmstime]} {
5663 set startmstime [clock clicks -milliseconds]
5664 }
5665 return [format "%.3f" [expr {([clock click -milliseconds] - $startmstime) / 1000.0}]]
5666}
5667
5668proc rowmenu {x y id} {
5669 global rowctxmenu commitrow selectedline rowmenuid curview
5670 global nullid nullid2 fakerowmenu mainhead
5671
5672 set rowmenuid $id
5673 if {![info exists selectedline]
5674 || $commitrow($curview,$id) eq $selectedline} {
5675 set state disabled
5676 } else {
5677 set state normal
5678 }
5679 if {$id ne $nullid && $id ne $nullid2} {
5680 set menu $rowctxmenu
5681 $menu entryconfigure 7 -label "Reset $mainhead branch to here"
5682 } else {
5683 set menu $fakerowmenu
5684 }
5685 $menu entryconfigure "Diff this*" -state $state
5686 $menu entryconfigure "Diff selected*" -state $state
5687 $menu entryconfigure "Make patch" -state $state
5688 tk_popup $menu $x $y
5689}
5690
5691proc diffvssel {dirn} {
5692 global rowmenuid selectedline displayorder
5693
5694 if {![info exists selectedline]} return
5695 if {$dirn} {
5696 set oldid [lindex $displayorder $selectedline]
5697 set newid $rowmenuid
5698 } else {
5699 set oldid $rowmenuid
5700 set newid [lindex $displayorder $selectedline]
5701 }
5702 addtohistory [list doseldiff $oldid $newid]
5703 doseldiff $oldid $newid
5704}
5705
5706proc doseldiff {oldid newid} {
5707 global ctext
5708 global commitinfo
5709
5710 $ctext conf -state normal
5711 clear_ctext
5712 init_flist "Top"
5713 $ctext insert end "From "
5714 $ctext tag conf link -foreground blue -underline 1
5715 $ctext tag bind link <Enter> { %W configure -cursor hand2 }
5716 $ctext tag bind link <Leave> { %W configure -cursor $curtextcursor }
5717 $ctext tag bind link0 <1> [list selbyid $oldid]
5718 $ctext insert end $oldid [list link link0]
5719 $ctext insert end "\n "
5720 $ctext insert end [lindex $commitinfo($oldid) 0]
5721 $ctext insert end "\n\nTo "
5722 $ctext tag bind link1 <1> [list selbyid $newid]
5723 $ctext insert end $newid [list link link1]
5724 $ctext insert end "\n "
5725 $ctext insert end [lindex $commitinfo($newid) 0]
5726 $ctext insert end "\n"
5727 $ctext conf -state disabled
5728 $ctext tag remove found 1.0 end
5729 startdiff [list $oldid $newid]
5730}
5731
5732proc mkpatch {} {
5733 global rowmenuid currentid commitinfo patchtop patchnum
5734
5735 if {![info exists currentid]} return
5736 set oldid $currentid
5737 set oldhead [lindex $commitinfo($oldid) 0]
5738 set newid $rowmenuid
5739 set newhead [lindex $commitinfo($newid) 0]
5740 set top .patch
5741 set patchtop $top
5742 catch {destroy $top}
5743 toplevel $top
5744 label $top.title -text "Generate patch"
5745 grid $top.title - -pady 10
5746 label $top.from -text "From:"
5747 entry $top.fromsha1 -width 40 -relief flat
5748 $top.fromsha1 insert 0 $oldid
5749 $top.fromsha1 conf -state readonly
5750 grid $top.from $top.fromsha1 -sticky w
5751 entry $top.fromhead -width 60 -relief flat
5752 $top.fromhead insert 0 $oldhead
5753 $top.fromhead conf -state readonly
5754 grid x $top.fromhead -sticky w
5755 label $top.to -text "To:"
5756 entry $top.tosha1 -width 40 -relief flat
5757 $top.tosha1 insert 0 $newid
5758 $top.tosha1 conf -state readonly
5759 grid $top.to $top.tosha1 -sticky w
5760 entry $top.tohead -width 60 -relief flat
5761 $top.tohead insert 0 $newhead
5762 $top.tohead conf -state readonly
5763 grid x $top.tohead -sticky w
5764 button $top.rev -text "Reverse" -command mkpatchrev -padx 5
5765 grid $top.rev x -pady 10
5766 label $top.flab -text "Output file:"
5767 entry $top.fname -width 60
5768 $top.fname insert 0 [file normalize "patch$patchnum.patch"]
5769 incr patchnum
5770 grid $top.flab $top.fname -sticky w
5771 frame $top.buts
5772 button $top.buts.gen -text "Generate" -command mkpatchgo
5773 button $top.buts.can -text "Cancel" -command mkpatchcan
5774 grid $top.buts.gen $top.buts.can
5775 grid columnconfigure $top.buts 0 -weight 1 -uniform a
5776 grid columnconfigure $top.buts 1 -weight 1 -uniform a
5777 grid $top.buts - -pady 10 -sticky ew
5778 focus $top.fname
5779}
5780
5781proc mkpatchrev {} {
5782 global patchtop
5783
5784 set oldid [$patchtop.fromsha1 get]
5785 set oldhead [$patchtop.fromhead get]
5786 set newid [$patchtop.tosha1 get]
5787 set newhead [$patchtop.tohead get]
5788 foreach e [list fromsha1 fromhead tosha1 tohead] \
5789 v [list $newid $newhead $oldid $oldhead] {
5790 $patchtop.$e conf -state normal
5791 $patchtop.$e delete 0 end
5792 $patchtop.$e insert 0 $v
5793 $patchtop.$e conf -state readonly
5794 }
5795}
5796
5797proc mkpatchgo {} {
5798 global patchtop nullid nullid2
5799
5800 set oldid [$patchtop.fromsha1 get]
5801 set newid [$patchtop.tosha1 get]
5802 set fname [$patchtop.fname get]
5803 set cmd [diffcmd [list $oldid $newid] -p]
5804 lappend cmd >$fname &
5805 if {[catch {eval exec $cmd} err]} {
5806 error_popup "Error creating patch: $err"
5807 }
5808 catch {destroy $patchtop}
5809 unset patchtop
5810}
5811
5812proc mkpatchcan {} {
5813 global patchtop
5814
5815 catch {destroy $patchtop}
5816 unset patchtop
5817}
5818
5819proc mktag {} {
5820 global rowmenuid mktagtop commitinfo
5821
5822 set top .maketag
5823 set mktagtop $top
5824 catch {destroy $top}
5825 toplevel $top
5826 label $top.title -text "Create tag"
5827 grid $top.title - -pady 10
5828 label $top.id -text "ID:"
5829 entry $top.sha1 -width 40 -relief flat
5830 $top.sha1 insert 0 $rowmenuid
5831 $top.sha1 conf -state readonly
5832 grid $top.id $top.sha1 -sticky w
5833 entry $top.head -width 60 -relief flat
5834 $top.head insert 0 [lindex $commitinfo($rowmenuid) 0]
5835 $top.head conf -state readonly
5836 grid x $top.head -sticky w
5837 label $top.tlab -text "Tag name:"
5838 entry $top.tag -width 60
5839 grid $top.tlab $top.tag -sticky w
5840 frame $top.buts
5841 button $top.buts.gen -text "Create" -command mktaggo
5842 button $top.buts.can -text "Cancel" -command mktagcan
5843 grid $top.buts.gen $top.buts.can
5844 grid columnconfigure $top.buts 0 -weight 1 -uniform a
5845 grid columnconfigure $top.buts 1 -weight 1 -uniform a
5846 grid $top.buts - -pady 10 -sticky ew
5847 focus $top.tag
5848}
5849
5850proc domktag {} {
5851 global mktagtop env tagids idtags
5852
5853 set id [$mktagtop.sha1 get]
5854 set tag [$mktagtop.tag get]
5855 if {$tag == {}} {
5856 error_popup "No tag name specified"
5857 return
5858 }
5859 if {[info exists tagids($tag)]} {
5860 error_popup "Tag \"$tag\" already exists"
5861 return
5862 }
5863 if {[catch {
5864 set dir [gitdir]
5865 set fname [file join $dir "refs/tags" $tag]
5866 set f [open $fname w]
5867 puts $f $id
5868 close $f
5869 } err]} {
5870 error_popup "Error creating tag: $err"
5871 return
5872 }
5873
5874 set tagids($tag) $id
5875 lappend idtags($id) $tag
5876 redrawtags $id
5877 addedtag $id
5878 dispneartags 0
5879 run refill_reflist
5880}
5881
5882proc redrawtags {id} {
5883 global canv linehtag commitrow idpos selectedline curview
5884 global mainfont canvxmax iddrawn
5885
5886 if {![info exists commitrow($curview,$id)]} return
5887 if {![info exists iddrawn($id)]} return
5888 drawcommits $commitrow($curview,$id)
5889 $canv delete tag.$id
5890 set xt [eval drawtags $id $idpos($id)]
5891 $canv coords $linehtag($commitrow($curview,$id)) $xt [lindex $idpos($id) 2]
5892 set text [$canv itemcget $linehtag($commitrow($curview,$id)) -text]
5893 set xr [expr {$xt + [font measure $mainfont $text]}]
5894 if {$xr > $canvxmax} {
5895 set canvxmax $xr
5896 setcanvscroll
5897 }
5898 if {[info exists selectedline]
5899 && $selectedline == $commitrow($curview,$id)} {
5900 selectline $selectedline 0
5901 }
5902}
5903
5904proc mktagcan {} {
5905 global mktagtop
5906
5907 catch {destroy $mktagtop}
5908 unset mktagtop
5909}
5910
5911proc mktaggo {} {
5912 domktag
5913 mktagcan
5914}
5915
5916proc writecommit {} {
5917 global rowmenuid wrcomtop commitinfo wrcomcmd
5918
5919 set top .writecommit
5920 set wrcomtop $top
5921 catch {destroy $top}
5922 toplevel $top
5923 label $top.title -text "Write commit to file"
5924 grid $top.title - -pady 10
5925 label $top.id -text "ID:"
5926 entry $top.sha1 -width 40 -relief flat
5927 $top.sha1 insert 0 $rowmenuid
5928 $top.sha1 conf -state readonly
5929 grid $top.id $top.sha1 -sticky w
5930 entry $top.head -width 60 -relief flat
5931 $top.head insert 0 [lindex $commitinfo($rowmenuid) 0]
5932 $top.head conf -state readonly
5933 grid x $top.head -sticky w
5934 label $top.clab -text "Command:"
5935 entry $top.cmd -width 60 -textvariable wrcomcmd
5936 grid $top.clab $top.cmd -sticky w -pady 10
5937 label $top.flab -text "Output file:"
5938 entry $top.fname -width 60
5939 $top.fname insert 0 [file normalize "commit-[string range $rowmenuid 0 6]"]
5940 grid $top.flab $top.fname -sticky w
5941 frame $top.buts
5942 button $top.buts.gen -text "Write" -command wrcomgo
5943 button $top.buts.can -text "Cancel" -command wrcomcan
5944 grid $top.buts.gen $top.buts.can
5945 grid columnconfigure $top.buts 0 -weight 1 -uniform a
5946 grid columnconfigure $top.buts 1 -weight 1 -uniform a
5947 grid $top.buts - -pady 10 -sticky ew
5948 focus $top.fname
5949}
5950
5951proc wrcomgo {} {
5952 global wrcomtop
5953
5954 set id [$wrcomtop.sha1 get]
5955 set cmd "echo $id | [$wrcomtop.cmd get]"
5956 set fname [$wrcomtop.fname get]
5957 if {[catch {exec sh -c $cmd >$fname &} err]} {
5958 error_popup "Error writing commit: $err"
5959 }
5960 catch {destroy $wrcomtop}
5961 unset wrcomtop
5962}
5963
5964proc wrcomcan {} {
5965 global wrcomtop
5966
5967 catch {destroy $wrcomtop}
5968 unset wrcomtop
5969}
5970
5971proc mkbranch {} {
5972 global rowmenuid mkbrtop
5973
5974 set top .makebranch
5975 catch {destroy $top}
5976 toplevel $top
5977 label $top.title -text "Create new branch"
5978 grid $top.title - -pady 10
5979 label $top.id -text "ID:"
5980 entry $top.sha1 -width 40 -relief flat
5981 $top.sha1 insert 0 $rowmenuid
5982 $top.sha1 conf -state readonly
5983 grid $top.id $top.sha1 -sticky w
5984 label $top.nlab -text "Name:"
5985 entry $top.name -width 40
5986 grid $top.nlab $top.name -sticky w
5987 frame $top.buts
5988 button $top.buts.go -text "Create" -command [list mkbrgo $top]
5989 button $top.buts.can -text "Cancel" -command "catch {destroy $top}"
5990 grid $top.buts.go $top.buts.can
5991 grid columnconfigure $top.buts 0 -weight 1 -uniform a
5992 grid columnconfigure $top.buts 1 -weight 1 -uniform a
5993 grid $top.buts - -pady 10 -sticky ew
5994 focus $top.name
5995}
5996
5997proc mkbrgo {top} {
5998 global headids idheads
5999
6000 set name [$top.name get]
6001 set id [$top.sha1 get]
6002 if {$name eq {}} {
6003 error_popup "Please specify a name for the new branch"
6004 return
6005 }
6006 catch {destroy $top}
6007 nowbusy newbranch
6008 update
6009 if {[catch {
6010 exec git branch $name $id
6011 } err]} {
6012 notbusy newbranch
6013 error_popup $err
6014 } else {
6015 set headids($name) $id
6016 lappend idheads($id) $name
6017 addedhead $id $name
6018 notbusy newbranch
6019 redrawtags $id
6020 dispneartags 0
6021 run refill_reflist
6022 }
6023}
6024
6025proc cherrypick {} {
6026 global rowmenuid curview commitrow
6027 global mainhead
6028
6029 set oldhead [exec git rev-parse HEAD]
6030 set dheads [descheads $rowmenuid]
6031 if {$dheads ne {} && [lsearch -exact $dheads $oldhead] >= 0} {
6032 set ok [confirm_popup "Commit [string range $rowmenuid 0 7] is already\
6033 included in branch $mainhead -- really re-apply it?"]
6034 if {!$ok} return
6035 }
6036 nowbusy cherrypick
6037 update
6038 # Unfortunately git-cherry-pick writes stuff to stderr even when
6039 # no error occurs, and exec takes that as an indication of error...
6040 if {[catch {exec sh -c "git cherry-pick -r $rowmenuid 2>&1"} err]} {
6041 notbusy cherrypick
6042 error_popup $err
6043 return
6044 }
6045 set newhead [exec git rev-parse HEAD]
6046 if {$newhead eq $oldhead} {
6047 notbusy cherrypick
6048 error_popup "No changes committed"
6049 return
6050 }
6051 addnewchild $newhead $oldhead
6052 if {[info exists commitrow($curview,$oldhead)]} {
6053 insertrow $commitrow($curview,$oldhead) $newhead
6054 if {$mainhead ne {}} {
6055 movehead $newhead $mainhead
6056 movedhead $newhead $mainhead
6057 }
6058 redrawtags $oldhead
6059 redrawtags $newhead
6060 }
6061 notbusy cherrypick
6062}
6063
6064proc resethead {} {
6065 global mainheadid mainhead rowmenuid confirm_ok resettype
6066 global showlocalchanges
6067
6068 set confirm_ok 0
6069 set w ".confirmreset"
6070 toplevel $w
6071 wm transient $w .
6072 wm title $w "Confirm reset"
6073 message $w.m -text \
6074 "Reset branch $mainhead to [string range $rowmenuid 0 7]?" \
6075 -justify center -aspect 1000
6076 pack $w.m -side top -fill x -padx 20 -pady 20
6077 frame $w.f -relief sunken -border 2
6078 message $w.f.rt -text "Reset type:" -aspect 1000
6079 grid $w.f.rt -sticky w
6080 set resettype mixed
6081 radiobutton $w.f.soft -value soft -variable resettype -justify left \
6082 -text "Soft: Leave working tree and index untouched"
6083 grid $w.f.soft -sticky w
6084 radiobutton $w.f.mixed -value mixed -variable resettype -justify left \
6085 -text "Mixed: Leave working tree untouched, reset index"
6086 grid $w.f.mixed -sticky w
6087 radiobutton $w.f.hard -value hard -variable resettype -justify left \
6088 -text "Hard: Reset working tree and index\n(discard ALL local changes)"
6089 grid $w.f.hard -sticky w
6090 pack $w.f -side top -fill x
6091 button $w.ok -text OK -command "set confirm_ok 1; destroy $w"
6092 pack $w.ok -side left -fill x -padx 20 -pady 20
6093 button $w.cancel -text Cancel -command "destroy $w"
6094 pack $w.cancel -side right -fill x -padx 20 -pady 20
6095 bind $w <Visibility> "grab $w; focus $w"
6096 tkwait window $w
6097 if {!$confirm_ok} return
6098 if {[catch {set fd [open \
6099 [list | sh -c "git reset --$resettype $rowmenuid 2>&1"] r]} err]} {
6100 error_popup $err
6101 } else {
6102 dohidelocalchanges
6103 set w ".resetprogress"
6104 filerun $fd [list readresetstat $fd $w]
6105 toplevel $w
6106 wm transient $w
6107 wm title $w "Reset progress"
6108 message $w.m -text "Reset in progress, please wait..." \
6109 -justify center -aspect 1000
6110 pack $w.m -side top -fill x -padx 20 -pady 5
6111 canvas $w.c -width 150 -height 20 -bg white
6112 $w.c create rect 0 0 0 20 -fill green -tags rect
6113 pack $w.c -side top -fill x -padx 20 -pady 5 -expand 1
6114 nowbusy reset
6115 }
6116}
6117
6118proc readresetstat {fd w} {
6119 global mainhead mainheadid showlocalchanges
6120
6121 if {[gets $fd line] >= 0} {
6122 if {[regexp {([0-9]+)% \(([0-9]+)/([0-9]+)\)} $line match p m n]} {
6123 set x [expr {($m * 150) / $n}]
6124 $w.c coords rect 0 0 $x 20
6125 }
6126 return 1
6127 }
6128 destroy $w
6129 notbusy reset
6130 if {[catch {close $fd} err]} {
6131 error_popup $err
6132 }
6133 set oldhead $mainheadid
6134 set newhead [exec git rev-parse HEAD]
6135 if {$newhead ne $oldhead} {
6136 movehead $newhead $mainhead
6137 movedhead $newhead $mainhead
6138 set mainheadid $newhead
6139 redrawtags $oldhead
6140 redrawtags $newhead
6141 }
6142 if {$showlocalchanges} {
6143 doshowlocalchanges
6144 }
6145 return 0
6146}
6147
6148# context menu for a head
6149proc headmenu {x y id head} {
6150 global headmenuid headmenuhead headctxmenu mainhead
6151
6152 set headmenuid $id
6153 set headmenuhead $head
6154 set state normal
6155 if {$head eq $mainhead} {
6156 set state disabled
6157 }
6158 $headctxmenu entryconfigure 0 -state $state
6159 $headctxmenu entryconfigure 1 -state $state
6160 tk_popup $headctxmenu $x $y
6161}
6162
6163proc cobranch {} {
6164 global headmenuid headmenuhead mainhead headids
6165 global showlocalchanges mainheadid
6166
6167 # check the tree is clean first??
6168 set oldmainhead $mainhead
6169 nowbusy checkout
6170 update
6171 dohidelocalchanges
6172 if {[catch {
6173 exec git checkout -q $headmenuhead
6174 } err]} {
6175 notbusy checkout
6176 error_popup $err
6177 } else {
6178 notbusy checkout
6179 set mainhead $headmenuhead
6180 set mainheadid $headmenuid
6181 if {[info exists headids($oldmainhead)]} {
6182 redrawtags $headids($oldmainhead)
6183 }
6184 redrawtags $headmenuid
6185 }
6186 if {$showlocalchanges} {
6187 dodiffindex
6188 }
6189}
6190
6191proc rmbranch {} {
6192 global headmenuid headmenuhead mainhead
6193 global idheads
6194
6195 set head $headmenuhead
6196 set id $headmenuid
6197 # this check shouldn't be needed any more...
6198 if {$head eq $mainhead} {
6199 error_popup "Cannot delete the currently checked-out branch"
6200 return
6201 }
6202 set dheads [descheads $id]
6203 if {[llength $dheads] == 1 && $idheads($dheads) eq $head} {
6204 # the stuff on this branch isn't on any other branch
6205 if {![confirm_popup "The commits on branch $head aren't on any other\
6206 branch.\nReally delete branch $head?"]} return
6207 }
6208 nowbusy rmbranch
6209 update
6210 if {[catch {exec git branch -D $head} err]} {
6211 notbusy rmbranch
6212 error_popup $err
6213 return
6214 }
6215 removehead $id $head
6216 removedhead $id $head
6217 redrawtags $id
6218 notbusy rmbranch
6219 dispneartags 0
6220 run refill_reflist
6221}
6222
6223# Display a list of tags and heads
6224proc showrefs {} {
6225 global showrefstop bgcolor fgcolor selectbgcolor mainfont
6226 global bglist fglist uifont reflistfilter reflist maincursor
6227
6228 set top .showrefs
6229 set showrefstop $top
6230 if {[winfo exists $top]} {
6231 raise $top
6232 refill_reflist
6233 return
6234 }
6235 toplevel $top
6236 wm title $top "Tags and heads: [file tail [pwd]]"
6237 text $top.list -background $bgcolor -foreground $fgcolor \
6238 -selectbackground $selectbgcolor -font $mainfont \
6239 -xscrollcommand "$top.xsb set" -yscrollcommand "$top.ysb set" \
6240 -width 30 -height 20 -cursor $maincursor \
6241 -spacing1 1 -spacing3 1 -state disabled
6242 $top.list tag configure highlight -background $selectbgcolor
6243 lappend bglist $top.list
6244 lappend fglist $top.list
6245 scrollbar $top.ysb -command "$top.list yview" -orient vertical
6246 scrollbar $top.xsb -command "$top.list xview" -orient horizontal
6247 grid $top.list $top.ysb -sticky nsew
6248 grid $top.xsb x -sticky ew
6249 frame $top.f
6250 label $top.f.l -text "Filter: " -font $uifont
6251 entry $top.f.e -width 20 -textvariable reflistfilter -font $uifont
6252 set reflistfilter "*"
6253 trace add variable reflistfilter write reflistfilter_change
6254 pack $top.f.e -side right -fill x -expand 1
6255 pack $top.f.l -side left
6256 grid $top.f - -sticky ew -pady 2
6257 button $top.close -command [list destroy $top] -text "Close" \
6258 -font $uifont
6259 grid $top.close -
6260 grid columnconfigure $top 0 -weight 1
6261 grid rowconfigure $top 0 -weight 1
6262 bind $top.list <1> {break}
6263 bind $top.list <B1-Motion> {break}
6264 bind $top.list <ButtonRelease-1> {sel_reflist %W %x %y; break}
6265 set reflist {}
6266 refill_reflist
6267}
6268
6269proc sel_reflist {w x y} {
6270 global showrefstop reflist headids tagids otherrefids
6271
6272 if {![winfo exists $showrefstop]} return
6273 set l [lindex [split [$w index "@$x,$y"] "."] 0]
6274 set ref [lindex $reflist [expr {$l-1}]]
6275 set n [lindex $ref 0]
6276 switch -- [lindex $ref 1] {
6277 "H" {selbyid $headids($n)}
6278 "T" {selbyid $tagids($n)}
6279 "o" {selbyid $otherrefids($n)}
6280 }
6281 $showrefstop.list tag add highlight $l.0 "$l.0 lineend"
6282}
6283
6284proc unsel_reflist {} {
6285 global showrefstop
6286
6287 if {![info exists showrefstop] || ![winfo exists $showrefstop]} return
6288 $showrefstop.list tag remove highlight 0.0 end
6289}
6290
6291proc reflistfilter_change {n1 n2 op} {
6292 global reflistfilter
6293
6294 after cancel refill_reflist
6295 after 200 refill_reflist
6296}
6297
6298proc refill_reflist {} {
6299 global reflist reflistfilter showrefstop headids tagids otherrefids
6300 global commitrow curview commitinterest
6301
6302 if {![info exists showrefstop] || ![winfo exists $showrefstop]} return
6303 set refs {}
6304 foreach n [array names headids] {
6305 if {[string match $reflistfilter $n]} {
6306 if {[info exists commitrow($curview,$headids($n))]} {
6307 lappend refs [list $n H]
6308 } else {
6309 set commitinterest($headids($n)) {run refill_reflist}
6310 }
6311 }
6312 }
6313 foreach n [array names tagids] {
6314 if {[string match $reflistfilter $n]} {
6315 if {[info exists commitrow($curview,$tagids($n))]} {
6316 lappend refs [list $n T]
6317 } else {
6318 set commitinterest($tagids($n)) {run refill_reflist}
6319 }
6320 }
6321 }
6322 foreach n [array names otherrefids] {
6323 if {[string match $reflistfilter $n]} {
6324 if {[info exists commitrow($curview,$otherrefids($n))]} {
6325 lappend refs [list $n o]
6326 } else {
6327 set commitinterest($otherrefids($n)) {run refill_reflist}
6328 }
6329 }
6330 }
6331 set refs [lsort -index 0 $refs]
6332 if {$refs eq $reflist} return
6333
6334 # Update the contents of $showrefstop.list according to the
6335 # differences between $reflist (old) and $refs (new)
6336 $showrefstop.list conf -state normal
6337 $showrefstop.list insert end "\n"
6338 set i 0
6339 set j 0
6340 while {$i < [llength $reflist] || $j < [llength $refs]} {
6341 if {$i < [llength $reflist]} {
6342 if {$j < [llength $refs]} {
6343 set cmp [string compare [lindex $reflist $i 0] \
6344 [lindex $refs $j 0]]
6345 if {$cmp == 0} {
6346 set cmp [string compare [lindex $reflist $i 1] \
6347 [lindex $refs $j 1]]
6348 }
6349 } else {
6350 set cmp -1
6351 }
6352 } else {
6353 set cmp 1
6354 }
6355 switch -- $cmp {
6356 -1 {
6357 $showrefstop.list delete "[expr {$j+1}].0" "[expr {$j+2}].0"
6358 incr i
6359 }
6360 0 {
6361 incr i
6362 incr j
6363 }
6364 1 {
6365 set l [expr {$j + 1}]
6366 $showrefstop.list image create $l.0 -align baseline \
6367 -image reficon-[lindex $refs $j 1] -padx 2
6368 $showrefstop.list insert $l.1 "[lindex $refs $j 0]\n"
6369 incr j
6370 }
6371 }
6372 }
6373 set reflist $refs
6374 # delete last newline
6375 $showrefstop.list delete end-2c end-1c
6376 $showrefstop.list conf -state disabled
6377}
6378
6379# Stuff for finding nearby tags
6380proc getallcommits {} {
6381 global allcommits allids nbmp nextarc seeds
6382
6383 if {![info exists allcommits]} {
6384 set allids {}
6385 set nbmp 0
6386 set nextarc 0
6387 set allcommits 0
6388 set seeds {}
6389 }
6390
6391 set cmd [concat | git rev-list --all --parents]
6392 foreach id $seeds {
6393 lappend cmd "^$id"
6394 }
6395 set fd [open $cmd r]
6396 fconfigure $fd -blocking 0
6397 incr allcommits
6398 nowbusy allcommits
6399 filerun $fd [list getallclines $fd]
6400}
6401
6402# Since most commits have 1 parent and 1 child, we group strings of
6403# such commits into "arcs" joining branch/merge points (BMPs), which
6404# are commits that either don't have 1 parent or don't have 1 child.
6405#
6406# arcnos(id) - incoming arcs for BMP, arc we're on for other nodes
6407# arcout(id) - outgoing arcs for BMP
6408# arcids(a) - list of IDs on arc including end but not start
6409# arcstart(a) - BMP ID at start of arc
6410# arcend(a) - BMP ID at end of arc
6411# growing(a) - arc a is still growing
6412# arctags(a) - IDs out of arcids (excluding end) that have tags
6413# archeads(a) - IDs out of arcids (excluding end) that have heads
6414# The start of an arc is at the descendent end, so "incoming" means
6415# coming from descendents, and "outgoing" means going towards ancestors.
6416
6417proc getallclines {fd} {
6418 global allids allparents allchildren idtags idheads nextarc nbmp
6419 global arcnos arcids arctags arcout arcend arcstart archeads growing
6420 global seeds allcommits
6421
6422 set nid 0
6423 while {[incr nid] <= 1000 && [gets $fd line] >= 0} {
6424 set id [lindex $line 0]
6425 if {[info exists allparents($id)]} {
6426 # seen it already
6427 continue
6428 }
6429 lappend allids $id
6430 set olds [lrange $line 1 end]
6431 set allparents($id) $olds
6432 if {![info exists allchildren($id)]} {
6433 set allchildren($id) {}
6434 set arcnos($id) {}
6435 lappend seeds $id
6436 } else {
6437 set a $arcnos($id)
6438 if {[llength $olds] == 1 && [llength $a] == 1} {
6439 lappend arcids($a) $id
6440 if {[info exists idtags($id)]} {
6441 lappend arctags($a) $id
6442 }
6443 if {[info exists idheads($id)]} {
6444 lappend archeads($a) $id
6445 }
6446 if {[info exists allparents($olds)]} {
6447 # seen parent already
6448 if {![info exists arcout($olds)]} {
6449 splitarc $olds
6450 }
6451 lappend arcids($a) $olds
6452 set arcend($a) $olds
6453 unset growing($a)
6454 }
6455 lappend allchildren($olds) $id
6456 lappend arcnos($olds) $a
6457 continue
6458 }
6459 }
6460 incr nbmp
6461 foreach a $arcnos($id) {
6462 lappend arcids($a) $id
6463 set arcend($a) $id
6464 unset growing($a)
6465 }
6466
6467 set ao {}
6468 foreach p $olds {
6469 lappend allchildren($p) $id
6470 set a [incr nextarc]
6471 set arcstart($a) $id
6472 set archeads($a) {}
6473 set arctags($a) {}
6474 set archeads($a) {}
6475 set arcids($a) {}
6476 lappend ao $a
6477 set growing($a) 1
6478 if {[info exists allparents($p)]} {
6479 # seen it already, may need to make a new branch
6480 if {![info exists arcout($p)]} {
6481 splitarc $p
6482 }
6483 lappend arcids($a) $p
6484 set arcend($a) $p
6485 unset growing($a)
6486 }
6487 lappend arcnos($p) $a
6488 }
6489 set arcout($id) $ao
6490 }
6491 if {$nid > 0} {
6492 global cached_dheads cached_dtags cached_atags
6493 catch {unset cached_dheads}
6494 catch {unset cached_dtags}
6495 catch {unset cached_atags}
6496 }
6497 if {![eof $fd]} {
6498 return [expr {$nid >= 1000? 2: 1}]
6499 }
6500 close $fd
6501 if {[incr allcommits -1] == 0} {
6502 notbusy allcommits
6503 }
6504 dispneartags 0
6505 return 0
6506}
6507
6508proc recalcarc {a} {
6509 global arctags archeads arcids idtags idheads
6510
6511 set at {}
6512 set ah {}
6513 foreach id [lrange $arcids($a) 0 end-1] {
6514 if {[info exists idtags($id)]} {
6515 lappend at $id
6516 }
6517 if {[info exists idheads($id)]} {
6518 lappend ah $id
6519 }
6520 }
6521 set arctags($a) $at
6522 set archeads($a) $ah
6523}
6524
6525proc splitarc {p} {
6526 global arcnos arcids nextarc nbmp arctags archeads idtags idheads
6527 global arcstart arcend arcout allparents growing
6528
6529 set a $arcnos($p)
6530 if {[llength $a] != 1} {
6531 puts "oops splitarc called but [llength $a] arcs already"
6532 return
6533 }
6534 set a [lindex $a 0]
6535 set i [lsearch -exact $arcids($a) $p]
6536 if {$i < 0} {
6537 puts "oops splitarc $p not in arc $a"
6538 return
6539 }
6540 set na [incr nextarc]
6541 if {[info exists arcend($a)]} {
6542 set arcend($na) $arcend($a)
6543 } else {
6544 set l [lindex $allparents([lindex $arcids($a) end]) 0]
6545 set j [lsearch -exact $arcnos($l) $a]
6546 set arcnos($l) [lreplace $arcnos($l) $j $j $na]
6547 }
6548 set tail [lrange $arcids($a) [expr {$i+1}] end]
6549 set arcids($a) [lrange $arcids($a) 0 $i]
6550 set arcend($a) $p
6551 set arcstart($na) $p
6552 set arcout($p) $na
6553 set arcids($na) $tail
6554 if {[info exists growing($a)]} {
6555 set growing($na) 1
6556 unset growing($a)
6557 }
6558 incr nbmp
6559
6560 foreach id $tail {
6561 if {[llength $arcnos($id)] == 1} {
6562 set arcnos($id) $na
6563 } else {
6564 set j [lsearch -exact $arcnos($id) $a]
6565 set arcnos($id) [lreplace $arcnos($id) $j $j $na]
6566 }
6567 }
6568
6569 # reconstruct tags and heads lists
6570 if {$arctags($a) ne {} || $archeads($a) ne {}} {
6571 recalcarc $a
6572 recalcarc $na
6573 } else {
6574 set arctags($na) {}
6575 set archeads($na) {}
6576 }
6577}
6578
6579# Update things for a new commit added that is a child of one
6580# existing commit. Used when cherry-picking.
6581proc addnewchild {id p} {
6582 global allids allparents allchildren idtags nextarc nbmp
6583 global arcnos arcids arctags arcout arcend arcstart archeads growing
6584 global seeds
6585
6586 lappend allids $id
6587 set allparents($id) [list $p]
6588 set allchildren($id) {}
6589 set arcnos($id) {}
6590 lappend seeds $id
6591 incr nbmp
6592 lappend allchildren($p) $id
6593 set a [incr nextarc]
6594 set arcstart($a) $id
6595 set archeads($a) {}
6596 set arctags($a) {}
6597 set arcids($a) [list $p]
6598 set arcend($a) $p
6599 if {![info exists arcout($p)]} {
6600 splitarc $p
6601 }
6602 lappend arcnos($p) $a
6603 set arcout($id) [list $a]
6604}
6605
6606# Returns 1 if a is an ancestor of b, -1 if b is an ancestor of a,
6607# or 0 if neither is true.
6608proc anc_or_desc {a b} {
6609 global arcout arcstart arcend arcnos cached_isanc
6610
6611 if {$arcnos($a) eq $arcnos($b)} {
6612 # Both are on the same arc(s); either both are the same BMP,
6613 # or if one is not a BMP, the other is also not a BMP or is
6614 # the BMP at end of the arc (and it only has 1 incoming arc).
6615 # Or both can be BMPs with no incoming arcs.
6616 if {$a eq $b || $arcnos($a) eq {}} {
6617 return 0
6618 }
6619 # assert {[llength $arcnos($a)] == 1}
6620 set arc [lindex $arcnos($a) 0]
6621 set i [lsearch -exact $arcids($arc) $a]
6622 set j [lsearch -exact $arcids($arc) $b]
6623 if {$i < 0 || $i > $j} {
6624 return 1
6625 } else {
6626 return -1
6627 }
6628 }
6629
6630 if {![info exists arcout($a)]} {
6631 set arc [lindex $arcnos($a) 0]
6632 if {[info exists arcend($arc)]} {
6633 set aend $arcend($arc)
6634 } else {
6635 set aend {}
6636 }
6637 set a $arcstart($arc)
6638 } else {
6639 set aend $a
6640 }
6641 if {![info exists arcout($b)]} {
6642 set arc [lindex $arcnos($b) 0]
6643 if {[info exists arcend($arc)]} {
6644 set bend $arcend($arc)
6645 } else {
6646 set bend {}
6647 }
6648 set b $arcstart($arc)
6649 } else {
6650 set bend $b
6651 }
6652 if {$a eq $bend} {
6653 return 1
6654 }
6655 if {$b eq $aend} {
6656 return -1
6657 }
6658 if {[info exists cached_isanc($a,$bend)]} {
6659 if {$cached_isanc($a,$bend)} {
6660 return 1
6661 }
6662 }
6663 if {[info exists cached_isanc($b,$aend)]} {
6664 if {$cached_isanc($b,$aend)} {
6665 return -1
6666 }
6667 if {[info exists cached_isanc($a,$bend)]} {
6668 return 0
6669 }
6670 }
6671
6672 set todo [list $a $b]
6673 set anc($a) a
6674 set anc($b) b
6675 for {set i 0} {$i < [llength $todo]} {incr i} {
6676 set x [lindex $todo $i]
6677 if {$anc($x) eq {}} {
6678 continue
6679 }
6680 foreach arc $arcnos($x) {
6681 set xd $arcstart($arc)
6682 if {$xd eq $bend} {
6683 set cached_isanc($a,$bend) 1
6684 set cached_isanc($b,$aend) 0
6685 return 1
6686 } elseif {$xd eq $aend} {
6687 set cached_isanc($b,$aend) 1
6688 set cached_isanc($a,$bend) 0
6689 return -1
6690 }
6691 if {![info exists anc($xd)]} {
6692 set anc($xd) $anc($x)
6693 lappend todo $xd
6694 } elseif {$anc($xd) ne $anc($x)} {
6695 set anc($xd) {}
6696 }
6697 }
6698 }
6699 set cached_isanc($a,$bend) 0
6700 set cached_isanc($b,$aend) 0
6701 return 0
6702}
6703
6704# This identifies whether $desc has an ancestor that is
6705# a growing tip of the graph and which is not an ancestor of $anc
6706# and returns 0 if so and 1 if not.
6707# If we subsequently discover a tag on such a growing tip, and that
6708# turns out to be a descendent of $anc (which it could, since we
6709# don't necessarily see children before parents), then $desc
6710# isn't a good choice to display as a descendent tag of
6711# $anc (since it is the descendent of another tag which is
6712# a descendent of $anc). Similarly, $anc isn't a good choice to
6713# display as a ancestor tag of $desc.
6714#
6715proc is_certain {desc anc} {
6716 global arcnos arcout arcstart arcend growing problems
6717
6718 set certain {}
6719 if {[llength $arcnos($anc)] == 1} {
6720 # tags on the same arc are certain
6721 if {$arcnos($desc) eq $arcnos($anc)} {
6722 return 1
6723 }
6724 if {![info exists arcout($anc)]} {
6725 # if $anc is partway along an arc, use the start of the arc instead
6726 set a [lindex $arcnos($anc) 0]
6727 set anc $arcstart($a)
6728 }
6729 }
6730 if {[llength $arcnos($desc)] > 1 || [info exists arcout($desc)]} {
6731 set x $desc
6732 } else {
6733 set a [lindex $arcnos($desc) 0]
6734 set x $arcend($a)
6735 }
6736 if {$x == $anc} {
6737 return 1
6738 }
6739 set anclist [list $x]
6740 set dl($x) 1
6741 set nnh 1
6742 set ngrowanc 0
6743 for {set i 0} {$i < [llength $anclist] && ($nnh > 0 || $ngrowanc > 0)} {incr i} {
6744 set x [lindex $anclist $i]
6745 if {$dl($x)} {
6746 incr nnh -1
6747 }
6748 set done($x) 1
6749 foreach a $arcout($x) {
6750 if {[info exists growing($a)]} {
6751 if {![info exists growanc($x)] && $dl($x)} {
6752 set growanc($x) 1
6753 incr ngrowanc
6754 }
6755 } else {
6756 set y $arcend($a)
6757 if {[info exists dl($y)]} {
6758 if {$dl($y)} {
6759 if {!$dl($x)} {
6760 set dl($y) 0
6761 if {![info exists done($y)]} {
6762 incr nnh -1
6763 }
6764 if {[info exists growanc($x)]} {
6765 incr ngrowanc -1
6766 }
6767 set xl [list $y]
6768 for {set k 0} {$k < [llength $xl]} {incr k} {
6769 set z [lindex $xl $k]
6770 foreach c $arcout($z) {
6771 if {[info exists arcend($c)]} {
6772 set v $arcend($c)
6773 if {[info exists dl($v)] && $dl($v)} {
6774 set dl($v) 0
6775 if {![info exists done($v)]} {
6776 incr nnh -1
6777 }
6778 if {[info exists growanc($v)]} {
6779 incr ngrowanc -1
6780 }
6781 lappend xl $v
6782 }
6783 }
6784 }
6785 }
6786 }
6787 }
6788 } elseif {$y eq $anc || !$dl($x)} {
6789 set dl($y) 0
6790 lappend anclist $y
6791 } else {
6792 set dl($y) 1
6793 lappend anclist $y
6794 incr nnh
6795 }
6796 }
6797 }
6798 }
6799 foreach x [array names growanc] {
6800 if {$dl($x)} {
6801 return 0
6802 }
6803 return 0
6804 }
6805 return 1
6806}
6807
6808proc validate_arctags {a} {
6809 global arctags idtags
6810
6811 set i -1
6812 set na $arctags($a)
6813 foreach id $arctags($a) {
6814 incr i
6815 if {![info exists idtags($id)]} {
6816 set na [lreplace $na $i $i]
6817 incr i -1
6818 }
6819 }
6820 set arctags($a) $na
6821}
6822
6823proc validate_archeads {a} {
6824 global archeads idheads
6825
6826 set i -1
6827 set na $archeads($a)
6828 foreach id $archeads($a) {
6829 incr i
6830 if {![info exists idheads($id)]} {
6831 set na [lreplace $na $i $i]
6832 incr i -1
6833 }
6834 }
6835 set archeads($a) $na
6836}
6837
6838# Return the list of IDs that have tags that are descendents of id,
6839# ignoring IDs that are descendents of IDs already reported.
6840proc desctags {id} {
6841 global arcnos arcstart arcids arctags idtags allparents
6842 global growing cached_dtags
6843
6844 if {![info exists allparents($id)]} {
6845 return {}
6846 }
6847 set t1 [clock clicks -milliseconds]
6848 set argid $id
6849 if {[llength $arcnos($id)] == 1 && [llength $allparents($id)] == 1} {
6850 # part-way along an arc; check that arc first
6851 set a [lindex $arcnos($id) 0]
6852 if {$arctags($a) ne {}} {
6853 validate_arctags $a
6854 set i [lsearch -exact $arcids($a) $id]
6855 set tid {}
6856 foreach t $arctags($a) {
6857 set j [lsearch -exact $arcids($a) $t]
6858 if {$j >= $i} break
6859 set tid $t
6860 }
6861 if {$tid ne {}} {
6862 return $tid
6863 }
6864 }
6865 set id $arcstart($a)
6866 if {[info exists idtags($id)]} {
6867 return $id
6868 }
6869 }
6870 if {[info exists cached_dtags($id)]} {
6871 return $cached_dtags($id)
6872 }
6873
6874 set origid $id
6875 set todo [list $id]
6876 set queued($id) 1
6877 set nc 1
6878 for {set i 0} {$i < [llength $todo] && $nc > 0} {incr i} {
6879 set id [lindex $todo $i]
6880 set done($id) 1
6881 set ta [info exists hastaggedancestor($id)]
6882 if {!$ta} {
6883 incr nc -1
6884 }
6885 # ignore tags on starting node
6886 if {!$ta && $i > 0} {
6887 if {[info exists idtags($id)]} {
6888 set tagloc($id) $id
6889 set ta 1
6890 } elseif {[info exists cached_dtags($id)]} {
6891 set tagloc($id) $cached_dtags($id)
6892 set ta 1
6893 }
6894 }
6895 foreach a $arcnos($id) {
6896 set d $arcstart($a)
6897 if {!$ta && $arctags($a) ne {}} {
6898 validate_arctags $a
6899 if {$arctags($a) ne {}} {
6900 lappend tagloc($id) [lindex $arctags($a) end]
6901 }
6902 }
6903 if {$ta || $arctags($a) ne {}} {
6904 set tomark [list $d]
6905 for {set j 0} {$j < [llength $tomark]} {incr j} {
6906 set dd [lindex $tomark $j]
6907 if {![info exists hastaggedancestor($dd)]} {
6908 if {[info exists done($dd)]} {
6909 foreach b $arcnos($dd) {
6910 lappend tomark $arcstart($b)
6911 }
6912 if {[info exists tagloc($dd)]} {
6913 unset tagloc($dd)
6914 }
6915 } elseif {[info exists queued($dd)]} {
6916 incr nc -1
6917 }
6918 set hastaggedancestor($dd) 1
6919 }
6920 }
6921 }
6922 if {![info exists queued($d)]} {
6923 lappend todo $d
6924 set queued($d) 1
6925 if {![info exists hastaggedancestor($d)]} {
6926 incr nc
6927 }
6928 }
6929 }
6930 }
6931 set tags {}
6932 foreach id [array names tagloc] {
6933 if {![info exists hastaggedancestor($id)]} {
6934 foreach t $tagloc($id) {
6935 if {[lsearch -exact $tags $t] < 0} {
6936 lappend tags $t
6937 }
6938 }
6939 }
6940 }
6941 set t2 [clock clicks -milliseconds]
6942 set loopix $i
6943
6944 # remove tags that are descendents of other tags
6945 for {set i 0} {$i < [llength $tags]} {incr i} {
6946 set a [lindex $tags $i]
6947 for {set j 0} {$j < $i} {incr j} {
6948 set b [lindex $tags $j]
6949 set r [anc_or_desc $a $b]
6950 if {$r == 1} {
6951 set tags [lreplace $tags $j $j]
6952 incr j -1
6953 incr i -1
6954 } elseif {$r == -1} {
6955 set tags [lreplace $tags $i $i]
6956 incr i -1
6957 break
6958 }
6959 }
6960 }
6961
6962 if {[array names growing] ne {}} {
6963 # graph isn't finished, need to check if any tag could get
6964 # eclipsed by another tag coming later. Simply ignore any
6965 # tags that could later get eclipsed.
6966 set ctags {}
6967 foreach t $tags {
6968 if {[is_certain $t $origid]} {
6969 lappend ctags $t
6970 }
6971 }
6972 if {$tags eq $ctags} {
6973 set cached_dtags($origid) $tags
6974 } else {
6975 set tags $ctags
6976 }
6977 } else {
6978 set cached_dtags($origid) $tags
6979 }
6980 set t3 [clock clicks -milliseconds]
6981 if {0 && $t3 - $t1 >= 100} {
6982 puts "iterating descendents ($loopix/[llength $todo] nodes) took\
6983 [expr {$t2-$t1}]+[expr {$t3-$t2}]ms, $nc candidates left"
6984 }
6985 return $tags
6986}
6987
6988proc anctags {id} {
6989 global arcnos arcids arcout arcend arctags idtags allparents
6990 global growing cached_atags
6991
6992 if {![info exists allparents($id)]} {
6993 return {}
6994 }
6995 set t1 [clock clicks -milliseconds]
6996 set argid $id
6997 if {[llength $arcnos($id)] == 1 && [llength $allparents($id)] == 1} {
6998 # part-way along an arc; check that arc first
6999 set a [lindex $arcnos($id) 0]
7000 if {$arctags($a) ne {}} {
7001 validate_arctags $a
7002 set i [lsearch -exact $arcids($a) $id]
7003 foreach t $arctags($a) {
7004 set j [lsearch -exact $arcids($a) $t]
7005 if {$j > $i} {
7006 return $t
7007 }
7008 }
7009 }
7010 if {![info exists arcend($a)]} {
7011 return {}
7012 }
7013 set id $arcend($a)
7014 if {[info exists idtags($id)]} {
7015 return $id
7016 }
7017 }
7018 if {[info exists cached_atags($id)]} {
7019 return $cached_atags($id)
7020 }
7021
7022 set origid $id
7023 set todo [list $id]
7024 set queued($id) 1
7025 set taglist {}
7026 set nc 1
7027 for {set i 0} {$i < [llength $todo] && $nc > 0} {incr i} {
7028 set id [lindex $todo $i]
7029 set done($id) 1
7030 set td [info exists hastaggeddescendent($id)]
7031 if {!$td} {
7032 incr nc -1
7033 }
7034 # ignore tags on starting node
7035 if {!$td && $i > 0} {
7036 if {[info exists idtags($id)]} {
7037 set tagloc($id) $id
7038 set td 1
7039 } elseif {[info exists cached_atags($id)]} {
7040 set tagloc($id) $cached_atags($id)
7041 set td 1
7042 }
7043 }
7044 foreach a $arcout($id) {
7045 if {!$td && $arctags($a) ne {}} {
7046 validate_arctags $a
7047 if {$arctags($a) ne {}} {
7048 lappend tagloc($id) [lindex $arctags($a) 0]
7049 }
7050 }
7051 if {![info exists arcend($a)]} continue
7052 set d $arcend($a)
7053 if {$td || $arctags($a) ne {}} {
7054 set tomark [list $d]
7055 for {set j 0} {$j < [llength $tomark]} {incr j} {
7056 set dd [lindex $tomark $j]
7057 if {![info exists hastaggeddescendent($dd)]} {
7058 if {[info exists done($dd)]} {
7059 foreach b $arcout($dd) {
7060 if {[info exists arcend($b)]} {
7061 lappend tomark $arcend($b)
7062 }
7063 }
7064 if {[info exists tagloc($dd)]} {
7065 unset tagloc($dd)
7066 }
7067 } elseif {[info exists queued($dd)]} {
7068 incr nc -1
7069 }
7070 set hastaggeddescendent($dd) 1
7071 }
7072 }
7073 }
7074 if {![info exists queued($d)]} {
7075 lappend todo $d
7076 set queued($d) 1
7077 if {![info exists hastaggeddescendent($d)]} {
7078 incr nc
7079 }
7080 }
7081 }
7082 }
7083 set t2 [clock clicks -milliseconds]
7084 set loopix $i
7085 set tags {}
7086 foreach id [array names tagloc] {
7087 if {![info exists hastaggeddescendent($id)]} {
7088 foreach t $tagloc($id) {
7089 if {[lsearch -exact $tags $t] < 0} {
7090 lappend tags $t
7091 }
7092 }
7093 }
7094 }
7095
7096 # remove tags that are ancestors of other tags
7097 for {set i 0} {$i < [llength $tags]} {incr i} {
7098 set a [lindex $tags $i]
7099 for {set j 0} {$j < $i} {incr j} {
7100 set b [lindex $tags $j]
7101 set r [anc_or_desc $a $b]
7102 if {$r == -1} {
7103 set tags [lreplace $tags $j $j]
7104 incr j -1
7105 incr i -1
7106 } elseif {$r == 1} {
7107 set tags [lreplace $tags $i $i]
7108 incr i -1
7109 break
7110 }
7111 }
7112 }
7113
7114 if {[array names growing] ne {}} {
7115 # graph isn't finished, need to check if any tag could get
7116 # eclipsed by another tag coming later. Simply ignore any
7117 # tags that could later get eclipsed.
7118 set ctags {}
7119 foreach t $tags {
7120 if {[is_certain $origid $t]} {
7121 lappend ctags $t
7122 }
7123 }
7124 if {$tags eq $ctags} {
7125 set cached_atags($origid) $tags
7126 } else {
7127 set tags $ctags
7128 }
7129 } else {
7130 set cached_atags($origid) $tags
7131 }
7132 set t3 [clock clicks -milliseconds]
7133 if {0 && $t3 - $t1 >= 100} {
7134 puts "iterating ancestors ($loopix/[llength $todo] nodes) took\
7135 [expr {$t2-$t1}]+[expr {$t3-$t2}]ms, $nc candidates left"
7136 }
7137 return $tags
7138}
7139
7140# Return the list of IDs that have heads that are descendents of id,
7141# including id itself if it has a head.
7142proc descheads {id} {
7143 global arcnos arcstart arcids archeads idheads cached_dheads
7144 global allparents
7145
7146 if {![info exists allparents($id)]} {
7147 return {}
7148 }
7149 set aret {}
7150 if {[llength $arcnos($id)] == 1 && [llength $allparents($id)] == 1} {
7151 # part-way along an arc; check it first
7152 set a [lindex $arcnos($id) 0]
7153 if {$archeads($a) ne {}} {
7154 validate_archeads $a
7155 set i [lsearch -exact $arcids($a) $id]
7156 foreach t $archeads($a) {
7157 set j [lsearch -exact $arcids($a) $t]
7158 if {$j > $i} break
7159 lappend aret $t
7160 }
7161 }
7162 set id $arcstart($a)
7163 }
7164 set origid $id
7165 set todo [list $id]
7166 set seen($id) 1
7167 set ret {}
7168 for {set i 0} {$i < [llength $todo]} {incr i} {
7169 set id [lindex $todo $i]
7170 if {[info exists cached_dheads($id)]} {
7171 set ret [concat $ret $cached_dheads($id)]
7172 } else {
7173 if {[info exists idheads($id)]} {
7174 lappend ret $id
7175 }
7176 foreach a $arcnos($id) {
7177 if {$archeads($a) ne {}} {
7178 validate_archeads $a
7179 if {$archeads($a) ne {}} {
7180 set ret [concat $ret $archeads($a)]
7181 }
7182 }
7183 set d $arcstart($a)
7184 if {![info exists seen($d)]} {
7185 lappend todo $d
7186 set seen($d) 1
7187 }
7188 }
7189 }
7190 }
7191 set ret [lsort -unique $ret]
7192 set cached_dheads($origid) $ret
7193 return [concat $ret $aret]
7194}
7195
7196proc addedtag {id} {
7197 global arcnos arcout cached_dtags cached_atags
7198
7199 if {![info exists arcnos($id)]} return
7200 if {![info exists arcout($id)]} {
7201 recalcarc [lindex $arcnos($id) 0]
7202 }
7203 catch {unset cached_dtags}
7204 catch {unset cached_atags}
7205}
7206
7207proc addedhead {hid head} {
7208 global arcnos arcout cached_dheads
7209
7210 if {![info exists arcnos($hid)]} return
7211 if {![info exists arcout($hid)]} {
7212 recalcarc [lindex $arcnos($hid) 0]
7213 }
7214 catch {unset cached_dheads}
7215}
7216
7217proc removedhead {hid head} {
7218 global cached_dheads
7219
7220 catch {unset cached_dheads}
7221}
7222
7223proc movedhead {hid head} {
7224 global arcnos arcout cached_dheads
7225
7226 if {![info exists arcnos($hid)]} return
7227 if {![info exists arcout($hid)]} {
7228 recalcarc [lindex $arcnos($hid) 0]
7229 }
7230 catch {unset cached_dheads}
7231}
7232
7233proc changedrefs {} {
7234 global cached_dheads cached_dtags cached_atags
7235 global arctags archeads arcnos arcout idheads idtags
7236
7237 foreach id [concat [array names idheads] [array names idtags]] {
7238 if {[info exists arcnos($id)] && ![info exists arcout($id)]} {
7239 set a [lindex $arcnos($id) 0]
7240 if {![info exists donearc($a)]} {
7241 recalcarc $a
7242 set donearc($a) 1
7243 }
7244 }
7245 }
7246 catch {unset cached_dtags}
7247 catch {unset cached_atags}
7248 catch {unset cached_dheads}
7249}
7250
7251proc rereadrefs {} {
7252 global idtags idheads idotherrefs mainhead
7253
7254 set refids [concat [array names idtags] \
7255 [array names idheads] [array names idotherrefs]]
7256 foreach id $refids {
7257 if {![info exists ref($id)]} {
7258 set ref($id) [listrefs $id]
7259 }
7260 }
7261 set oldmainhead $mainhead
7262 readrefs
7263 changedrefs
7264 set refids [lsort -unique [concat $refids [array names idtags] \
7265 [array names idheads] [array names idotherrefs]]]
7266 foreach id $refids {
7267 set v [listrefs $id]
7268 if {![info exists ref($id)] || $ref($id) != $v ||
7269 ($id eq $oldmainhead && $id ne $mainhead) ||
7270 ($id eq $mainhead && $id ne $oldmainhead)} {
7271 redrawtags $id
7272 }
7273 }
7274 run refill_reflist
7275}
7276
7277proc listrefs {id} {
7278 global idtags idheads idotherrefs
7279
7280 set x {}
7281 if {[info exists idtags($id)]} {
7282 set x $idtags($id)
7283 }
7284 set y {}
7285 if {[info exists idheads($id)]} {
7286 set y $idheads($id)
7287 }
7288 set z {}
7289 if {[info exists idotherrefs($id)]} {
7290 set z $idotherrefs($id)
7291 }
7292 return [list $x $y $z]
7293}
7294
7295proc showtag {tag isnew} {
7296 global ctext tagcontents tagids linknum tagobjid
7297
7298 if {$isnew} {
7299 addtohistory [list showtag $tag 0]
7300 }
7301 $ctext conf -state normal
7302 clear_ctext
7303 set linknum 0
7304 if {![info exists tagcontents($tag)]} {
7305 catch {
7306 set tagcontents($tag) [exec git cat-file tag $tagobjid($tag)]
7307 }
7308 }
7309 if {[info exists tagcontents($tag)]} {
7310 set text $tagcontents($tag)
7311 } else {
7312 set text "Tag: $tag\nId: $tagids($tag)"
7313 }
7314 appendwithlinks $text {}
7315 $ctext conf -state disabled
7316 init_flist {}
7317}
7318
7319proc doquit {} {
7320 global stopped
7321 set stopped 100
7322 savestuff .
7323 destroy .
7324}
7325
7326proc doprefs {} {
7327 global maxwidth maxgraphpct diffopts
7328 global oldprefs prefstop showneartags showlocalchanges
7329 global bgcolor fgcolor ctext diffcolors selectbgcolor
7330 global uifont tabstop
7331
7332 set top .gitkprefs
7333 set prefstop $top
7334 if {[winfo exists $top]} {
7335 raise $top
7336 return
7337 }
7338 foreach v {maxwidth maxgraphpct diffopts showneartags showlocalchanges} {
7339 set oldprefs($v) [set $v]
7340 }
7341 toplevel $top
7342 wm title $top "Gitk preferences"
7343 label $top.ldisp -text "Commit list display options"
7344 $top.ldisp configure -font $uifont
7345 grid $top.ldisp - -sticky w -pady 10
7346 label $top.spacer -text " "
7347 label $top.maxwidthl -text "Maximum graph width (lines)" \
7348 -font optionfont
7349 spinbox $top.maxwidth -from 0 -to 100 -width 4 -textvariable maxwidth
7350 grid $top.spacer $top.maxwidthl $top.maxwidth -sticky w
7351 label $top.maxpctl -text "Maximum graph width (% of pane)" \
7352 -font optionfont
7353 spinbox $top.maxpct -from 1 -to 100 -width 4 -textvariable maxgraphpct
7354 grid x $top.maxpctl $top.maxpct -sticky w
7355 frame $top.showlocal
7356 label $top.showlocal.l -text "Show local changes" -font optionfont
7357 checkbutton $top.showlocal.b -variable showlocalchanges
7358 pack $top.showlocal.b $top.showlocal.l -side left
7359 grid x $top.showlocal -sticky w
7360
7361 label $top.ddisp -text "Diff display options"
7362 $top.ddisp configure -font $uifont
7363 grid $top.ddisp - -sticky w -pady 10
7364 label $top.diffoptl -text "Options for diff program" \
7365 -font optionfont
7366 entry $top.diffopt -width 20 -textvariable diffopts
7367 grid x $top.diffoptl $top.diffopt -sticky w
7368 frame $top.ntag
7369 label $top.ntag.l -text "Display nearby tags" -font optionfont
7370 checkbutton $top.ntag.b -variable showneartags
7371 pack $top.ntag.b $top.ntag.l -side left
7372 grid x $top.ntag -sticky w
7373 label $top.tabstopl -text "tabstop" -font optionfont
7374 spinbox $top.tabstop -from 1 -to 20 -width 4 -textvariable tabstop
7375 grid x $top.tabstopl $top.tabstop -sticky w
7376
7377 label $top.cdisp -text "Colors: press to choose"
7378 $top.cdisp configure -font $uifont
7379 grid $top.cdisp - -sticky w -pady 10
7380 label $top.bg -padx 40 -relief sunk -background $bgcolor
7381 button $top.bgbut -text "Background" -font optionfont \
7382 -command [list choosecolor bgcolor 0 $top.bg background setbg]
7383 grid x $top.bgbut $top.bg -sticky w
7384 label $top.fg -padx 40 -relief sunk -background $fgcolor
7385 button $top.fgbut -text "Foreground" -font optionfont \
7386 -command [list choosecolor fgcolor 0 $top.fg foreground setfg]
7387 grid x $top.fgbut $top.fg -sticky w
7388 label $top.diffold -padx 40 -relief sunk -background [lindex $diffcolors 0]
7389 button $top.diffoldbut -text "Diff: old lines" -font optionfont \
7390 -command [list choosecolor diffcolors 0 $top.diffold "diff old lines" \
7391 [list $ctext tag conf d0 -foreground]]
7392 grid x $top.diffoldbut $top.diffold -sticky w
7393 label $top.diffnew -padx 40 -relief sunk -background [lindex $diffcolors 1]
7394 button $top.diffnewbut -text "Diff: new lines" -font optionfont \
7395 -command [list choosecolor diffcolors 1 $top.diffnew "diff new lines" \
7396 [list $ctext tag conf d1 -foreground]]
7397 grid x $top.diffnewbut $top.diffnew -sticky w
7398 label $top.hunksep -padx 40 -relief sunk -background [lindex $diffcolors 2]
7399 button $top.hunksepbut -text "Diff: hunk header" -font optionfont \
7400 -command [list choosecolor diffcolors 2 $top.hunksep \
7401 "diff hunk header" \
7402 [list $ctext tag conf hunksep -foreground]]
7403 grid x $top.hunksepbut $top.hunksep -sticky w
7404 label $top.selbgsep -padx 40 -relief sunk -background $selectbgcolor
7405 button $top.selbgbut -text "Select bg" -font optionfont \
7406 -command [list choosecolor selectbgcolor 0 $top.selbgsep background setselbg]
7407 grid x $top.selbgbut $top.selbgsep -sticky w
7408
7409 frame $top.buts
7410 button $top.buts.ok -text "OK" -command prefsok -default active
7411 $top.buts.ok configure -font $uifont
7412 button $top.buts.can -text "Cancel" -command prefscan -default normal
7413 $top.buts.can configure -font $uifont
7414 grid $top.buts.ok $top.buts.can
7415 grid columnconfigure $top.buts 0 -weight 1 -uniform a
7416 grid columnconfigure $top.buts 1 -weight 1 -uniform a
7417 grid $top.buts - - -pady 10 -sticky ew
7418 bind $top <Visibility> "focus $top.buts.ok"
7419}
7420
7421proc choosecolor {v vi w x cmd} {
7422 global $v
7423
7424 set c [tk_chooseColor -initialcolor [lindex [set $v] $vi] \
7425 -title "Gitk: choose color for $x"]
7426 if {$c eq {}} return
7427 $w conf -background $c
7428 lset $v $vi $c
7429 eval $cmd $c
7430}
7431
7432proc setselbg {c} {
7433 global bglist cflist
7434 foreach w $bglist {
7435 $w configure -selectbackground $c
7436 }
7437 $cflist tag configure highlight \
7438 -background [$cflist cget -selectbackground]
7439 allcanvs itemconf secsel -fill $c
7440}
7441
7442proc setbg {c} {
7443 global bglist
7444
7445 foreach w $bglist {
7446 $w conf -background $c
7447 }
7448}
7449
7450proc setfg {c} {
7451 global fglist canv
7452
7453 foreach w $fglist {
7454 $w conf -foreground $c
7455 }
7456 allcanvs itemconf text -fill $c
7457 $canv itemconf circle -outline $c
7458}
7459
7460proc prefscan {} {
7461 global maxwidth maxgraphpct diffopts
7462 global oldprefs prefstop showneartags showlocalchanges
7463
7464 foreach v {maxwidth maxgraphpct diffopts showneartags showlocalchanges} {
7465 set $v $oldprefs($v)
7466 }
7467 catch {destroy $prefstop}
7468 unset prefstop
7469}
7470
7471proc prefsok {} {
7472 global maxwidth maxgraphpct
7473 global oldprefs prefstop showneartags showlocalchanges
7474 global charspc ctext tabstop
7475
7476 catch {destroy $prefstop}
7477 unset prefstop
7478 $ctext configure -tabs "[expr {$tabstop * $charspc}]"
7479 if {$showlocalchanges != $oldprefs(showlocalchanges)} {
7480 if {$showlocalchanges} {
7481 doshowlocalchanges
7482 } else {
7483 dohidelocalchanges
7484 }
7485 }
7486 if {$maxwidth != $oldprefs(maxwidth)
7487 || $maxgraphpct != $oldprefs(maxgraphpct)} {
7488 redisplay
7489 } elseif {$showneartags != $oldprefs(showneartags)} {
7490 reselectline
7491 }
7492}
7493
7494proc formatdate {d} {
7495 global datetimeformat
7496 if {$d ne {}} {
7497 set d [clock format $d -format $datetimeformat]
7498 }
7499 return $d
7500}
7501
7502# This list of encoding names and aliases is distilled from
7503# http://www.iana.org/assignments/character-sets.
7504# Not all of them are supported by Tcl.
7505set encoding_aliases {
7506 { ANSI_X3.4-1968 iso-ir-6 ANSI_X3.4-1986 ISO_646.irv:1991 ASCII
7507 ISO646-US US-ASCII us IBM367 cp367 csASCII }
7508 { ISO-10646-UTF-1 csISO10646UTF1 }
7509 { ISO_646.basic:1983 ref csISO646basic1983 }
7510 { INVARIANT csINVARIANT }
7511 { ISO_646.irv:1983 iso-ir-2 irv csISO2IntlRefVersion }
7512 { BS_4730 iso-ir-4 ISO646-GB gb uk csISO4UnitedKingdom }
7513 { NATS-SEFI iso-ir-8-1 csNATSSEFI }
7514 { NATS-SEFI-ADD iso-ir-8-2 csNATSSEFIADD }
7515 { NATS-DANO iso-ir-9-1 csNATSDANO }
7516 { NATS-DANO-ADD iso-ir-9-2 csNATSDANOADD }
7517 { SEN_850200_B iso-ir-10 FI ISO646-FI ISO646-SE se csISO10Swedish }
7518 { SEN_850200_C iso-ir-11 ISO646-SE2 se2 csISO11SwedishForNames }
7519 { KS_C_5601-1987 iso-ir-149 KS_C_5601-1989 KSC_5601 korean csKSC56011987 }
7520 { ISO-2022-KR csISO2022KR }
7521 { EUC-KR csEUCKR }
7522 { ISO-2022-JP csISO2022JP }
7523 { ISO-2022-JP-2 csISO2022JP2 }
7524 { JIS_C6220-1969-jp JIS_C6220-1969 iso-ir-13 katakana x0201-7
7525 csISO13JISC6220jp }
7526 { JIS_C6220-1969-ro iso-ir-14 jp ISO646-JP csISO14JISC6220ro }
7527 { IT iso-ir-15 ISO646-IT csISO15Italian }
7528 { PT iso-ir-16 ISO646-PT csISO16Portuguese }
7529 { ES iso-ir-17 ISO646-ES csISO17Spanish }
7530 { greek7-old iso-ir-18 csISO18Greek7Old }
7531 { latin-greek iso-ir-19 csISO19LatinGreek }
7532 { DIN_66003 iso-ir-21 de ISO646-DE csISO21German }
7533 { NF_Z_62-010_(1973) iso-ir-25 ISO646-FR1 csISO25French }
7534 { Latin-greek-1 iso-ir-27 csISO27LatinGreek1 }
7535 { ISO_5427 iso-ir-37 csISO5427Cyrillic }
7536 { JIS_C6226-1978 iso-ir-42 csISO42JISC62261978 }
7537 { BS_viewdata iso-ir-47 csISO47BSViewdata }
7538 { INIS iso-ir-49 csISO49INIS }
7539 { INIS-8 iso-ir-50 csISO50INIS8 }
7540 { INIS-cyrillic iso-ir-51 csISO51INISCyrillic }
7541 { ISO_5427:1981 iso-ir-54 ISO5427Cyrillic1981 }
7542 { ISO_5428:1980 iso-ir-55 csISO5428Greek }
7543 { GB_1988-80 iso-ir-57 cn ISO646-CN csISO57GB1988 }
7544 { GB_2312-80 iso-ir-58 chinese csISO58GB231280 }
7545 { NS_4551-1 iso-ir-60 ISO646-NO no csISO60DanishNorwegian
7546 csISO60Norwegian1 }
7547 { NS_4551-2 ISO646-NO2 iso-ir-61 no2 csISO61Norwegian2 }
7548 { NF_Z_62-010 iso-ir-69 ISO646-FR fr csISO69French }
7549 { videotex-suppl iso-ir-70 csISO70VideotexSupp1 }
7550 { PT2 iso-ir-84 ISO646-PT2 csISO84Portuguese2 }
7551 { ES2 iso-ir-85 ISO646-ES2 csISO85Spanish2 }
7552 { MSZ_7795.3 iso-ir-86 ISO646-HU hu csISO86Hungarian }
7553 { JIS_C6226-1983 iso-ir-87 x0208 JIS_X0208-1983 csISO87JISX0208 }
7554 { greek7 iso-ir-88 csISO88Greek7 }
7555 { ASMO_449 ISO_9036 arabic7 iso-ir-89 csISO89ASMO449 }
7556 { iso-ir-90 csISO90 }
7557 { JIS_C6229-1984-a iso-ir-91 jp-ocr-a csISO91JISC62291984a }
7558 { JIS_C6229-1984-b iso-ir-92 ISO646-JP-OCR-B jp-ocr-b
7559 csISO92JISC62991984b }
7560 { JIS_C6229-1984-b-add iso-ir-93 jp-ocr-b-add csISO93JIS62291984badd }
7561 { JIS_C6229-1984-hand iso-ir-94 jp-ocr-hand csISO94JIS62291984hand }
7562 { JIS_C6229-1984-hand-add iso-ir-95 jp-ocr-hand-add
7563 csISO95JIS62291984handadd }
7564 { JIS_C6229-1984-kana iso-ir-96 csISO96JISC62291984kana }
7565 { ISO_2033-1983 iso-ir-98 e13b csISO2033 }
7566 { ANSI_X3.110-1983 iso-ir-99 CSA_T500-1983 NAPLPS csISO99NAPLPS }
7567 { ISO_8859-1:1987 iso-ir-100 ISO_8859-1 ISO-8859-1 latin1 l1 IBM819
7568 CP819 csISOLatin1 }
7569 { ISO_8859-2:1987 iso-ir-101 ISO_8859-2 ISO-8859-2 latin2 l2 csISOLatin2 }
7570 { T.61-7bit iso-ir-102 csISO102T617bit }
7571 { T.61-8bit T.61 iso-ir-103 csISO103T618bit }
7572 { ISO_8859-3:1988 iso-ir-109 ISO_8859-3 ISO-8859-3 latin3 l3 csISOLatin3 }
7573 { ISO_8859-4:1988 iso-ir-110 ISO_8859-4 ISO-8859-4 latin4 l4 csISOLatin4 }
7574 { ECMA-cyrillic iso-ir-111 KOI8-E csISO111ECMACyrillic }
7575 { CSA_Z243.4-1985-1 iso-ir-121 ISO646-CA csa7-1 ca csISO121Canadian1 }
7576 { CSA_Z243.4-1985-2 iso-ir-122 ISO646-CA2 csa7-2 csISO122Canadian2 }
7577 { CSA_Z243.4-1985-gr iso-ir-123 csISO123CSAZ24341985gr }
7578 { ISO_8859-6:1987 iso-ir-127 ISO_8859-6 ISO-8859-6 ECMA-114 ASMO-708
7579 arabic csISOLatinArabic }
7580 { ISO_8859-6-E csISO88596E ISO-8859-6-E }
7581 { ISO_8859-6-I csISO88596I ISO-8859-6-I }
7582 { ISO_8859-7:1987 iso-ir-126 ISO_8859-7 ISO-8859-7 ELOT_928 ECMA-118
7583 greek greek8 csISOLatinGreek }
7584 { T.101-G2 iso-ir-128 csISO128T101G2 }
7585 { ISO_8859-8:1988 iso-ir-138 ISO_8859-8 ISO-8859-8 hebrew
7586 csISOLatinHebrew }
7587 { ISO_8859-8-E csISO88598E ISO-8859-8-E }
7588 { ISO_8859-8-I csISO88598I ISO-8859-8-I }
7589 { CSN_369103 iso-ir-139 csISO139CSN369103 }
7590 { JUS_I.B1.002 iso-ir-141 ISO646-YU js yu csISO141JUSIB1002 }
7591 { ISO_6937-2-add iso-ir-142 csISOTextComm }
7592 { IEC_P27-1 iso-ir-143 csISO143IECP271 }
7593 { ISO_8859-5:1988 iso-ir-144 ISO_8859-5 ISO-8859-5 cyrillic
7594 csISOLatinCyrillic }
7595 { JUS_I.B1.003-serb iso-ir-146 serbian csISO146Serbian }
7596 { JUS_I.B1.003-mac macedonian iso-ir-147 csISO147Macedonian }
7597 { ISO_8859-9:1989 iso-ir-148 ISO_8859-9 ISO-8859-9 latin5 l5 csISOLatin5 }
7598 { greek-ccitt iso-ir-150 csISO150 csISO150GreekCCITT }
7599 { NC_NC00-10:81 cuba iso-ir-151 ISO646-CU csISO151Cuba }
7600 { ISO_6937-2-25 iso-ir-152 csISO6937Add }
7601 { GOST_19768-74 ST_SEV_358-88 iso-ir-153 csISO153GOST1976874 }
7602 { ISO_8859-supp iso-ir-154 latin1-2-5 csISO8859Supp }
7603 { ISO_10367-box iso-ir-155 csISO10367Box }
7604 { ISO-8859-10 iso-ir-157 l6 ISO_8859-10:1992 csISOLatin6 latin6 }
7605 { latin-lap lap iso-ir-158 csISO158Lap }
7606 { JIS_X0212-1990 x0212 iso-ir-159 csISO159JISX02121990 }
7607 { DS_2089 DS2089 ISO646-DK dk csISO646Danish }
7608 { us-dk csUSDK }
7609 { dk-us csDKUS }
7610 { JIS_X0201 X0201 csHalfWidthKatakana }
7611 { KSC5636 ISO646-KR csKSC5636 }
7612 { ISO-10646-UCS-2 csUnicode }
7613 { ISO-10646-UCS-4 csUCS4 }
7614 { DEC-MCS dec csDECMCS }
7615 { hp-roman8 roman8 r8 csHPRoman8 }
7616 { macintosh mac csMacintosh }
7617 { IBM037 cp037 ebcdic-cp-us ebcdic-cp-ca ebcdic-cp-wt ebcdic-cp-nl
7618 csIBM037 }
7619 { IBM038 EBCDIC-INT cp038 csIBM038 }
7620 { IBM273 CP273 csIBM273 }
7621 { IBM274 EBCDIC-BE CP274 csIBM274 }
7622 { IBM275 EBCDIC-BR cp275 csIBM275 }
7623 { IBM277 EBCDIC-CP-DK EBCDIC-CP-NO csIBM277 }
7624 { IBM278 CP278 ebcdic-cp-fi ebcdic-cp-se csIBM278 }
7625 { IBM280 CP280 ebcdic-cp-it csIBM280 }
7626 { IBM281 EBCDIC-JP-E cp281 csIBM281 }
7627 { IBM284 CP284 ebcdic-cp-es csIBM284 }
7628 { IBM285 CP285 ebcdic-cp-gb csIBM285 }
7629 { IBM290 cp290 EBCDIC-JP-kana csIBM290 }
7630 { IBM297 cp297 ebcdic-cp-fr csIBM297 }
7631 { IBM420 cp420 ebcdic-cp-ar1 csIBM420 }
7632 { IBM423 cp423 ebcdic-cp-gr csIBM423 }
7633 { IBM424 cp424 ebcdic-cp-he csIBM424 }
7634 { IBM437 cp437 437 csPC8CodePage437 }
7635 { IBM500 CP500 ebcdic-cp-be ebcdic-cp-ch csIBM500 }
7636 { IBM775 cp775 csPC775Baltic }
7637 { IBM850 cp850 850 csPC850Multilingual }
7638 { IBM851 cp851 851 csIBM851 }
7639 { IBM852 cp852 852 csPCp852 }
7640 { IBM855 cp855 855 csIBM855 }
7641 { IBM857 cp857 857 csIBM857 }
7642 { IBM860 cp860 860 csIBM860 }
7643 { IBM861 cp861 861 cp-is csIBM861 }
7644 { IBM862 cp862 862 csPC862LatinHebrew }
7645 { IBM863 cp863 863 csIBM863 }
7646 { IBM864 cp864 csIBM864 }
7647 { IBM865 cp865 865 csIBM865 }
7648 { IBM866 cp866 866 csIBM866 }
7649 { IBM868 CP868 cp-ar csIBM868 }
7650 { IBM869 cp869 869 cp-gr csIBM869 }
7651 { IBM870 CP870 ebcdic-cp-roece ebcdic-cp-yu csIBM870 }
7652 { IBM871 CP871 ebcdic-cp-is csIBM871 }
7653 { IBM880 cp880 EBCDIC-Cyrillic csIBM880 }
7654 { IBM891 cp891 csIBM891 }
7655 { IBM903 cp903 csIBM903 }
7656 { IBM904 cp904 904 csIBBM904 }
7657 { IBM905 CP905 ebcdic-cp-tr csIBM905 }
7658 { IBM918 CP918 ebcdic-cp-ar2 csIBM918 }
7659 { IBM1026 CP1026 csIBM1026 }
7660 { EBCDIC-AT-DE csIBMEBCDICATDE }
7661 { EBCDIC-AT-DE-A csEBCDICATDEA }
7662 { EBCDIC-CA-FR csEBCDICCAFR }
7663 { EBCDIC-DK-NO csEBCDICDKNO }
7664 { EBCDIC-DK-NO-A csEBCDICDKNOA }
7665 { EBCDIC-FI-SE csEBCDICFISE }
7666 { EBCDIC-FI-SE-A csEBCDICFISEA }
7667 { EBCDIC-FR csEBCDICFR }
7668 { EBCDIC-IT csEBCDICIT }
7669 { EBCDIC-PT csEBCDICPT }
7670 { EBCDIC-ES csEBCDICES }
7671 { EBCDIC-ES-A csEBCDICESA }
7672 { EBCDIC-ES-S csEBCDICESS }
7673 { EBCDIC-UK csEBCDICUK }
7674 { EBCDIC-US csEBCDICUS }
7675 { UNKNOWN-8BIT csUnknown8BiT }
7676 { MNEMONIC csMnemonic }
7677 { MNEM csMnem }
7678 { VISCII csVISCII }
7679 { VIQR csVIQR }
7680 { KOI8-R csKOI8R }
7681 { IBM00858 CCSID00858 CP00858 PC-Multilingual-850+euro }
7682 { IBM00924 CCSID00924 CP00924 ebcdic-Latin9--euro }
7683 { IBM01140 CCSID01140 CP01140 ebcdic-us-37+euro }
7684 { IBM01141 CCSID01141 CP01141 ebcdic-de-273+euro }
7685 { IBM01142 CCSID01142 CP01142 ebcdic-dk-277+euro ebcdic-no-277+euro }
7686 { IBM01143 CCSID01143 CP01143 ebcdic-fi-278+euro ebcdic-se-278+euro }
7687 { IBM01144 CCSID01144 CP01144 ebcdic-it-280+euro }
7688 { IBM01145 CCSID01145 CP01145 ebcdic-es-284+euro }
7689 { IBM01146 CCSID01146 CP01146 ebcdic-gb-285+euro }
7690 { IBM01147 CCSID01147 CP01147 ebcdic-fr-297+euro }
7691 { IBM01148 CCSID01148 CP01148 ebcdic-international-500+euro }
7692 { IBM01149 CCSID01149 CP01149 ebcdic-is-871+euro }
7693 { IBM1047 IBM-1047 }
7694 { PTCP154 csPTCP154 PT154 CP154 Cyrillic-Asian }
7695 { Amiga-1251 Ami1251 Amiga1251 Ami-1251 }
7696 { UNICODE-1-1 csUnicode11 }
7697 { CESU-8 csCESU-8 }
7698 { BOCU-1 csBOCU-1 }
7699 { UNICODE-1-1-UTF-7 csUnicode11UTF7 }
7700 { ISO-8859-14 iso-ir-199 ISO_8859-14:1998 ISO_8859-14 latin8 iso-celtic
7701 l8 }
7702 { ISO-8859-15 ISO_8859-15 Latin-9 }
7703 { ISO-8859-16 iso-ir-226 ISO_8859-16:2001 ISO_8859-16 latin10 l10 }
7704 { GBK CP936 MS936 windows-936 }
7705 { JIS_Encoding csJISEncoding }
7706 { Shift_JIS MS_Kanji csShiftJIS }
7707 { Extended_UNIX_Code_Packed_Format_for_Japanese csEUCPkdFmtJapanese
7708 EUC-JP }
7709 { Extended_UNIX_Code_Fixed_Width_for_Japanese csEUCFixWidJapanese }
7710 { ISO-10646-UCS-Basic csUnicodeASCII }
7711 { ISO-10646-Unicode-Latin1 csUnicodeLatin1 ISO-10646 }
7712 { ISO-Unicode-IBM-1261 csUnicodeIBM1261 }
7713 { ISO-Unicode-IBM-1268 csUnicodeIBM1268 }
7714 { ISO-Unicode-IBM-1276 csUnicodeIBM1276 }
7715 { ISO-Unicode-IBM-1264 csUnicodeIBM1264 }
7716 { ISO-Unicode-IBM-1265 csUnicodeIBM1265 }
7717 { ISO-8859-1-Windows-3.0-Latin-1 csWindows30Latin1 }
7718 { ISO-8859-1-Windows-3.1-Latin-1 csWindows31Latin1 }
7719 { ISO-8859-2-Windows-Latin-2 csWindows31Latin2 }
7720 { ISO-8859-9-Windows-Latin-5 csWindows31Latin5 }
7721 { Adobe-Standard-Encoding csAdobeStandardEncoding }
7722 { Ventura-US csVenturaUS }
7723 { Ventura-International csVenturaInternational }
7724 { PC8-Danish-Norwegian csPC8DanishNorwegian }
7725 { PC8-Turkish csPC8Turkish }
7726 { IBM-Symbols csIBMSymbols }
7727 { IBM-Thai csIBMThai }
7728 { HP-Legal csHPLegal }
7729 { HP-Pi-font csHPPiFont }
7730 { HP-Math8 csHPMath8 }
7731 { Adobe-Symbol-Encoding csHPPSMath }
7732 { HP-DeskTop csHPDesktop }
7733 { Ventura-Math csVenturaMath }
7734 { Microsoft-Publishing csMicrosoftPublishing }
7735 { Windows-31J csWindows31J }
7736 { GB2312 csGB2312 }
7737 { Big5 csBig5 }
7738}
7739
7740proc tcl_encoding {enc} {
7741 global encoding_aliases
7742 set names [encoding names]
7743 set lcnames [string tolower $names]
7744 set enc [string tolower $enc]
7745 set i [lsearch -exact $lcnames $enc]
7746 if {$i < 0} {
7747 # look for "isonnn" instead of "iso-nnn" or "iso_nnn"
7748 if {[regsub {^iso[-_]} $enc iso encx]} {
7749 set i [lsearch -exact $lcnames $encx]
7750 }
7751 }
7752 if {$i < 0} {
7753 foreach l $encoding_aliases {
7754 set ll [string tolower $l]
7755 if {[lsearch -exact $ll $enc] < 0} continue
7756 # look through the aliases for one that tcl knows about
7757 foreach e $ll {
7758 set i [lsearch -exact $lcnames $e]
7759 if {$i < 0} {
7760 if {[regsub {^iso[-_]} $e iso ex]} {
7761 set i [lsearch -exact $lcnames $ex]
7762 }
7763 }
7764 if {$i >= 0} break
7765 }
7766 break
7767 }
7768 }
7769 if {$i >= 0} {
7770 return [lindex $names $i]
7771 }
7772 return {}
7773}
7774
7775# defaults...
7776set datemode 0
7777set diffopts "-U 5 -p"
7778set wrcomcmd "git diff-tree --stdin -p --pretty"
7779
7780set gitencoding {}
7781catch {
7782 set gitencoding [exec git config --get i18n.commitencoding]
7783}
7784if {$gitencoding == ""} {
7785 set gitencoding "utf-8"
7786}
7787set tclencoding [tcl_encoding $gitencoding]
7788if {$tclencoding == {}} {
7789 puts stderr "Warning: encoding $gitencoding is not supported by Tcl/Tk"
7790}
7791
7792set mainfont {Helvetica 9}
7793set textfont {Courier 9}
7794set uifont {Helvetica 9 bold}
7795set tabstop 8
7796set findmergefiles 0
7797set maxgraphpct 50
7798set maxwidth 16
7799set revlistorder 0
7800set fastdate 0
7801set uparrowlen 5
7802set downarrowlen 5
7803set mingaplen 100
7804set cmitmode "patch"
7805set wrapcomment "none"
7806set showneartags 1
7807set maxrefs 20
7808set maxlinelen 200
7809set showlocalchanges 1
7810set datetimeformat "%Y-%m-%d %H:%M:%S"
7811
7812set colors {green red blue magenta darkgrey brown orange}
7813set bgcolor white
7814set fgcolor black
7815set diffcolors {red "#00a000" blue}
7816set diffcontext 3
7817set selectbgcolor gray85
7818
7819catch {source ~/.gitk}
7820
7821font create optionfont -family sans-serif -size -12
7822
7823# check that we can find a .git directory somewhere...
7824if {[catch {set gitdir [gitdir]}]} {
7825 show_error {} . "Cannot find a git repository here."
7826 exit 1
7827}
7828if {![file isdirectory $gitdir]} {
7829 show_error {} . "Cannot find the git directory \"$gitdir\"."
7830 exit 1
7831}
7832
7833set revtreeargs {}
7834set cmdline_files {}
7835set i 0
7836foreach arg $argv {
7837 switch -- $arg {
7838 "" { }
7839 "-d" { set datemode 1 }
7840 "--" {
7841 set cmdline_files [lrange $argv [expr {$i + 1}] end]
7842 break
7843 }
7844 default {
7845 lappend revtreeargs $arg
7846 }
7847 }
7848 incr i
7849}
7850
7851if {$i >= [llength $argv] && $revtreeargs ne {}} {
7852 # no -- on command line, but some arguments (other than -d)
7853 if {[catch {
7854 set f [eval exec git rev-parse --no-revs --no-flags $revtreeargs]
7855 set cmdline_files [split $f "\n"]
7856 set n [llength $cmdline_files]
7857 set revtreeargs [lrange $revtreeargs 0 end-$n]
7858 # Unfortunately git rev-parse doesn't produce an error when
7859 # something is both a revision and a filename. To be consistent
7860 # with git log and git rev-list, check revtreeargs for filenames.
7861 foreach arg $revtreeargs {
7862 if {[file exists $arg]} {
7863 show_error {} . "Ambiguous argument '$arg': both revision\
7864 and filename"
7865 exit 1
7866 }
7867 }
7868 } err]} {
7869 # unfortunately we get both stdout and stderr in $err,
7870 # so look for "fatal:".
7871 set i [string first "fatal:" $err]
7872 if {$i > 0} {
7873 set err [string range $err [expr {$i + 6}] end]
7874 }
7875 show_error {} . "Bad arguments to gitk:\n$err"
7876 exit 1
7877 }
7878}
7879
7880set nullid "0000000000000000000000000000000000000000"
7881set nullid2 "0000000000000000000000000000000000000001"
7882
7883
7884set runq {}
7885set history {}
7886set historyindex 0
7887set fh_serial 0
7888set nhl_names {}
7889set highlight_paths {}
7890set searchdirn -forwards
7891set boldrows {}
7892set boldnamerows {}
7893set diffelide {0 0}
7894set markingmatches 0
7895
7896set optim_delay 16
7897
7898set nextviewnum 1
7899set curview 0
7900set selectedview 0
7901set selectedhlview None
7902set viewfiles(0) {}
7903set viewperm(0) 0
7904set viewargs(0) {}
7905
7906set cmdlineok 0
7907set stopped 0
7908set stuffsaved 0
7909set patchnum 0
7910set lookingforhead 0
7911set localirow -1
7912set localfrow -1
7913set lserial 0
7914setcoords
7915makewindow
7916# wait for the window to become visible
7917tkwait visibility .
7918wm title . "[file tail $argv0]: [file tail [pwd]]"
7919readrefs
7920
7921if {$cmdline_files ne {} || $revtreeargs ne {}} {
7922 # create a view for the files/dirs specified on the command line
7923 set curview 1
7924 set selectedview 1
7925 set nextviewnum 2
7926 set viewname(1) "Command line"
7927 set viewfiles(1) $cmdline_files
7928 set viewargs(1) $revtreeargs
7929 set viewperm(1) 0
7930 addviewmenu 1
7931 .bar.view entryconf Edit* -state normal
7932 .bar.view entryconf Delete* -state normal
7933}
7934
7935if {[info exists permviews]} {
7936 foreach v $permviews {
7937 set n $nextviewnum
7938 incr nextviewnum
7939 set viewname($n) [lindex $v 0]
7940 set viewfiles($n) [lindex $v 1]
7941 set viewargs($n) [lindex $v 2]
7942 set viewperm($n) 1
7943 addviewmenu $n
7944 }
7945}
7946getcommits