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