verify-tag: also grok CR/LFs in the tag signature
[git/mingw/4msysgit/wingit-dll.git] / gitk
blob1103bafc256038d4fa8749c7d844c93b4d4a25cb
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
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 regetallcommits
317 showview $n
320 proc parsecommit {id contents listed} {
321 global commitinfo cdate
323 set inhdr 1
324 set comment {}
325 set headline {}
326 set auname {}
327 set audate {}
328 set comname {}
329 set comdate {}
330 set hdrend [string first "\n\n" $contents]
331 if {$hdrend < 0} {
332 # should never happen...
333 set hdrend [string length $contents]
335 set header [string range $contents 0 [expr {$hdrend - 1}]]
336 set comment [string range $contents [expr {$hdrend + 2}] end]
337 foreach line [split $header "\n"] {
338 set tag [lindex $line 0]
339 if {$tag == "author"} {
340 set audate [lindex $line end-1]
341 set auname [lrange $line 1 end-2]
342 } elseif {$tag == "committer"} {
343 set comdate [lindex $line end-1]
344 set comname [lrange $line 1 end-2]
347 set headline {}
348 # take the first non-blank line of the comment as the headline
349 set headline [string trimleft $comment]
350 set i [string first "\n" $headline]
351 if {$i >= 0} {
352 set headline [string range $headline 0 $i]
354 set headline [string trimright $headline]
355 set i [string first "\r" $headline]
356 if {$i >= 0} {
357 set headline [string trimright [string range $headline 0 $i]]
359 if {!$listed} {
360 # git rev-list indents the comment by 4 spaces;
361 # if we got this via git cat-file, add the indentation
362 set newcomment {}
363 foreach line [split $comment "\n"] {
364 append newcomment " "
365 append newcomment $line
366 append newcomment "\n"
368 set comment $newcomment
370 if {$comdate != {}} {
371 set cdate($id) $comdate
373 set commitinfo($id) [list $headline $auname $audate \
374 $comname $comdate $comment]
377 proc getcommit {id} {
378 global commitdata commitinfo
380 if {[info exists commitdata($id)]} {
381 parsecommit $id $commitdata($id) 1
382 } else {
383 readcommit $id
384 if {![info exists commitinfo($id)]} {
385 set commitinfo($id) {"No commit information available"}
388 return 1
391 proc readrefs {} {
392 global tagids idtags headids idheads tagobjid
393 global otherrefids idotherrefs mainhead mainheadid
395 foreach v {tagids idtags headids idheads otherrefids idotherrefs} {
396 catch {unset $v}
398 set refd [open [list | git show-ref -d] r]
399 while {[gets $refd line] >= 0} {
400 if {[string index $line 40] ne " "} continue
401 set id [string range $line 0 39]
402 set ref [string range $line 41 end]
403 if {![string match "refs/*" $ref]} continue
404 set name [string range $ref 5 end]
405 if {[string match "remotes/*" $name]} {
406 if {![string match "*/HEAD" $name]} {
407 set headids($name) $id
408 lappend idheads($id) $name
410 } elseif {[string match "heads/*" $name]} {
411 set name [string range $name 6 end]
412 set headids($name) $id
413 lappend idheads($id) $name
414 } elseif {[string match "tags/*" $name]} {
415 # this lets refs/tags/foo^{} overwrite refs/tags/foo,
416 # which is what we want since the former is the commit ID
417 set name [string range $name 5 end]
418 if {[string match "*^{}" $name]} {
419 set name [string range $name 0 end-3]
420 } else {
421 set tagobjid($name) $id
423 set tagids($name) $id
424 lappend idtags($id) $name
425 } else {
426 set otherrefids($name) $id
427 lappend idotherrefs($id) $name
430 close $refd
431 set mainhead {}
432 set mainheadid {}
433 catch {
434 set thehead [exec git symbolic-ref HEAD]
435 if {[string match "refs/heads/*" $thehead]} {
436 set mainhead [string range $thehead 11 end]
437 if {[info exists headids($mainhead)]} {
438 set mainheadid $headids($mainhead)
444 # skip over fake commits
445 proc first_real_row {} {
446 global nullid nullid2 displayorder numcommits
448 for {set row 0} {$row < $numcommits} {incr row} {
449 set id [lindex $displayorder $row]
450 if {$id ne $nullid && $id ne $nullid2} {
451 break
454 return $row
457 # update things for a head moved to a child of its previous location
458 proc movehead {id name} {
459 global headids idheads
461 removehead $headids($name) $name
462 set headids($name) $id
463 lappend idheads($id) $name
466 # update things when a head has been removed
467 proc removehead {id name} {
468 global headids idheads
470 if {$idheads($id) eq $name} {
471 unset idheads($id)
472 } else {
473 set i [lsearch -exact $idheads($id) $name]
474 if {$i >= 0} {
475 set idheads($id) [lreplace $idheads($id) $i $i]
478 unset headids($name)
481 proc show_error {w top msg} {
482 message $w.m -text $msg -justify center -aspect 400
483 pack $w.m -side top -fill x -padx 20 -pady 20
484 button $w.ok -text OK -command "destroy $top"
485 pack $w.ok -side bottom -fill x
486 bind $top <Visibility> "grab $top; focus $top"
487 bind $top <Key-Return> "destroy $top"
488 tkwait window $top
491 proc error_popup msg {
492 set w .error
493 toplevel $w
494 wm transient $w .
495 show_error $w $w $msg
498 proc confirm_popup msg {
499 global confirm_ok
500 set confirm_ok 0
501 set w .confirm
502 toplevel $w
503 wm transient $w .
504 message $w.m -text $msg -justify center -aspect 400
505 pack $w.m -side top -fill x -padx 20 -pady 20
506 button $w.ok -text OK -command "set confirm_ok 1; destroy $w"
507 pack $w.ok -side left -fill x
508 button $w.cancel -text Cancel -command "destroy $w"
509 pack $w.cancel -side right -fill x
510 bind $w <Visibility> "grab $w; focus $w"
511 tkwait window $w
512 return $confirm_ok
515 proc makewindow {} {
516 global canv canv2 canv3 linespc charspc ctext cflist
517 global textfont mainfont uifont tabstop
518 global findtype findtypemenu findloc findstring fstring geometry
519 global entries sha1entry sha1string sha1but
520 global maincursor textcursor curtextcursor
521 global rowctxmenu fakerowmenu mergemax wrapcomment
522 global highlight_files gdttype
523 global searchstring sstring
524 global bgcolor fgcolor bglist fglist diffcolors selectbgcolor
525 global headctxmenu
527 menu .bar
528 .bar add cascade -label "File" -menu .bar.file
529 .bar configure -font $uifont
530 menu .bar.file
531 .bar.file add command -label "Update" -command updatecommits
532 .bar.file add command -label "Reread references" -command rereadrefs
533 .bar.file add command -label "Quit" -command doquit
534 .bar.file configure -font $uifont
535 menu .bar.edit
536 .bar add cascade -label "Edit" -menu .bar.edit
537 .bar.edit add command -label "Preferences" -command doprefs
538 .bar.edit configure -font $uifont
540 menu .bar.view -font $uifont
541 .bar add cascade -label "View" -menu .bar.view
542 .bar.view add command -label "New view..." -command {newview 0}
543 .bar.view add command -label "Edit view..." -command editview \
544 -state disabled
545 .bar.view add command -label "Delete view" -command delview -state disabled
546 .bar.view add separator
547 .bar.view add radiobutton -label "All files" -command {showview 0} \
548 -variable selectedview -value 0
550 menu .bar.help
551 .bar add cascade -label "Help" -menu .bar.help
552 .bar.help add command -label "About gitk" -command about
553 .bar.help add command -label "Key bindings" -command keys
554 .bar.help configure -font $uifont
555 . configure -menu .bar
557 # the gui has upper and lower half, parts of a paned window.
558 panedwindow .ctop -orient vertical
560 # possibly use assumed geometry
561 if {![info exists geometry(pwsash0)]} {
562 set geometry(topheight) [expr {15 * $linespc}]
563 set geometry(topwidth) [expr {80 * $charspc}]
564 set geometry(botheight) [expr {15 * $linespc}]
565 set geometry(botwidth) [expr {50 * $charspc}]
566 set geometry(pwsash0) "[expr {40 * $charspc}] 2"
567 set geometry(pwsash1) "[expr {60 * $charspc}] 2"
570 # the upper half will have a paned window, a scroll bar to the right, and some stuff below
571 frame .tf -height $geometry(topheight) -width $geometry(topwidth)
572 frame .tf.histframe
573 panedwindow .tf.histframe.pwclist -orient horizontal -sashpad 0 -handlesize 4
575 # create three canvases
576 set cscroll .tf.histframe.csb
577 set canv .tf.histframe.pwclist.canv
578 canvas $canv \
579 -selectbackground $selectbgcolor \
580 -background $bgcolor -bd 0 \
581 -yscrollincr $linespc -yscrollcommand "scrollcanv $cscroll"
582 .tf.histframe.pwclist add $canv
583 set canv2 .tf.histframe.pwclist.canv2
584 canvas $canv2 \
585 -selectbackground $selectbgcolor \
586 -background $bgcolor -bd 0 -yscrollincr $linespc
587 .tf.histframe.pwclist add $canv2
588 set canv3 .tf.histframe.pwclist.canv3
589 canvas $canv3 \
590 -selectbackground $selectbgcolor \
591 -background $bgcolor -bd 0 -yscrollincr $linespc
592 .tf.histframe.pwclist add $canv3
593 eval .tf.histframe.pwclist sash place 0 $geometry(pwsash0)
594 eval .tf.histframe.pwclist sash place 1 $geometry(pwsash1)
596 # a scroll bar to rule them
597 scrollbar $cscroll -command {allcanvs yview} -highlightthickness 0
598 pack $cscroll -side right -fill y
599 bind .tf.histframe.pwclist <Configure> {resizeclistpanes %W %w}
600 lappend bglist $canv $canv2 $canv3
601 pack .tf.histframe.pwclist -fill both -expand 1 -side left
603 # we have two button bars at bottom of top frame. Bar 1
604 frame .tf.bar
605 frame .tf.lbar -height 15
607 set sha1entry .tf.bar.sha1
608 set entries $sha1entry
609 set sha1but .tf.bar.sha1label
610 button $sha1but -text "SHA1 ID: " -state disabled -relief flat \
611 -command gotocommit -width 8 -font $uifont
612 $sha1but conf -disabledforeground [$sha1but cget -foreground]
613 pack .tf.bar.sha1label -side left
614 entry $sha1entry -width 40 -font $textfont -textvariable sha1string
615 trace add variable sha1string write sha1change
616 pack $sha1entry -side left -pady 2
618 image create bitmap bm-left -data {
619 #define left_width 16
620 #define left_height 16
621 static unsigned char left_bits[] = {
622 0x00, 0x00, 0xc0, 0x01, 0xe0, 0x00, 0x70, 0x00, 0x38, 0x00, 0x1c, 0x00,
623 0x0e, 0x00, 0xff, 0x7f, 0xff, 0x7f, 0xff, 0x7f, 0x0e, 0x00, 0x1c, 0x00,
624 0x38, 0x00, 0x70, 0x00, 0xe0, 0x00, 0xc0, 0x01};
626 image create bitmap bm-right -data {
627 #define right_width 16
628 #define right_height 16
629 static unsigned char right_bits[] = {
630 0x00, 0x00, 0xc0, 0x01, 0x80, 0x03, 0x00, 0x07, 0x00, 0x0e, 0x00, 0x1c,
631 0x00, 0x38, 0xff, 0x7f, 0xff, 0x7f, 0xff, 0x7f, 0x00, 0x38, 0x00, 0x1c,
632 0x00, 0x0e, 0x00, 0x07, 0x80, 0x03, 0xc0, 0x01};
634 button .tf.bar.leftbut -image bm-left -command goback \
635 -state disabled -width 26
636 pack .tf.bar.leftbut -side left -fill y
637 button .tf.bar.rightbut -image bm-right -command goforw \
638 -state disabled -width 26
639 pack .tf.bar.rightbut -side left -fill y
641 button .tf.bar.findbut -text "Find" -command dofind -font $uifont
642 pack .tf.bar.findbut -side left
643 set findstring {}
644 set fstring .tf.bar.findstring
645 lappend entries $fstring
646 entry $fstring -width 30 -font $textfont -textvariable findstring
647 trace add variable findstring write find_change
648 pack $fstring -side left -expand 1 -fill x -in .tf.bar
649 set findtype Exact
650 set findtypemenu [tk_optionMenu .tf.bar.findtype \
651 findtype Exact IgnCase Regexp]
652 trace add variable findtype write find_change
653 .tf.bar.findtype configure -font $uifont
654 .tf.bar.findtype.menu configure -font $uifont
655 set findloc "All fields"
656 tk_optionMenu .tf.bar.findloc findloc "All fields" Headline \
657 Comments Author Committer
658 trace add variable findloc write find_change
659 .tf.bar.findloc configure -font $uifont
660 .tf.bar.findloc.menu configure -font $uifont
661 pack .tf.bar.findloc -side right
662 pack .tf.bar.findtype -side right
664 # build up the bottom bar of upper window
665 label .tf.lbar.flabel -text "Highlight: Commits " \
666 -font $uifont
667 pack .tf.lbar.flabel -side left -fill y
668 set gdttype "touching paths:"
669 set gm [tk_optionMenu .tf.lbar.gdttype gdttype "touching paths:" \
670 "adding/removing string:"]
671 trace add variable gdttype write hfiles_change
672 $gm conf -font $uifont
673 .tf.lbar.gdttype conf -font $uifont
674 pack .tf.lbar.gdttype -side left -fill y
675 entry .tf.lbar.fent -width 25 -font $textfont \
676 -textvariable highlight_files
677 trace add variable highlight_files write hfiles_change
678 lappend entries .tf.lbar.fent
679 pack .tf.lbar.fent -side left -fill x -expand 1
680 label .tf.lbar.vlabel -text " OR in view" -font $uifont
681 pack .tf.lbar.vlabel -side left -fill y
682 global viewhlmenu selectedhlview
683 set viewhlmenu [tk_optionMenu .tf.lbar.vhl selectedhlview None]
684 $viewhlmenu entryconf None -command delvhighlight
685 $viewhlmenu conf -font $uifont
686 .tf.lbar.vhl conf -font $uifont
687 pack .tf.lbar.vhl -side left -fill y
688 label .tf.lbar.rlabel -text " OR " -font $uifont
689 pack .tf.lbar.rlabel -side left -fill y
690 global highlight_related
691 set m [tk_optionMenu .tf.lbar.relm highlight_related None \
692 "Descendent" "Not descendent" "Ancestor" "Not ancestor"]
693 $m conf -font $uifont
694 .tf.lbar.relm conf -font $uifont
695 trace add variable highlight_related write vrel_change
696 pack .tf.lbar.relm -side left -fill y
698 # Finish putting the upper half of the viewer together
699 pack .tf.lbar -in .tf -side bottom -fill x
700 pack .tf.bar -in .tf -side bottom -fill x
701 pack .tf.histframe -fill both -side top -expand 1
702 .ctop add .tf
703 .ctop paneconfigure .tf -height $geometry(topheight)
704 .ctop paneconfigure .tf -width $geometry(topwidth)
706 # now build up the bottom
707 panedwindow .pwbottom -orient horizontal
709 # lower left, a text box over search bar, scroll bar to the right
710 # if we know window height, then that will set the lower text height, otherwise
711 # we set lower text height which will drive window height
712 if {[info exists geometry(main)]} {
713 frame .bleft -width $geometry(botwidth)
714 } else {
715 frame .bleft -width $geometry(botwidth) -height $geometry(botheight)
717 frame .bleft.top
718 frame .bleft.mid
720 button .bleft.top.search -text "Search" -command dosearch \
721 -font $uifont
722 pack .bleft.top.search -side left -padx 5
723 set sstring .bleft.top.sstring
724 entry $sstring -width 20 -font $textfont -textvariable searchstring
725 lappend entries $sstring
726 trace add variable searchstring write incrsearch
727 pack $sstring -side left -expand 1 -fill x
728 radiobutton .bleft.mid.diff -text "Diff" \
729 -command changediffdisp -variable diffelide -value {0 0}
730 radiobutton .bleft.mid.old -text "Old version" \
731 -command changediffdisp -variable diffelide -value {0 1}
732 radiobutton .bleft.mid.new -text "New version" \
733 -command changediffdisp -variable diffelide -value {1 0}
734 pack .bleft.mid.diff .bleft.mid.old .bleft.mid.new -side left
735 set ctext .bleft.ctext
736 text $ctext -background $bgcolor -foreground $fgcolor \
737 -tabs "[expr {$tabstop * $charspc}]" \
738 -state disabled -font $textfont \
739 -yscrollcommand scrolltext -wrap none
740 scrollbar .bleft.sb -command "$ctext yview"
741 pack .bleft.top -side top -fill x
742 pack .bleft.mid -side top -fill x
743 pack .bleft.sb -side right -fill y
744 pack $ctext -side left -fill both -expand 1
745 lappend bglist $ctext
746 lappend fglist $ctext
748 $ctext tag conf comment -wrap $wrapcomment
749 $ctext tag conf filesep -font [concat $textfont bold] -back "#aaaaaa"
750 $ctext tag conf hunksep -fore [lindex $diffcolors 2]
751 $ctext tag conf d0 -fore [lindex $diffcolors 0]
752 $ctext tag conf d1 -fore [lindex $diffcolors 1]
753 $ctext tag conf m0 -fore red
754 $ctext tag conf m1 -fore blue
755 $ctext tag conf m2 -fore green
756 $ctext tag conf m3 -fore purple
757 $ctext tag conf m4 -fore brown
758 $ctext tag conf m5 -fore "#009090"
759 $ctext tag conf m6 -fore magenta
760 $ctext tag conf m7 -fore "#808000"
761 $ctext tag conf m8 -fore "#009000"
762 $ctext tag conf m9 -fore "#ff0080"
763 $ctext tag conf m10 -fore cyan
764 $ctext tag conf m11 -fore "#b07070"
765 $ctext tag conf m12 -fore "#70b0f0"
766 $ctext tag conf m13 -fore "#70f0b0"
767 $ctext tag conf m14 -fore "#f0b070"
768 $ctext tag conf m15 -fore "#ff70b0"
769 $ctext tag conf mmax -fore darkgrey
770 set mergemax 16
771 $ctext tag conf mresult -font [concat $textfont bold]
772 $ctext tag conf msep -font [concat $textfont bold]
773 $ctext tag conf found -back yellow
775 .pwbottom add .bleft
776 .pwbottom paneconfigure .bleft -width $geometry(botwidth)
778 # lower right
779 frame .bright
780 frame .bright.mode
781 radiobutton .bright.mode.patch -text "Patch" \
782 -command reselectline -variable cmitmode -value "patch"
783 .bright.mode.patch configure -font $uifont
784 radiobutton .bright.mode.tree -text "Tree" \
785 -command reselectline -variable cmitmode -value "tree"
786 .bright.mode.tree configure -font $uifont
787 grid .bright.mode.patch .bright.mode.tree -sticky ew
788 pack .bright.mode -side top -fill x
789 set cflist .bright.cfiles
790 set indent [font measure $mainfont "nn"]
791 text $cflist \
792 -selectbackground $selectbgcolor \
793 -background $bgcolor -foreground $fgcolor \
794 -font $mainfont \
795 -tabs [list $indent [expr {2 * $indent}]] \
796 -yscrollcommand ".bright.sb set" \
797 -cursor [. cget -cursor] \
798 -spacing1 1 -spacing3 1
799 lappend bglist $cflist
800 lappend fglist $cflist
801 scrollbar .bright.sb -command "$cflist yview"
802 pack .bright.sb -side right -fill y
803 pack $cflist -side left -fill both -expand 1
804 $cflist tag configure highlight \
805 -background [$cflist cget -selectbackground]
806 $cflist tag configure bold -font [concat $mainfont bold]
808 .pwbottom add .bright
809 .ctop add .pwbottom
811 # restore window position if known
812 if {[info exists geometry(main)]} {
813 wm geometry . "$geometry(main)"
816 if {[tk windowingsystem] eq {aqua}} {
817 set M1B M1
818 } else {
819 set M1B Control
822 bind .pwbottom <Configure> {resizecdetpanes %W %w}
823 pack .ctop -fill both -expand 1
824 bindall <1> {selcanvline %W %x %y}
825 #bindall <B1-Motion> {selcanvline %W %x %y}
826 bindall <ButtonRelease-4> "allcanvs yview scroll -5 units"
827 bindall <ButtonRelease-5> "allcanvs yview scroll 5 units"
828 bindall <2> "canvscan mark %W %x %y"
829 bindall <B2-Motion> "canvscan dragto %W %x %y"
830 bindkey <Home> selfirstline
831 bindkey <End> sellastline
832 bind . <Key-Up> "selnextline -1"
833 bind . <Key-Down> "selnextline 1"
834 bind . <Shift-Key-Up> "next_highlight -1"
835 bind . <Shift-Key-Down> "next_highlight 1"
836 bindkey <Key-Right> "goforw"
837 bindkey <Key-Left> "goback"
838 bind . <Key-Prior> "selnextpage -1"
839 bind . <Key-Next> "selnextpage 1"
840 bind . <$M1B-Home> "allcanvs yview moveto 0.0"
841 bind . <$M1B-End> "allcanvs yview moveto 1.0"
842 bind . <$M1B-Key-Up> "allcanvs yview scroll -1 units"
843 bind . <$M1B-Key-Down> "allcanvs yview scroll 1 units"
844 bind . <$M1B-Key-Prior> "allcanvs yview scroll -1 pages"
845 bind . <$M1B-Key-Next> "allcanvs yview scroll 1 pages"
846 bindkey <Key-Delete> "$ctext yview scroll -1 pages"
847 bindkey <Key-BackSpace> "$ctext yview scroll -1 pages"
848 bindkey <Key-space> "$ctext yview scroll 1 pages"
849 bindkey p "selnextline -1"
850 bindkey n "selnextline 1"
851 bindkey z "goback"
852 bindkey x "goforw"
853 bindkey i "selnextline -1"
854 bindkey k "selnextline 1"
855 bindkey j "goback"
856 bindkey l "goforw"
857 bindkey b "$ctext yview scroll -1 pages"
858 bindkey d "$ctext yview scroll 18 units"
859 bindkey u "$ctext yview scroll -18 units"
860 bindkey / {findnext 1}
861 bindkey <Key-Return> {findnext 0}
862 bindkey ? findprev
863 bindkey f nextfile
864 bindkey <F5> updatecommits
865 bind . <$M1B-q> doquit
866 bind . <$M1B-f> dofind
867 bind . <$M1B-g> {findnext 0}
868 bind . <$M1B-r> dosearchback
869 bind . <$M1B-s> dosearch
870 bind . <$M1B-equal> {incrfont 1}
871 bind . <$M1B-KP_Add> {incrfont 1}
872 bind . <$M1B-minus> {incrfont -1}
873 bind . <$M1B-KP_Subtract> {incrfont -1}
874 wm protocol . WM_DELETE_WINDOW doquit
875 bind . <Button-1> "click %W"
876 bind $fstring <Key-Return> dofind
877 bind $sha1entry <Key-Return> gotocommit
878 bind $sha1entry <<PasteSelection>> clearsha1
879 bind $cflist <1> {sel_flist %W %x %y; break}
880 bind $cflist <B1-Motion> {sel_flist %W %x %y; break}
881 bind $cflist <ButtonRelease-1> {treeclick %W %x %y}
883 set maincursor [. cget -cursor]
884 set textcursor [$ctext cget -cursor]
885 set curtextcursor $textcursor
887 set rowctxmenu .rowctxmenu
888 menu $rowctxmenu -tearoff 0
889 $rowctxmenu add command -label "Diff this -> selected" \
890 -command {diffvssel 0}
891 $rowctxmenu add command -label "Diff selected -> this" \
892 -command {diffvssel 1}
893 $rowctxmenu add command -label "Make patch" -command mkpatch
894 $rowctxmenu add command -label "Create tag" -command mktag
895 $rowctxmenu add command -label "Write commit to file" -command writecommit
896 $rowctxmenu add command -label "Create new branch" -command mkbranch
897 $rowctxmenu add command -label "Cherry-pick this commit" \
898 -command cherrypick
899 $rowctxmenu add command -label "Reset HEAD branch to here" \
900 -command resethead
902 set fakerowmenu .fakerowmenu
903 menu $fakerowmenu -tearoff 0
904 $fakerowmenu add command -label "Diff this -> selected" \
905 -command {diffvssel 0}
906 $fakerowmenu add command -label "Diff selected -> this" \
907 -command {diffvssel 1}
908 $fakerowmenu add command -label "Make patch" -command mkpatch
909 # $fakerowmenu add command -label "Commit" -command {mkcommit 0}
910 # $fakerowmenu add command -label "Commit all" -command {mkcommit 1}
911 # $fakerowmenu add command -label "Revert local changes" -command revertlocal
913 set headctxmenu .headctxmenu
914 menu $headctxmenu -tearoff 0
915 $headctxmenu add command -label "Check out this branch" \
916 -command cobranch
917 $headctxmenu add command -label "Remove this branch" \
918 -command rmbranch
921 # mouse-2 makes all windows scan vertically, but only the one
922 # the cursor is in scans horizontally
923 proc canvscan {op w x y} {
924 global canv canv2 canv3
925 foreach c [list $canv $canv2 $canv3] {
926 if {$c == $w} {
927 $c scan $op $x $y
928 } else {
929 $c scan $op 0 $y
934 proc scrollcanv {cscroll f0 f1} {
935 $cscroll set $f0 $f1
936 drawfrac $f0 $f1
937 flushhighlights
940 # when we make a key binding for the toplevel, make sure
941 # it doesn't get triggered when that key is pressed in the
942 # find string entry widget.
943 proc bindkey {ev script} {
944 global entries
945 bind . $ev $script
946 set escript [bind Entry $ev]
947 if {$escript == {}} {
948 set escript [bind Entry <Key>]
950 foreach e $entries {
951 bind $e $ev "$escript; break"
955 # set the focus back to the toplevel for any click outside
956 # the entry widgets
957 proc click {w} {
958 global entries
959 foreach e $entries {
960 if {$w == $e} return
962 focus .
965 proc savestuff {w} {
966 global canv canv2 canv3 ctext cflist mainfont textfont uifont tabstop
967 global stuffsaved findmergefiles maxgraphpct
968 global maxwidth showneartags showlocalchanges
969 global viewname viewfiles viewargs viewperm nextviewnum
970 global cmitmode wrapcomment
971 global colors bgcolor fgcolor diffcolors selectbgcolor
973 if {$stuffsaved} return
974 if {![winfo viewable .]} return
975 catch {
976 set f [open "~/.gitk-new" w]
977 puts $f [list set mainfont $mainfont]
978 puts $f [list set textfont $textfont]
979 puts $f [list set uifont $uifont]
980 puts $f [list set tabstop $tabstop]
981 puts $f [list set findmergefiles $findmergefiles]
982 puts $f [list set maxgraphpct $maxgraphpct]
983 puts $f [list set maxwidth $maxwidth]
984 puts $f [list set cmitmode $cmitmode]
985 puts $f [list set wrapcomment $wrapcomment]
986 puts $f [list set showneartags $showneartags]
987 puts $f [list set showlocalchanges $showlocalchanges]
988 puts $f [list set bgcolor $bgcolor]
989 puts $f [list set fgcolor $fgcolor]
990 puts $f [list set colors $colors]
991 puts $f [list set diffcolors $diffcolors]
992 puts $f [list set selectbgcolor $selectbgcolor]
994 puts $f "set geometry(main) [wm geometry .]"
995 puts $f "set geometry(topwidth) [winfo width .tf]"
996 puts $f "set geometry(topheight) [winfo height .tf]"
997 puts $f "set geometry(pwsash0) \"[.tf.histframe.pwclist sash coord 0]\""
998 puts $f "set geometry(pwsash1) \"[.tf.histframe.pwclist sash coord 1]\""
999 puts $f "set geometry(botwidth) [winfo width .bleft]"
1000 puts $f "set geometry(botheight) [winfo height .bleft]"
1002 puts -nonewline $f "set permviews {"
1003 for {set v 0} {$v < $nextviewnum} {incr v} {
1004 if {$viewperm($v)} {
1005 puts $f "{[list $viewname($v) $viewfiles($v) $viewargs($v)]}"
1008 puts $f "}"
1009 close $f
1010 catch {file delete "~/.gitk"}
1011 file rename -force "~/.gitk-new" "~/.gitk"
1013 set stuffsaved 1
1016 proc resizeclistpanes {win w} {
1017 global oldwidth
1018 if {[info exists oldwidth($win)]} {
1019 set s0 [$win sash coord 0]
1020 set s1 [$win sash coord 1]
1021 if {$w < 60} {
1022 set sash0 [expr {int($w/2 - 2)}]
1023 set sash1 [expr {int($w*5/6 - 2)}]
1024 } else {
1025 set factor [expr {1.0 * $w / $oldwidth($win)}]
1026 set sash0 [expr {int($factor * [lindex $s0 0])}]
1027 set sash1 [expr {int($factor * [lindex $s1 0])}]
1028 if {$sash0 < 30} {
1029 set sash0 30
1031 if {$sash1 < $sash0 + 20} {
1032 set sash1 [expr {$sash0 + 20}]
1034 if {$sash1 > $w - 10} {
1035 set sash1 [expr {$w - 10}]
1036 if {$sash0 > $sash1 - 20} {
1037 set sash0 [expr {$sash1 - 20}]
1041 $win sash place 0 $sash0 [lindex $s0 1]
1042 $win sash place 1 $sash1 [lindex $s1 1]
1044 set oldwidth($win) $w
1047 proc resizecdetpanes {win w} {
1048 global oldwidth
1049 if {[info exists oldwidth($win)]} {
1050 set s0 [$win sash coord 0]
1051 if {$w < 60} {
1052 set sash0 [expr {int($w*3/4 - 2)}]
1053 } else {
1054 set factor [expr {1.0 * $w / $oldwidth($win)}]
1055 set sash0 [expr {int($factor * [lindex $s0 0])}]
1056 if {$sash0 < 45} {
1057 set sash0 45
1059 if {$sash0 > $w - 15} {
1060 set sash0 [expr {$w - 15}]
1063 $win sash place 0 $sash0 [lindex $s0 1]
1065 set oldwidth($win) $w
1068 proc allcanvs args {
1069 global canv canv2 canv3
1070 eval $canv $args
1071 eval $canv2 $args
1072 eval $canv3 $args
1075 proc bindall {event action} {
1076 global canv canv2 canv3
1077 bind $canv $event $action
1078 bind $canv2 $event $action
1079 bind $canv3 $event $action
1082 proc about {} {
1083 global uifont
1084 set w .about
1085 if {[winfo exists $w]} {
1086 raise $w
1087 return
1089 toplevel $w
1090 wm title $w "About gitk"
1091 message $w.m -text {
1092 Gitk - a commit viewer for git
1094 Copyright © 2005-2006 Paul Mackerras
1096 Use and redistribute under the terms of the GNU General Public License} \
1097 -justify center -aspect 400 -border 2 -bg white -relief groove
1098 pack $w.m -side top -fill x -padx 2 -pady 2
1099 $w.m configure -font $uifont
1100 button $w.ok -text Close -command "destroy $w" -default active
1101 pack $w.ok -side bottom
1102 $w.ok configure -font $uifont
1103 bind $w <Visibility> "focus $w.ok"
1104 bind $w <Key-Escape> "destroy $w"
1105 bind $w <Key-Return> "destroy $w"
1108 proc keys {} {
1109 global uifont
1110 set w .keys
1111 if {[winfo exists $w]} {
1112 raise $w
1113 return
1115 if {[tk windowingsystem] eq {aqua}} {
1116 set M1T Cmd
1117 } else {
1118 set M1T Ctrl
1120 toplevel $w
1121 wm title $w "Gitk key bindings"
1122 message $w.m -text "
1123 Gitk key bindings:
1125 <$M1T-Q> Quit
1126 <Home> Move to first commit
1127 <End> Move to last commit
1128 <Up>, p, i Move up one commit
1129 <Down>, n, k Move down one commit
1130 <Left>, z, j Go back in history list
1131 <Right>, x, l Go forward in history list
1132 <PageUp> Move up one page in commit list
1133 <PageDown> Move down one page in commit list
1134 <$M1T-Home> Scroll to top of commit list
1135 <$M1T-End> Scroll to bottom of commit list
1136 <$M1T-Up> Scroll commit list up one line
1137 <$M1T-Down> Scroll commit list down one line
1138 <$M1T-PageUp> Scroll commit list up one page
1139 <$M1T-PageDown> Scroll commit list down one page
1140 <Shift-Up> Move to previous highlighted line
1141 <Shift-Down> Move to next highlighted line
1142 <Delete>, b Scroll diff view up one page
1143 <Backspace> Scroll diff view up one page
1144 <Space> Scroll diff view down one page
1145 u Scroll diff view up 18 lines
1146 d Scroll diff view down 18 lines
1147 <$M1T-F> Find
1148 <$M1T-G> Move to next find hit
1149 <Return> Move to next find hit
1150 / Move to next find hit, or redo find
1151 ? Move to previous find hit
1152 f Scroll diff view to next file
1153 <$M1T-S> Search for next hit in diff view
1154 <$M1T-R> Search for previous hit in diff view
1155 <$M1T-KP+> Increase font size
1156 <$M1T-plus> Increase font size
1157 <$M1T-KP-> Decrease font size
1158 <$M1T-minus> Decrease font size
1159 <F5> Update
1161 -justify left -bg white -border 2 -relief groove
1162 pack $w.m -side top -fill both -padx 2 -pady 2
1163 $w.m configure -font $uifont
1164 button $w.ok -text Close -command "destroy $w" -default active
1165 pack $w.ok -side bottom
1166 $w.ok configure -font $uifont
1167 bind $w <Visibility> "focus $w.ok"
1168 bind $w <Key-Escape> "destroy $w"
1169 bind $w <Key-Return> "destroy $w"
1172 # Procedures for manipulating the file list window at the
1173 # bottom right of the overall window.
1175 proc treeview {w l openlevs} {
1176 global treecontents treediropen treeheight treeparent treeindex
1178 set ix 0
1179 set treeindex() 0
1180 set lev 0
1181 set prefix {}
1182 set prefixend -1
1183 set prefendstack {}
1184 set htstack {}
1185 set ht 0
1186 set treecontents() {}
1187 $w conf -state normal
1188 foreach f $l {
1189 while {[string range $f 0 $prefixend] ne $prefix} {
1190 if {$lev <= $openlevs} {
1191 $w mark set e:$treeindex($prefix) "end -1c"
1192 $w mark gravity e:$treeindex($prefix) left
1194 set treeheight($prefix) $ht
1195 incr ht [lindex $htstack end]
1196 set htstack [lreplace $htstack end end]
1197 set prefixend [lindex $prefendstack end]
1198 set prefendstack [lreplace $prefendstack end end]
1199 set prefix [string range $prefix 0 $prefixend]
1200 incr lev -1
1202 set tail [string range $f [expr {$prefixend+1}] end]
1203 while {[set slash [string first "/" $tail]] >= 0} {
1204 lappend htstack $ht
1205 set ht 0
1206 lappend prefendstack $prefixend
1207 incr prefixend [expr {$slash + 1}]
1208 set d [string range $tail 0 $slash]
1209 lappend treecontents($prefix) $d
1210 set oldprefix $prefix
1211 append prefix $d
1212 set treecontents($prefix) {}
1213 set treeindex($prefix) [incr ix]
1214 set treeparent($prefix) $oldprefix
1215 set tail [string range $tail [expr {$slash+1}] end]
1216 if {$lev <= $openlevs} {
1217 set ht 1
1218 set treediropen($prefix) [expr {$lev < $openlevs}]
1219 set bm [expr {$lev == $openlevs? "tri-rt": "tri-dn"}]
1220 $w mark set d:$ix "end -1c"
1221 $w mark gravity d:$ix left
1222 set str "\n"
1223 for {set i 0} {$i < $lev} {incr i} {append str "\t"}
1224 $w insert end $str
1225 $w image create end -align center -image $bm -padx 1 \
1226 -name a:$ix
1227 $w insert end $d [highlight_tag $prefix]
1228 $w mark set s:$ix "end -1c"
1229 $w mark gravity s:$ix left
1231 incr lev
1233 if {$tail ne {}} {
1234 if {$lev <= $openlevs} {
1235 incr ht
1236 set str "\n"
1237 for {set i 0} {$i < $lev} {incr i} {append str "\t"}
1238 $w insert end $str
1239 $w insert end $tail [highlight_tag $f]
1241 lappend treecontents($prefix) $tail
1244 while {$htstack ne {}} {
1245 set treeheight($prefix) $ht
1246 incr ht [lindex $htstack end]
1247 set htstack [lreplace $htstack end end]
1248 set prefixend [lindex $prefendstack end]
1249 set prefendstack [lreplace $prefendstack end end]
1250 set prefix [string range $prefix 0 $prefixend]
1252 $w conf -state disabled
1255 proc linetoelt {l} {
1256 global treeheight treecontents
1258 set y 2
1259 set prefix {}
1260 while {1} {
1261 foreach e $treecontents($prefix) {
1262 if {$y == $l} {
1263 return "$prefix$e"
1265 set n 1
1266 if {[string index $e end] eq "/"} {
1267 set n $treeheight($prefix$e)
1268 if {$y + $n > $l} {
1269 append prefix $e
1270 incr y
1271 break
1274 incr y $n
1279 proc highlight_tree {y prefix} {
1280 global treeheight treecontents cflist
1282 foreach e $treecontents($prefix) {
1283 set path $prefix$e
1284 if {[highlight_tag $path] ne {}} {
1285 $cflist tag add bold $y.0 "$y.0 lineend"
1287 incr y
1288 if {[string index $e end] eq "/" && $treeheight($path) > 1} {
1289 set y [highlight_tree $y $path]
1292 return $y
1295 proc treeclosedir {w dir} {
1296 global treediropen treeheight treeparent treeindex
1298 set ix $treeindex($dir)
1299 $w conf -state normal
1300 $w delete s:$ix e:$ix
1301 set treediropen($dir) 0
1302 $w image configure a:$ix -image tri-rt
1303 $w conf -state disabled
1304 set n [expr {1 - $treeheight($dir)}]
1305 while {$dir ne {}} {
1306 incr treeheight($dir) $n
1307 set dir $treeparent($dir)
1311 proc treeopendir {w dir} {
1312 global treediropen treeheight treeparent treecontents treeindex
1314 set ix $treeindex($dir)
1315 $w conf -state normal
1316 $w image configure a:$ix -image tri-dn
1317 $w mark set e:$ix s:$ix
1318 $w mark gravity e:$ix right
1319 set lev 0
1320 set str "\n"
1321 set n [llength $treecontents($dir)]
1322 for {set x $dir} {$x ne {}} {set x $treeparent($x)} {
1323 incr lev
1324 append str "\t"
1325 incr treeheight($x) $n
1327 foreach e $treecontents($dir) {
1328 set de $dir$e
1329 if {[string index $e end] eq "/"} {
1330 set iy $treeindex($de)
1331 $w mark set d:$iy e:$ix
1332 $w mark gravity d:$iy left
1333 $w insert e:$ix $str
1334 set treediropen($de) 0
1335 $w image create e:$ix -align center -image tri-rt -padx 1 \
1336 -name a:$iy
1337 $w insert e:$ix $e [highlight_tag $de]
1338 $w mark set s:$iy e:$ix
1339 $w mark gravity s:$iy left
1340 set treeheight($de) 1
1341 } else {
1342 $w insert e:$ix $str
1343 $w insert e:$ix $e [highlight_tag $de]
1346 $w mark gravity e:$ix left
1347 $w conf -state disabled
1348 set treediropen($dir) 1
1349 set top [lindex [split [$w index @0,0] .] 0]
1350 set ht [$w cget -height]
1351 set l [lindex [split [$w index s:$ix] .] 0]
1352 if {$l < $top} {
1353 $w yview $l.0
1354 } elseif {$l + $n + 1 > $top + $ht} {
1355 set top [expr {$l + $n + 2 - $ht}]
1356 if {$l < $top} {
1357 set top $l
1359 $w yview $top.0
1363 proc treeclick {w x y} {
1364 global treediropen cmitmode ctext cflist cflist_top
1366 if {$cmitmode ne "tree"} return
1367 if {![info exists cflist_top]} return
1368 set l [lindex [split [$w index "@$x,$y"] "."] 0]
1369 $cflist tag remove highlight $cflist_top.0 "$cflist_top.0 lineend"
1370 $cflist tag add highlight $l.0 "$l.0 lineend"
1371 set cflist_top $l
1372 if {$l == 1} {
1373 $ctext yview 1.0
1374 return
1376 set e [linetoelt $l]
1377 if {[string index $e end] ne "/"} {
1378 showfile $e
1379 } elseif {$treediropen($e)} {
1380 treeclosedir $w $e
1381 } else {
1382 treeopendir $w $e
1386 proc setfilelist {id} {
1387 global treefilelist cflist
1389 treeview $cflist $treefilelist($id) 0
1392 image create bitmap tri-rt -background black -foreground blue -data {
1393 #define tri-rt_width 13
1394 #define tri-rt_height 13
1395 static unsigned char tri-rt_bits[] = {
1396 0x00, 0x00, 0x00, 0x00, 0x10, 0x00, 0x30, 0x00, 0x70, 0x00, 0xf0, 0x00,
1397 0xf0, 0x01, 0xf0, 0x00, 0x70, 0x00, 0x30, 0x00, 0x10, 0x00, 0x00, 0x00,
1398 0x00, 0x00};
1399 } -maskdata {
1400 #define tri-rt-mask_width 13
1401 #define tri-rt-mask_height 13
1402 static unsigned char tri-rt-mask_bits[] = {
1403 0x08, 0x00, 0x18, 0x00, 0x38, 0x00, 0x78, 0x00, 0xf8, 0x00, 0xf8, 0x01,
1404 0xf8, 0x03, 0xf8, 0x01, 0xf8, 0x00, 0x78, 0x00, 0x38, 0x00, 0x18, 0x00,
1405 0x08, 0x00};
1407 image create bitmap tri-dn -background black -foreground blue -data {
1408 #define tri-dn_width 13
1409 #define tri-dn_height 13
1410 static unsigned char tri-dn_bits[] = {
1411 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0xfc, 0x07, 0xf8, 0x03,
1412 0xf0, 0x01, 0xe0, 0x00, 0x40, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
1413 0x00, 0x00};
1414 } -maskdata {
1415 #define tri-dn-mask_width 13
1416 #define tri-dn-mask_height 13
1417 static unsigned char tri-dn-mask_bits[] = {
1418 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0xff, 0x1f, 0xfe, 0x0f, 0xfc, 0x07,
1419 0xf8, 0x03, 0xf0, 0x01, 0xe0, 0x00, 0x40, 0x00, 0x00, 0x00, 0x00, 0x00,
1420 0x00, 0x00};
1423 proc init_flist {first} {
1424 global cflist cflist_top selectedline difffilestart
1426 $cflist conf -state normal
1427 $cflist delete 0.0 end
1428 if {$first ne {}} {
1429 $cflist insert end $first
1430 set cflist_top 1
1431 $cflist tag add highlight 1.0 "1.0 lineend"
1432 } else {
1433 catch {unset cflist_top}
1435 $cflist conf -state disabled
1436 set difffilestart {}
1439 proc highlight_tag {f} {
1440 global highlight_paths
1442 foreach p $highlight_paths {
1443 if {[string match $p $f]} {
1444 return "bold"
1447 return {}
1450 proc highlight_filelist {} {
1451 global cmitmode cflist
1453 $cflist conf -state normal
1454 if {$cmitmode ne "tree"} {
1455 set end [lindex [split [$cflist index end] .] 0]
1456 for {set l 2} {$l < $end} {incr l} {
1457 set line [$cflist get $l.0 "$l.0 lineend"]
1458 if {[highlight_tag $line] ne {}} {
1459 $cflist tag add bold $l.0 "$l.0 lineend"
1462 } else {
1463 highlight_tree 2 {}
1465 $cflist conf -state disabled
1468 proc unhighlight_filelist {} {
1469 global cflist
1471 $cflist conf -state normal
1472 $cflist tag remove bold 1.0 end
1473 $cflist conf -state disabled
1476 proc add_flist {fl} {
1477 global cflist
1479 $cflist conf -state normal
1480 foreach f $fl {
1481 $cflist insert end "\n"
1482 $cflist insert end $f [highlight_tag $f]
1484 $cflist conf -state disabled
1487 proc sel_flist {w x y} {
1488 global ctext difffilestart cflist cflist_top cmitmode
1490 if {$cmitmode eq "tree"} return
1491 if {![info exists cflist_top]} return
1492 set l [lindex [split [$w index "@$x,$y"] "."] 0]
1493 $cflist tag remove highlight $cflist_top.0 "$cflist_top.0 lineend"
1494 $cflist tag add highlight $l.0 "$l.0 lineend"
1495 set cflist_top $l
1496 if {$l == 1} {
1497 $ctext yview 1.0
1498 } else {
1499 catch {$ctext yview [lindex $difffilestart [expr {$l - 2}]]}
1503 # Functions for adding and removing shell-type quoting
1505 proc shellquote {str} {
1506 if {![string match "*\['\"\\ \t]*" $str]} {
1507 return $str
1509 if {![string match "*\['\"\\]*" $str]} {
1510 return "\"$str\""
1512 if {![string match "*'*" $str]} {
1513 return "'$str'"
1515 return "\"[string map {\" \\\" \\ \\\\} $str]\""
1518 proc shellarglist {l} {
1519 set str {}
1520 foreach a $l {
1521 if {$str ne {}} {
1522 append str " "
1524 append str [shellquote $a]
1526 return $str
1529 proc shelldequote {str} {
1530 set ret {}
1531 set used -1
1532 while {1} {
1533 incr used
1534 if {![regexp -start $used -indices "\['\"\\\\ \t]" $str first]} {
1535 append ret [string range $str $used end]
1536 set used [string length $str]
1537 break
1539 set first [lindex $first 0]
1540 set ch [string index $str $first]
1541 if {$first > $used} {
1542 append ret [string range $str $used [expr {$first - 1}]]
1543 set used $first
1545 if {$ch eq " " || $ch eq "\t"} break
1546 incr used
1547 if {$ch eq "'"} {
1548 set first [string first "'" $str $used]
1549 if {$first < 0} {
1550 error "unmatched single-quote"
1552 append ret [string range $str $used [expr {$first - 1}]]
1553 set used $first
1554 continue
1556 if {$ch eq "\\"} {
1557 if {$used >= [string length $str]} {
1558 error "trailing backslash"
1560 append ret [string index $str $used]
1561 continue
1563 # here ch == "\""
1564 while {1} {
1565 if {![regexp -start $used -indices "\[\"\\\\]" $str first]} {
1566 error "unmatched double-quote"
1568 set first [lindex $first 0]
1569 set ch [string index $str $first]
1570 if {$first > $used} {
1571 append ret [string range $str $used [expr {$first - 1}]]
1572 set used $first
1574 if {$ch eq "\""} break
1575 incr used
1576 append ret [string index $str $used]
1577 incr used
1580 return [list $used $ret]
1583 proc shellsplit {str} {
1584 set l {}
1585 while {1} {
1586 set str [string trimleft $str]
1587 if {$str eq {}} break
1588 set dq [shelldequote $str]
1589 set n [lindex $dq 0]
1590 set word [lindex $dq 1]
1591 set str [string range $str $n end]
1592 lappend l $word
1594 return $l
1597 # Code to implement multiple views
1599 proc newview {ishighlight} {
1600 global nextviewnum newviewname newviewperm uifont newishighlight
1601 global newviewargs revtreeargs
1603 set newishighlight $ishighlight
1604 set top .gitkview
1605 if {[winfo exists $top]} {
1606 raise $top
1607 return
1609 set newviewname($nextviewnum) "View $nextviewnum"
1610 set newviewperm($nextviewnum) 0
1611 set newviewargs($nextviewnum) [shellarglist $revtreeargs]
1612 vieweditor $top $nextviewnum "Gitk view definition"
1615 proc editview {} {
1616 global curview
1617 global viewname viewperm newviewname newviewperm
1618 global viewargs newviewargs
1620 set top .gitkvedit-$curview
1621 if {[winfo exists $top]} {
1622 raise $top
1623 return
1625 set newviewname($curview) $viewname($curview)
1626 set newviewperm($curview) $viewperm($curview)
1627 set newviewargs($curview) [shellarglist $viewargs($curview)]
1628 vieweditor $top $curview "Gitk: edit view $viewname($curview)"
1631 proc vieweditor {top n title} {
1632 global newviewname newviewperm viewfiles
1633 global uifont
1635 toplevel $top
1636 wm title $top $title
1637 label $top.nl -text "Name" -font $uifont
1638 entry $top.name -width 20 -textvariable newviewname($n) -font $uifont
1639 grid $top.nl $top.name -sticky w -pady 5
1640 checkbutton $top.perm -text "Remember this view" -variable newviewperm($n) \
1641 -font $uifont
1642 grid $top.perm - -pady 5 -sticky w
1643 message $top.al -aspect 1000 -font $uifont \
1644 -text "Commits to include (arguments to git rev-list):"
1645 grid $top.al - -sticky w -pady 5
1646 entry $top.args -width 50 -textvariable newviewargs($n) \
1647 -background white -font $uifont
1648 grid $top.args - -sticky ew -padx 5
1649 message $top.l -aspect 1000 -font $uifont \
1650 -text "Enter files and directories to include, one per line:"
1651 grid $top.l - -sticky w
1652 text $top.t -width 40 -height 10 -background white -font $uifont
1653 if {[info exists viewfiles($n)]} {
1654 foreach f $viewfiles($n) {
1655 $top.t insert end $f
1656 $top.t insert end "\n"
1658 $top.t delete {end - 1c} end
1659 $top.t mark set insert 0.0
1661 grid $top.t - -sticky ew -padx 5
1662 frame $top.buts
1663 button $top.buts.ok -text "OK" -command [list newviewok $top $n] \
1664 -font $uifont
1665 button $top.buts.can -text "Cancel" -command [list destroy $top] \
1666 -font $uifont
1667 grid $top.buts.ok $top.buts.can
1668 grid columnconfigure $top.buts 0 -weight 1 -uniform a
1669 grid columnconfigure $top.buts 1 -weight 1 -uniform a
1670 grid $top.buts - -pady 10 -sticky ew
1671 focus $top.t
1674 proc doviewmenu {m first cmd op argv} {
1675 set nmenu [$m index end]
1676 for {set i $first} {$i <= $nmenu} {incr i} {
1677 if {[$m entrycget $i -command] eq $cmd} {
1678 eval $m $op $i $argv
1679 break
1684 proc allviewmenus {n op args} {
1685 global viewhlmenu
1687 doviewmenu .bar.view 5 [list showview $n] $op $args
1688 doviewmenu $viewhlmenu 1 [list addvhighlight $n] $op $args
1691 proc newviewok {top n} {
1692 global nextviewnum newviewperm newviewname newishighlight
1693 global viewname viewfiles viewperm selectedview curview
1694 global viewargs newviewargs viewhlmenu
1696 if {[catch {
1697 set newargs [shellsplit $newviewargs($n)]
1698 } err]} {
1699 error_popup "Error in commit selection arguments: $err"
1700 wm raise $top
1701 focus $top
1702 return
1704 set files {}
1705 foreach f [split [$top.t get 0.0 end] "\n"] {
1706 set ft [string trim $f]
1707 if {$ft ne {}} {
1708 lappend files $ft
1711 if {![info exists viewfiles($n)]} {
1712 # creating a new view
1713 incr nextviewnum
1714 set viewname($n) $newviewname($n)
1715 set viewperm($n) $newviewperm($n)
1716 set viewfiles($n) $files
1717 set viewargs($n) $newargs
1718 addviewmenu $n
1719 if {!$newishighlight} {
1720 run showview $n
1721 } else {
1722 run addvhighlight $n
1724 } else {
1725 # editing an existing view
1726 set viewperm($n) $newviewperm($n)
1727 if {$newviewname($n) ne $viewname($n)} {
1728 set viewname($n) $newviewname($n)
1729 doviewmenu .bar.view 5 [list showview $n] \
1730 entryconf [list -label $viewname($n)]
1731 doviewmenu $viewhlmenu 1 [list addvhighlight $n] \
1732 entryconf [list -label $viewname($n) -value $viewname($n)]
1734 if {$files ne $viewfiles($n) || $newargs ne $viewargs($n)} {
1735 set viewfiles($n) $files
1736 set viewargs($n) $newargs
1737 if {$curview == $n} {
1738 run updatecommits
1742 catch {destroy $top}
1745 proc delview {} {
1746 global curview viewdata viewperm hlview selectedhlview
1748 if {$curview == 0} return
1749 if {[info exists hlview] && $hlview == $curview} {
1750 set selectedhlview None
1751 unset hlview
1753 allviewmenus $curview delete
1754 set viewdata($curview) {}
1755 set viewperm($curview) 0
1756 showview 0
1759 proc addviewmenu {n} {
1760 global viewname viewhlmenu
1762 .bar.view add radiobutton -label $viewname($n) \
1763 -command [list showview $n] -variable selectedview -value $n
1764 $viewhlmenu add radiobutton -label $viewname($n) \
1765 -command [list addvhighlight $n] -variable selectedhlview
1768 proc flatten {var} {
1769 global $var
1771 set ret {}
1772 foreach i [array names $var] {
1773 lappend ret $i [set $var\($i\)]
1775 return $ret
1778 proc unflatten {var l} {
1779 global $var
1781 catch {unset $var}
1782 foreach {i v} $l {
1783 set $var\($i\) $v
1787 proc showview {n} {
1788 global curview viewdata viewfiles
1789 global displayorder parentlist rowidlist rowoffsets
1790 global colormap rowtextx commitrow nextcolor canvxmax
1791 global numcommits rowrangelist commitlisted idrowranges rowchk
1792 global selectedline currentid canv canvy0
1793 global treediffs
1794 global pending_select phase
1795 global commitidx rowlaidout rowoptim
1796 global commfd
1797 global selectedview selectfirst
1798 global vparentlist vdisporder vcmitlisted
1799 global hlview selectedhlview
1801 if {$n == $curview} return
1802 set selid {}
1803 if {[info exists selectedline]} {
1804 set selid $currentid
1805 set y [yc $selectedline]
1806 set ymax [lindex [$canv cget -scrollregion] 3]
1807 set span [$canv yview]
1808 set ytop [expr {[lindex $span 0] * $ymax}]
1809 set ybot [expr {[lindex $span 1] * $ymax}]
1810 if {$ytop < $y && $y < $ybot} {
1811 set yscreen [expr {$y - $ytop}]
1812 } else {
1813 set yscreen [expr {($ybot - $ytop) / 2}]
1815 } elseif {[info exists pending_select]} {
1816 set selid $pending_select
1817 unset pending_select
1819 unselectline
1820 normalline
1821 if {$curview >= 0} {
1822 set vparentlist($curview) $parentlist
1823 set vdisporder($curview) $displayorder
1824 set vcmitlisted($curview) $commitlisted
1825 if {$phase ne {}} {
1826 set viewdata($curview) \
1827 [list $phase $rowidlist $rowoffsets $rowrangelist \
1828 [flatten idrowranges] [flatten idinlist] \
1829 $rowlaidout $rowoptim $numcommits]
1830 } elseif {![info exists viewdata($curview)]
1831 || [lindex $viewdata($curview) 0] ne {}} {
1832 set viewdata($curview) \
1833 [list {} $rowidlist $rowoffsets $rowrangelist]
1836 catch {unset treediffs}
1837 clear_display
1838 if {[info exists hlview] && $hlview == $n} {
1839 unset hlview
1840 set selectedhlview None
1843 set curview $n
1844 set selectedview $n
1845 .bar.view entryconf Edit* -state [expr {$n == 0? "disabled": "normal"}]
1846 .bar.view entryconf Delete* -state [expr {$n == 0? "disabled": "normal"}]
1848 if {![info exists viewdata($n)]} {
1849 if {$selid ne {}} {
1850 set pending_select $selid
1852 getcommits
1853 return
1856 set v $viewdata($n)
1857 set phase [lindex $v 0]
1858 set displayorder $vdisporder($n)
1859 set parentlist $vparentlist($n)
1860 set commitlisted $vcmitlisted($n)
1861 set rowidlist [lindex $v 1]
1862 set rowoffsets [lindex $v 2]
1863 set rowrangelist [lindex $v 3]
1864 if {$phase eq {}} {
1865 set numcommits [llength $displayorder]
1866 catch {unset idrowranges}
1867 } else {
1868 unflatten idrowranges [lindex $v 4]
1869 unflatten idinlist [lindex $v 5]
1870 set rowlaidout [lindex $v 6]
1871 set rowoptim [lindex $v 7]
1872 set numcommits [lindex $v 8]
1873 catch {unset rowchk}
1876 catch {unset colormap}
1877 catch {unset rowtextx}
1878 set nextcolor 0
1879 set canvxmax [$canv cget -width]
1880 set curview $n
1881 set row 0
1882 setcanvscroll
1883 set yf 0
1884 set row {}
1885 set selectfirst 0
1886 if {$selid ne {} && [info exists commitrow($n,$selid)]} {
1887 set row $commitrow($n,$selid)
1888 # try to get the selected row in the same position on the screen
1889 set ymax [lindex [$canv cget -scrollregion] 3]
1890 set ytop [expr {[yc $row] - $yscreen}]
1891 if {$ytop < 0} {
1892 set ytop 0
1894 set yf [expr {$ytop * 1.0 / $ymax}]
1896 allcanvs yview moveto $yf
1897 drawvisible
1898 if {$row ne {}} {
1899 selectline $row 0
1900 } elseif {$selid ne {}} {
1901 set pending_select $selid
1902 } else {
1903 set row [first_real_row]
1904 if {$row < $numcommits} {
1905 selectline $row 0
1906 } else {
1907 set selectfirst 1
1910 if {$phase ne {}} {
1911 if {$phase eq "getcommits"} {
1912 show_status "Reading commits..."
1914 run chewcommits $n
1915 } elseif {$numcommits == 0} {
1916 show_status "No commits selected"
1920 # Stuff relating to the highlighting facility
1922 proc ishighlighted {row} {
1923 global vhighlights fhighlights nhighlights rhighlights
1925 if {[info exists nhighlights($row)] && $nhighlights($row) > 0} {
1926 return $nhighlights($row)
1928 if {[info exists vhighlights($row)] && $vhighlights($row) > 0} {
1929 return $vhighlights($row)
1931 if {[info exists fhighlights($row)] && $fhighlights($row) > 0} {
1932 return $fhighlights($row)
1934 if {[info exists rhighlights($row)] && $rhighlights($row) > 0} {
1935 return $rhighlights($row)
1937 return 0
1940 proc bolden {row font} {
1941 global canv linehtag selectedline boldrows
1943 lappend boldrows $row
1944 $canv itemconf $linehtag($row) -font $font
1945 if {[info exists selectedline] && $row == $selectedline} {
1946 $canv delete secsel
1947 set t [eval $canv create rect [$canv bbox $linehtag($row)] \
1948 -outline {{}} -tags secsel \
1949 -fill [$canv cget -selectbackground]]
1950 $canv lower $t
1954 proc bolden_name {row font} {
1955 global canv2 linentag selectedline boldnamerows
1957 lappend boldnamerows $row
1958 $canv2 itemconf $linentag($row) -font $font
1959 if {[info exists selectedline] && $row == $selectedline} {
1960 $canv2 delete secsel
1961 set t [eval $canv2 create rect [$canv2 bbox $linentag($row)] \
1962 -outline {{}} -tags secsel \
1963 -fill [$canv2 cget -selectbackground]]
1964 $canv2 lower $t
1968 proc unbolden {} {
1969 global mainfont boldrows
1971 set stillbold {}
1972 foreach row $boldrows {
1973 if {![ishighlighted $row]} {
1974 bolden $row $mainfont
1975 } else {
1976 lappend stillbold $row
1979 set boldrows $stillbold
1982 proc addvhighlight {n} {
1983 global hlview curview viewdata vhl_done vhighlights commitidx
1985 if {[info exists hlview]} {
1986 delvhighlight
1988 set hlview $n
1989 if {$n != $curview && ![info exists viewdata($n)]} {
1990 set viewdata($n) [list getcommits {{}} {{}} {} {} {} 0 0 0 {}]
1991 set vparentlist($n) {}
1992 set vdisporder($n) {}
1993 set vcmitlisted($n) {}
1994 start_rev_list $n
1996 set vhl_done $commitidx($hlview)
1997 if {$vhl_done > 0} {
1998 drawvisible
2002 proc delvhighlight {} {
2003 global hlview vhighlights
2005 if {![info exists hlview]} return
2006 unset hlview
2007 catch {unset vhighlights}
2008 unbolden
2011 proc vhighlightmore {} {
2012 global hlview vhl_done commitidx vhighlights
2013 global displayorder vdisporder curview mainfont
2015 set font [concat $mainfont bold]
2016 set max $commitidx($hlview)
2017 if {$hlview == $curview} {
2018 set disp $displayorder
2019 } else {
2020 set disp $vdisporder($hlview)
2022 set vr [visiblerows]
2023 set r0 [lindex $vr 0]
2024 set r1 [lindex $vr 1]
2025 for {set i $vhl_done} {$i < $max} {incr i} {
2026 set id [lindex $disp $i]
2027 if {[info exists commitrow($curview,$id)]} {
2028 set row $commitrow($curview,$id)
2029 if {$r0 <= $row && $row <= $r1} {
2030 if {![highlighted $row]} {
2031 bolden $row $font
2033 set vhighlights($row) 1
2037 set vhl_done $max
2040 proc askvhighlight {row id} {
2041 global hlview vhighlights commitrow iddrawn mainfont
2043 if {[info exists commitrow($hlview,$id)]} {
2044 if {[info exists iddrawn($id)] && ![ishighlighted $row]} {
2045 bolden $row [concat $mainfont bold]
2047 set vhighlights($row) 1
2048 } else {
2049 set vhighlights($row) 0
2053 proc hfiles_change {name ix op} {
2054 global highlight_files filehighlight fhighlights fh_serial
2055 global mainfont highlight_paths
2057 if {[info exists filehighlight]} {
2058 # delete previous highlights
2059 catch {close $filehighlight}
2060 unset filehighlight
2061 catch {unset fhighlights}
2062 unbolden
2063 unhighlight_filelist
2065 set highlight_paths {}
2066 after cancel do_file_hl $fh_serial
2067 incr fh_serial
2068 if {$highlight_files ne {}} {
2069 after 300 do_file_hl $fh_serial
2073 proc makepatterns {l} {
2074 set ret {}
2075 foreach e $l {
2076 set ee [string map {"*" "\\*" "?" "\\?" "\[" "\\\[" "\\" "\\\\"} $e]
2077 if {[string index $ee end] eq "/"} {
2078 lappend ret "$ee*"
2079 } else {
2080 lappend ret $ee
2081 lappend ret "$ee/*"
2084 return $ret
2087 proc do_file_hl {serial} {
2088 global highlight_files filehighlight highlight_paths gdttype fhl_list
2090 if {$gdttype eq "touching paths:"} {
2091 if {[catch {set paths [shellsplit $highlight_files]}]} return
2092 set highlight_paths [makepatterns $paths]
2093 highlight_filelist
2094 set gdtargs [concat -- $paths]
2095 } else {
2096 set gdtargs [list "-S$highlight_files"]
2098 set cmd [concat | git diff-tree -r -s --stdin $gdtargs]
2099 set filehighlight [open $cmd r+]
2100 fconfigure $filehighlight -blocking 0
2101 filerun $filehighlight readfhighlight
2102 set fhl_list {}
2103 drawvisible
2104 flushhighlights
2107 proc flushhighlights {} {
2108 global filehighlight fhl_list
2110 if {[info exists filehighlight]} {
2111 lappend fhl_list {}
2112 puts $filehighlight ""
2113 flush $filehighlight
2117 proc askfilehighlight {row id} {
2118 global filehighlight fhighlights fhl_list
2120 lappend fhl_list $id
2121 set fhighlights($row) -1
2122 puts $filehighlight $id
2125 proc readfhighlight {} {
2126 global filehighlight fhighlights commitrow curview mainfont iddrawn
2127 global fhl_list
2129 if {![info exists filehighlight]} {
2130 return 0
2132 set nr 0
2133 while {[incr nr] <= 100 && [gets $filehighlight line] >= 0} {
2134 set line [string trim $line]
2135 set i [lsearch -exact $fhl_list $line]
2136 if {$i < 0} continue
2137 for {set j 0} {$j < $i} {incr j} {
2138 set id [lindex $fhl_list $j]
2139 if {[info exists commitrow($curview,$id)]} {
2140 set fhighlights($commitrow($curview,$id)) 0
2143 set fhl_list [lrange $fhl_list [expr {$i+1}] end]
2144 if {$line eq {}} continue
2145 if {![info exists commitrow($curview,$line)]} continue
2146 set row $commitrow($curview,$line)
2147 if {[info exists iddrawn($line)] && ![ishighlighted $row]} {
2148 bolden $row [concat $mainfont bold]
2150 set fhighlights($row) 1
2152 if {[eof $filehighlight]} {
2153 # strange...
2154 puts "oops, git diff-tree died"
2155 catch {close $filehighlight}
2156 unset filehighlight
2157 return 0
2159 next_hlcont
2160 return 1
2163 proc find_change {name ix op} {
2164 global nhighlights mainfont boldnamerows
2165 global findstring findpattern findtype
2167 # delete previous highlights, if any
2168 foreach row $boldnamerows {
2169 bolden_name $row $mainfont
2171 set boldnamerows {}
2172 catch {unset nhighlights}
2173 unbolden
2174 unmarkmatches
2175 if {$findtype ne "Regexp"} {
2176 set e [string map {"*" "\\*" "?" "\\?" "\[" "\\\[" "\\" "\\\\"} \
2177 $findstring]
2178 set findpattern "*$e*"
2180 drawvisible
2183 proc doesmatch {f} {
2184 global findtype findstring findpattern
2186 if {$findtype eq "Regexp"} {
2187 return [regexp $findstring $f]
2188 } elseif {$findtype eq "IgnCase"} {
2189 return [string match -nocase $findpattern $f]
2190 } else {
2191 return [string match $findpattern $f]
2195 proc askfindhighlight {row id} {
2196 global nhighlights commitinfo iddrawn mainfont
2197 global findloc
2198 global markingmatches
2200 if {![info exists commitinfo($id)]} {
2201 getcommit $id
2203 set info $commitinfo($id)
2204 set isbold 0
2205 set fldtypes {Headline Author Date Committer CDate Comments}
2206 foreach f $info ty $fldtypes {
2207 if {($findloc eq "All fields" || $findloc eq $ty) &&
2208 [doesmatch $f]} {
2209 if {$ty eq "Author"} {
2210 set isbold 2
2211 break
2213 set isbold 1
2216 if {$isbold && [info exists iddrawn($id)]} {
2217 set f [concat $mainfont bold]
2218 if {![ishighlighted $row]} {
2219 bolden $row $f
2220 if {$isbold > 1} {
2221 bolden_name $row $f
2224 if {$markingmatches} {
2225 markrowmatches $row $id
2228 set nhighlights($row) $isbold
2231 proc markrowmatches {row id} {
2232 global canv canv2 linehtag linentag commitinfo findloc
2234 set headline [lindex $commitinfo($id) 0]
2235 set author [lindex $commitinfo($id) 1]
2236 $canv delete match$row
2237 $canv2 delete match$row
2238 if {$findloc eq "All fields" || $findloc eq "Headline"} {
2239 set m [findmatches $headline]
2240 if {$m ne {}} {
2241 markmatches $canv $row $headline $linehtag($row) $m \
2242 [$canv itemcget $linehtag($row) -font] $row
2245 if {$findloc eq "All fields" || $findloc eq "Author"} {
2246 set m [findmatches $author]
2247 if {$m ne {}} {
2248 markmatches $canv2 $row $author $linentag($row) $m \
2249 [$canv2 itemcget $linentag($row) -font] $row
2254 proc vrel_change {name ix op} {
2255 global highlight_related
2257 rhighlight_none
2258 if {$highlight_related ne "None"} {
2259 run drawvisible
2263 # prepare for testing whether commits are descendents or ancestors of a
2264 proc rhighlight_sel {a} {
2265 global descendent desc_todo ancestor anc_todo
2266 global highlight_related rhighlights
2268 catch {unset descendent}
2269 set desc_todo [list $a]
2270 catch {unset ancestor}
2271 set anc_todo [list $a]
2272 if {$highlight_related ne "None"} {
2273 rhighlight_none
2274 run drawvisible
2278 proc rhighlight_none {} {
2279 global rhighlights
2281 catch {unset rhighlights}
2282 unbolden
2285 proc is_descendent {a} {
2286 global curview children commitrow descendent desc_todo
2288 set v $curview
2289 set la $commitrow($v,$a)
2290 set todo $desc_todo
2291 set leftover {}
2292 set done 0
2293 for {set i 0} {$i < [llength $todo]} {incr i} {
2294 set do [lindex $todo $i]
2295 if {$commitrow($v,$do) < $la} {
2296 lappend leftover $do
2297 continue
2299 foreach nk $children($v,$do) {
2300 if {![info exists descendent($nk)]} {
2301 set descendent($nk) 1
2302 lappend todo $nk
2303 if {$nk eq $a} {
2304 set done 1
2308 if {$done} {
2309 set desc_todo [concat $leftover [lrange $todo [expr {$i+1}] end]]
2310 return
2313 set descendent($a) 0
2314 set desc_todo $leftover
2317 proc is_ancestor {a} {
2318 global curview parentlist commitrow ancestor anc_todo
2320 set v $curview
2321 set la $commitrow($v,$a)
2322 set todo $anc_todo
2323 set leftover {}
2324 set done 0
2325 for {set i 0} {$i < [llength $todo]} {incr i} {
2326 set do [lindex $todo $i]
2327 if {![info exists commitrow($v,$do)] || $commitrow($v,$do) > $la} {
2328 lappend leftover $do
2329 continue
2331 foreach np [lindex $parentlist $commitrow($v,$do)] {
2332 if {![info exists ancestor($np)]} {
2333 set ancestor($np) 1
2334 lappend todo $np
2335 if {$np eq $a} {
2336 set done 1
2340 if {$done} {
2341 set anc_todo [concat $leftover [lrange $todo [expr {$i+1}] end]]
2342 return
2345 set ancestor($a) 0
2346 set anc_todo $leftover
2349 proc askrelhighlight {row id} {
2350 global descendent highlight_related iddrawn mainfont rhighlights
2351 global selectedline ancestor
2353 if {![info exists selectedline]} return
2354 set isbold 0
2355 if {$highlight_related eq "Descendent" ||
2356 $highlight_related eq "Not descendent"} {
2357 if {![info exists descendent($id)]} {
2358 is_descendent $id
2360 if {$descendent($id) == ($highlight_related eq "Descendent")} {
2361 set isbold 1
2363 } elseif {$highlight_related eq "Ancestor" ||
2364 $highlight_related eq "Not ancestor"} {
2365 if {![info exists ancestor($id)]} {
2366 is_ancestor $id
2368 if {$ancestor($id) == ($highlight_related eq "Ancestor")} {
2369 set isbold 1
2372 if {[info exists iddrawn($id)]} {
2373 if {$isbold && ![ishighlighted $row]} {
2374 bolden $row [concat $mainfont bold]
2377 set rhighlights($row) $isbold
2380 proc next_hlcont {} {
2381 global fhl_row fhl_dirn displayorder numcommits
2382 global vhighlights fhighlights nhighlights rhighlights
2383 global hlview filehighlight findstring highlight_related
2385 if {![info exists fhl_dirn] || $fhl_dirn == 0} return
2386 set row $fhl_row
2387 while {1} {
2388 if {$row < 0 || $row >= $numcommits} {
2389 bell
2390 set fhl_dirn 0
2391 return
2393 set id [lindex $displayorder $row]
2394 if {[info exists hlview]} {
2395 if {![info exists vhighlights($row)]} {
2396 askvhighlight $row $id
2398 if {$vhighlights($row) > 0} break
2400 if {$findstring ne {}} {
2401 if {![info exists nhighlights($row)]} {
2402 askfindhighlight $row $id
2404 if {$nhighlights($row) > 0} break
2406 if {$highlight_related ne "None"} {
2407 if {![info exists rhighlights($row)]} {
2408 askrelhighlight $row $id
2410 if {$rhighlights($row) > 0} break
2412 if {[info exists filehighlight]} {
2413 if {![info exists fhighlights($row)]} {
2414 # ask for a few more while we're at it...
2415 set r $row
2416 for {set n 0} {$n < 100} {incr n} {
2417 if {![info exists fhighlights($r)]} {
2418 askfilehighlight $r [lindex $displayorder $r]
2420 incr r $fhl_dirn
2421 if {$r < 0 || $r >= $numcommits} break
2423 flushhighlights
2425 if {$fhighlights($row) < 0} {
2426 set fhl_row $row
2427 return
2429 if {$fhighlights($row) > 0} break
2431 incr row $fhl_dirn
2433 set fhl_dirn 0
2434 selectline $row 1
2437 proc next_highlight {dirn} {
2438 global selectedline fhl_row fhl_dirn
2439 global hlview filehighlight findstring highlight_related
2441 if {![info exists selectedline]} return
2442 if {!([info exists hlview] || $findstring ne {} ||
2443 $highlight_related ne "None" || [info exists filehighlight])} return
2444 set fhl_row [expr {$selectedline + $dirn}]
2445 set fhl_dirn $dirn
2446 next_hlcont
2449 proc cancel_next_highlight {} {
2450 global fhl_dirn
2452 set fhl_dirn 0
2455 # Graph layout functions
2457 proc shortids {ids} {
2458 set res {}
2459 foreach id $ids {
2460 if {[llength $id] > 1} {
2461 lappend res [shortids $id]
2462 } elseif {[regexp {^[0-9a-f]{40}$} $id]} {
2463 lappend res [string range $id 0 7]
2464 } else {
2465 lappend res $id
2468 return $res
2471 proc incrange {l x o} {
2472 set n [llength $l]
2473 while {$x < $n} {
2474 set e [lindex $l $x]
2475 if {$e ne {}} {
2476 lset l $x [expr {$e + $o}]
2478 incr x
2480 return $l
2483 proc ntimes {n o} {
2484 set ret {}
2485 for {} {$n > 0} {incr n -1} {
2486 lappend ret $o
2488 return $ret
2491 proc usedinrange {id l1 l2} {
2492 global children commitrow curview
2494 if {[info exists commitrow($curview,$id)]} {
2495 set r $commitrow($curview,$id)
2496 if {$l1 <= $r && $r <= $l2} {
2497 return [expr {$r - $l1 + 1}]
2500 set kids $children($curview,$id)
2501 foreach c $kids {
2502 set r $commitrow($curview,$c)
2503 if {$l1 <= $r && $r <= $l2} {
2504 return [expr {$r - $l1 + 1}]
2507 return 0
2510 proc sanity {row {full 0}} {
2511 global rowidlist rowoffsets
2513 set col -1
2514 set ids [lindex $rowidlist $row]
2515 foreach id $ids {
2516 incr col
2517 if {$id eq {}} continue
2518 if {$col < [llength $ids] - 1 &&
2519 [lsearch -exact -start [expr {$col+1}] $ids $id] >= 0} {
2520 puts "oops: [shortids $id] repeated in row $row col $col: {[shortids [lindex $rowidlist $row]]}"
2522 set o [lindex $rowoffsets $row $col]
2523 set y $row
2524 set x $col
2525 while {$o ne {}} {
2526 incr y -1
2527 incr x $o
2528 if {[lindex $rowidlist $y $x] != $id} {
2529 puts "oops: rowoffsets wrong at row [expr {$y+1}] col [expr {$x-$o}]"
2530 puts " id=[shortids $id] check started at row $row"
2531 for {set i $row} {$i >= $y} {incr i -1} {
2532 puts " row $i ids={[shortids [lindex $rowidlist $i]]} offs={[lindex $rowoffsets $i]}"
2534 break
2536 if {!$full} break
2537 set o [lindex $rowoffsets $y $x]
2542 proc makeuparrow {oid x y z} {
2543 global rowidlist rowoffsets uparrowlen idrowranges displayorder
2545 for {set i 1} {$i < $uparrowlen && $y > 1} {incr i} {
2546 incr y -1
2547 incr x $z
2548 set off0 [lindex $rowoffsets $y]
2549 for {set x0 $x} {1} {incr x0} {
2550 if {$x0 >= [llength $off0]} {
2551 set x0 [llength [lindex $rowoffsets [expr {$y-1}]]]
2552 break
2554 set z [lindex $off0 $x0]
2555 if {$z ne {}} {
2556 incr x0 $z
2557 break
2560 set z [expr {$x0 - $x}]
2561 lset rowidlist $y [linsert [lindex $rowidlist $y] $x $oid]
2562 lset rowoffsets $y [linsert [lindex $rowoffsets $y] $x $z]
2564 set tmp [lreplace [lindex $rowoffsets $y] $x $x {}]
2565 lset rowoffsets $y [incrange $tmp [expr {$x+1}] -1]
2566 lappend idrowranges($oid) [lindex $displayorder $y]
2569 proc initlayout {} {
2570 global rowidlist rowoffsets displayorder commitlisted
2571 global rowlaidout rowoptim
2572 global idinlist rowchk rowrangelist idrowranges
2573 global numcommits canvxmax canv
2574 global nextcolor
2575 global parentlist
2576 global colormap rowtextx
2577 global selectfirst
2579 set numcommits 0
2580 set displayorder {}
2581 set commitlisted {}
2582 set parentlist {}
2583 set rowrangelist {}
2584 set nextcolor 0
2585 set rowidlist {{}}
2586 set rowoffsets {{}}
2587 catch {unset idinlist}
2588 catch {unset rowchk}
2589 set rowlaidout 0
2590 set rowoptim 0
2591 set canvxmax [$canv cget -width]
2592 catch {unset colormap}
2593 catch {unset rowtextx}
2594 catch {unset idrowranges}
2595 set selectfirst 1
2598 proc setcanvscroll {} {
2599 global canv canv2 canv3 numcommits linespc canvxmax canvy0
2601 set ymax [expr {$canvy0 + ($numcommits - 0.5) * $linespc + 2}]
2602 $canv conf -scrollregion [list 0 0 $canvxmax $ymax]
2603 $canv2 conf -scrollregion [list 0 0 0 $ymax]
2604 $canv3 conf -scrollregion [list 0 0 0 $ymax]
2607 proc visiblerows {} {
2608 global canv numcommits linespc
2610 set ymax [lindex [$canv cget -scrollregion] 3]
2611 if {$ymax eq {} || $ymax == 0} return
2612 set f [$canv yview]
2613 set y0 [expr {int([lindex $f 0] * $ymax)}]
2614 set r0 [expr {int(($y0 - 3) / $linespc) - 1}]
2615 if {$r0 < 0} {
2616 set r0 0
2618 set y1 [expr {int([lindex $f 1] * $ymax)}]
2619 set r1 [expr {int(($y1 - 3) / $linespc) + 1}]
2620 if {$r1 >= $numcommits} {
2621 set r1 [expr {$numcommits - 1}]
2623 return [list $r0 $r1]
2626 proc layoutmore {tmax allread} {
2627 global rowlaidout rowoptim commitidx numcommits optim_delay
2628 global uparrowlen curview rowidlist idinlist
2630 set showlast 0
2631 set showdelay $optim_delay
2632 set optdelay [expr {$uparrowlen + 1}]
2633 while {1} {
2634 if {$rowoptim - $showdelay > $numcommits} {
2635 showstuff [expr {$rowoptim - $showdelay}] $showlast
2636 } elseif {$rowlaidout - $optdelay > $rowoptim} {
2637 set nr [expr {$rowlaidout - $optdelay - $rowoptim}]
2638 if {$nr > 100} {
2639 set nr 100
2641 optimize_rows $rowoptim 0 [expr {$rowoptim + $nr}]
2642 incr rowoptim $nr
2643 } elseif {$commitidx($curview) > $rowlaidout} {
2644 set nr [expr {$commitidx($curview) - $rowlaidout}]
2645 # may need to increase this threshold if uparrowlen or
2646 # mingaplen are increased...
2647 if {$nr > 150} {
2648 set nr 150
2650 set row $rowlaidout
2651 set rowlaidout [layoutrows $row [expr {$row + $nr}] $allread]
2652 if {$rowlaidout == $row} {
2653 return 0
2655 } elseif {$allread} {
2656 set optdelay 0
2657 set nrows $commitidx($curview)
2658 if {[lindex $rowidlist $nrows] ne {} ||
2659 [array names idinlist] ne {}} {
2660 layouttail
2661 set rowlaidout $commitidx($curview)
2662 } elseif {$rowoptim == $nrows} {
2663 set showdelay 0
2664 set showlast 1
2665 if {$numcommits == $nrows} {
2666 return 0
2669 } else {
2670 return 0
2672 if {$tmax ne {} && [clock clicks -milliseconds] >= $tmax} {
2673 return 1
2678 proc showstuff {canshow last} {
2679 global numcommits commitrow pending_select selectedline curview
2680 global lookingforhead mainheadid displayorder selectfirst
2681 global lastscrollset
2683 if {$numcommits == 0} {
2684 global phase
2685 set phase "incrdraw"
2686 allcanvs delete all
2688 set r0 $numcommits
2689 set prev $numcommits
2690 set numcommits $canshow
2691 set t [clock clicks -milliseconds]
2692 if {$prev < 100 || $last || $t - $lastscrollset > 500} {
2693 set lastscrollset $t
2694 setcanvscroll
2696 set rows [visiblerows]
2697 set r1 [lindex $rows 1]
2698 if {$r1 >= $canshow} {
2699 set r1 [expr {$canshow - 1}]
2701 if {$r0 <= $r1} {
2702 drawcommits $r0 $r1
2704 if {[info exists pending_select] &&
2705 [info exists commitrow($curview,$pending_select)] &&
2706 $commitrow($curview,$pending_select) < $numcommits} {
2707 selectline $commitrow($curview,$pending_select) 1
2709 if {$selectfirst} {
2710 if {[info exists selectedline] || [info exists pending_select]} {
2711 set selectfirst 0
2712 } else {
2713 set l [first_real_row]
2714 selectline $l 1
2715 set selectfirst 0
2718 if {$lookingforhead && [info exists commitrow($curview,$mainheadid)]
2719 && ($last || $commitrow($curview,$mainheadid) < $numcommits - 1)} {
2720 set lookingforhead 0
2721 dodiffindex
2725 proc doshowlocalchanges {} {
2726 global lookingforhead curview mainheadid phase commitrow
2728 if {[info exists commitrow($curview,$mainheadid)] &&
2729 ($phase eq {} || $commitrow($curview,$mainheadid) < $numcommits - 1)} {
2730 dodiffindex
2731 } elseif {$phase ne {}} {
2732 set lookingforhead 1
2736 proc dohidelocalchanges {} {
2737 global lookingforhead localfrow localirow lserial
2739 set lookingforhead 0
2740 if {$localfrow >= 0} {
2741 removerow $localfrow
2742 set localfrow -1
2743 if {$localirow > 0} {
2744 incr localirow -1
2747 if {$localirow >= 0} {
2748 removerow $localirow
2749 set localirow -1
2751 incr lserial
2754 # spawn off a process to do git diff-index --cached HEAD
2755 proc dodiffindex {} {
2756 global localirow localfrow lserial
2758 incr lserial
2759 set localfrow -1
2760 set localirow -1
2761 set fd [open "|git diff-index --cached HEAD" r]
2762 fconfigure $fd -blocking 0
2763 filerun $fd [list readdiffindex $fd $lserial]
2766 proc readdiffindex {fd serial} {
2767 global localirow commitrow mainheadid nullid2 curview
2768 global commitinfo commitdata lserial
2770 set isdiff 1
2771 if {[gets $fd line] < 0} {
2772 if {![eof $fd]} {
2773 return 1
2775 set isdiff 0
2777 # we only need to see one line and we don't really care what it says...
2778 close $fd
2780 # now see if there are any local changes not checked in to the index
2781 if {$serial == $lserial} {
2782 set fd [open "|git diff-files" r]
2783 fconfigure $fd -blocking 0
2784 filerun $fd [list readdifffiles $fd $serial]
2787 if {$isdiff && $serial == $lserial && $localirow == -1} {
2788 # add the line for the changes in the index to the graph
2789 set localirow $commitrow($curview,$mainheadid)
2790 set hl "Local changes checked in to index but not committed"
2791 set commitinfo($nullid2) [list $hl {} {} {} {} " $hl\n"]
2792 set commitdata($nullid2) "\n $hl\n"
2793 insertrow $localirow $nullid2
2795 return 0
2798 proc readdifffiles {fd serial} {
2799 global localirow localfrow commitrow mainheadid nullid curview
2800 global commitinfo commitdata lserial
2802 set isdiff 1
2803 if {[gets $fd line] < 0} {
2804 if {![eof $fd]} {
2805 return 1
2807 set isdiff 0
2809 # we only need to see one line and we don't really care what it says...
2810 close $fd
2812 if {$isdiff && $serial == $lserial && $localfrow == -1} {
2813 # add the line for the local diff to the graph
2814 if {$localirow >= 0} {
2815 set localfrow $localirow
2816 incr localirow
2817 } else {
2818 set localfrow $commitrow($curview,$mainheadid)
2820 set hl "Local uncommitted changes, not checked in to index"
2821 set commitinfo($nullid) [list $hl {} {} {} {} " $hl\n"]
2822 set commitdata($nullid) "\n $hl\n"
2823 insertrow $localfrow $nullid
2825 return 0
2828 proc layoutrows {row endrow last} {
2829 global rowidlist rowoffsets displayorder
2830 global uparrowlen downarrowlen maxwidth mingaplen
2831 global children parentlist
2832 global idrowranges
2833 global commitidx curview
2834 global idinlist rowchk rowrangelist
2836 set idlist [lindex $rowidlist $row]
2837 set offs [lindex $rowoffsets $row]
2838 while {$row < $endrow} {
2839 set id [lindex $displayorder $row]
2840 set oldolds {}
2841 set newolds {}
2842 foreach p [lindex $parentlist $row] {
2843 if {![info exists idinlist($p)]} {
2844 lappend newolds $p
2845 } elseif {!$idinlist($p)} {
2846 lappend oldolds $p
2849 set nev [expr {[llength $idlist] + [llength $newolds]
2850 + [llength $oldolds] - $maxwidth + 1}]
2851 if {$nev > 0} {
2852 if {!$last &&
2853 $row + $uparrowlen + $mingaplen >= $commitidx($curview)} break
2854 for {set x [llength $idlist]} {[incr x -1] >= 0} {} {
2855 set i [lindex $idlist $x]
2856 if {![info exists rowchk($i)] || $row >= $rowchk($i)} {
2857 set r [usedinrange $i [expr {$row - $downarrowlen}] \
2858 [expr {$row + $uparrowlen + $mingaplen}]]
2859 if {$r == 0} {
2860 set idlist [lreplace $idlist $x $x]
2861 set offs [lreplace $offs $x $x]
2862 set offs [incrange $offs $x 1]
2863 set idinlist($i) 0
2864 set rm1 [expr {$row - 1}]
2865 lappend idrowranges($i) [lindex $displayorder $rm1]
2866 if {[incr nev -1] <= 0} break
2867 continue
2869 set rowchk($id) [expr {$row + $r}]
2872 lset rowidlist $row $idlist
2873 lset rowoffsets $row $offs
2875 set col [lsearch -exact $idlist $id]
2876 if {$col < 0} {
2877 set col [llength $idlist]
2878 lappend idlist $id
2879 lset rowidlist $row $idlist
2880 set z {}
2881 if {$children($curview,$id) ne {}} {
2882 set z [expr {[llength [lindex $rowidlist [expr {$row-1}]]] - $col}]
2883 unset idinlist($id)
2885 lappend offs $z
2886 lset rowoffsets $row $offs
2887 if {$z ne {}} {
2888 makeuparrow $id $col $row $z
2890 } else {
2891 unset idinlist($id)
2893 set ranges {}
2894 if {[info exists idrowranges($id)]} {
2895 set ranges $idrowranges($id)
2896 lappend ranges $id
2897 unset idrowranges($id)
2899 lappend rowrangelist $ranges
2900 incr row
2901 set offs [ntimes [llength $idlist] 0]
2902 set l [llength $newolds]
2903 set idlist [eval lreplace \$idlist $col $col $newolds]
2904 set o 0
2905 if {$l != 1} {
2906 set offs [lrange $offs 0 [expr {$col - 1}]]
2907 foreach x $newolds {
2908 lappend offs {}
2909 incr o -1
2911 incr o
2912 set tmp [expr {[llength $idlist] - [llength $offs]}]
2913 if {$tmp > 0} {
2914 set offs [concat $offs [ntimes $tmp $o]]
2916 } else {
2917 lset offs $col {}
2919 foreach i $newolds {
2920 set idinlist($i) 1
2921 set idrowranges($i) $id
2923 incr col $l
2924 foreach oid $oldolds {
2925 set idinlist($oid) 1
2926 set idlist [linsert $idlist $col $oid]
2927 set offs [linsert $offs $col $o]
2928 makeuparrow $oid $col $row $o
2929 incr col
2931 lappend rowidlist $idlist
2932 lappend rowoffsets $offs
2934 return $row
2937 proc addextraid {id row} {
2938 global displayorder commitrow commitinfo
2939 global commitidx commitlisted
2940 global parentlist children curview
2942 incr commitidx($curview)
2943 lappend displayorder $id
2944 lappend commitlisted 0
2945 lappend parentlist {}
2946 set commitrow($curview,$id) $row
2947 readcommit $id
2948 if {![info exists commitinfo($id)]} {
2949 set commitinfo($id) {"No commit information available"}
2951 if {![info exists children($curview,$id)]} {
2952 set children($curview,$id) {}
2956 proc layouttail {} {
2957 global rowidlist rowoffsets idinlist commitidx curview
2958 global idrowranges rowrangelist
2960 set row $commitidx($curview)
2961 set idlist [lindex $rowidlist $row]
2962 while {$idlist ne {}} {
2963 set col [expr {[llength $idlist] - 1}]
2964 set id [lindex $idlist $col]
2965 addextraid $id $row
2966 unset idinlist($id)
2967 lappend idrowranges($id) $id
2968 lappend rowrangelist $idrowranges($id)
2969 unset idrowranges($id)
2970 incr row
2971 set offs [ntimes $col 0]
2972 set idlist [lreplace $idlist $col $col]
2973 lappend rowidlist $idlist
2974 lappend rowoffsets $offs
2977 foreach id [array names idinlist] {
2978 unset idinlist($id)
2979 addextraid $id $row
2980 lset rowidlist $row [list $id]
2981 lset rowoffsets $row 0
2982 makeuparrow $id 0 $row 0
2983 lappend idrowranges($id) $id
2984 lappend rowrangelist $idrowranges($id)
2985 unset idrowranges($id)
2986 incr row
2987 lappend rowidlist {}
2988 lappend rowoffsets {}
2992 proc insert_pad {row col npad} {
2993 global rowidlist rowoffsets
2995 set pad [ntimes $npad {}]
2996 lset rowidlist $row [eval linsert [list [lindex $rowidlist $row]] $col $pad]
2997 set tmp [eval linsert [list [lindex $rowoffsets $row]] $col $pad]
2998 lset rowoffsets $row [incrange $tmp [expr {$col + $npad}] [expr {-$npad}]]
3001 proc optimize_rows {row col endrow} {
3002 global rowidlist rowoffsets displayorder
3004 for {} {$row < $endrow} {incr row} {
3005 set idlist [lindex $rowidlist $row]
3006 set offs [lindex $rowoffsets $row]
3007 set haspad 0
3008 for {} {$col < [llength $offs]} {incr col} {
3009 if {[lindex $idlist $col] eq {}} {
3010 set haspad 1
3011 continue
3013 set z [lindex $offs $col]
3014 if {$z eq {}} continue
3015 set isarrow 0
3016 set x0 [expr {$col + $z}]
3017 set y0 [expr {$row - 1}]
3018 set z0 [lindex $rowoffsets $y0 $x0]
3019 if {$z0 eq {}} {
3020 set id [lindex $idlist $col]
3021 set ranges [rowranges $id]
3022 if {$ranges ne {} && $y0 > [lindex $ranges 0]} {
3023 set isarrow 1
3026 # Looking at lines from this row to the previous row,
3027 # make them go straight up if they end in an arrow on
3028 # the previous row; otherwise make them go straight up
3029 # or at 45 degrees.
3030 if {$z < -1 || ($z < 0 && $isarrow)} {
3031 # Line currently goes left too much;
3032 # insert pads in the previous row, then optimize it
3033 set npad [expr {-1 - $z + $isarrow}]
3034 set offs [incrange $offs $col $npad]
3035 insert_pad $y0 $x0 $npad
3036 if {$y0 > 0} {
3037 optimize_rows $y0 $x0 $row
3039 set z [lindex $offs $col]
3040 set x0 [expr {$col + $z}]
3041 set z0 [lindex $rowoffsets $y0 $x0]
3042 } elseif {$z > 1 || ($z > 0 && $isarrow)} {
3043 # Line currently goes right too much;
3044 # insert pads in this line and adjust the next's rowoffsets
3045 set npad [expr {$z - 1 + $isarrow}]
3046 set y1 [expr {$row + 1}]
3047 set offs2 [lindex $rowoffsets $y1]
3048 set x1 -1
3049 foreach z $offs2 {
3050 incr x1
3051 if {$z eq {} || $x1 + $z < $col} continue
3052 if {$x1 + $z > $col} {
3053 incr npad
3055 lset rowoffsets $y1 [incrange $offs2 $x1 $npad]
3056 break
3058 set pad [ntimes $npad {}]
3059 set idlist [eval linsert \$idlist $col $pad]
3060 set tmp [eval linsert \$offs $col $pad]
3061 incr col $npad
3062 set offs [incrange $tmp $col [expr {-$npad}]]
3063 set z [lindex $offs $col]
3064 set haspad 1
3066 if {$z0 eq {} && !$isarrow} {
3067 # this line links to its first child on row $row-2
3068 set rm2 [expr {$row - 2}]
3069 set id [lindex $displayorder $rm2]
3070 set xc [lsearch -exact [lindex $rowidlist $rm2] $id]
3071 if {$xc >= 0} {
3072 set z0 [expr {$xc - $x0}]
3075 # avoid lines jigging left then immediately right
3076 if {$z0 ne {} && $z < 0 && $z0 > 0} {
3077 insert_pad $y0 $x0 1
3078 set offs [incrange $offs $col 1]
3079 optimize_rows $y0 [expr {$x0 + 1}] $row
3082 if {!$haspad} {
3083 set o {}
3084 # Find the first column that doesn't have a line going right
3085 for {set col [llength $idlist]} {[incr col -1] >= 0} {} {
3086 set o [lindex $offs $col]
3087 if {$o eq {}} {
3088 # check if this is the link to the first child
3089 set id [lindex $idlist $col]
3090 set ranges [rowranges $id]
3091 if {$ranges ne {} && $row == [lindex $ranges 0]} {
3092 # it is, work out offset to child
3093 set y0 [expr {$row - 1}]
3094 set id [lindex $displayorder $y0]
3095 set x0 [lsearch -exact [lindex $rowidlist $y0] $id]
3096 if {$x0 >= 0} {
3097 set o [expr {$x0 - $col}]
3101 if {$o eq {} || $o <= 0} break
3103 # Insert a pad at that column as long as it has a line and
3104 # isn't the last column, and adjust the next row' offsets
3105 if {$o ne {} && [incr col] < [llength $idlist]} {
3106 set y1 [expr {$row + 1}]
3107 set offs2 [lindex $rowoffsets $y1]
3108 set x1 -1
3109 foreach z $offs2 {
3110 incr x1
3111 if {$z eq {} || $x1 + $z < $col} continue
3112 lset rowoffsets $y1 [incrange $offs2 $x1 1]
3113 break
3115 set idlist [linsert $idlist $col {}]
3116 set tmp [linsert $offs $col {}]
3117 incr col
3118 set offs [incrange $tmp $col -1]
3121 lset rowidlist $row $idlist
3122 lset rowoffsets $row $offs
3123 set col 0
3127 proc xc {row col} {
3128 global canvx0 linespc
3129 return [expr {$canvx0 + $col * $linespc}]
3132 proc yc {row} {
3133 global canvy0 linespc
3134 return [expr {$canvy0 + $row * $linespc}]
3137 proc linewidth {id} {
3138 global thickerline lthickness
3140 set wid $lthickness
3141 if {[info exists thickerline] && $id eq $thickerline} {
3142 set wid [expr {2 * $lthickness}]
3144 return $wid
3147 proc rowranges {id} {
3148 global phase idrowranges commitrow rowlaidout rowrangelist curview
3150 set ranges {}
3151 if {$phase eq {} ||
3152 ([info exists commitrow($curview,$id)]
3153 && $commitrow($curview,$id) < $rowlaidout)} {
3154 set ranges [lindex $rowrangelist $commitrow($curview,$id)]
3155 } elseif {[info exists idrowranges($id)]} {
3156 set ranges $idrowranges($id)
3158 set linenos {}
3159 foreach rid $ranges {
3160 lappend linenos $commitrow($curview,$rid)
3162 if {$linenos ne {}} {
3163 lset linenos 0 [expr {[lindex $linenos 0] + 1}]
3165 return $linenos
3168 # work around tk8.4 refusal to draw arrows on diagonal segments
3169 proc adjarrowhigh {coords} {
3170 global linespc
3172 set x0 [lindex $coords 0]
3173 set x1 [lindex $coords 2]
3174 if {$x0 != $x1} {
3175 set y0 [lindex $coords 1]
3176 set y1 [lindex $coords 3]
3177 if {$y0 - $y1 <= 2 * $linespc && $x1 == [lindex $coords 4]} {
3178 # we have a nearby vertical segment, just trim off the diag bit
3179 set coords [lrange $coords 2 end]
3180 } else {
3181 set slope [expr {($x0 - $x1) / ($y0 - $y1)}]
3182 set xi [expr {$x0 - $slope * $linespc / 2}]
3183 set yi [expr {$y0 - $linespc / 2}]
3184 set coords [lreplace $coords 0 1 $xi $y0 $xi $yi]
3187 return $coords
3190 proc drawlineseg {id row endrow arrowlow} {
3191 global rowidlist displayorder iddrawn linesegs
3192 global canv colormap linespc curview maxlinelen
3194 set cols [list [lsearch -exact [lindex $rowidlist $row] $id]]
3195 set le [expr {$row + 1}]
3196 set arrowhigh 1
3197 while {1} {
3198 set c [lsearch -exact [lindex $rowidlist $le] $id]
3199 if {$c < 0} {
3200 incr le -1
3201 break
3203 lappend cols $c
3204 set x [lindex $displayorder $le]
3205 if {$x eq $id} {
3206 set arrowhigh 0
3207 break
3209 if {[info exists iddrawn($x)] || $le == $endrow} {
3210 set c [lsearch -exact [lindex $rowidlist [expr {$le+1}]] $id]
3211 if {$c >= 0} {
3212 lappend cols $c
3213 set arrowhigh 0
3215 break
3217 incr le
3219 if {$le <= $row} {
3220 return $row
3223 set lines {}
3224 set i 0
3225 set joinhigh 0
3226 if {[info exists linesegs($id)]} {
3227 set lines $linesegs($id)
3228 foreach li $lines {
3229 set r0 [lindex $li 0]
3230 if {$r0 > $row} {
3231 if {$r0 == $le && [lindex $li 1] - $row <= $maxlinelen} {
3232 set joinhigh 1
3234 break
3236 incr i
3239 set joinlow 0
3240 if {$i > 0} {
3241 set li [lindex $lines [expr {$i-1}]]
3242 set r1 [lindex $li 1]
3243 if {$r1 == $row && $le - [lindex $li 0] <= $maxlinelen} {
3244 set joinlow 1
3248 set x [lindex $cols [expr {$le - $row}]]
3249 set xp [lindex $cols [expr {$le - 1 - $row}]]
3250 set dir [expr {$xp - $x}]
3251 if {$joinhigh} {
3252 set ith [lindex $lines $i 2]
3253 set coords [$canv coords $ith]
3254 set ah [$canv itemcget $ith -arrow]
3255 set arrowhigh [expr {$ah eq "first" || $ah eq "both"}]
3256 set x2 [lindex $cols [expr {$le + 1 - $row}]]
3257 if {$x2 ne {} && $x - $x2 == $dir} {
3258 set coords [lrange $coords 0 end-2]
3260 } else {
3261 set coords [list [xc $le $x] [yc $le]]
3263 if {$joinlow} {
3264 set itl [lindex $lines [expr {$i-1}] 2]
3265 set al [$canv itemcget $itl -arrow]
3266 set arrowlow [expr {$al eq "last" || $al eq "both"}]
3267 } elseif {$arrowlow &&
3268 [lsearch -exact [lindex $rowidlist [expr {$row-1}]] $id] >= 0} {
3269 set arrowlow 0
3271 set arrow [lindex {none first last both} [expr {$arrowhigh + 2*$arrowlow}]]
3272 for {set y $le} {[incr y -1] > $row} {} {
3273 set x $xp
3274 set xp [lindex $cols [expr {$y - 1 - $row}]]
3275 set ndir [expr {$xp - $x}]
3276 if {$dir != $ndir || $xp < 0} {
3277 lappend coords [xc $y $x] [yc $y]
3279 set dir $ndir
3281 if {!$joinlow} {
3282 if {$xp < 0} {
3283 # join parent line to first child
3284 set ch [lindex $displayorder $row]
3285 set xc [lsearch -exact [lindex $rowidlist $row] $ch]
3286 if {$xc < 0} {
3287 puts "oops: drawlineseg: child $ch not on row $row"
3288 } else {
3289 if {$xc < $x - 1} {
3290 lappend coords [xc $row [expr {$x-1}]] [yc $row]
3291 } elseif {$xc > $x + 1} {
3292 lappend coords [xc $row [expr {$x+1}]] [yc $row]
3294 set x $xc
3296 lappend coords [xc $row $x] [yc $row]
3297 } else {
3298 set xn [xc $row $xp]
3299 set yn [yc $row]
3300 # work around tk8.4 refusal to draw arrows on diagonal segments
3301 if {$arrowlow && $xn != [lindex $coords end-1]} {
3302 if {[llength $coords] < 4 ||
3303 [lindex $coords end-3] != [lindex $coords end-1] ||
3304 [lindex $coords end] - $yn > 2 * $linespc} {
3305 set xn [xc $row [expr {$xp - 0.5 * $dir}]]
3306 set yo [yc [expr {$row + 0.5}]]
3307 lappend coords $xn $yo $xn $yn
3309 } else {
3310 lappend coords $xn $yn
3313 if {!$joinhigh} {
3314 if {$arrowhigh} {
3315 set coords [adjarrowhigh $coords]
3317 assigncolor $id
3318 set t [$canv create line $coords -width [linewidth $id] \
3319 -fill $colormap($id) -tags lines.$id -arrow $arrow]
3320 $canv lower $t
3321 bindline $t $id
3322 set lines [linsert $lines $i [list $row $le $t]]
3323 } else {
3324 $canv coords $ith $coords
3325 if {$arrow ne $ah} {
3326 $canv itemconf $ith -arrow $arrow
3328 lset lines $i 0 $row
3330 } else {
3331 set xo [lsearch -exact [lindex $rowidlist [expr {$row - 1}]] $id]
3332 set ndir [expr {$xo - $xp}]
3333 set clow [$canv coords $itl]
3334 if {$dir == $ndir} {
3335 set clow [lrange $clow 2 end]
3337 set coords [concat $coords $clow]
3338 if {!$joinhigh} {
3339 lset lines [expr {$i-1}] 1 $le
3340 if {$arrowhigh} {
3341 set coords [adjarrowhigh $coords]
3343 } else {
3344 # coalesce two pieces
3345 $canv delete $ith
3346 set b [lindex $lines [expr {$i-1}] 0]
3347 set e [lindex $lines $i 1]
3348 set lines [lreplace $lines [expr {$i-1}] $i [list $b $e $itl]]
3350 $canv coords $itl $coords
3351 if {$arrow ne $al} {
3352 $canv itemconf $itl -arrow $arrow
3356 set linesegs($id) $lines
3357 return $le
3360 proc drawparentlinks {id row} {
3361 global rowidlist canv colormap curview parentlist
3362 global idpos
3364 set rowids [lindex $rowidlist $row]
3365 set col [lsearch -exact $rowids $id]
3366 if {$col < 0} return
3367 set olds [lindex $parentlist $row]
3368 set row2 [expr {$row + 1}]
3369 set x [xc $row $col]
3370 set y [yc $row]
3371 set y2 [yc $row2]
3372 set ids [lindex $rowidlist $row2]
3373 # rmx = right-most X coord used
3374 set rmx 0
3375 foreach p $olds {
3376 set i [lsearch -exact $ids $p]
3377 if {$i < 0} {
3378 puts "oops, parent $p of $id not in list"
3379 continue
3381 set x2 [xc $row2 $i]
3382 if {$x2 > $rmx} {
3383 set rmx $x2
3385 if {[lsearch -exact $rowids $p] < 0} {
3386 # drawlineseg will do this one for us
3387 continue
3389 assigncolor $p
3390 # should handle duplicated parents here...
3391 set coords [list $x $y]
3392 if {$i < $col - 1} {
3393 lappend coords [xc $row [expr {$i + 1}]] $y
3394 } elseif {$i > $col + 1} {
3395 lappend coords [xc $row [expr {$i - 1}]] $y
3397 lappend coords $x2 $y2
3398 set t [$canv create line $coords -width [linewidth $p] \
3399 -fill $colormap($p) -tags lines.$p]
3400 $canv lower $t
3401 bindline $t $p
3403 if {$rmx > [lindex $idpos($id) 1]} {
3404 lset idpos($id) 1 $rmx
3405 redrawtags $id
3409 proc drawlines {id} {
3410 global canv
3412 $canv itemconf lines.$id -width [linewidth $id]
3415 proc drawcmittext {id row col} {
3416 global linespc canv canv2 canv3 canvy0 fgcolor curview
3417 global commitlisted commitinfo rowidlist parentlist
3418 global rowtextx idpos idtags idheads idotherrefs
3419 global linehtag linentag linedtag
3420 global mainfont canvxmax boldrows boldnamerows fgcolor nullid nullid2
3422 # listed is 0 for boundary, 1 for normal, 2 for left, 3 for right
3423 set listed [lindex $commitlisted $row]
3424 if {$id eq $nullid} {
3425 set ofill red
3426 } elseif {$id eq $nullid2} {
3427 set ofill green
3428 } else {
3429 set ofill [expr {$listed != 0? "blue": "white"}]
3431 set x [xc $row $col]
3432 set y [yc $row]
3433 set orad [expr {$linespc / 3}]
3434 if {$listed <= 1} {
3435 set t [$canv create oval [expr {$x - $orad}] [expr {$y - $orad}] \
3436 [expr {$x + $orad - 1}] [expr {$y + $orad - 1}] \
3437 -fill $ofill -outline $fgcolor -width 1 -tags circle]
3438 } elseif {$listed == 2} {
3439 # triangle pointing left for left-side commits
3440 set t [$canv create polygon \
3441 [expr {$x - $orad}] $y \
3442 [expr {$x + $orad - 1}] [expr {$y - $orad}] \
3443 [expr {$x + $orad - 1}] [expr {$y + $orad - 1}] \
3444 -fill $ofill -outline $fgcolor -width 1 -tags circle]
3445 } else {
3446 # triangle pointing right for right-side commits
3447 set t [$canv create polygon \
3448 [expr {$x + $orad - 1}] $y \
3449 [expr {$x - $orad}] [expr {$y - $orad}] \
3450 [expr {$x - $orad}] [expr {$y + $orad - 1}] \
3451 -fill $ofill -outline $fgcolor -width 1 -tags circle]
3453 $canv raise $t
3454 $canv bind $t <1> {selcanvline {} %x %y}
3455 set rmx [llength [lindex $rowidlist $row]]
3456 set olds [lindex $parentlist $row]
3457 if {$olds ne {}} {
3458 set nextids [lindex $rowidlist [expr {$row + 1}]]
3459 foreach p $olds {
3460 set i [lsearch -exact $nextids $p]
3461 if {$i > $rmx} {
3462 set rmx $i
3466 set xt [xc $row $rmx]
3467 set rowtextx($row) $xt
3468 set idpos($id) [list $x $xt $y]
3469 if {[info exists idtags($id)] || [info exists idheads($id)]
3470 || [info exists idotherrefs($id)]} {
3471 set xt [drawtags $id $x $xt $y]
3473 set headline [lindex $commitinfo($id) 0]
3474 set name [lindex $commitinfo($id) 1]
3475 set date [lindex $commitinfo($id) 2]
3476 set date [formatdate $date]
3477 set font $mainfont
3478 set nfont $mainfont
3479 set isbold [ishighlighted $row]
3480 if {$isbold > 0} {
3481 lappend boldrows $row
3482 lappend font bold
3483 if {$isbold > 1} {
3484 lappend boldnamerows $row
3485 lappend nfont bold
3488 set linehtag($row) [$canv create text $xt $y -anchor w -fill $fgcolor \
3489 -text $headline -font $font -tags text]
3490 $canv bind $linehtag($row) <Button-3> "rowmenu %X %Y $id"
3491 set linentag($row) [$canv2 create text 3 $y -anchor w -fill $fgcolor \
3492 -text $name -font $nfont -tags text]
3493 set linedtag($row) [$canv3 create text 3 $y -anchor w -fill $fgcolor \
3494 -text $date -font $mainfont -tags text]
3495 set xr [expr {$xt + [font measure $mainfont $headline]}]
3496 if {$xr > $canvxmax} {
3497 set canvxmax $xr
3498 setcanvscroll
3502 proc drawcmitrow {row} {
3503 global displayorder rowidlist
3504 global iddrawn markingmatches
3505 global commitinfo parentlist numcommits
3506 global filehighlight fhighlights findstring nhighlights
3507 global hlview vhighlights
3508 global highlight_related rhighlights
3510 if {$row >= $numcommits} return
3512 set id [lindex $displayorder $row]
3513 if {[info exists hlview] && ![info exists vhighlights($row)]} {
3514 askvhighlight $row $id
3516 if {[info exists filehighlight] && ![info exists fhighlights($row)]} {
3517 askfilehighlight $row $id
3519 if {$findstring ne {} && ![info exists nhighlights($row)]} {
3520 askfindhighlight $row $id
3522 if {$highlight_related ne "None" && ![info exists rhighlights($row)]} {
3523 askrelhighlight $row $id
3525 if {![info exists iddrawn($id)]} {
3526 set col [lsearch -exact [lindex $rowidlist $row] $id]
3527 if {$col < 0} {
3528 puts "oops, row $row id $id not in list"
3529 return
3531 if {![info exists commitinfo($id)]} {
3532 getcommit $id
3534 assigncolor $id
3535 drawcmittext $id $row $col
3536 set iddrawn($id) 1
3538 if {$markingmatches} {
3539 markrowmatches $row $id
3543 proc drawcommits {row {endrow {}}} {
3544 global numcommits iddrawn displayorder curview
3545 global parentlist rowidlist
3547 if {$row < 0} {
3548 set row 0
3550 if {$endrow eq {}} {
3551 set endrow $row
3553 if {$endrow >= $numcommits} {
3554 set endrow [expr {$numcommits - 1}]
3557 # make the lines join to already-drawn rows either side
3558 set r [expr {$row - 1}]
3559 if {$r < 0 || ![info exists iddrawn([lindex $displayorder $r])]} {
3560 set r $row
3562 set er [expr {$endrow + 1}]
3563 if {$er >= $numcommits ||
3564 ![info exists iddrawn([lindex $displayorder $er])]} {
3565 set er $endrow
3567 for {} {$r <= $er} {incr r} {
3568 set id [lindex $displayorder $r]
3569 set wasdrawn [info exists iddrawn($id)]
3570 drawcmitrow $r
3571 if {$r == $er} break
3572 set nextid [lindex $displayorder [expr {$r + 1}]]
3573 if {$wasdrawn && [info exists iddrawn($nextid)]} {
3574 catch {unset prevlines}
3575 continue
3577 drawparentlinks $id $r
3579 if {[info exists lineends($r)]} {
3580 foreach lid $lineends($r) {
3581 unset prevlines($lid)
3584 set rowids [lindex $rowidlist $r]
3585 foreach lid $rowids {
3586 if {$lid eq {}} continue
3587 if {$lid eq $id} {
3588 # see if this is the first child of any of its parents
3589 foreach p [lindex $parentlist $r] {
3590 if {[lsearch -exact $rowids $p] < 0} {
3591 # make this line extend up to the child
3592 set le [drawlineseg $p $r $er 0]
3593 lappend lineends($le) $p
3594 set prevlines($p) 1
3597 } elseif {![info exists prevlines($lid)]} {
3598 set le [drawlineseg $lid $r $er 1]
3599 lappend lineends($le) $lid
3600 set prevlines($lid) 1
3606 proc drawfrac {f0 f1} {
3607 global canv linespc
3609 set ymax [lindex [$canv cget -scrollregion] 3]
3610 if {$ymax eq {} || $ymax == 0} return
3611 set y0 [expr {int($f0 * $ymax)}]
3612 set row [expr {int(($y0 - 3) / $linespc) - 1}]
3613 set y1 [expr {int($f1 * $ymax)}]
3614 set endrow [expr {int(($y1 - 3) / $linespc) + 1}]
3615 drawcommits $row $endrow
3618 proc drawvisible {} {
3619 global canv
3620 eval drawfrac [$canv yview]
3623 proc clear_display {} {
3624 global iddrawn linesegs
3625 global vhighlights fhighlights nhighlights rhighlights
3627 allcanvs delete all
3628 catch {unset iddrawn}
3629 catch {unset linesegs}
3630 catch {unset vhighlights}
3631 catch {unset fhighlights}
3632 catch {unset nhighlights}
3633 catch {unset rhighlights}
3636 proc findcrossings {id} {
3637 global rowidlist parentlist numcommits rowoffsets displayorder
3639 set cross {}
3640 set ccross {}
3641 foreach {s e} [rowranges $id] {
3642 if {$e >= $numcommits} {
3643 set e [expr {$numcommits - 1}]
3645 if {$e <= $s} continue
3646 set x [lsearch -exact [lindex $rowidlist $e] $id]
3647 if {$x < 0} {
3648 puts "findcrossings: oops, no [shortids $id] in row $e"
3649 continue
3651 for {set row $e} {[incr row -1] >= $s} {} {
3652 set olds [lindex $parentlist $row]
3653 set kid [lindex $displayorder $row]
3654 set kidx [lsearch -exact [lindex $rowidlist $row] $kid]
3655 if {$kidx < 0} continue
3656 set nextrow [lindex $rowidlist [expr {$row + 1}]]
3657 foreach p $olds {
3658 set px [lsearch -exact $nextrow $p]
3659 if {$px < 0} continue
3660 if {($kidx < $x && $x < $px) || ($px < $x && $x < $kidx)} {
3661 if {[lsearch -exact $ccross $p] >= 0} continue
3662 if {$x == $px + ($kidx < $px? -1: 1)} {
3663 lappend ccross $p
3664 } elseif {[lsearch -exact $cross $p] < 0} {
3665 lappend cross $p
3669 set inc [lindex $rowoffsets $row $x]
3670 if {$inc eq {}} break
3671 incr x $inc
3674 return [concat $ccross {{}} $cross]
3677 proc assigncolor {id} {
3678 global colormap colors nextcolor
3679 global commitrow parentlist children children curview
3681 if {[info exists colormap($id)]} return
3682 set ncolors [llength $colors]
3683 if {[info exists children($curview,$id)]} {
3684 set kids $children($curview,$id)
3685 } else {
3686 set kids {}
3688 if {[llength $kids] == 1} {
3689 set child [lindex $kids 0]
3690 if {[info exists colormap($child)]
3691 && [llength [lindex $parentlist $commitrow($curview,$child)]] == 1} {
3692 set colormap($id) $colormap($child)
3693 return
3696 set badcolors {}
3697 set origbad {}
3698 foreach x [findcrossings $id] {
3699 if {$x eq {}} {
3700 # delimiter between corner crossings and other crossings
3701 if {[llength $badcolors] >= $ncolors - 1} break
3702 set origbad $badcolors
3704 if {[info exists colormap($x)]
3705 && [lsearch -exact $badcolors $colormap($x)] < 0} {
3706 lappend badcolors $colormap($x)
3709 if {[llength $badcolors] >= $ncolors} {
3710 set badcolors $origbad
3712 set origbad $badcolors
3713 if {[llength $badcolors] < $ncolors - 1} {
3714 foreach child $kids {
3715 if {[info exists colormap($child)]
3716 && [lsearch -exact $badcolors $colormap($child)] < 0} {
3717 lappend badcolors $colormap($child)
3719 foreach p [lindex $parentlist $commitrow($curview,$child)] {
3720 if {[info exists colormap($p)]
3721 && [lsearch -exact $badcolors $colormap($p)] < 0} {
3722 lappend badcolors $colormap($p)
3726 if {[llength $badcolors] >= $ncolors} {
3727 set badcolors $origbad
3730 for {set i 0} {$i <= $ncolors} {incr i} {
3731 set c [lindex $colors $nextcolor]
3732 if {[incr nextcolor] >= $ncolors} {
3733 set nextcolor 0
3735 if {[lsearch -exact $badcolors $c]} break
3737 set colormap($id) $c
3740 proc bindline {t id} {
3741 global canv
3743 $canv bind $t <Enter> "lineenter %x %y $id"
3744 $canv bind $t <Motion> "linemotion %x %y $id"
3745 $canv bind $t <Leave> "lineleave $id"
3746 $canv bind $t <Button-1> "lineclick %x %y $id 1"
3749 proc drawtags {id x xt y1} {
3750 global idtags idheads idotherrefs mainhead
3751 global linespc lthickness
3752 global canv mainfont commitrow rowtextx curview fgcolor bgcolor
3754 set marks {}
3755 set ntags 0
3756 set nheads 0
3757 if {[info exists idtags($id)]} {
3758 set marks $idtags($id)
3759 set ntags [llength $marks]
3761 if {[info exists idheads($id)]} {
3762 set marks [concat $marks $idheads($id)]
3763 set nheads [llength $idheads($id)]
3765 if {[info exists idotherrefs($id)]} {
3766 set marks [concat $marks $idotherrefs($id)]
3768 if {$marks eq {}} {
3769 return $xt
3772 set delta [expr {int(0.5 * ($linespc - $lthickness))}]
3773 set yt [expr {$y1 - 0.5 * $linespc}]
3774 set yb [expr {$yt + $linespc - 1}]
3775 set xvals {}
3776 set wvals {}
3777 set i -1
3778 foreach tag $marks {
3779 incr i
3780 if {$i >= $ntags && $i < $ntags + $nheads && $tag eq $mainhead} {
3781 set wid [font measure [concat $mainfont bold] $tag]
3782 } else {
3783 set wid [font measure $mainfont $tag]
3785 lappend xvals $xt
3786 lappend wvals $wid
3787 set xt [expr {$xt + $delta + $wid + $lthickness + $linespc}]
3789 set t [$canv create line $x $y1 [lindex $xvals end] $y1 \
3790 -width $lthickness -fill black -tags tag.$id]
3791 $canv lower $t
3792 foreach tag $marks x $xvals wid $wvals {
3793 set xl [expr {$x + $delta}]
3794 set xr [expr {$x + $delta + $wid + $lthickness}]
3795 set font $mainfont
3796 if {[incr ntags -1] >= 0} {
3797 # draw a tag
3798 set t [$canv create polygon $x [expr {$yt + $delta}] $xl $yt \
3799 $xr $yt $xr $yb $xl $yb $x [expr {$yb - $delta}] \
3800 -width 1 -outline black -fill yellow -tags tag.$id]
3801 $canv bind $t <1> [list showtag $tag 1]
3802 set rowtextx($commitrow($curview,$id)) [expr {$xr + $linespc}]
3803 } else {
3804 # draw a head or other ref
3805 if {[incr nheads -1] >= 0} {
3806 set col green
3807 if {$tag eq $mainhead} {
3808 lappend font bold
3810 } else {
3811 set col "#ddddff"
3813 set xl [expr {$xl - $delta/2}]
3814 $canv create polygon $x $yt $xr $yt $xr $yb $x $yb \
3815 -width 1 -outline black -fill $col -tags tag.$id
3816 if {[regexp {^(remotes/.*/|remotes/)} $tag match remoteprefix]} {
3817 set rwid [font measure $mainfont $remoteprefix]
3818 set xi [expr {$x + 1}]
3819 set yti [expr {$yt + 1}]
3820 set xri [expr {$x + $rwid}]
3821 $canv create polygon $xi $yti $xri $yti $xri $yb $xi $yb \
3822 -width 0 -fill "#ffddaa" -tags tag.$id
3825 set t [$canv create text $xl $y1 -anchor w -text $tag -fill $fgcolor \
3826 -font $font -tags [list tag.$id text]]
3827 if {$ntags >= 0} {
3828 $canv bind $t <1> [list showtag $tag 1]
3829 } elseif {$nheads >= 0} {
3830 $canv bind $t <Button-3> [list headmenu %X %Y $id $tag]
3833 return $xt
3836 proc xcoord {i level ln} {
3837 global canvx0 xspc1 xspc2
3839 set x [expr {$canvx0 + $i * $xspc1($ln)}]
3840 if {$i > 0 && $i == $level} {
3841 set x [expr {$x + 0.5 * ($xspc2 - $xspc1($ln))}]
3842 } elseif {$i > $level} {
3843 set x [expr {$x + $xspc2 - $xspc1($ln)}]
3845 return $x
3848 proc show_status {msg} {
3849 global canv mainfont fgcolor
3851 clear_display
3852 $canv create text 3 3 -anchor nw -text $msg -font $mainfont \
3853 -tags text -fill $fgcolor
3856 # Insert a new commit as the child of the commit on row $row.
3857 # The new commit will be displayed on row $row and the commits
3858 # on that row and below will move down one row.
3859 proc insertrow {row newcmit} {
3860 global displayorder parentlist commitlisted children
3861 global commitrow curview rowidlist rowoffsets numcommits
3862 global rowrangelist rowlaidout rowoptim numcommits
3863 global selectedline rowchk commitidx
3865 if {$row >= $numcommits} {
3866 puts "oops, inserting new row $row but only have $numcommits rows"
3867 return
3869 set p [lindex $displayorder $row]
3870 set displayorder [linsert $displayorder $row $newcmit]
3871 set parentlist [linsert $parentlist $row $p]
3872 set kids $children($curview,$p)
3873 lappend kids $newcmit
3874 set children($curview,$p) $kids
3875 set children($curview,$newcmit) {}
3876 set commitlisted [linsert $commitlisted $row 1]
3877 set l [llength $displayorder]
3878 for {set r $row} {$r < $l} {incr r} {
3879 set id [lindex $displayorder $r]
3880 set commitrow($curview,$id) $r
3882 incr commitidx($curview)
3884 set idlist [lindex $rowidlist $row]
3885 set offs [lindex $rowoffsets $row]
3886 set newoffs {}
3887 foreach x $idlist {
3888 if {$x eq {} || ($x eq $p && [llength $kids] == 1)} {
3889 lappend newoffs {}
3890 } else {
3891 lappend newoffs 0
3894 if {[llength $kids] == 1} {
3895 set col [lsearch -exact $idlist $p]
3896 lset idlist $col $newcmit
3897 } else {
3898 set col [llength $idlist]
3899 lappend idlist $newcmit
3900 lappend offs {}
3901 lset rowoffsets $row $offs
3903 set rowidlist [linsert $rowidlist $row $idlist]
3904 set rowoffsets [linsert $rowoffsets [expr {$row+1}] $newoffs]
3906 set rowrangelist [linsert $rowrangelist $row {}]
3907 if {[llength $kids] > 1} {
3908 set rp1 [expr {$row + 1}]
3909 set ranges [lindex $rowrangelist $rp1]
3910 if {$ranges eq {}} {
3911 set ranges [list $newcmit $p]
3912 } elseif {[lindex $ranges end-1] eq $p} {
3913 lset ranges end-1 $newcmit
3915 lset rowrangelist $rp1 $ranges
3918 catch {unset rowchk}
3920 incr rowlaidout
3921 incr rowoptim
3922 incr numcommits
3924 if {[info exists selectedline] && $selectedline >= $row} {
3925 incr selectedline
3927 redisplay
3930 # Remove a commit that was inserted with insertrow on row $row.
3931 proc removerow {row} {
3932 global displayorder parentlist commitlisted children
3933 global commitrow curview rowidlist rowoffsets numcommits
3934 global rowrangelist idrowranges rowlaidout rowoptim numcommits
3935 global linesegends selectedline rowchk commitidx
3937 if {$row >= $numcommits} {
3938 puts "oops, removing row $row but only have $numcommits rows"
3939 return
3941 set rp1 [expr {$row + 1}]
3942 set id [lindex $displayorder $row]
3943 set p [lindex $parentlist $row]
3944 set displayorder [lreplace $displayorder $row $row]
3945 set parentlist [lreplace $parentlist $row $row]
3946 set commitlisted [lreplace $commitlisted $row $row]
3947 set kids $children($curview,$p)
3948 set i [lsearch -exact $kids $id]
3949 if {$i >= 0} {
3950 set kids [lreplace $kids $i $i]
3951 set children($curview,$p) $kids
3953 set l [llength $displayorder]
3954 for {set r $row} {$r < $l} {incr r} {
3955 set id [lindex $displayorder $r]
3956 set commitrow($curview,$id) $r
3958 incr commitidx($curview) -1
3960 set rowidlist [lreplace $rowidlist $row $row]
3961 set rowoffsets [lreplace $rowoffsets $rp1 $rp1]
3962 if {$kids ne {}} {
3963 set offs [lindex $rowoffsets $row]
3964 set offs [lreplace $offs end end]
3965 lset rowoffsets $row $offs
3968 set rowrangelist [lreplace $rowrangelist $row $row]
3969 if {[llength $kids] > 0} {
3970 set ranges [lindex $rowrangelist $row]
3971 if {[lindex $ranges end-1] eq $id} {
3972 set ranges [lreplace $ranges end-1 end]
3973 lset rowrangelist $row $ranges
3977 catch {unset rowchk}
3979 incr rowlaidout -1
3980 incr rowoptim -1
3981 incr numcommits -1
3983 if {[info exists selectedline] && $selectedline > $row} {
3984 incr selectedline -1
3986 redisplay
3989 # Don't change the text pane cursor if it is currently the hand cursor,
3990 # showing that we are over a sha1 ID link.
3991 proc settextcursor {c} {
3992 global ctext curtextcursor
3994 if {[$ctext cget -cursor] == $curtextcursor} {
3995 $ctext config -cursor $c
3997 set curtextcursor $c
4000 proc nowbusy {what} {
4001 global isbusy
4003 if {[array names isbusy] eq {}} {
4004 . config -cursor watch
4005 settextcursor watch
4007 set isbusy($what) 1
4010 proc notbusy {what} {
4011 global isbusy maincursor textcursor
4013 catch {unset isbusy($what)}
4014 if {[array names isbusy] eq {}} {
4015 . config -cursor $maincursor
4016 settextcursor $textcursor
4020 proc findmatches {f} {
4021 global findtype findstring
4022 if {$findtype == "Regexp"} {
4023 set matches [regexp -indices -all -inline $findstring $f]
4024 } else {
4025 set fs $findstring
4026 if {$findtype == "IgnCase"} {
4027 set f [string tolower $f]
4028 set fs [string tolower $fs]
4030 set matches {}
4031 set i 0
4032 set l [string length $fs]
4033 while {[set j [string first $fs $f $i]] >= 0} {
4034 lappend matches [list $j [expr {$j+$l-1}]]
4035 set i [expr {$j + $l}]
4038 return $matches
4041 proc dofind {{rev 0}} {
4042 global findstring findstartline findcurline selectedline numcommits
4044 unmarkmatches
4045 cancel_next_highlight
4046 focus .
4047 if {$findstring eq {} || $numcommits == 0} return
4048 if {![info exists selectedline]} {
4049 set findstartline [lindex [visiblerows] $rev]
4050 } else {
4051 set findstartline $selectedline
4053 set findcurline $findstartline
4054 nowbusy finding
4055 if {!$rev} {
4056 run findmore
4057 } else {
4058 if {$findcurline == 0} {
4059 set findcurline $numcommits
4061 incr findcurline -1
4062 run findmorerev
4066 proc findnext {restart} {
4067 global findcurline
4068 if {![info exists findcurline]} {
4069 if {$restart} {
4070 dofind
4071 } else {
4072 bell
4074 } else {
4075 run findmore
4076 nowbusy finding
4080 proc findprev {} {
4081 global findcurline
4082 if {![info exists findcurline]} {
4083 dofind 1
4084 } else {
4085 run findmorerev
4086 nowbusy finding
4090 proc findmore {} {
4091 global commitdata commitinfo numcommits findstring findpattern findloc
4092 global findstartline findcurline displayorder
4094 set fldtypes {Headline Author Date Committer CDate Comments}
4095 set l [expr {$findcurline + 1}]
4096 if {$l >= $numcommits} {
4097 set l 0
4099 if {$l <= $findstartline} {
4100 set lim [expr {$findstartline + 1}]
4101 } else {
4102 set lim $numcommits
4104 if {$lim - $l > 500} {
4105 set lim [expr {$l + 500}]
4107 set last 0
4108 for {} {$l < $lim} {incr l} {
4109 set id [lindex $displayorder $l]
4110 # shouldn't happen unless git log doesn't give all the commits...
4111 if {![info exists commitdata($id)]} continue
4112 if {![doesmatch $commitdata($id)]} continue
4113 if {![info exists commitinfo($id)]} {
4114 getcommit $id
4116 set info $commitinfo($id)
4117 foreach f $info ty $fldtypes {
4118 if {($findloc eq "All fields" || $findloc eq $ty) &&
4119 [doesmatch $f]} {
4120 findselectline $l
4121 notbusy finding
4122 return 0
4126 if {$l == $findstartline + 1} {
4127 bell
4128 unset findcurline
4129 notbusy finding
4130 return 0
4132 set findcurline [expr {$l - 1}]
4133 return 1
4136 proc findmorerev {} {
4137 global commitdata commitinfo numcommits findstring findpattern findloc
4138 global findstartline findcurline displayorder
4140 set fldtypes {Headline Author Date Committer CDate Comments}
4141 set l $findcurline
4142 if {$l == 0} {
4143 set l $numcommits
4145 incr l -1
4146 if {$l >= $findstartline} {
4147 set lim [expr {$findstartline - 1}]
4148 } else {
4149 set lim -1
4151 if {$l - $lim > 500} {
4152 set lim [expr {$l - 500}]
4154 set last 0
4155 for {} {$l > $lim} {incr l -1} {
4156 set id [lindex $displayorder $l]
4157 if {![doesmatch $commitdata($id)]} continue
4158 if {![info exists commitinfo($id)]} {
4159 getcommit $id
4161 set info $commitinfo($id)
4162 foreach f $info ty $fldtypes {
4163 if {($findloc eq "All fields" || $findloc eq $ty) &&
4164 [doesmatch $f]} {
4165 findselectline $l
4166 notbusy finding
4167 return 0
4171 if {$l == -1} {
4172 bell
4173 unset findcurline
4174 notbusy finding
4175 return 0
4177 set findcurline [expr {$l + 1}]
4178 return 1
4181 proc findselectline {l} {
4182 global findloc commentend ctext findcurline markingmatches
4184 set markingmatches 1
4185 set findcurline $l
4186 selectline $l 1
4187 if {$findloc == "All fields" || $findloc == "Comments"} {
4188 # highlight the matches in the comments
4189 set f [$ctext get 1.0 $commentend]
4190 set matches [findmatches $f]
4191 foreach match $matches {
4192 set start [lindex $match 0]
4193 set end [expr {[lindex $match 1] + 1}]
4194 $ctext tag add found "1.0 + $start c" "1.0 + $end c"
4197 drawvisible
4200 # mark the bits of a headline or author that match a find string
4201 proc markmatches {canv l str tag matches font row} {
4202 global selectedline
4204 set bbox [$canv bbox $tag]
4205 set x0 [lindex $bbox 0]
4206 set y0 [lindex $bbox 1]
4207 set y1 [lindex $bbox 3]
4208 foreach match $matches {
4209 set start [lindex $match 0]
4210 set end [lindex $match 1]
4211 if {$start > $end} continue
4212 set xoff [font measure $font [string range $str 0 [expr {$start-1}]]]
4213 set xlen [font measure $font [string range $str 0 [expr {$end}]]]
4214 set t [$canv create rect [expr {$x0+$xoff}] $y0 \
4215 [expr {$x0+$xlen+2}] $y1 \
4216 -outline {} -tags [list match$l matches] -fill yellow]
4217 $canv lower $t
4218 if {[info exists selectedline] && $row == $selectedline} {
4219 $canv raise $t secsel
4224 proc unmarkmatches {} {
4225 global findids markingmatches findcurline
4227 allcanvs delete matches
4228 catch {unset findids}
4229 set markingmatches 0
4230 catch {unset findcurline}
4233 proc selcanvline {w x y} {
4234 global canv canvy0 ctext linespc
4235 global rowtextx
4236 set ymax [lindex [$canv cget -scrollregion] 3]
4237 if {$ymax == {}} return
4238 set yfrac [lindex [$canv yview] 0]
4239 set y [expr {$y + $yfrac * $ymax}]
4240 set l [expr {int(($y - $canvy0) / $linespc + 0.5)}]
4241 if {$l < 0} {
4242 set l 0
4244 if {$w eq $canv} {
4245 if {![info exists rowtextx($l)] || $x < $rowtextx($l)} return
4247 unmarkmatches
4248 selectline $l 1
4251 proc commit_descriptor {p} {
4252 global commitinfo
4253 if {![info exists commitinfo($p)]} {
4254 getcommit $p
4256 set l "..."
4257 if {[llength $commitinfo($p)] > 1} {
4258 set l [lindex $commitinfo($p) 0]
4260 return "$p ($l)\n"
4263 # append some text to the ctext widget, and make any SHA1 ID
4264 # that we know about be a clickable link.
4265 proc appendwithlinks {text tags} {
4266 global ctext commitrow linknum curview
4268 set start [$ctext index "end - 1c"]
4269 $ctext insert end $text $tags
4270 set links [regexp -indices -all -inline {[0-9a-f]{40}} $text]
4271 foreach l $links {
4272 set s [lindex $l 0]
4273 set e [lindex $l 1]
4274 set linkid [string range $text $s $e]
4275 if {![info exists commitrow($curview,$linkid)]} continue
4276 incr e
4277 $ctext tag add link "$start + $s c" "$start + $e c"
4278 $ctext tag add link$linknum "$start + $s c" "$start + $e c"
4279 $ctext tag bind link$linknum <1> \
4280 [list selectline $commitrow($curview,$linkid) 1]
4281 incr linknum
4283 $ctext tag conf link -foreground blue -underline 1
4284 $ctext tag bind link <Enter> { %W configure -cursor hand2 }
4285 $ctext tag bind link <Leave> { %W configure -cursor $curtextcursor }
4288 proc viewnextline {dir} {
4289 global canv linespc
4291 $canv delete hover
4292 set ymax [lindex [$canv cget -scrollregion] 3]
4293 set wnow [$canv yview]
4294 set wtop [expr {[lindex $wnow 0] * $ymax}]
4295 set newtop [expr {$wtop + $dir * $linespc}]
4296 if {$newtop < 0} {
4297 set newtop 0
4298 } elseif {$newtop > $ymax} {
4299 set newtop $ymax
4301 allcanvs yview moveto [expr {$newtop * 1.0 / $ymax}]
4304 # add a list of tag or branch names at position pos
4305 # returns the number of names inserted
4306 proc appendrefs {pos ids var} {
4307 global ctext commitrow linknum curview $var maxrefs
4309 if {[catch {$ctext index $pos}]} {
4310 return 0
4312 $ctext conf -state normal
4313 $ctext delete $pos "$pos lineend"
4314 set tags {}
4315 foreach id $ids {
4316 foreach tag [set $var\($id\)] {
4317 lappend tags [list $tag $id]
4320 if {[llength $tags] > $maxrefs} {
4321 $ctext insert $pos "many ([llength $tags])"
4322 } else {
4323 set tags [lsort -index 0 -decreasing $tags]
4324 set sep {}
4325 foreach ti $tags {
4326 set id [lindex $ti 1]
4327 set lk link$linknum
4328 incr linknum
4329 $ctext tag delete $lk
4330 $ctext insert $pos $sep
4331 $ctext insert $pos [lindex $ti 0] $lk
4332 if {[info exists commitrow($curview,$id)]} {
4333 $ctext tag conf $lk -foreground blue
4334 $ctext tag bind $lk <1> \
4335 [list selectline $commitrow($curview,$id) 1]
4336 $ctext tag conf $lk -underline 1
4337 $ctext tag bind $lk <Enter> { %W configure -cursor hand2 }
4338 $ctext tag bind $lk <Leave> \
4339 { %W configure -cursor $curtextcursor }
4341 set sep ", "
4344 $ctext conf -state disabled
4345 return [llength $tags]
4348 # called when we have finished computing the nearby tags
4349 proc dispneartags {delay} {
4350 global selectedline currentid showneartags tagphase
4352 if {![info exists selectedline] || !$showneartags} return
4353 after cancel dispnexttag
4354 if {$delay} {
4355 after 200 dispnexttag
4356 set tagphase -1
4357 } else {
4358 after idle dispnexttag
4359 set tagphase 0
4363 proc dispnexttag {} {
4364 global selectedline currentid showneartags tagphase ctext
4366 if {![info exists selectedline] || !$showneartags} return
4367 switch -- $tagphase {
4369 set dtags [desctags $currentid]
4370 if {$dtags ne {}} {
4371 appendrefs precedes $dtags idtags
4375 set atags [anctags $currentid]
4376 if {$atags ne {}} {
4377 appendrefs follows $atags idtags
4381 set dheads [descheads $currentid]
4382 if {$dheads ne {}} {
4383 if {[appendrefs branch $dheads idheads] > 1
4384 && [$ctext get "branch -3c"] eq "h"} {
4385 # turn "Branch" into "Branches"
4386 $ctext conf -state normal
4387 $ctext insert "branch -2c" "es"
4388 $ctext conf -state disabled
4393 if {[incr tagphase] <= 2} {
4394 after idle dispnexttag
4398 proc selectline {l isnew} {
4399 global canv canv2 canv3 ctext commitinfo selectedline
4400 global displayorder linehtag linentag linedtag
4401 global canvy0 linespc parentlist children curview
4402 global currentid sha1entry
4403 global commentend idtags linknum
4404 global mergemax numcommits pending_select
4405 global cmitmode showneartags allcommits
4407 catch {unset pending_select}
4408 $canv delete hover
4409 normalline
4410 cancel_next_highlight
4411 if {$l < 0 || $l >= $numcommits} return
4412 set y [expr {$canvy0 + $l * $linespc}]
4413 set ymax [lindex [$canv cget -scrollregion] 3]
4414 set ytop [expr {$y - $linespc - 1}]
4415 set ybot [expr {$y + $linespc + 1}]
4416 set wnow [$canv yview]
4417 set wtop [expr {[lindex $wnow 0] * $ymax}]
4418 set wbot [expr {[lindex $wnow 1] * $ymax}]
4419 set wh [expr {$wbot - $wtop}]
4420 set newtop $wtop
4421 if {$ytop < $wtop} {
4422 if {$ybot < $wtop} {
4423 set newtop [expr {$y - $wh / 2.0}]
4424 } else {
4425 set newtop $ytop
4426 if {$newtop > $wtop - $linespc} {
4427 set newtop [expr {$wtop - $linespc}]
4430 } elseif {$ybot > $wbot} {
4431 if {$ytop > $wbot} {
4432 set newtop [expr {$y - $wh / 2.0}]
4433 } else {
4434 set newtop [expr {$ybot - $wh}]
4435 if {$newtop < $wtop + $linespc} {
4436 set newtop [expr {$wtop + $linespc}]
4440 if {$newtop != $wtop} {
4441 if {$newtop < 0} {
4442 set newtop 0
4444 allcanvs yview moveto [expr {$newtop * 1.0 / $ymax}]
4445 drawvisible
4448 if {![info exists linehtag($l)]} return
4449 $canv delete secsel
4450 set t [eval $canv create rect [$canv bbox $linehtag($l)] -outline {{}} \
4451 -tags secsel -fill [$canv cget -selectbackground]]
4452 $canv lower $t
4453 $canv2 delete secsel
4454 set t [eval $canv2 create rect [$canv2 bbox $linentag($l)] -outline {{}} \
4455 -tags secsel -fill [$canv2 cget -selectbackground]]
4456 $canv2 lower $t
4457 $canv3 delete secsel
4458 set t [eval $canv3 create rect [$canv3 bbox $linedtag($l)] -outline {{}} \
4459 -tags secsel -fill [$canv3 cget -selectbackground]]
4460 $canv3 lower $t
4462 if {$isnew} {
4463 addtohistory [list selectline $l 0]
4466 set selectedline $l
4468 set id [lindex $displayorder $l]
4469 set currentid $id
4470 $sha1entry delete 0 end
4471 $sha1entry insert 0 $id
4472 $sha1entry selection from 0
4473 $sha1entry selection to end
4474 rhighlight_sel $id
4476 $ctext conf -state normal
4477 clear_ctext
4478 set linknum 0
4479 set info $commitinfo($id)
4480 set date [formatdate [lindex $info 2]]
4481 $ctext insert end "Author: [lindex $info 1] $date\n"
4482 set date [formatdate [lindex $info 4]]
4483 $ctext insert end "Committer: [lindex $info 3] $date\n"
4484 if {[info exists idtags($id)]} {
4485 $ctext insert end "Tags:"
4486 foreach tag $idtags($id) {
4487 $ctext insert end " $tag"
4489 $ctext insert end "\n"
4492 set headers {}
4493 set olds [lindex $parentlist $l]
4494 if {[llength $olds] > 1} {
4495 set np 0
4496 foreach p $olds {
4497 if {$np >= $mergemax} {
4498 set tag mmax
4499 } else {
4500 set tag m$np
4502 $ctext insert end "Parent: " $tag
4503 appendwithlinks [commit_descriptor $p] {}
4504 incr np
4506 } else {
4507 foreach p $olds {
4508 append headers "Parent: [commit_descriptor $p]"
4512 foreach c $children($curview,$id) {
4513 append headers "Child: [commit_descriptor $c]"
4516 # make anything that looks like a SHA1 ID be a clickable link
4517 appendwithlinks $headers {}
4518 if {$showneartags} {
4519 if {![info exists allcommits]} {
4520 getallcommits
4522 $ctext insert end "Branch: "
4523 $ctext mark set branch "end -1c"
4524 $ctext mark gravity branch left
4525 $ctext insert end "\nFollows: "
4526 $ctext mark set follows "end -1c"
4527 $ctext mark gravity follows left
4528 $ctext insert end "\nPrecedes: "
4529 $ctext mark set precedes "end -1c"
4530 $ctext mark gravity precedes left
4531 $ctext insert end "\n"
4532 dispneartags 1
4534 $ctext insert end "\n"
4535 set comment [lindex $info 5]
4536 if {[string first "\r" $comment] >= 0} {
4537 set comment [string map {"\r" "\n "} $comment]
4539 appendwithlinks $comment {comment}
4541 $ctext tag remove found 1.0 end
4542 $ctext conf -state disabled
4543 set commentend [$ctext index "end - 1c"]
4545 init_flist "Comments"
4546 if {$cmitmode eq "tree"} {
4547 gettree $id
4548 } elseif {[llength $olds] <= 1} {
4549 startdiff $id
4550 } else {
4551 mergediff $id $l
4555 proc selfirstline {} {
4556 unmarkmatches
4557 selectline 0 1
4560 proc sellastline {} {
4561 global numcommits
4562 unmarkmatches
4563 set l [expr {$numcommits - 1}]
4564 selectline $l 1
4567 proc selnextline {dir} {
4568 global selectedline
4569 if {![info exists selectedline]} return
4570 set l [expr {$selectedline + $dir}]
4571 unmarkmatches
4572 selectline $l 1
4575 proc selnextpage {dir} {
4576 global canv linespc selectedline numcommits
4578 set lpp [expr {([winfo height $canv] - 2) / $linespc}]
4579 if {$lpp < 1} {
4580 set lpp 1
4582 allcanvs yview scroll [expr {$dir * $lpp}] units
4583 drawvisible
4584 if {![info exists selectedline]} return
4585 set l [expr {$selectedline + $dir * $lpp}]
4586 if {$l < 0} {
4587 set l 0
4588 } elseif {$l >= $numcommits} {
4589 set l [expr $numcommits - 1]
4591 unmarkmatches
4592 selectline $l 1
4595 proc unselectline {} {
4596 global selectedline currentid
4598 catch {unset selectedline}
4599 catch {unset currentid}
4600 allcanvs delete secsel
4601 rhighlight_none
4602 cancel_next_highlight
4605 proc reselectline {} {
4606 global selectedline
4608 if {[info exists selectedline]} {
4609 selectline $selectedline 0
4613 proc addtohistory {cmd} {
4614 global history historyindex curview
4616 set elt [list $curview $cmd]
4617 if {$historyindex > 0
4618 && [lindex $history [expr {$historyindex - 1}]] == $elt} {
4619 return
4622 if {$historyindex < [llength $history]} {
4623 set history [lreplace $history $historyindex end $elt]
4624 } else {
4625 lappend history $elt
4627 incr historyindex
4628 if {$historyindex > 1} {
4629 .tf.bar.leftbut conf -state normal
4630 } else {
4631 .tf.bar.leftbut conf -state disabled
4633 .tf.bar.rightbut conf -state disabled
4636 proc godo {elt} {
4637 global curview
4639 set view [lindex $elt 0]
4640 set cmd [lindex $elt 1]
4641 if {$curview != $view} {
4642 showview $view
4644 eval $cmd
4647 proc goback {} {
4648 global history historyindex
4650 if {$historyindex > 1} {
4651 incr historyindex -1
4652 godo [lindex $history [expr {$historyindex - 1}]]
4653 .tf.bar.rightbut conf -state normal
4655 if {$historyindex <= 1} {
4656 .tf.bar.leftbut conf -state disabled
4660 proc goforw {} {
4661 global history historyindex
4663 if {$historyindex < [llength $history]} {
4664 set cmd [lindex $history $historyindex]
4665 incr historyindex
4666 godo $cmd
4667 .tf.bar.leftbut conf -state normal
4669 if {$historyindex >= [llength $history]} {
4670 .tf.bar.rightbut conf -state disabled
4674 proc gettree {id} {
4675 global treefilelist treeidlist diffids diffmergeid treepending
4676 global nullid nullid2
4678 set diffids $id
4679 catch {unset diffmergeid}
4680 if {![info exists treefilelist($id)]} {
4681 if {![info exists treepending]} {
4682 if {$id eq $nullid} {
4683 set cmd [list | git ls-files]
4684 } elseif {$id eq $nullid2} {
4685 set cmd [list | git ls-files --stage -t]
4686 } else {
4687 set cmd [list | git ls-tree -r $id]
4689 if {[catch {set gtf [open $cmd r]}]} {
4690 return
4692 set treepending $id
4693 set treefilelist($id) {}
4694 set treeidlist($id) {}
4695 fconfigure $gtf -blocking 0
4696 filerun $gtf [list gettreeline $gtf $id]
4698 } else {
4699 setfilelist $id
4703 proc gettreeline {gtf id} {
4704 global treefilelist treeidlist treepending cmitmode diffids nullid nullid2
4706 set nl 0
4707 while {[incr nl] <= 1000 && [gets $gtf line] >= 0} {
4708 if {$diffids eq $nullid} {
4709 set fname $line
4710 } else {
4711 if {$diffids ne $nullid2 && [lindex $line 1] ne "blob"} continue
4712 set i [string first "\t" $line]
4713 if {$i < 0} continue
4714 set sha1 [lindex $line 2]
4715 set fname [string range $line [expr {$i+1}] end]
4716 if {[string index $fname 0] eq "\""} {
4717 set fname [lindex $fname 0]
4719 lappend treeidlist($id) $sha1
4721 lappend treefilelist($id) $fname
4723 if {![eof $gtf]} {
4724 return [expr {$nl >= 1000? 2: 1}]
4726 close $gtf
4727 unset treepending
4728 if {$cmitmode ne "tree"} {
4729 if {![info exists diffmergeid]} {
4730 gettreediffs $diffids
4732 } elseif {$id ne $diffids} {
4733 gettree $diffids
4734 } else {
4735 setfilelist $id
4737 return 0
4740 proc showfile {f} {
4741 global treefilelist treeidlist diffids nullid nullid2
4742 global ctext commentend
4744 set i [lsearch -exact $treefilelist($diffids) $f]
4745 if {$i < 0} {
4746 puts "oops, $f not in list for id $diffids"
4747 return
4749 if {$diffids eq $nullid} {
4750 if {[catch {set bf [open $f r]} err]} {
4751 puts "oops, can't read $f: $err"
4752 return
4754 } else {
4755 set blob [lindex $treeidlist($diffids) $i]
4756 if {[catch {set bf [open [concat | git cat-file blob $blob] r]} err]} {
4757 puts "oops, error reading blob $blob: $err"
4758 return
4761 fconfigure $bf -blocking 0
4762 filerun $bf [list getblobline $bf $diffids]
4763 $ctext config -state normal
4764 clear_ctext $commentend
4765 $ctext insert end "\n"
4766 $ctext insert end "$f\n" filesep
4767 $ctext config -state disabled
4768 $ctext yview $commentend
4771 proc getblobline {bf id} {
4772 global diffids cmitmode ctext
4774 if {$id ne $diffids || $cmitmode ne "tree"} {
4775 catch {close $bf}
4776 return 0
4778 $ctext config -state normal
4779 set nl 0
4780 while {[incr nl] <= 1000 && [gets $bf line] >= 0} {
4781 $ctext insert end "$line\n"
4783 if {[eof $bf]} {
4784 # delete last newline
4785 $ctext delete "end - 2c" "end - 1c"
4786 close $bf
4787 return 0
4789 $ctext config -state disabled
4790 return [expr {$nl >= 1000? 2: 1}]
4793 proc mergediff {id l} {
4794 global diffmergeid diffopts mdifffd
4795 global diffids
4796 global parentlist
4798 set diffmergeid $id
4799 set diffids $id
4800 # this doesn't seem to actually affect anything...
4801 set env(GIT_DIFF_OPTS) $diffopts
4802 set cmd [concat | git diff-tree --no-commit-id --cc $id]
4803 if {[catch {set mdf [open $cmd r]} err]} {
4804 error_popup "Error getting merge diffs: $err"
4805 return
4807 fconfigure $mdf -blocking 0
4808 set mdifffd($id) $mdf
4809 set np [llength [lindex $parentlist $l]]
4810 filerun $mdf [list getmergediffline $mdf $id $np]
4813 proc getmergediffline {mdf id np} {
4814 global diffmergeid ctext cflist mergemax
4815 global difffilestart mdifffd
4817 $ctext conf -state normal
4818 set nr 0
4819 while {[incr nr] <= 1000 && [gets $mdf line] >= 0} {
4820 if {![info exists diffmergeid] || $id != $diffmergeid
4821 || $mdf != $mdifffd($id)} {
4822 close $mdf
4823 return 0
4825 if {[regexp {^diff --cc (.*)} $line match fname]} {
4826 # start of a new file
4827 $ctext insert end "\n"
4828 set here [$ctext index "end - 1c"]
4829 lappend difffilestart $here
4830 add_flist [list $fname]
4831 set l [expr {(78 - [string length $fname]) / 2}]
4832 set pad [string range "----------------------------------------" 1 $l]
4833 $ctext insert end "$pad $fname $pad\n" filesep
4834 } elseif {[regexp {^@@} $line]} {
4835 $ctext insert end "$line\n" hunksep
4836 } elseif {[regexp {^[0-9a-f]{40}$} $line] || [regexp {^index} $line]} {
4837 # do nothing
4838 } else {
4839 # parse the prefix - one ' ', '-' or '+' for each parent
4840 set spaces {}
4841 set minuses {}
4842 set pluses {}
4843 set isbad 0
4844 for {set j 0} {$j < $np} {incr j} {
4845 set c [string range $line $j $j]
4846 if {$c == " "} {
4847 lappend spaces $j
4848 } elseif {$c == "-"} {
4849 lappend minuses $j
4850 } elseif {$c == "+"} {
4851 lappend pluses $j
4852 } else {
4853 set isbad 1
4854 break
4857 set tags {}
4858 set num {}
4859 if {!$isbad && $minuses ne {} && $pluses eq {}} {
4860 # line doesn't appear in result, parents in $minuses have the line
4861 set num [lindex $minuses 0]
4862 } elseif {!$isbad && $pluses ne {} && $minuses eq {}} {
4863 # line appears in result, parents in $pluses don't have the line
4864 lappend tags mresult
4865 set num [lindex $spaces 0]
4867 if {$num ne {}} {
4868 if {$num >= $mergemax} {
4869 set num "max"
4871 lappend tags m$num
4873 $ctext insert end "$line\n" $tags
4876 $ctext conf -state disabled
4877 if {[eof $mdf]} {
4878 close $mdf
4879 return 0
4881 return [expr {$nr >= 1000? 2: 1}]
4884 proc startdiff {ids} {
4885 global treediffs diffids treepending diffmergeid nullid nullid2
4887 set diffids $ids
4888 catch {unset diffmergeid}
4889 if {![info exists treediffs($ids)] ||
4890 [lsearch -exact $ids $nullid] >= 0 ||
4891 [lsearch -exact $ids $nullid2] >= 0} {
4892 if {![info exists treepending]} {
4893 gettreediffs $ids
4895 } else {
4896 addtocflist $ids
4900 proc addtocflist {ids} {
4901 global treediffs cflist
4902 add_flist $treediffs($ids)
4903 getblobdiffs $ids
4906 proc diffcmd {ids flags} {
4907 global nullid nullid2
4909 set i [lsearch -exact $ids $nullid]
4910 set j [lsearch -exact $ids $nullid2]
4911 if {$i >= 0} {
4912 if {[llength $ids] > 1 && $j < 0} {
4913 # comparing working directory with some specific revision
4914 set cmd [concat | git diff-index $flags]
4915 if {$i == 0} {
4916 lappend cmd -R [lindex $ids 1]
4917 } else {
4918 lappend cmd [lindex $ids 0]
4920 } else {
4921 # comparing working directory with index
4922 set cmd [concat | git diff-files $flags]
4923 if {$j == 1} {
4924 lappend cmd -R
4927 } elseif {$j >= 0} {
4928 set cmd [concat | git diff-index --cached $flags]
4929 if {[llength $ids] > 1} {
4930 # comparing index with specific revision
4931 if {$i == 0} {
4932 lappend cmd -R [lindex $ids 1]
4933 } else {
4934 lappend cmd [lindex $ids 0]
4936 } else {
4937 # comparing index with HEAD
4938 lappend cmd HEAD
4940 } else {
4941 set cmd [concat | git diff-tree -r $flags $ids]
4943 return $cmd
4946 proc gettreediffs {ids} {
4947 global treediff treepending
4949 set treepending $ids
4950 set treediff {}
4951 if {[catch {set gdtf [open [diffcmd $ids {--no-commit-id}] r]}]} return
4952 fconfigure $gdtf -blocking 0
4953 filerun $gdtf [list gettreediffline $gdtf $ids]
4956 proc gettreediffline {gdtf ids} {
4957 global treediff treediffs treepending diffids diffmergeid
4958 global cmitmode
4960 set nr 0
4961 while {[incr nr] <= 1000 && [gets $gdtf line] >= 0} {
4962 set i [string first "\t" $line]
4963 if {$i >= 0} {
4964 set file [string range $line [expr {$i+1}] end]
4965 if {[string index $file 0] eq "\""} {
4966 set file [lindex $file 0]
4968 lappend treediff $file
4971 if {![eof $gdtf]} {
4972 return [expr {$nr >= 1000? 2: 1}]
4974 close $gdtf
4975 set treediffs($ids) $treediff
4976 unset treepending
4977 if {$cmitmode eq "tree"} {
4978 gettree $diffids
4979 } elseif {$ids != $diffids} {
4980 if {![info exists diffmergeid]} {
4981 gettreediffs $diffids
4983 } else {
4984 addtocflist $ids
4986 return 0
4989 proc getblobdiffs {ids} {
4990 global diffopts blobdifffd diffids env
4991 global diffinhdr treediffs
4993 set env(GIT_DIFF_OPTS) $diffopts
4994 if {[catch {set bdf [open [diffcmd $ids {-p -C --no-commit-id}] r]} err]} {
4995 puts "error getting diffs: $err"
4996 return
4998 set diffinhdr 0
4999 fconfigure $bdf -blocking 0
5000 set blobdifffd($ids) $bdf
5001 filerun $bdf [list getblobdiffline $bdf $diffids]
5004 proc setinlist {var i val} {
5005 global $var
5007 while {[llength [set $var]] < $i} {
5008 lappend $var {}
5010 if {[llength [set $var]] == $i} {
5011 lappend $var $val
5012 } else {
5013 lset $var $i $val
5017 proc makediffhdr {fname ids} {
5018 global ctext curdiffstart treediffs
5020 set i [lsearch -exact $treediffs($ids) $fname]
5021 if {$i >= 0} {
5022 setinlist difffilestart $i $curdiffstart
5024 set l [expr {(78 - [string length $fname]) / 2}]
5025 set pad [string range "----------------------------------------" 1 $l]
5026 $ctext insert $curdiffstart "$pad $fname $pad" filesep
5029 proc getblobdiffline {bdf ids} {
5030 global diffids blobdifffd ctext curdiffstart
5031 global diffnexthead diffnextnote difffilestart
5032 global diffinhdr treediffs
5034 set nr 0
5035 $ctext conf -state normal
5036 while {[incr nr] <= 1000 && [gets $bdf line] >= 0} {
5037 if {$ids != $diffids || $bdf != $blobdifffd($ids)} {
5038 close $bdf
5039 return 0
5041 if {![string compare -length 11 "diff --git " $line]} {
5042 # trim off "diff --git "
5043 set line [string range $line 11 end]
5044 set diffinhdr 1
5045 # start of a new file
5046 $ctext insert end "\n"
5047 set curdiffstart [$ctext index "end - 1c"]
5048 $ctext insert end "\n" filesep
5049 # If the name hasn't changed the length will be odd,
5050 # the middle char will be a space, and the two bits either
5051 # side will be a/name and b/name, or "a/name" and "b/name".
5052 # If the name has changed we'll get "rename from" and
5053 # "rename to" lines following this, and we'll use them
5054 # to get the filenames.
5055 # This complexity is necessary because spaces in the filename(s)
5056 # don't get escaped.
5057 set l [string length $line]
5058 set i [expr {$l / 2}]
5059 if {!(($l & 1) && [string index $line $i] eq " " &&
5060 [string range $line 2 [expr {$i - 1}]] eq \
5061 [string range $line [expr {$i + 3}] end])} {
5062 continue
5064 # unescape if quoted and chop off the a/ from the front
5065 if {[string index $line 0] eq "\""} {
5066 set fname [string range [lindex $line 0] 2 end]
5067 } else {
5068 set fname [string range $line 2 [expr {$i - 1}]]
5070 makediffhdr $fname $ids
5072 } elseif {[regexp {^@@ -([0-9]+)(,[0-9]+)? \+([0-9]+)(,[0-9]+)? @@(.*)} \
5073 $line match f1l f1c f2l f2c rest]} {
5074 $ctext insert end "$line\n" hunksep
5075 set diffinhdr 0
5077 } elseif {$diffinhdr} {
5078 if {![string compare -length 12 "rename from " $line]} {
5079 set fname [string range $line 12 end]
5080 if {[string index $fname 0] eq "\""} {
5081 set fname [lindex $fname 0]
5083 set i [lsearch -exact $treediffs($ids) $fname]
5084 if {$i >= 0} {
5085 setinlist difffilestart $i $curdiffstart
5087 } elseif {![string compare -length 10 $line "rename to "]} {
5088 set fname [string range $line 10 end]
5089 if {[string index $fname 0] eq "\""} {
5090 set fname [lindex $fname 0]
5092 makediffhdr $fname $ids
5093 } elseif {[string compare -length 3 $line "---"] == 0} {
5094 # do nothing
5095 continue
5096 } elseif {[string compare -length 3 $line "+++"] == 0} {
5097 set diffinhdr 0
5098 continue
5100 $ctext insert end "$line\n" filesep
5102 } else {
5103 set x [string range $line 0 0]
5104 if {$x == "-" || $x == "+"} {
5105 set tag [expr {$x == "+"}]
5106 $ctext insert end "$line\n" d$tag
5107 } elseif {$x == " "} {
5108 $ctext insert end "$line\n"
5109 } else {
5110 # "\ No newline at end of file",
5111 # or something else we don't recognize
5112 $ctext insert end "$line\n" hunksep
5116 $ctext conf -state disabled
5117 if {[eof $bdf]} {
5118 close $bdf
5119 return 0
5121 return [expr {$nr >= 1000? 2: 1}]
5124 proc changediffdisp {} {
5125 global ctext diffelide
5127 $ctext tag conf d0 -elide [lindex $diffelide 0]
5128 $ctext tag conf d1 -elide [lindex $diffelide 1]
5131 proc prevfile {} {
5132 global difffilestart ctext
5133 set prev [lindex $difffilestart 0]
5134 set here [$ctext index @0,0]
5135 foreach loc $difffilestart {
5136 if {[$ctext compare $loc >= $here]} {
5137 $ctext yview $prev
5138 return
5140 set prev $loc
5142 $ctext yview $prev
5145 proc nextfile {} {
5146 global difffilestart ctext
5147 set here [$ctext index @0,0]
5148 foreach loc $difffilestart {
5149 if {[$ctext compare $loc > $here]} {
5150 $ctext yview $loc
5151 return
5156 proc clear_ctext {{first 1.0}} {
5157 global ctext smarktop smarkbot
5159 set l [lindex [split $first .] 0]
5160 if {![info exists smarktop] || [$ctext compare $first < $smarktop.0]} {
5161 set smarktop $l
5163 if {![info exists smarkbot] || [$ctext compare $first < $smarkbot.0]} {
5164 set smarkbot $l
5166 $ctext delete $first end
5169 proc incrsearch {name ix op} {
5170 global ctext searchstring searchdirn
5172 $ctext tag remove found 1.0 end
5173 if {[catch {$ctext index anchor}]} {
5174 # no anchor set, use start of selection, or of visible area
5175 set sel [$ctext tag ranges sel]
5176 if {$sel ne {}} {
5177 $ctext mark set anchor [lindex $sel 0]
5178 } elseif {$searchdirn eq "-forwards"} {
5179 $ctext mark set anchor @0,0
5180 } else {
5181 $ctext mark set anchor @0,[winfo height $ctext]
5184 if {$searchstring ne {}} {
5185 set here [$ctext search $searchdirn -- $searchstring anchor]
5186 if {$here ne {}} {
5187 $ctext see $here
5189 searchmarkvisible 1
5193 proc dosearch {} {
5194 global sstring ctext searchstring searchdirn
5196 focus $sstring
5197 $sstring icursor end
5198 set searchdirn -forwards
5199 if {$searchstring ne {}} {
5200 set sel [$ctext tag ranges sel]
5201 if {$sel ne {}} {
5202 set start "[lindex $sel 0] + 1c"
5203 } elseif {[catch {set start [$ctext index anchor]}]} {
5204 set start "@0,0"
5206 set match [$ctext search -count mlen -- $searchstring $start]
5207 $ctext tag remove sel 1.0 end
5208 if {$match eq {}} {
5209 bell
5210 return
5212 $ctext see $match
5213 set mend "$match + $mlen c"
5214 $ctext tag add sel $match $mend
5215 $ctext mark unset anchor
5219 proc dosearchback {} {
5220 global sstring ctext searchstring searchdirn
5222 focus $sstring
5223 $sstring icursor end
5224 set searchdirn -backwards
5225 if {$searchstring ne {}} {
5226 set sel [$ctext tag ranges sel]
5227 if {$sel ne {}} {
5228 set start [lindex $sel 0]
5229 } elseif {[catch {set start [$ctext index anchor]}]} {
5230 set start @0,[winfo height $ctext]
5232 set match [$ctext search -backwards -count ml -- $searchstring $start]
5233 $ctext tag remove sel 1.0 end
5234 if {$match eq {}} {
5235 bell
5236 return
5238 $ctext see $match
5239 set mend "$match + $ml c"
5240 $ctext tag add sel $match $mend
5241 $ctext mark unset anchor
5245 proc searchmark {first last} {
5246 global ctext searchstring
5248 set mend $first.0
5249 while {1} {
5250 set match [$ctext search -count mlen -- $searchstring $mend $last.end]
5251 if {$match eq {}} break
5252 set mend "$match + $mlen c"
5253 $ctext tag add found $match $mend
5257 proc searchmarkvisible {doall} {
5258 global ctext smarktop smarkbot
5260 set topline [lindex [split [$ctext index @0,0] .] 0]
5261 set botline [lindex [split [$ctext index @0,[winfo height $ctext]] .] 0]
5262 if {$doall || $botline < $smarktop || $topline > $smarkbot} {
5263 # no overlap with previous
5264 searchmark $topline $botline
5265 set smarktop $topline
5266 set smarkbot $botline
5267 } else {
5268 if {$topline < $smarktop} {
5269 searchmark $topline [expr {$smarktop-1}]
5270 set smarktop $topline
5272 if {$botline > $smarkbot} {
5273 searchmark [expr {$smarkbot+1}] $botline
5274 set smarkbot $botline
5279 proc scrolltext {f0 f1} {
5280 global searchstring
5282 .bleft.sb set $f0 $f1
5283 if {$searchstring ne {}} {
5284 searchmarkvisible 0
5288 proc setcoords {} {
5289 global linespc charspc canvx0 canvy0 mainfont
5290 global xspc1 xspc2 lthickness
5292 set linespc [font metrics $mainfont -linespace]
5293 set charspc [font measure $mainfont "m"]
5294 set canvy0 [expr {int(3 + 0.5 * $linespc)}]
5295 set canvx0 [expr {int(3 + 0.5 * $linespc)}]
5296 set lthickness [expr {int($linespc / 9) + 1}]
5297 set xspc1(0) $linespc
5298 set xspc2 $linespc
5301 proc redisplay {} {
5302 global canv
5303 global selectedline
5305 set ymax [lindex [$canv cget -scrollregion] 3]
5306 if {$ymax eq {} || $ymax == 0} return
5307 set span [$canv yview]
5308 clear_display
5309 setcanvscroll
5310 allcanvs yview moveto [lindex $span 0]
5311 drawvisible
5312 if {[info exists selectedline]} {
5313 selectline $selectedline 0
5314 allcanvs yview moveto [lindex $span 0]
5318 proc incrfont {inc} {
5319 global mainfont textfont ctext canv phase cflist
5320 global charspc tabstop
5321 global stopped entries
5322 unmarkmatches
5323 set mainfont [lreplace $mainfont 1 1 [expr {[lindex $mainfont 1] + $inc}]]
5324 set textfont [lreplace $textfont 1 1 [expr {[lindex $textfont 1] + $inc}]]
5325 setcoords
5326 $ctext conf -font $textfont -tabs "[expr {$tabstop * $charspc}]"
5327 $cflist conf -font $textfont
5328 $ctext tag conf filesep -font [concat $textfont bold]
5329 foreach e $entries {
5330 $e conf -font $mainfont
5332 if {$phase eq "getcommits"} {
5333 $canv itemconf textitems -font $mainfont
5335 redisplay
5338 proc clearsha1 {} {
5339 global sha1entry sha1string
5340 if {[string length $sha1string] == 40} {
5341 $sha1entry delete 0 end
5345 proc sha1change {n1 n2 op} {
5346 global sha1string currentid sha1but
5347 if {$sha1string == {}
5348 || ([info exists currentid] && $sha1string == $currentid)} {
5349 set state disabled
5350 } else {
5351 set state normal
5353 if {[$sha1but cget -state] == $state} return
5354 if {$state == "normal"} {
5355 $sha1but conf -state normal -relief raised -text "Goto: "
5356 } else {
5357 $sha1but conf -state disabled -relief flat -text "SHA1 ID: "
5361 proc gotocommit {} {
5362 global sha1string currentid commitrow tagids headids
5363 global displayorder numcommits curview
5365 if {$sha1string == {}
5366 || ([info exists currentid] && $sha1string == $currentid)} return
5367 if {[info exists tagids($sha1string)]} {
5368 set id $tagids($sha1string)
5369 } elseif {[info exists headids($sha1string)]} {
5370 set id $headids($sha1string)
5371 } else {
5372 set id [string tolower $sha1string]
5373 if {[regexp {^[0-9a-f]{4,39}$} $id]} {
5374 set matches {}
5375 foreach i $displayorder {
5376 if {[string match $id* $i]} {
5377 lappend matches $i
5380 if {$matches ne {}} {
5381 if {[llength $matches] > 1} {
5382 error_popup "Short SHA1 id $id is ambiguous"
5383 return
5385 set id [lindex $matches 0]
5389 if {[info exists commitrow($curview,$id)]} {
5390 selectline $commitrow($curview,$id) 1
5391 return
5393 if {[regexp {^[0-9a-fA-F]{4,}$} $sha1string]} {
5394 set type "SHA1 id"
5395 } else {
5396 set type "Tag/Head"
5398 error_popup "$type $sha1string is not known"
5401 proc lineenter {x y id} {
5402 global hoverx hovery hoverid hovertimer
5403 global commitinfo canv
5405 if {![info exists commitinfo($id)] && ![getcommit $id]} return
5406 set hoverx $x
5407 set hovery $y
5408 set hoverid $id
5409 if {[info exists hovertimer]} {
5410 after cancel $hovertimer
5412 set hovertimer [after 500 linehover]
5413 $canv delete hover
5416 proc linemotion {x y id} {
5417 global hoverx hovery hoverid hovertimer
5419 if {[info exists hoverid] && $id == $hoverid} {
5420 set hoverx $x
5421 set hovery $y
5422 if {[info exists hovertimer]} {
5423 after cancel $hovertimer
5425 set hovertimer [after 500 linehover]
5429 proc lineleave {id} {
5430 global hoverid hovertimer canv
5432 if {[info exists hoverid] && $id == $hoverid} {
5433 $canv delete hover
5434 if {[info exists hovertimer]} {
5435 after cancel $hovertimer
5436 unset hovertimer
5438 unset hoverid
5442 proc linehover {} {
5443 global hoverx hovery hoverid hovertimer
5444 global canv linespc lthickness
5445 global commitinfo mainfont
5447 set text [lindex $commitinfo($hoverid) 0]
5448 set ymax [lindex [$canv cget -scrollregion] 3]
5449 if {$ymax == {}} return
5450 set yfrac [lindex [$canv yview] 0]
5451 set x [expr {$hoverx + 2 * $linespc}]
5452 set y [expr {$hovery + $yfrac * $ymax - $linespc / 2}]
5453 set x0 [expr {$x - 2 * $lthickness}]
5454 set y0 [expr {$y - 2 * $lthickness}]
5455 set x1 [expr {$x + [font measure $mainfont $text] + 2 * $lthickness}]
5456 set y1 [expr {$y + $linespc + 2 * $lthickness}]
5457 set t [$canv create rectangle $x0 $y0 $x1 $y1 \
5458 -fill \#ffff80 -outline black -width 1 -tags hover]
5459 $canv raise $t
5460 set t [$canv create text $x $y -anchor nw -text $text -tags hover \
5461 -font $mainfont]
5462 $canv raise $t
5465 proc clickisonarrow {id y} {
5466 global lthickness
5468 set ranges [rowranges $id]
5469 set thresh [expr {2 * $lthickness + 6}]
5470 set n [expr {[llength $ranges] - 1}]
5471 for {set i 1} {$i < $n} {incr i} {
5472 set row [lindex $ranges $i]
5473 if {abs([yc $row] - $y) < $thresh} {
5474 return $i
5477 return {}
5480 proc arrowjump {id n y} {
5481 global canv
5483 # 1 <-> 2, 3 <-> 4, etc...
5484 set n [expr {(($n - 1) ^ 1) + 1}]
5485 set row [lindex [rowranges $id] $n]
5486 set yt [yc $row]
5487 set ymax [lindex [$canv cget -scrollregion] 3]
5488 if {$ymax eq {} || $ymax <= 0} return
5489 set view [$canv yview]
5490 set yspan [expr {[lindex $view 1] - [lindex $view 0]}]
5491 set yfrac [expr {$yt / $ymax - $yspan / 2}]
5492 if {$yfrac < 0} {
5493 set yfrac 0
5495 allcanvs yview moveto $yfrac
5498 proc lineclick {x y id isnew} {
5499 global ctext commitinfo children canv thickerline curview
5501 if {![info exists commitinfo($id)] && ![getcommit $id]} return
5502 unmarkmatches
5503 unselectline
5504 normalline
5505 $canv delete hover
5506 # draw this line thicker than normal
5507 set thickerline $id
5508 drawlines $id
5509 if {$isnew} {
5510 set ymax [lindex [$canv cget -scrollregion] 3]
5511 if {$ymax eq {}} return
5512 set yfrac [lindex [$canv yview] 0]
5513 set y [expr {$y + $yfrac * $ymax}]
5515 set dirn [clickisonarrow $id $y]
5516 if {$dirn ne {}} {
5517 arrowjump $id $dirn $y
5518 return
5521 if {$isnew} {
5522 addtohistory [list lineclick $x $y $id 0]
5524 # fill the details pane with info about this line
5525 $ctext conf -state normal
5526 clear_ctext
5527 $ctext tag conf link -foreground blue -underline 1
5528 $ctext tag bind link <Enter> { %W configure -cursor hand2 }
5529 $ctext tag bind link <Leave> { %W configure -cursor $curtextcursor }
5530 $ctext insert end "Parent:\t"
5531 $ctext insert end $id [list link link0]
5532 $ctext tag bind link0 <1> [list selbyid $id]
5533 set info $commitinfo($id)
5534 $ctext insert end "\n\t[lindex $info 0]\n"
5535 $ctext insert end "\tAuthor:\t[lindex $info 1]\n"
5536 set date [formatdate [lindex $info 2]]
5537 $ctext insert end "\tDate:\t$date\n"
5538 set kids $children($curview,$id)
5539 if {$kids ne {}} {
5540 $ctext insert end "\nChildren:"
5541 set i 0
5542 foreach child $kids {
5543 incr i
5544 if {![info exists commitinfo($child)] && ![getcommit $child]} continue
5545 set info $commitinfo($child)
5546 $ctext insert end "\n\t"
5547 $ctext insert end $child [list link link$i]
5548 $ctext tag bind link$i <1> [list selbyid $child]
5549 $ctext insert end "\n\t[lindex $info 0]"
5550 $ctext insert end "\n\tAuthor:\t[lindex $info 1]"
5551 set date [formatdate [lindex $info 2]]
5552 $ctext insert end "\n\tDate:\t$date\n"
5555 $ctext conf -state disabled
5556 init_flist {}
5559 proc normalline {} {
5560 global thickerline
5561 if {[info exists thickerline]} {
5562 set id $thickerline
5563 unset thickerline
5564 drawlines $id
5568 proc selbyid {id} {
5569 global commitrow curview
5570 if {[info exists commitrow($curview,$id)]} {
5571 selectline $commitrow($curview,$id) 1
5575 proc mstime {} {
5576 global startmstime
5577 if {![info exists startmstime]} {
5578 set startmstime [clock clicks -milliseconds]
5580 return [format "%.3f" [expr {([clock click -milliseconds] - $startmstime) / 1000.0}]]
5583 proc rowmenu {x y id} {
5584 global rowctxmenu commitrow selectedline rowmenuid curview
5585 global nullid nullid2 fakerowmenu mainhead
5587 set rowmenuid $id
5588 if {![info exists selectedline]
5589 || $commitrow($curview,$id) eq $selectedline} {
5590 set state disabled
5591 } else {
5592 set state normal
5594 if {$id ne $nullid && $id ne $nullid2} {
5595 set menu $rowctxmenu
5596 $menu entryconfigure 7 -label "Reset $mainhead branch to here"
5597 } else {
5598 set menu $fakerowmenu
5600 $menu entryconfigure "Diff this*" -state $state
5601 $menu entryconfigure "Diff selected*" -state $state
5602 $menu entryconfigure "Make patch" -state $state
5603 tk_popup $menu $x $y
5606 proc diffvssel {dirn} {
5607 global rowmenuid selectedline displayorder
5609 if {![info exists selectedline]} return
5610 if {$dirn} {
5611 set oldid [lindex $displayorder $selectedline]
5612 set newid $rowmenuid
5613 } else {
5614 set oldid $rowmenuid
5615 set newid [lindex $displayorder $selectedline]
5617 addtohistory [list doseldiff $oldid $newid]
5618 doseldiff $oldid $newid
5621 proc doseldiff {oldid newid} {
5622 global ctext
5623 global commitinfo
5625 $ctext conf -state normal
5626 clear_ctext
5627 init_flist "Top"
5628 $ctext insert end "From "
5629 $ctext tag conf link -foreground blue -underline 1
5630 $ctext tag bind link <Enter> { %W configure -cursor hand2 }
5631 $ctext tag bind link <Leave> { %W configure -cursor $curtextcursor }
5632 $ctext tag bind link0 <1> [list selbyid $oldid]
5633 $ctext insert end $oldid [list link link0]
5634 $ctext insert end "\n "
5635 $ctext insert end [lindex $commitinfo($oldid) 0]
5636 $ctext insert end "\n\nTo "
5637 $ctext tag bind link1 <1> [list selbyid $newid]
5638 $ctext insert end $newid [list link link1]
5639 $ctext insert end "\n "
5640 $ctext insert end [lindex $commitinfo($newid) 0]
5641 $ctext insert end "\n"
5642 $ctext conf -state disabled
5643 $ctext tag remove found 1.0 end
5644 startdiff [list $oldid $newid]
5647 proc mkpatch {} {
5648 global rowmenuid currentid commitinfo patchtop patchnum
5650 if {![info exists currentid]} return
5651 set oldid $currentid
5652 set oldhead [lindex $commitinfo($oldid) 0]
5653 set newid $rowmenuid
5654 set newhead [lindex $commitinfo($newid) 0]
5655 set top .patch
5656 set patchtop $top
5657 catch {destroy $top}
5658 toplevel $top
5659 label $top.title -text "Generate patch"
5660 grid $top.title - -pady 10
5661 label $top.from -text "From:"
5662 entry $top.fromsha1 -width 40 -relief flat
5663 $top.fromsha1 insert 0 $oldid
5664 $top.fromsha1 conf -state readonly
5665 grid $top.from $top.fromsha1 -sticky w
5666 entry $top.fromhead -width 60 -relief flat
5667 $top.fromhead insert 0 $oldhead
5668 $top.fromhead conf -state readonly
5669 grid x $top.fromhead -sticky w
5670 label $top.to -text "To:"
5671 entry $top.tosha1 -width 40 -relief flat
5672 $top.tosha1 insert 0 $newid
5673 $top.tosha1 conf -state readonly
5674 grid $top.to $top.tosha1 -sticky w
5675 entry $top.tohead -width 60 -relief flat
5676 $top.tohead insert 0 $newhead
5677 $top.tohead conf -state readonly
5678 grid x $top.tohead -sticky w
5679 button $top.rev -text "Reverse" -command mkpatchrev -padx 5
5680 grid $top.rev x -pady 10
5681 label $top.flab -text "Output file:"
5682 entry $top.fname -width 60
5683 $top.fname insert 0 [file normalize "patch$patchnum.patch"]
5684 incr patchnum
5685 grid $top.flab $top.fname -sticky w
5686 frame $top.buts
5687 button $top.buts.gen -text "Generate" -command mkpatchgo
5688 button $top.buts.can -text "Cancel" -command mkpatchcan
5689 grid $top.buts.gen $top.buts.can
5690 grid columnconfigure $top.buts 0 -weight 1 -uniform a
5691 grid columnconfigure $top.buts 1 -weight 1 -uniform a
5692 grid $top.buts - -pady 10 -sticky ew
5693 focus $top.fname
5696 proc mkpatchrev {} {
5697 global patchtop
5699 set oldid [$patchtop.fromsha1 get]
5700 set oldhead [$patchtop.fromhead get]
5701 set newid [$patchtop.tosha1 get]
5702 set newhead [$patchtop.tohead get]
5703 foreach e [list fromsha1 fromhead tosha1 tohead] \
5704 v [list $newid $newhead $oldid $oldhead] {
5705 $patchtop.$e conf -state normal
5706 $patchtop.$e delete 0 end
5707 $patchtop.$e insert 0 $v
5708 $patchtop.$e conf -state readonly
5712 proc mkpatchgo {} {
5713 global patchtop nullid nullid2
5715 set oldid [$patchtop.fromsha1 get]
5716 set newid [$patchtop.tosha1 get]
5717 set fname [$patchtop.fname get]
5718 set cmd [diffcmd [list $oldid $newid] -p]
5719 lappend cmd >$fname &
5720 if {[catch {eval exec $cmd} err]} {
5721 error_popup "Error creating patch: $err"
5723 catch {destroy $patchtop}
5724 unset patchtop
5727 proc mkpatchcan {} {
5728 global patchtop
5730 catch {destroy $patchtop}
5731 unset patchtop
5734 proc mktag {} {
5735 global rowmenuid mktagtop commitinfo
5737 set top .maketag
5738 set mktagtop $top
5739 catch {destroy $top}
5740 toplevel $top
5741 label $top.title -text "Create tag"
5742 grid $top.title - -pady 10
5743 label $top.id -text "ID:"
5744 entry $top.sha1 -width 40 -relief flat
5745 $top.sha1 insert 0 $rowmenuid
5746 $top.sha1 conf -state readonly
5747 grid $top.id $top.sha1 -sticky w
5748 entry $top.head -width 60 -relief flat
5749 $top.head insert 0 [lindex $commitinfo($rowmenuid) 0]
5750 $top.head conf -state readonly
5751 grid x $top.head -sticky w
5752 label $top.tlab -text "Tag name:"
5753 entry $top.tag -width 60
5754 grid $top.tlab $top.tag -sticky w
5755 frame $top.buts
5756 button $top.buts.gen -text "Create" -command mktaggo
5757 button $top.buts.can -text "Cancel" -command mktagcan
5758 grid $top.buts.gen $top.buts.can
5759 grid columnconfigure $top.buts 0 -weight 1 -uniform a
5760 grid columnconfigure $top.buts 1 -weight 1 -uniform a
5761 grid $top.buts - -pady 10 -sticky ew
5762 focus $top.tag
5765 proc domktag {} {
5766 global mktagtop env tagids idtags
5768 set id [$mktagtop.sha1 get]
5769 set tag [$mktagtop.tag get]
5770 if {$tag == {}} {
5771 error_popup "No tag name specified"
5772 return
5774 if {[info exists tagids($tag)]} {
5775 error_popup "Tag \"$tag\" already exists"
5776 return
5778 if {[catch {
5779 set dir [gitdir]
5780 set fname [file join $dir "refs/tags" $tag]
5781 set f [open $fname w]
5782 puts $f $id
5783 close $f
5784 } err]} {
5785 error_popup "Error creating tag: $err"
5786 return
5789 set tagids($tag) $id
5790 lappend idtags($id) $tag
5791 redrawtags $id
5792 addedtag $id
5795 proc redrawtags {id} {
5796 global canv linehtag commitrow idpos selectedline curview
5797 global mainfont canvxmax iddrawn
5799 if {![info exists commitrow($curview,$id)]} return
5800 if {![info exists iddrawn($id)]} return
5801 drawcommits $commitrow($curview,$id)
5802 $canv delete tag.$id
5803 set xt [eval drawtags $id $idpos($id)]
5804 $canv coords $linehtag($commitrow($curview,$id)) $xt [lindex $idpos($id) 2]
5805 set text [$canv itemcget $linehtag($commitrow($curview,$id)) -text]
5806 set xr [expr {$xt + [font measure $mainfont $text]}]
5807 if {$xr > $canvxmax} {
5808 set canvxmax $xr
5809 setcanvscroll
5811 if {[info exists selectedline]
5812 && $selectedline == $commitrow($curview,$id)} {
5813 selectline $selectedline 0
5817 proc mktagcan {} {
5818 global mktagtop
5820 catch {destroy $mktagtop}
5821 unset mktagtop
5824 proc mktaggo {} {
5825 domktag
5826 mktagcan
5829 proc writecommit {} {
5830 global rowmenuid wrcomtop commitinfo wrcomcmd
5832 set top .writecommit
5833 set wrcomtop $top
5834 catch {destroy $top}
5835 toplevel $top
5836 label $top.title -text "Write commit to file"
5837 grid $top.title - -pady 10
5838 label $top.id -text "ID:"
5839 entry $top.sha1 -width 40 -relief flat
5840 $top.sha1 insert 0 $rowmenuid
5841 $top.sha1 conf -state readonly
5842 grid $top.id $top.sha1 -sticky w
5843 entry $top.head -width 60 -relief flat
5844 $top.head insert 0 [lindex $commitinfo($rowmenuid) 0]
5845 $top.head conf -state readonly
5846 grid x $top.head -sticky w
5847 label $top.clab -text "Command:"
5848 entry $top.cmd -width 60 -textvariable wrcomcmd
5849 grid $top.clab $top.cmd -sticky w -pady 10
5850 label $top.flab -text "Output file:"
5851 entry $top.fname -width 60
5852 $top.fname insert 0 [file normalize "commit-[string range $rowmenuid 0 6]"]
5853 grid $top.flab $top.fname -sticky w
5854 frame $top.buts
5855 button $top.buts.gen -text "Write" -command wrcomgo
5856 button $top.buts.can -text "Cancel" -command wrcomcan
5857 grid $top.buts.gen $top.buts.can
5858 grid columnconfigure $top.buts 0 -weight 1 -uniform a
5859 grid columnconfigure $top.buts 1 -weight 1 -uniform a
5860 grid $top.buts - -pady 10 -sticky ew
5861 focus $top.fname
5864 proc wrcomgo {} {
5865 global wrcomtop
5867 set id [$wrcomtop.sha1 get]
5868 set cmd "echo $id | [$wrcomtop.cmd get]"
5869 set fname [$wrcomtop.fname get]
5870 if {[catch {exec sh -c $cmd >$fname &} err]} {
5871 error_popup "Error writing commit: $err"
5873 catch {destroy $wrcomtop}
5874 unset wrcomtop
5877 proc wrcomcan {} {
5878 global wrcomtop
5880 catch {destroy $wrcomtop}
5881 unset wrcomtop
5884 proc mkbranch {} {
5885 global rowmenuid mkbrtop
5887 set top .makebranch
5888 catch {destroy $top}
5889 toplevel $top
5890 label $top.title -text "Create new branch"
5891 grid $top.title - -pady 10
5892 label $top.id -text "ID:"
5893 entry $top.sha1 -width 40 -relief flat
5894 $top.sha1 insert 0 $rowmenuid
5895 $top.sha1 conf -state readonly
5896 grid $top.id $top.sha1 -sticky w
5897 label $top.nlab -text "Name:"
5898 entry $top.name -width 40
5899 grid $top.nlab $top.name -sticky w
5900 frame $top.buts
5901 button $top.buts.go -text "Create" -command [list mkbrgo $top]
5902 button $top.buts.can -text "Cancel" -command "catch {destroy $top}"
5903 grid $top.buts.go $top.buts.can
5904 grid columnconfigure $top.buts 0 -weight 1 -uniform a
5905 grid columnconfigure $top.buts 1 -weight 1 -uniform a
5906 grid $top.buts - -pady 10 -sticky ew
5907 focus $top.name
5910 proc mkbrgo {top} {
5911 global headids idheads
5913 set name [$top.name get]
5914 set id [$top.sha1 get]
5915 if {$name eq {}} {
5916 error_popup "Please specify a name for the new branch"
5917 return
5919 catch {destroy $top}
5920 nowbusy newbranch
5921 update
5922 if {[catch {
5923 exec git branch $name $id
5924 } err]} {
5925 notbusy newbranch
5926 error_popup $err
5927 } else {
5928 set headids($name) $id
5929 lappend idheads($id) $name
5930 addedhead $id $name
5931 notbusy newbranch
5932 redrawtags $id
5933 dispneartags 0
5937 proc cherrypick {} {
5938 global rowmenuid curview commitrow
5939 global mainhead
5941 set oldhead [exec git rev-parse HEAD]
5942 set dheads [descheads $rowmenuid]
5943 if {$dheads ne {} && [lsearch -exact $dheads $oldhead] >= 0} {
5944 set ok [confirm_popup "Commit [string range $rowmenuid 0 7] is already\
5945 included in branch $mainhead -- really re-apply it?"]
5946 if {!$ok} return
5948 nowbusy cherrypick
5949 update
5950 # Unfortunately git-cherry-pick writes stuff to stderr even when
5951 # no error occurs, and exec takes that as an indication of error...
5952 if {[catch {exec sh -c "git cherry-pick -r $rowmenuid 2>&1"} err]} {
5953 notbusy cherrypick
5954 error_popup $err
5955 return
5957 set newhead [exec git rev-parse HEAD]
5958 if {$newhead eq $oldhead} {
5959 notbusy cherrypick
5960 error_popup "No changes committed"
5961 return
5963 addnewchild $newhead $oldhead
5964 if {[info exists commitrow($curview,$oldhead)]} {
5965 insertrow $commitrow($curview,$oldhead) $newhead
5966 if {$mainhead ne {}} {
5967 movehead $newhead $mainhead
5968 movedhead $newhead $mainhead
5970 redrawtags $oldhead
5971 redrawtags $newhead
5973 notbusy cherrypick
5976 proc resethead {} {
5977 global mainheadid mainhead rowmenuid confirm_ok resettype
5978 global showlocalchanges
5980 set confirm_ok 0
5981 set w ".confirmreset"
5982 toplevel $w
5983 wm transient $w .
5984 wm title $w "Confirm reset"
5985 message $w.m -text \
5986 "Reset branch $mainhead to [string range $rowmenuid 0 7]?" \
5987 -justify center -aspect 1000
5988 pack $w.m -side top -fill x -padx 20 -pady 20
5989 frame $w.f -relief sunken -border 2
5990 message $w.f.rt -text "Reset type:" -aspect 1000
5991 grid $w.f.rt -sticky w
5992 set resettype mixed
5993 radiobutton $w.f.soft -value soft -variable resettype -justify left \
5994 -text "Soft: Leave working tree and index untouched"
5995 grid $w.f.soft -sticky w
5996 radiobutton $w.f.mixed -value mixed -variable resettype -justify left \
5997 -text "Mixed: Leave working tree untouched, reset index"
5998 grid $w.f.mixed -sticky w
5999 radiobutton $w.f.hard -value hard -variable resettype -justify left \
6000 -text "Hard: Reset working tree and index\n(discard ALL local changes)"
6001 grid $w.f.hard -sticky w
6002 pack $w.f -side top -fill x
6003 button $w.ok -text OK -command "set confirm_ok 1; destroy $w"
6004 pack $w.ok -side left -fill x -padx 20 -pady 20
6005 button $w.cancel -text Cancel -command "destroy $w"
6006 pack $w.cancel -side right -fill x -padx 20 -pady 20
6007 bind $w <Visibility> "grab $w; focus $w"
6008 tkwait window $w
6009 if {!$confirm_ok} return
6010 if {[catch {set fd [open \
6011 [list | sh -c "git reset --$resettype $rowmenuid 2>&1"] r]} err]} {
6012 error_popup $err
6013 } else {
6014 dohidelocalchanges
6015 set w ".resetprogress"
6016 filerun $fd [list readresetstat $fd $w]
6017 toplevel $w
6018 wm transient $w
6019 wm title $w "Reset progress"
6020 message $w.m -text "Reset in progress, please wait..." \
6021 -justify center -aspect 1000
6022 pack $w.m -side top -fill x -padx 20 -pady 5
6023 canvas $w.c -width 150 -height 20 -bg white
6024 $w.c create rect 0 0 0 20 -fill green -tags rect
6025 pack $w.c -side top -fill x -padx 20 -pady 5 -expand 1
6026 nowbusy reset
6030 proc readresetstat {fd w} {
6031 global mainhead mainheadid showlocalchanges
6033 if {[gets $fd line] >= 0} {
6034 if {[regexp {([0-9]+)% \(([0-9]+)/([0-9]+)\)} $line match p m n]} {
6035 set x [expr {($m * 150) / $n}]
6036 $w.c coords rect 0 0 $x 20
6038 return 1
6040 destroy $w
6041 notbusy reset
6042 if {[catch {close $fd} err]} {
6043 error_popup $err
6045 set oldhead $mainheadid
6046 set newhead [exec git rev-parse HEAD]
6047 if {$newhead ne $oldhead} {
6048 movehead $newhead $mainhead
6049 movedhead $newhead $mainhead
6050 set mainheadid $newhead
6051 redrawtags $oldhead
6052 redrawtags $newhead
6054 if {$showlocalchanges} {
6055 doshowlocalchanges
6057 return 0
6060 # context menu for a head
6061 proc headmenu {x y id head} {
6062 global headmenuid headmenuhead headctxmenu mainhead
6064 set headmenuid $id
6065 set headmenuhead $head
6066 set state normal
6067 if {$head eq $mainhead} {
6068 set state disabled
6070 $headctxmenu entryconfigure 0 -state $state
6071 $headctxmenu entryconfigure 1 -state $state
6072 tk_popup $headctxmenu $x $y
6075 proc cobranch {} {
6076 global headmenuid headmenuhead mainhead headids
6077 global showlocalchanges mainheadid
6079 # check the tree is clean first??
6080 set oldmainhead $mainhead
6081 nowbusy checkout
6082 update
6083 dohidelocalchanges
6084 if {[catch {
6085 exec git checkout -q $headmenuhead
6086 } err]} {
6087 notbusy checkout
6088 error_popup $err
6089 } else {
6090 notbusy checkout
6091 set mainhead $headmenuhead
6092 set mainheadid $headmenuid
6093 if {[info exists headids($oldmainhead)]} {
6094 redrawtags $headids($oldmainhead)
6096 redrawtags $headmenuid
6098 if {$showlocalchanges} {
6099 dodiffindex
6103 proc rmbranch {} {
6104 global headmenuid headmenuhead mainhead
6105 global headids idheads
6107 set head $headmenuhead
6108 set id $headmenuid
6109 # this check shouldn't be needed any more...
6110 if {$head eq $mainhead} {
6111 error_popup "Cannot delete the currently checked-out branch"
6112 return
6114 set dheads [descheads $id]
6115 if {$dheads eq $headids($head)} {
6116 # the stuff on this branch isn't on any other branch
6117 if {![confirm_popup "The commits on branch $head aren't on any other\
6118 branch.\nReally delete branch $head?"]} return
6120 nowbusy rmbranch
6121 update
6122 if {[catch {exec git branch -D $head} err]} {
6123 notbusy rmbranch
6124 error_popup $err
6125 return
6127 removehead $id $head
6128 removedhead $id $head
6129 redrawtags $id
6130 notbusy rmbranch
6131 dispneartags 0
6134 # Stuff for finding nearby tags
6135 proc getallcommits {} {
6136 global allcommits allids nbmp nextarc seeds
6138 set allids {}
6139 set nbmp 0
6140 set nextarc 0
6141 set allcommits 0
6142 set seeds {}
6143 regetallcommits
6146 # Called when the graph might have changed
6147 proc regetallcommits {} {
6148 global allcommits seeds
6150 set cmd [concat | git rev-list --all --parents]
6151 foreach id $seeds {
6152 lappend cmd "^$id"
6154 set fd [open $cmd r]
6155 fconfigure $fd -blocking 0
6156 incr allcommits
6157 nowbusy allcommits
6158 filerun $fd [list getallclines $fd]
6161 # Since most commits have 1 parent and 1 child, we group strings of
6162 # such commits into "arcs" joining branch/merge points (BMPs), which
6163 # are commits that either don't have 1 parent or don't have 1 child.
6165 # arcnos(id) - incoming arcs for BMP, arc we're on for other nodes
6166 # arcout(id) - outgoing arcs for BMP
6167 # arcids(a) - list of IDs on arc including end but not start
6168 # arcstart(a) - BMP ID at start of arc
6169 # arcend(a) - BMP ID at end of arc
6170 # growing(a) - arc a is still growing
6171 # arctags(a) - IDs out of arcids (excluding end) that have tags
6172 # archeads(a) - IDs out of arcids (excluding end) that have heads
6173 # The start of an arc is at the descendent end, so "incoming" means
6174 # coming from descendents, and "outgoing" means going towards ancestors.
6176 proc getallclines {fd} {
6177 global allids allparents allchildren idtags idheads nextarc nbmp
6178 global arcnos arcids arctags arcout arcend arcstart archeads growing
6179 global seeds allcommits
6181 set nid 0
6182 while {[incr nid] <= 1000 && [gets $fd line] >= 0} {
6183 set id [lindex $line 0]
6184 if {[info exists allparents($id)]} {
6185 # seen it already
6186 continue
6188 lappend allids $id
6189 set olds [lrange $line 1 end]
6190 set allparents($id) $olds
6191 if {![info exists allchildren($id)]} {
6192 set allchildren($id) {}
6193 set arcnos($id) {}
6194 lappend seeds $id
6195 } else {
6196 set a $arcnos($id)
6197 if {[llength $olds] == 1 && [llength $a] == 1} {
6198 lappend arcids($a) $id
6199 if {[info exists idtags($id)]} {
6200 lappend arctags($a) $id
6202 if {[info exists idheads($id)]} {
6203 lappend archeads($a) $id
6205 if {[info exists allparents($olds)]} {
6206 # seen parent already
6207 if {![info exists arcout($olds)]} {
6208 splitarc $olds
6210 lappend arcids($a) $olds
6211 set arcend($a) $olds
6212 unset growing($a)
6214 lappend allchildren($olds) $id
6215 lappend arcnos($olds) $a
6216 continue
6219 incr nbmp
6220 foreach a $arcnos($id) {
6221 lappend arcids($a) $id
6222 set arcend($a) $id
6223 unset growing($a)
6226 set ao {}
6227 foreach p $olds {
6228 lappend allchildren($p) $id
6229 set a [incr nextarc]
6230 set arcstart($a) $id
6231 set archeads($a) {}
6232 set arctags($a) {}
6233 set archeads($a) {}
6234 set arcids($a) {}
6235 lappend ao $a
6236 set growing($a) 1
6237 if {[info exists allparents($p)]} {
6238 # seen it already, may need to make a new branch
6239 if {![info exists arcout($p)]} {
6240 splitarc $p
6242 lappend arcids($a) $p
6243 set arcend($a) $p
6244 unset growing($a)
6246 lappend arcnos($p) $a
6248 set arcout($id) $ao
6250 if {$nid > 0} {
6251 global cached_dheads cached_dtags cached_atags
6252 catch {unset cached_dheads}
6253 catch {unset cached_dtags}
6254 catch {unset cached_atags}
6256 if {![eof $fd]} {
6257 return [expr {$nid >= 1000? 2: 1}]
6259 close $fd
6260 if {[incr allcommits -1] == 0} {
6261 notbusy allcommits
6263 dispneartags 0
6264 return 0
6267 proc recalcarc {a} {
6268 global arctags archeads arcids idtags idheads
6270 set at {}
6271 set ah {}
6272 foreach id [lrange $arcids($a) 0 end-1] {
6273 if {[info exists idtags($id)]} {
6274 lappend at $id
6276 if {[info exists idheads($id)]} {
6277 lappend ah $id
6280 set arctags($a) $at
6281 set archeads($a) $ah
6284 proc splitarc {p} {
6285 global arcnos arcids nextarc nbmp arctags archeads idtags idheads
6286 global arcstart arcend arcout allparents growing
6288 set a $arcnos($p)
6289 if {[llength $a] != 1} {
6290 puts "oops splitarc called but [llength $a] arcs already"
6291 return
6293 set a [lindex $a 0]
6294 set i [lsearch -exact $arcids($a) $p]
6295 if {$i < 0} {
6296 puts "oops splitarc $p not in arc $a"
6297 return
6299 set na [incr nextarc]
6300 if {[info exists arcend($a)]} {
6301 set arcend($na) $arcend($a)
6302 } else {
6303 set l [lindex $allparents([lindex $arcids($a) end]) 0]
6304 set j [lsearch -exact $arcnos($l) $a]
6305 set arcnos($l) [lreplace $arcnos($l) $j $j $na]
6307 set tail [lrange $arcids($a) [expr {$i+1}] end]
6308 set arcids($a) [lrange $arcids($a) 0 $i]
6309 set arcend($a) $p
6310 set arcstart($na) $p
6311 set arcout($p) $na
6312 set arcids($na) $tail
6313 if {[info exists growing($a)]} {
6314 set growing($na) 1
6315 unset growing($a)
6317 incr nbmp
6319 foreach id $tail {
6320 if {[llength $arcnos($id)] == 1} {
6321 set arcnos($id) $na
6322 } else {
6323 set j [lsearch -exact $arcnos($id) $a]
6324 set arcnos($id) [lreplace $arcnos($id) $j $j $na]
6328 # reconstruct tags and heads lists
6329 if {$arctags($a) ne {} || $archeads($a) ne {}} {
6330 recalcarc $a
6331 recalcarc $na
6332 } else {
6333 set arctags($na) {}
6334 set archeads($na) {}
6338 # Update things for a new commit added that is a child of one
6339 # existing commit. Used when cherry-picking.
6340 proc addnewchild {id p} {
6341 global allids allparents allchildren idtags nextarc nbmp
6342 global arcnos arcids arctags arcout arcend arcstart archeads growing
6343 global seeds
6345 lappend allids $id
6346 set allparents($id) [list $p]
6347 set allchildren($id) {}
6348 set arcnos($id) {}
6349 lappend seeds $id
6350 incr nbmp
6351 lappend allchildren($p) $id
6352 set a [incr nextarc]
6353 set arcstart($a) $id
6354 set archeads($a) {}
6355 set arctags($a) {}
6356 set arcids($a) [list $p]
6357 set arcend($a) $p
6358 if {![info exists arcout($p)]} {
6359 splitarc $p
6361 lappend arcnos($p) $a
6362 set arcout($id) [list $a]
6365 # Returns 1 if a is an ancestor of b, -1 if b is an ancestor of a,
6366 # or 0 if neither is true.
6367 proc anc_or_desc {a b} {
6368 global arcout arcstart arcend arcnos cached_isanc
6370 if {$arcnos($a) eq $arcnos($b)} {
6371 # Both are on the same arc(s); either both are the same BMP,
6372 # or if one is not a BMP, the other is also not a BMP or is
6373 # the BMP at end of the arc (and it only has 1 incoming arc).
6374 # Or both can be BMPs with no incoming arcs.
6375 if {$a eq $b || $arcnos($a) eq {}} {
6376 return 0
6378 # assert {[llength $arcnos($a)] == 1}
6379 set arc [lindex $arcnos($a) 0]
6380 set i [lsearch -exact $arcids($arc) $a]
6381 set j [lsearch -exact $arcids($arc) $b]
6382 if {$i < 0 || $i > $j} {
6383 return 1
6384 } else {
6385 return -1
6389 if {![info exists arcout($a)]} {
6390 set arc [lindex $arcnos($a) 0]
6391 if {[info exists arcend($arc)]} {
6392 set aend $arcend($arc)
6393 } else {
6394 set aend {}
6396 set a $arcstart($arc)
6397 } else {
6398 set aend $a
6400 if {![info exists arcout($b)]} {
6401 set arc [lindex $arcnos($b) 0]
6402 if {[info exists arcend($arc)]} {
6403 set bend $arcend($arc)
6404 } else {
6405 set bend {}
6407 set b $arcstart($arc)
6408 } else {
6409 set bend $b
6411 if {$a eq $bend} {
6412 return 1
6414 if {$b eq $aend} {
6415 return -1
6417 if {[info exists cached_isanc($a,$bend)]} {
6418 if {$cached_isanc($a,$bend)} {
6419 return 1
6422 if {[info exists cached_isanc($b,$aend)]} {
6423 if {$cached_isanc($b,$aend)} {
6424 return -1
6426 if {[info exists cached_isanc($a,$bend)]} {
6427 return 0
6431 set todo [list $a $b]
6432 set anc($a) a
6433 set anc($b) b
6434 for {set i 0} {$i < [llength $todo]} {incr i} {
6435 set x [lindex $todo $i]
6436 if {$anc($x) eq {}} {
6437 continue
6439 foreach arc $arcnos($x) {
6440 set xd $arcstart($arc)
6441 if {$xd eq $bend} {
6442 set cached_isanc($a,$bend) 1
6443 set cached_isanc($b,$aend) 0
6444 return 1
6445 } elseif {$xd eq $aend} {
6446 set cached_isanc($b,$aend) 1
6447 set cached_isanc($a,$bend) 0
6448 return -1
6450 if {![info exists anc($xd)]} {
6451 set anc($xd) $anc($x)
6452 lappend todo $xd
6453 } elseif {$anc($xd) ne $anc($x)} {
6454 set anc($xd) {}
6458 set cached_isanc($a,$bend) 0
6459 set cached_isanc($b,$aend) 0
6460 return 0
6463 # This identifies whether $desc has an ancestor that is
6464 # a growing tip of the graph and which is not an ancestor of $anc
6465 # and returns 0 if so and 1 if not.
6466 # If we subsequently discover a tag on such a growing tip, and that
6467 # turns out to be a descendent of $anc (which it could, since we
6468 # don't necessarily see children before parents), then $desc
6469 # isn't a good choice to display as a descendent tag of
6470 # $anc (since it is the descendent of another tag which is
6471 # a descendent of $anc). Similarly, $anc isn't a good choice to
6472 # display as a ancestor tag of $desc.
6474 proc is_certain {desc anc} {
6475 global arcnos arcout arcstart arcend growing problems
6477 set certain {}
6478 if {[llength $arcnos($anc)] == 1} {
6479 # tags on the same arc are certain
6480 if {$arcnos($desc) eq $arcnos($anc)} {
6481 return 1
6483 if {![info exists arcout($anc)]} {
6484 # if $anc is partway along an arc, use the start of the arc instead
6485 set a [lindex $arcnos($anc) 0]
6486 set anc $arcstart($a)
6489 if {[llength $arcnos($desc)] > 1 || [info exists arcout($desc)]} {
6490 set x $desc
6491 } else {
6492 set a [lindex $arcnos($desc) 0]
6493 set x $arcend($a)
6495 if {$x == $anc} {
6496 return 1
6498 set anclist [list $x]
6499 set dl($x) 1
6500 set nnh 1
6501 set ngrowanc 0
6502 for {set i 0} {$i < [llength $anclist] && ($nnh > 0 || $ngrowanc > 0)} {incr i} {
6503 set x [lindex $anclist $i]
6504 if {$dl($x)} {
6505 incr nnh -1
6507 set done($x) 1
6508 foreach a $arcout($x) {
6509 if {[info exists growing($a)]} {
6510 if {![info exists growanc($x)] && $dl($x)} {
6511 set growanc($x) 1
6512 incr ngrowanc
6514 } else {
6515 set y $arcend($a)
6516 if {[info exists dl($y)]} {
6517 if {$dl($y)} {
6518 if {!$dl($x)} {
6519 set dl($y) 0
6520 if {![info exists done($y)]} {
6521 incr nnh -1
6523 if {[info exists growanc($x)]} {
6524 incr ngrowanc -1
6526 set xl [list $y]
6527 for {set k 0} {$k < [llength $xl]} {incr k} {
6528 set z [lindex $xl $k]
6529 foreach c $arcout($z) {
6530 if {[info exists arcend($c)]} {
6531 set v $arcend($c)
6532 if {[info exists dl($v)] && $dl($v)} {
6533 set dl($v) 0
6534 if {![info exists done($v)]} {
6535 incr nnh -1
6537 if {[info exists growanc($v)]} {
6538 incr ngrowanc -1
6540 lappend xl $v
6547 } elseif {$y eq $anc || !$dl($x)} {
6548 set dl($y) 0
6549 lappend anclist $y
6550 } else {
6551 set dl($y) 1
6552 lappend anclist $y
6553 incr nnh
6558 foreach x [array names growanc] {
6559 if {$dl($x)} {
6560 return 0
6562 return 0
6564 return 1
6567 proc validate_arctags {a} {
6568 global arctags idtags
6570 set i -1
6571 set na $arctags($a)
6572 foreach id $arctags($a) {
6573 incr i
6574 if {![info exists idtags($id)]} {
6575 set na [lreplace $na $i $i]
6576 incr i -1
6579 set arctags($a) $na
6582 proc validate_archeads {a} {
6583 global archeads idheads
6585 set i -1
6586 set na $archeads($a)
6587 foreach id $archeads($a) {
6588 incr i
6589 if {![info exists idheads($id)]} {
6590 set na [lreplace $na $i $i]
6591 incr i -1
6594 set archeads($a) $na
6597 # Return the list of IDs that have tags that are descendents of id,
6598 # ignoring IDs that are descendents of IDs already reported.
6599 proc desctags {id} {
6600 global arcnos arcstart arcids arctags idtags allparents
6601 global growing cached_dtags
6603 if {![info exists allparents($id)]} {
6604 return {}
6606 set t1 [clock clicks -milliseconds]
6607 set argid $id
6608 if {[llength $arcnos($id)] == 1 && [llength $allparents($id)] == 1} {
6609 # part-way along an arc; check that arc first
6610 set a [lindex $arcnos($id) 0]
6611 if {$arctags($a) ne {}} {
6612 validate_arctags $a
6613 set i [lsearch -exact $arcids($a) $id]
6614 set tid {}
6615 foreach t $arctags($a) {
6616 set j [lsearch -exact $arcids($a) $t]
6617 if {$j >= $i} break
6618 set tid $t
6620 if {$tid ne {}} {
6621 return $tid
6624 set id $arcstart($a)
6625 if {[info exists idtags($id)]} {
6626 return $id
6629 if {[info exists cached_dtags($id)]} {
6630 return $cached_dtags($id)
6633 set origid $id
6634 set todo [list $id]
6635 set queued($id) 1
6636 set nc 1
6637 for {set i 0} {$i < [llength $todo] && $nc > 0} {incr i} {
6638 set id [lindex $todo $i]
6639 set done($id) 1
6640 set ta [info exists hastaggedancestor($id)]
6641 if {!$ta} {
6642 incr nc -1
6644 # ignore tags on starting node
6645 if {!$ta && $i > 0} {
6646 if {[info exists idtags($id)]} {
6647 set tagloc($id) $id
6648 set ta 1
6649 } elseif {[info exists cached_dtags($id)]} {
6650 set tagloc($id) $cached_dtags($id)
6651 set ta 1
6654 foreach a $arcnos($id) {
6655 set d $arcstart($a)
6656 if {!$ta && $arctags($a) ne {}} {
6657 validate_arctags $a
6658 if {$arctags($a) ne {}} {
6659 lappend tagloc($id) [lindex $arctags($a) end]
6662 if {$ta || $arctags($a) ne {}} {
6663 set tomark [list $d]
6664 for {set j 0} {$j < [llength $tomark]} {incr j} {
6665 set dd [lindex $tomark $j]
6666 if {![info exists hastaggedancestor($dd)]} {
6667 if {[info exists done($dd)]} {
6668 foreach b $arcnos($dd) {
6669 lappend tomark $arcstart($b)
6671 if {[info exists tagloc($dd)]} {
6672 unset tagloc($dd)
6674 } elseif {[info exists queued($dd)]} {
6675 incr nc -1
6677 set hastaggedancestor($dd) 1
6681 if {![info exists queued($d)]} {
6682 lappend todo $d
6683 set queued($d) 1
6684 if {![info exists hastaggedancestor($d)]} {
6685 incr nc
6690 set tags {}
6691 foreach id [array names tagloc] {
6692 if {![info exists hastaggedancestor($id)]} {
6693 foreach t $tagloc($id) {
6694 if {[lsearch -exact $tags $t] < 0} {
6695 lappend tags $t
6700 set t2 [clock clicks -milliseconds]
6701 set loopix $i
6703 # remove tags that are descendents of other tags
6704 for {set i 0} {$i < [llength $tags]} {incr i} {
6705 set a [lindex $tags $i]
6706 for {set j 0} {$j < $i} {incr j} {
6707 set b [lindex $tags $j]
6708 set r [anc_or_desc $a $b]
6709 if {$r == 1} {
6710 set tags [lreplace $tags $j $j]
6711 incr j -1
6712 incr i -1
6713 } elseif {$r == -1} {
6714 set tags [lreplace $tags $i $i]
6715 incr i -1
6716 break
6721 if {[array names growing] ne {}} {
6722 # graph isn't finished, need to check if any tag could get
6723 # eclipsed by another tag coming later. Simply ignore any
6724 # tags that could later get eclipsed.
6725 set ctags {}
6726 foreach t $tags {
6727 if {[is_certain $t $origid]} {
6728 lappend ctags $t
6731 if {$tags eq $ctags} {
6732 set cached_dtags($origid) $tags
6733 } else {
6734 set tags $ctags
6736 } else {
6737 set cached_dtags($origid) $tags
6739 set t3 [clock clicks -milliseconds]
6740 if {0 && $t3 - $t1 >= 100} {
6741 puts "iterating descendents ($loopix/[llength $todo] nodes) took\
6742 [expr {$t2-$t1}]+[expr {$t3-$t2}]ms, $nc candidates left"
6744 return $tags
6747 proc anctags {id} {
6748 global arcnos arcids arcout arcend arctags idtags allparents
6749 global growing cached_atags
6751 if {![info exists allparents($id)]} {
6752 return {}
6754 set t1 [clock clicks -milliseconds]
6755 set argid $id
6756 if {[llength $arcnos($id)] == 1 && [llength $allparents($id)] == 1} {
6757 # part-way along an arc; check that arc first
6758 set a [lindex $arcnos($id) 0]
6759 if {$arctags($a) ne {}} {
6760 validate_arctags $a
6761 set i [lsearch -exact $arcids($a) $id]
6762 foreach t $arctags($a) {
6763 set j [lsearch -exact $arcids($a) $t]
6764 if {$j > $i} {
6765 return $t
6769 if {![info exists arcend($a)]} {
6770 return {}
6772 set id $arcend($a)
6773 if {[info exists idtags($id)]} {
6774 return $id
6777 if {[info exists cached_atags($id)]} {
6778 return $cached_atags($id)
6781 set origid $id
6782 set todo [list $id]
6783 set queued($id) 1
6784 set taglist {}
6785 set nc 1
6786 for {set i 0} {$i < [llength $todo] && $nc > 0} {incr i} {
6787 set id [lindex $todo $i]
6788 set done($id) 1
6789 set td [info exists hastaggeddescendent($id)]
6790 if {!$td} {
6791 incr nc -1
6793 # ignore tags on starting node
6794 if {!$td && $i > 0} {
6795 if {[info exists idtags($id)]} {
6796 set tagloc($id) $id
6797 set td 1
6798 } elseif {[info exists cached_atags($id)]} {
6799 set tagloc($id) $cached_atags($id)
6800 set td 1
6803 foreach a $arcout($id) {
6804 if {!$td && $arctags($a) ne {}} {
6805 validate_arctags $a
6806 if {$arctags($a) ne {}} {
6807 lappend tagloc($id) [lindex $arctags($a) 0]
6810 if {![info exists arcend($a)]} continue
6811 set d $arcend($a)
6812 if {$td || $arctags($a) ne {}} {
6813 set tomark [list $d]
6814 for {set j 0} {$j < [llength $tomark]} {incr j} {
6815 set dd [lindex $tomark $j]
6816 if {![info exists hastaggeddescendent($dd)]} {
6817 if {[info exists done($dd)]} {
6818 foreach b $arcout($dd) {
6819 if {[info exists arcend($b)]} {
6820 lappend tomark $arcend($b)
6823 if {[info exists tagloc($dd)]} {
6824 unset tagloc($dd)
6826 } elseif {[info exists queued($dd)]} {
6827 incr nc -1
6829 set hastaggeddescendent($dd) 1
6833 if {![info exists queued($d)]} {
6834 lappend todo $d
6835 set queued($d) 1
6836 if {![info exists hastaggeddescendent($d)]} {
6837 incr nc
6842 set t2 [clock clicks -milliseconds]
6843 set loopix $i
6844 set tags {}
6845 foreach id [array names tagloc] {
6846 if {![info exists hastaggeddescendent($id)]} {
6847 foreach t $tagloc($id) {
6848 if {[lsearch -exact $tags $t] < 0} {
6849 lappend tags $t
6855 # remove tags that are ancestors of other tags
6856 for {set i 0} {$i < [llength $tags]} {incr i} {
6857 set a [lindex $tags $i]
6858 for {set j 0} {$j < $i} {incr j} {
6859 set b [lindex $tags $j]
6860 set r [anc_or_desc $a $b]
6861 if {$r == -1} {
6862 set tags [lreplace $tags $j $j]
6863 incr j -1
6864 incr i -1
6865 } elseif {$r == 1} {
6866 set tags [lreplace $tags $i $i]
6867 incr i -1
6868 break
6873 if {[array names growing] ne {}} {
6874 # graph isn't finished, need to check if any tag could get
6875 # eclipsed by another tag coming later. Simply ignore any
6876 # tags that could later get eclipsed.
6877 set ctags {}
6878 foreach t $tags {
6879 if {[is_certain $origid $t]} {
6880 lappend ctags $t
6883 if {$tags eq $ctags} {
6884 set cached_atags($origid) $tags
6885 } else {
6886 set tags $ctags
6888 } else {
6889 set cached_atags($origid) $tags
6891 set t3 [clock clicks -milliseconds]
6892 if {0 && $t3 - $t1 >= 100} {
6893 puts "iterating ancestors ($loopix/[llength $todo] nodes) took\
6894 [expr {$t2-$t1}]+[expr {$t3-$t2}]ms, $nc candidates left"
6896 return $tags
6899 # Return the list of IDs that have heads that are descendents of id,
6900 # including id itself if it has a head.
6901 proc descheads {id} {
6902 global arcnos arcstart arcids archeads idheads cached_dheads
6903 global allparents
6905 if {![info exists allparents($id)]} {
6906 return {}
6908 set aret {}
6909 if {[llength $arcnos($id)] == 1 && [llength $allparents($id)] == 1} {
6910 # part-way along an arc; check it first
6911 set a [lindex $arcnos($id) 0]
6912 if {$archeads($a) ne {}} {
6913 validate_archeads $a
6914 set i [lsearch -exact $arcids($a) $id]
6915 foreach t $archeads($a) {
6916 set j [lsearch -exact $arcids($a) $t]
6917 if {$j > $i} break
6918 lappend aret $t
6921 set id $arcstart($a)
6923 set origid $id
6924 set todo [list $id]
6925 set seen($id) 1
6926 set ret {}
6927 for {set i 0} {$i < [llength $todo]} {incr i} {
6928 set id [lindex $todo $i]
6929 if {[info exists cached_dheads($id)]} {
6930 set ret [concat $ret $cached_dheads($id)]
6931 } else {
6932 if {[info exists idheads($id)]} {
6933 lappend ret $id
6935 foreach a $arcnos($id) {
6936 if {$archeads($a) ne {}} {
6937 validate_archeads $a
6938 if {$archeads($a) ne {}} {
6939 set ret [concat $ret $archeads($a)]
6942 set d $arcstart($a)
6943 if {![info exists seen($d)]} {
6944 lappend todo $d
6945 set seen($d) 1
6950 set ret [lsort -unique $ret]
6951 set cached_dheads($origid) $ret
6952 return [concat $ret $aret]
6955 proc addedtag {id} {
6956 global arcnos arcout cached_dtags cached_atags
6958 if {![info exists arcnos($id)]} return
6959 if {![info exists arcout($id)]} {
6960 recalcarc [lindex $arcnos($id) 0]
6962 catch {unset cached_dtags}
6963 catch {unset cached_atags}
6966 proc addedhead {hid head} {
6967 global arcnos arcout cached_dheads
6969 if {![info exists arcnos($hid)]} return
6970 if {![info exists arcout($hid)]} {
6971 recalcarc [lindex $arcnos($hid) 0]
6973 catch {unset cached_dheads}
6976 proc removedhead {hid head} {
6977 global cached_dheads
6979 catch {unset cached_dheads}
6982 proc movedhead {hid head} {
6983 global arcnos arcout cached_dheads
6985 if {![info exists arcnos($hid)]} return
6986 if {![info exists arcout($hid)]} {
6987 recalcarc [lindex $arcnos($hid) 0]
6989 catch {unset cached_dheads}
6992 proc changedrefs {} {
6993 global cached_dheads cached_dtags cached_atags
6994 global arctags archeads arcnos arcout idheads idtags
6996 foreach id [concat [array names idheads] [array names idtags]] {
6997 if {[info exists arcnos($id)] && ![info exists arcout($id)]} {
6998 set a [lindex $arcnos($id) 0]
6999 if {![info exists donearc($a)]} {
7000 recalcarc $a
7001 set donearc($a) 1
7005 catch {unset cached_dtags}
7006 catch {unset cached_atags}
7007 catch {unset cached_dheads}
7010 proc rereadrefs {} {
7011 global idtags idheads idotherrefs mainhead
7013 set refids [concat [array names idtags] \
7014 [array names idheads] [array names idotherrefs]]
7015 foreach id $refids {
7016 if {![info exists ref($id)]} {
7017 set ref($id) [listrefs $id]
7020 set oldmainhead $mainhead
7021 readrefs
7022 changedrefs
7023 set refids [lsort -unique [concat $refids [array names idtags] \
7024 [array names idheads] [array names idotherrefs]]]
7025 foreach id $refids {
7026 set v [listrefs $id]
7027 if {![info exists ref($id)] || $ref($id) != $v ||
7028 ($id eq $oldmainhead && $id ne $mainhead) ||
7029 ($id eq $mainhead && $id ne $oldmainhead)} {
7030 redrawtags $id
7035 proc listrefs {id} {
7036 global idtags idheads idotherrefs
7038 set x {}
7039 if {[info exists idtags($id)]} {
7040 set x $idtags($id)
7042 set y {}
7043 if {[info exists idheads($id)]} {
7044 set y $idheads($id)
7046 set z {}
7047 if {[info exists idotherrefs($id)]} {
7048 set z $idotherrefs($id)
7050 return [list $x $y $z]
7053 proc showtag {tag isnew} {
7054 global ctext tagcontents tagids linknum tagobjid
7056 if {$isnew} {
7057 addtohistory [list showtag $tag 0]
7059 $ctext conf -state normal
7060 clear_ctext
7061 set linknum 0
7062 if {![info exists tagcontents($tag)]} {
7063 catch {
7064 set tagcontents($tag) [exec git cat-file tag $tagobjid($tag)]
7067 if {[info exists tagcontents($tag)]} {
7068 set text $tagcontents($tag)
7069 } else {
7070 set text "Tag: $tag\nId: $tagids($tag)"
7072 appendwithlinks $text {}
7073 $ctext conf -state disabled
7074 init_flist {}
7077 proc doquit {} {
7078 global stopped
7079 set stopped 100
7080 savestuff .
7081 destroy .
7084 proc doprefs {} {
7085 global maxwidth maxgraphpct diffopts
7086 global oldprefs prefstop showneartags showlocalchanges
7087 global bgcolor fgcolor ctext diffcolors selectbgcolor
7088 global uifont tabstop
7090 set top .gitkprefs
7091 set prefstop $top
7092 if {[winfo exists $top]} {
7093 raise $top
7094 return
7096 foreach v {maxwidth maxgraphpct diffopts showneartags showlocalchanges} {
7097 set oldprefs($v) [set $v]
7099 toplevel $top
7100 wm title $top "Gitk preferences"
7101 label $top.ldisp -text "Commit list display options"
7102 $top.ldisp configure -font $uifont
7103 grid $top.ldisp - -sticky w -pady 10
7104 label $top.spacer -text " "
7105 label $top.maxwidthl -text "Maximum graph width (lines)" \
7106 -font optionfont
7107 spinbox $top.maxwidth -from 0 -to 100 -width 4 -textvariable maxwidth
7108 grid $top.spacer $top.maxwidthl $top.maxwidth -sticky w
7109 label $top.maxpctl -text "Maximum graph width (% of pane)" \
7110 -font optionfont
7111 spinbox $top.maxpct -from 1 -to 100 -width 4 -textvariable maxgraphpct
7112 grid x $top.maxpctl $top.maxpct -sticky w
7113 frame $top.showlocal
7114 label $top.showlocal.l -text "Show local changes" -font optionfont
7115 checkbutton $top.showlocal.b -variable showlocalchanges
7116 pack $top.showlocal.b $top.showlocal.l -side left
7117 grid x $top.showlocal -sticky w
7119 label $top.ddisp -text "Diff display options"
7120 $top.ddisp configure -font $uifont
7121 grid $top.ddisp - -sticky w -pady 10
7122 label $top.diffoptl -text "Options for diff program" \
7123 -font optionfont
7124 entry $top.diffopt -width 20 -textvariable diffopts
7125 grid x $top.diffoptl $top.diffopt -sticky w
7126 frame $top.ntag
7127 label $top.ntag.l -text "Display nearby tags" -font optionfont
7128 checkbutton $top.ntag.b -variable showneartags
7129 pack $top.ntag.b $top.ntag.l -side left
7130 grid x $top.ntag -sticky w
7131 label $top.tabstopl -text "tabstop" -font optionfont
7132 spinbox $top.tabstop -from 1 -to 20 -width 4 -textvariable tabstop
7133 grid x $top.tabstopl $top.tabstop -sticky w
7135 label $top.cdisp -text "Colors: press to choose"
7136 $top.cdisp configure -font $uifont
7137 grid $top.cdisp - -sticky w -pady 10
7138 label $top.bg -padx 40 -relief sunk -background $bgcolor
7139 button $top.bgbut -text "Background" -font optionfont \
7140 -command [list choosecolor bgcolor 0 $top.bg background setbg]
7141 grid x $top.bgbut $top.bg -sticky w
7142 label $top.fg -padx 40 -relief sunk -background $fgcolor
7143 button $top.fgbut -text "Foreground" -font optionfont \
7144 -command [list choosecolor fgcolor 0 $top.fg foreground setfg]
7145 grid x $top.fgbut $top.fg -sticky w
7146 label $top.diffold -padx 40 -relief sunk -background [lindex $diffcolors 0]
7147 button $top.diffoldbut -text "Diff: old lines" -font optionfont \
7148 -command [list choosecolor diffcolors 0 $top.diffold "diff old lines" \
7149 [list $ctext tag conf d0 -foreground]]
7150 grid x $top.diffoldbut $top.diffold -sticky w
7151 label $top.diffnew -padx 40 -relief sunk -background [lindex $diffcolors 1]
7152 button $top.diffnewbut -text "Diff: new lines" -font optionfont \
7153 -command [list choosecolor diffcolors 1 $top.diffnew "diff new lines" \
7154 [list $ctext tag conf d1 -foreground]]
7155 grid x $top.diffnewbut $top.diffnew -sticky w
7156 label $top.hunksep -padx 40 -relief sunk -background [lindex $diffcolors 2]
7157 button $top.hunksepbut -text "Diff: hunk header" -font optionfont \
7158 -command [list choosecolor diffcolors 2 $top.hunksep \
7159 "diff hunk header" \
7160 [list $ctext tag conf hunksep -foreground]]
7161 grid x $top.hunksepbut $top.hunksep -sticky w
7162 label $top.selbgsep -padx 40 -relief sunk -background $selectbgcolor
7163 button $top.selbgbut -text "Select bg" -font optionfont \
7164 -command [list choosecolor selectbgcolor 0 $top.selbgsep background setselbg]
7165 grid x $top.selbgbut $top.selbgsep -sticky w
7167 frame $top.buts
7168 button $top.buts.ok -text "OK" -command prefsok -default active
7169 $top.buts.ok configure -font $uifont
7170 button $top.buts.can -text "Cancel" -command prefscan -default normal
7171 $top.buts.can configure -font $uifont
7172 grid $top.buts.ok $top.buts.can
7173 grid columnconfigure $top.buts 0 -weight 1 -uniform a
7174 grid columnconfigure $top.buts 1 -weight 1 -uniform a
7175 grid $top.buts - - -pady 10 -sticky ew
7176 bind $top <Visibility> "focus $top.buts.ok"
7179 proc choosecolor {v vi w x cmd} {
7180 global $v
7182 set c [tk_chooseColor -initialcolor [lindex [set $v] $vi] \
7183 -title "Gitk: choose color for $x"]
7184 if {$c eq {}} return
7185 $w conf -background $c
7186 lset $v $vi $c
7187 eval $cmd $c
7190 proc setselbg {c} {
7191 global bglist cflist
7192 foreach w $bglist {
7193 $w configure -selectbackground $c
7195 $cflist tag configure highlight \
7196 -background [$cflist cget -selectbackground]
7197 allcanvs itemconf secsel -fill $c
7200 proc setbg {c} {
7201 global bglist
7203 foreach w $bglist {
7204 $w conf -background $c
7208 proc setfg {c} {
7209 global fglist canv
7211 foreach w $fglist {
7212 $w conf -foreground $c
7214 allcanvs itemconf text -fill $c
7215 $canv itemconf circle -outline $c
7218 proc prefscan {} {
7219 global maxwidth maxgraphpct diffopts
7220 global oldprefs prefstop showneartags showlocalchanges
7222 foreach v {maxwidth maxgraphpct diffopts showneartags showlocalchanges} {
7223 set $v $oldprefs($v)
7225 catch {destroy $prefstop}
7226 unset prefstop
7229 proc prefsok {} {
7230 global maxwidth maxgraphpct
7231 global oldprefs prefstop showneartags showlocalchanges
7232 global charspc ctext tabstop
7234 catch {destroy $prefstop}
7235 unset prefstop
7236 $ctext configure -tabs "[expr {$tabstop * $charspc}]"
7237 if {$showlocalchanges != $oldprefs(showlocalchanges)} {
7238 if {$showlocalchanges} {
7239 doshowlocalchanges
7240 } else {
7241 dohidelocalchanges
7244 if {$maxwidth != $oldprefs(maxwidth)
7245 || $maxgraphpct != $oldprefs(maxgraphpct)} {
7246 redisplay
7247 } elseif {$showneartags != $oldprefs(showneartags)} {
7248 reselectline
7252 proc formatdate {d} {
7253 if {$d ne {}} {
7254 set d [clock format $d -format "%Y-%m-%d %H:%M:%S"]
7256 return $d
7259 # This list of encoding names and aliases is distilled from
7260 # http://www.iana.org/assignments/character-sets.
7261 # Not all of them are supported by Tcl.
7262 set encoding_aliases {
7263 { ANSI_X3.4-1968 iso-ir-6 ANSI_X3.4-1986 ISO_646.irv:1991 ASCII
7264 ISO646-US US-ASCII us IBM367 cp367 csASCII }
7265 { ISO-10646-UTF-1 csISO10646UTF1 }
7266 { ISO_646.basic:1983 ref csISO646basic1983 }
7267 { INVARIANT csINVARIANT }
7268 { ISO_646.irv:1983 iso-ir-2 irv csISO2IntlRefVersion }
7269 { BS_4730 iso-ir-4 ISO646-GB gb uk csISO4UnitedKingdom }
7270 { NATS-SEFI iso-ir-8-1 csNATSSEFI }
7271 { NATS-SEFI-ADD iso-ir-8-2 csNATSSEFIADD }
7272 { NATS-DANO iso-ir-9-1 csNATSDANO }
7273 { NATS-DANO-ADD iso-ir-9-2 csNATSDANOADD }
7274 { SEN_850200_B iso-ir-10 FI ISO646-FI ISO646-SE se csISO10Swedish }
7275 { SEN_850200_C iso-ir-11 ISO646-SE2 se2 csISO11SwedishForNames }
7276 { KS_C_5601-1987 iso-ir-149 KS_C_5601-1989 KSC_5601 korean csKSC56011987 }
7277 { ISO-2022-KR csISO2022KR }
7278 { EUC-KR csEUCKR }
7279 { ISO-2022-JP csISO2022JP }
7280 { ISO-2022-JP-2 csISO2022JP2 }
7281 { JIS_C6220-1969-jp JIS_C6220-1969 iso-ir-13 katakana x0201-7
7282 csISO13JISC6220jp }
7283 { JIS_C6220-1969-ro iso-ir-14 jp ISO646-JP csISO14JISC6220ro }
7284 { IT iso-ir-15 ISO646-IT csISO15Italian }
7285 { PT iso-ir-16 ISO646-PT csISO16Portuguese }
7286 { ES iso-ir-17 ISO646-ES csISO17Spanish }
7287 { greek7-old iso-ir-18 csISO18Greek7Old }
7288 { latin-greek iso-ir-19 csISO19LatinGreek }
7289 { DIN_66003 iso-ir-21 de ISO646-DE csISO21German }
7290 { NF_Z_62-010_(1973) iso-ir-25 ISO646-FR1 csISO25French }
7291 { Latin-greek-1 iso-ir-27 csISO27LatinGreek1 }
7292 { ISO_5427 iso-ir-37 csISO5427Cyrillic }
7293 { JIS_C6226-1978 iso-ir-42 csISO42JISC62261978 }
7294 { BS_viewdata iso-ir-47 csISO47BSViewdata }
7295 { INIS iso-ir-49 csISO49INIS }
7296 { INIS-8 iso-ir-50 csISO50INIS8 }
7297 { INIS-cyrillic iso-ir-51 csISO51INISCyrillic }
7298 { ISO_5427:1981 iso-ir-54 ISO5427Cyrillic1981 }
7299 { ISO_5428:1980 iso-ir-55 csISO5428Greek }
7300 { GB_1988-80 iso-ir-57 cn ISO646-CN csISO57GB1988 }
7301 { GB_2312-80 iso-ir-58 chinese csISO58GB231280 }
7302 { NS_4551-1 iso-ir-60 ISO646-NO no csISO60DanishNorwegian
7303 csISO60Norwegian1 }
7304 { NS_4551-2 ISO646-NO2 iso-ir-61 no2 csISO61Norwegian2 }
7305 { NF_Z_62-010 iso-ir-69 ISO646-FR fr csISO69French }
7306 { videotex-suppl iso-ir-70 csISO70VideotexSupp1 }
7307 { PT2 iso-ir-84 ISO646-PT2 csISO84Portuguese2 }
7308 { ES2 iso-ir-85 ISO646-ES2 csISO85Spanish2 }
7309 { MSZ_7795.3 iso-ir-86 ISO646-HU hu csISO86Hungarian }
7310 { JIS_C6226-1983 iso-ir-87 x0208 JIS_X0208-1983 csISO87JISX0208 }
7311 { greek7 iso-ir-88 csISO88Greek7 }
7312 { ASMO_449 ISO_9036 arabic7 iso-ir-89 csISO89ASMO449 }
7313 { iso-ir-90 csISO90 }
7314 { JIS_C6229-1984-a iso-ir-91 jp-ocr-a csISO91JISC62291984a }
7315 { JIS_C6229-1984-b iso-ir-92 ISO646-JP-OCR-B jp-ocr-b
7316 csISO92JISC62991984b }
7317 { JIS_C6229-1984-b-add iso-ir-93 jp-ocr-b-add csISO93JIS62291984badd }
7318 { JIS_C6229-1984-hand iso-ir-94 jp-ocr-hand csISO94JIS62291984hand }
7319 { JIS_C6229-1984-hand-add iso-ir-95 jp-ocr-hand-add
7320 csISO95JIS62291984handadd }
7321 { JIS_C6229-1984-kana iso-ir-96 csISO96JISC62291984kana }
7322 { ISO_2033-1983 iso-ir-98 e13b csISO2033 }
7323 { ANSI_X3.110-1983 iso-ir-99 CSA_T500-1983 NAPLPS csISO99NAPLPS }
7324 { ISO_8859-1:1987 iso-ir-100 ISO_8859-1 ISO-8859-1 latin1 l1 IBM819
7325 CP819 csISOLatin1 }
7326 { ISO_8859-2:1987 iso-ir-101 ISO_8859-2 ISO-8859-2 latin2 l2 csISOLatin2 }
7327 { T.61-7bit iso-ir-102 csISO102T617bit }
7328 { T.61-8bit T.61 iso-ir-103 csISO103T618bit }
7329 { ISO_8859-3:1988 iso-ir-109 ISO_8859-3 ISO-8859-3 latin3 l3 csISOLatin3 }
7330 { ISO_8859-4:1988 iso-ir-110 ISO_8859-4 ISO-8859-4 latin4 l4 csISOLatin4 }
7331 { ECMA-cyrillic iso-ir-111 KOI8-E csISO111ECMACyrillic }
7332 { CSA_Z243.4-1985-1 iso-ir-121 ISO646-CA csa7-1 ca csISO121Canadian1 }
7333 { CSA_Z243.4-1985-2 iso-ir-122 ISO646-CA2 csa7-2 csISO122Canadian2 }
7334 { CSA_Z243.4-1985-gr iso-ir-123 csISO123CSAZ24341985gr }
7335 { ISO_8859-6:1987 iso-ir-127 ISO_8859-6 ISO-8859-6 ECMA-114 ASMO-708
7336 arabic csISOLatinArabic }
7337 { ISO_8859-6-E csISO88596E ISO-8859-6-E }
7338 { ISO_8859-6-I csISO88596I ISO-8859-6-I }
7339 { ISO_8859-7:1987 iso-ir-126 ISO_8859-7 ISO-8859-7 ELOT_928 ECMA-118
7340 greek greek8 csISOLatinGreek }
7341 { T.101-G2 iso-ir-128 csISO128T101G2 }
7342 { ISO_8859-8:1988 iso-ir-138 ISO_8859-8 ISO-8859-8 hebrew
7343 csISOLatinHebrew }
7344 { ISO_8859-8-E csISO88598E ISO-8859-8-E }
7345 { ISO_8859-8-I csISO88598I ISO-8859-8-I }
7346 { CSN_369103 iso-ir-139 csISO139CSN369103 }
7347 { JUS_I.B1.002 iso-ir-141 ISO646-YU js yu csISO141JUSIB1002 }
7348 { ISO_6937-2-add iso-ir-142 csISOTextComm }
7349 { IEC_P27-1 iso-ir-143 csISO143IECP271 }
7350 { ISO_8859-5:1988 iso-ir-144 ISO_8859-5 ISO-8859-5 cyrillic
7351 csISOLatinCyrillic }
7352 { JUS_I.B1.003-serb iso-ir-146 serbian csISO146Serbian }
7353 { JUS_I.B1.003-mac macedonian iso-ir-147 csISO147Macedonian }
7354 { ISO_8859-9:1989 iso-ir-148 ISO_8859-9 ISO-8859-9 latin5 l5 csISOLatin5 }
7355 { greek-ccitt iso-ir-150 csISO150 csISO150GreekCCITT }
7356 { NC_NC00-10:81 cuba iso-ir-151 ISO646-CU csISO151Cuba }
7357 { ISO_6937-2-25 iso-ir-152 csISO6937Add }
7358 { GOST_19768-74 ST_SEV_358-88 iso-ir-153 csISO153GOST1976874 }
7359 { ISO_8859-supp iso-ir-154 latin1-2-5 csISO8859Supp }
7360 { ISO_10367-box iso-ir-155 csISO10367Box }
7361 { ISO-8859-10 iso-ir-157 l6 ISO_8859-10:1992 csISOLatin6 latin6 }
7362 { latin-lap lap iso-ir-158 csISO158Lap }
7363 { JIS_X0212-1990 x0212 iso-ir-159 csISO159JISX02121990 }
7364 { DS_2089 DS2089 ISO646-DK dk csISO646Danish }
7365 { us-dk csUSDK }
7366 { dk-us csDKUS }
7367 { JIS_X0201 X0201 csHalfWidthKatakana }
7368 { KSC5636 ISO646-KR csKSC5636 }
7369 { ISO-10646-UCS-2 csUnicode }
7370 { ISO-10646-UCS-4 csUCS4 }
7371 { DEC-MCS dec csDECMCS }
7372 { hp-roman8 roman8 r8 csHPRoman8 }
7373 { macintosh mac csMacintosh }
7374 { IBM037 cp037 ebcdic-cp-us ebcdic-cp-ca ebcdic-cp-wt ebcdic-cp-nl
7375 csIBM037 }
7376 { IBM038 EBCDIC-INT cp038 csIBM038 }
7377 { IBM273 CP273 csIBM273 }
7378 { IBM274 EBCDIC-BE CP274 csIBM274 }
7379 { IBM275 EBCDIC-BR cp275 csIBM275 }
7380 { IBM277 EBCDIC-CP-DK EBCDIC-CP-NO csIBM277 }
7381 { IBM278 CP278 ebcdic-cp-fi ebcdic-cp-se csIBM278 }
7382 { IBM280 CP280 ebcdic-cp-it csIBM280 }
7383 { IBM281 EBCDIC-JP-E cp281 csIBM281 }
7384 { IBM284 CP284 ebcdic-cp-es csIBM284 }
7385 { IBM285 CP285 ebcdic-cp-gb csIBM285 }
7386 { IBM290 cp290 EBCDIC-JP-kana csIBM290 }
7387 { IBM297 cp297 ebcdic-cp-fr csIBM297 }
7388 { IBM420 cp420 ebcdic-cp-ar1 csIBM420 }
7389 { IBM423 cp423 ebcdic-cp-gr csIBM423 }
7390 { IBM424 cp424 ebcdic-cp-he csIBM424 }
7391 { IBM437 cp437 437 csPC8CodePage437 }
7392 { IBM500 CP500 ebcdic-cp-be ebcdic-cp-ch csIBM500 }
7393 { IBM775 cp775 csPC775Baltic }
7394 { IBM850 cp850 850 csPC850Multilingual }
7395 { IBM851 cp851 851 csIBM851 }
7396 { IBM852 cp852 852 csPCp852 }
7397 { IBM855 cp855 855 csIBM855 }
7398 { IBM857 cp857 857 csIBM857 }
7399 { IBM860 cp860 860 csIBM860 }
7400 { IBM861 cp861 861 cp-is csIBM861 }
7401 { IBM862 cp862 862 csPC862LatinHebrew }
7402 { IBM863 cp863 863 csIBM863 }
7403 { IBM864 cp864 csIBM864 }
7404 { IBM865 cp865 865 csIBM865 }
7405 { IBM866 cp866 866 csIBM866 }
7406 { IBM868 CP868 cp-ar csIBM868 }
7407 { IBM869 cp869 869 cp-gr csIBM869 }
7408 { IBM870 CP870 ebcdic-cp-roece ebcdic-cp-yu csIBM870 }
7409 { IBM871 CP871 ebcdic-cp-is csIBM871 }
7410 { IBM880 cp880 EBCDIC-Cyrillic csIBM880 }
7411 { IBM891 cp891 csIBM891 }
7412 { IBM903 cp903 csIBM903 }
7413 { IBM904 cp904 904 csIBBM904 }
7414 { IBM905 CP905 ebcdic-cp-tr csIBM905 }
7415 { IBM918 CP918 ebcdic-cp-ar2 csIBM918 }
7416 { IBM1026 CP1026 csIBM1026 }
7417 { EBCDIC-AT-DE csIBMEBCDICATDE }
7418 { EBCDIC-AT-DE-A csEBCDICATDEA }
7419 { EBCDIC-CA-FR csEBCDICCAFR }
7420 { EBCDIC-DK-NO csEBCDICDKNO }
7421 { EBCDIC-DK-NO-A csEBCDICDKNOA }
7422 { EBCDIC-FI-SE csEBCDICFISE }
7423 { EBCDIC-FI-SE-A csEBCDICFISEA }
7424 { EBCDIC-FR csEBCDICFR }
7425 { EBCDIC-IT csEBCDICIT }
7426 { EBCDIC-PT csEBCDICPT }
7427 { EBCDIC-ES csEBCDICES }
7428 { EBCDIC-ES-A csEBCDICESA }
7429 { EBCDIC-ES-S csEBCDICESS }
7430 { EBCDIC-UK csEBCDICUK }
7431 { EBCDIC-US csEBCDICUS }
7432 { UNKNOWN-8BIT csUnknown8BiT }
7433 { MNEMONIC csMnemonic }
7434 { MNEM csMnem }
7435 { VISCII csVISCII }
7436 { VIQR csVIQR }
7437 { KOI8-R csKOI8R }
7438 { IBM00858 CCSID00858 CP00858 PC-Multilingual-850+euro }
7439 { IBM00924 CCSID00924 CP00924 ebcdic-Latin9--euro }
7440 { IBM01140 CCSID01140 CP01140 ebcdic-us-37+euro }
7441 { IBM01141 CCSID01141 CP01141 ebcdic-de-273+euro }
7442 { IBM01142 CCSID01142 CP01142 ebcdic-dk-277+euro ebcdic-no-277+euro }
7443 { IBM01143 CCSID01143 CP01143 ebcdic-fi-278+euro ebcdic-se-278+euro }
7444 { IBM01144 CCSID01144 CP01144 ebcdic-it-280+euro }
7445 { IBM01145 CCSID01145 CP01145 ebcdic-es-284+euro }
7446 { IBM01146 CCSID01146 CP01146 ebcdic-gb-285+euro }
7447 { IBM01147 CCSID01147 CP01147 ebcdic-fr-297+euro }
7448 { IBM01148 CCSID01148 CP01148 ebcdic-international-500+euro }
7449 { IBM01149 CCSID01149 CP01149 ebcdic-is-871+euro }
7450 { IBM1047 IBM-1047 }
7451 { PTCP154 csPTCP154 PT154 CP154 Cyrillic-Asian }
7452 { Amiga-1251 Ami1251 Amiga1251 Ami-1251 }
7453 { UNICODE-1-1 csUnicode11 }
7454 { CESU-8 csCESU-8 }
7455 { BOCU-1 csBOCU-1 }
7456 { UNICODE-1-1-UTF-7 csUnicode11UTF7 }
7457 { ISO-8859-14 iso-ir-199 ISO_8859-14:1998 ISO_8859-14 latin8 iso-celtic
7458 l8 }
7459 { ISO-8859-15 ISO_8859-15 Latin-9 }
7460 { ISO-8859-16 iso-ir-226 ISO_8859-16:2001 ISO_8859-16 latin10 l10 }
7461 { GBK CP936 MS936 windows-936 }
7462 { JIS_Encoding csJISEncoding }
7463 { Shift_JIS MS_Kanji csShiftJIS }
7464 { Extended_UNIX_Code_Packed_Format_for_Japanese csEUCPkdFmtJapanese
7465 EUC-JP }
7466 { Extended_UNIX_Code_Fixed_Width_for_Japanese csEUCFixWidJapanese }
7467 { ISO-10646-UCS-Basic csUnicodeASCII }
7468 { ISO-10646-Unicode-Latin1 csUnicodeLatin1 ISO-10646 }
7469 { ISO-Unicode-IBM-1261 csUnicodeIBM1261 }
7470 { ISO-Unicode-IBM-1268 csUnicodeIBM1268 }
7471 { ISO-Unicode-IBM-1276 csUnicodeIBM1276 }
7472 { ISO-Unicode-IBM-1264 csUnicodeIBM1264 }
7473 { ISO-Unicode-IBM-1265 csUnicodeIBM1265 }
7474 { ISO-8859-1-Windows-3.0-Latin-1 csWindows30Latin1 }
7475 { ISO-8859-1-Windows-3.1-Latin-1 csWindows31Latin1 }
7476 { ISO-8859-2-Windows-Latin-2 csWindows31Latin2 }
7477 { ISO-8859-9-Windows-Latin-5 csWindows31Latin5 }
7478 { Adobe-Standard-Encoding csAdobeStandardEncoding }
7479 { Ventura-US csVenturaUS }
7480 { Ventura-International csVenturaInternational }
7481 { PC8-Danish-Norwegian csPC8DanishNorwegian }
7482 { PC8-Turkish csPC8Turkish }
7483 { IBM-Symbols csIBMSymbols }
7484 { IBM-Thai csIBMThai }
7485 { HP-Legal csHPLegal }
7486 { HP-Pi-font csHPPiFont }
7487 { HP-Math8 csHPMath8 }
7488 { Adobe-Symbol-Encoding csHPPSMath }
7489 { HP-DeskTop csHPDesktop }
7490 { Ventura-Math csVenturaMath }
7491 { Microsoft-Publishing csMicrosoftPublishing }
7492 { Windows-31J csWindows31J }
7493 { GB2312 csGB2312 }
7494 { Big5 csBig5 }
7497 proc tcl_encoding {enc} {
7498 global encoding_aliases
7499 set names [encoding names]
7500 set lcnames [string tolower $names]
7501 set enc [string tolower $enc]
7502 set i [lsearch -exact $lcnames $enc]
7503 if {$i < 0} {
7504 # look for "isonnn" instead of "iso-nnn" or "iso_nnn"
7505 if {[regsub {^iso[-_]} $enc iso encx]} {
7506 set i [lsearch -exact $lcnames $encx]
7509 if {$i < 0} {
7510 foreach l $encoding_aliases {
7511 set ll [string tolower $l]
7512 if {[lsearch -exact $ll $enc] < 0} continue
7513 # look through the aliases for one that tcl knows about
7514 foreach e $ll {
7515 set i [lsearch -exact $lcnames $e]
7516 if {$i < 0} {
7517 if {[regsub {^iso[-_]} $e iso ex]} {
7518 set i [lsearch -exact $lcnames $ex]
7521 if {$i >= 0} break
7523 break
7526 if {$i >= 0} {
7527 return [lindex $names $i]
7529 return {}
7532 # defaults...
7533 set datemode 0
7534 set diffopts "-U 5 -p"
7535 set wrcomcmd "git diff-tree --stdin -p --pretty"
7537 set gitencoding {}
7538 catch {
7539 set gitencoding [exec git config --get i18n.commitencoding]
7541 if {$gitencoding == ""} {
7542 set gitencoding "utf-8"
7544 set tclencoding [tcl_encoding $gitencoding]
7545 if {$tclencoding == {}} {
7546 puts stderr "Warning: encoding $gitencoding is not supported by Tcl/Tk"
7549 set mainfont {Helvetica 9}
7550 set textfont {Courier 9}
7551 set uifont {Helvetica 9 bold}
7552 set tabstop 8
7553 set findmergefiles 0
7554 set maxgraphpct 50
7555 set maxwidth 16
7556 set revlistorder 0
7557 set fastdate 0
7558 set uparrowlen 7
7559 set downarrowlen 7
7560 set mingaplen 30
7561 set cmitmode "patch"
7562 set wrapcomment "none"
7563 set showneartags 1
7564 set maxrefs 20
7565 set maxlinelen 200
7566 set showlocalchanges 1
7568 set colors {green red blue magenta darkgrey brown orange}
7569 set bgcolor white
7570 set fgcolor black
7571 set diffcolors {red "#00a000" blue}
7572 set selectbgcolor gray85
7574 catch {source ~/.gitk}
7576 font create optionfont -family sans-serif -size -12
7578 # check that we can find a .git directory somewhere...
7579 set gitdir [gitdir]
7580 if {![file isdirectory $gitdir]} {
7581 show_error {} . "Cannot find the git directory \"$gitdir\"."
7582 exit 1
7585 set revtreeargs {}
7586 set cmdline_files {}
7587 set i 0
7588 foreach arg $argv {
7589 switch -- $arg {
7590 "" { }
7591 "-d" { set datemode 1 }
7592 "--" {
7593 set cmdline_files [lrange $argv [expr {$i + 1}] end]
7594 break
7596 default {
7597 lappend revtreeargs $arg
7600 incr i
7603 if {$i >= [llength $argv] && $revtreeargs ne {}} {
7604 # no -- on command line, but some arguments (other than -d)
7605 if {[catch {
7606 set f [eval exec git rev-parse --no-revs --no-flags $revtreeargs]
7607 set cmdline_files [split $f "\n"]
7608 set n [llength $cmdline_files]
7609 set revtreeargs [lrange $revtreeargs 0 end-$n]
7610 # Unfortunately git rev-parse doesn't produce an error when
7611 # something is both a revision and a filename. To be consistent
7612 # with git log and git rev-list, check revtreeargs for filenames.
7613 foreach arg $revtreeargs {
7614 if {[file exists $arg]} {
7615 show_error {} . "Ambiguous argument '$arg': both revision\
7616 and filename"
7617 exit 1
7620 } err]} {
7621 # unfortunately we get both stdout and stderr in $err,
7622 # so look for "fatal:".
7623 set i [string first "fatal:" $err]
7624 if {$i > 0} {
7625 set err [string range $err [expr {$i + 6}] end]
7627 show_error {} . "Bad arguments to gitk:\n$err"
7628 exit 1
7632 set nullid "0000000000000000000000000000000000000000"
7633 set nullid2 "0000000000000000000000000000000000000001"
7636 set runq {}
7637 set history {}
7638 set historyindex 0
7639 set fh_serial 0
7640 set nhl_names {}
7641 set highlight_paths {}
7642 set searchdirn -forwards
7643 set boldrows {}
7644 set boldnamerows {}
7645 set diffelide {0 0}
7646 set markingmatches 0
7648 set optim_delay 16
7650 set nextviewnum 1
7651 set curview 0
7652 set selectedview 0
7653 set selectedhlview None
7654 set viewfiles(0) {}
7655 set viewperm(0) 0
7656 set viewargs(0) {}
7658 set cmdlineok 0
7659 set stopped 0
7660 set stuffsaved 0
7661 set patchnum 0
7662 set lookingforhead 0
7663 set localirow -1
7664 set localfrow -1
7665 set lserial 0
7666 setcoords
7667 makewindow
7668 # wait for the window to become visible
7669 tkwait visibility .
7670 wm title . "[file tail $argv0]: [file tail [pwd]]"
7671 readrefs
7673 if {$cmdline_files ne {} || $revtreeargs ne {}} {
7674 # create a view for the files/dirs specified on the command line
7675 set curview 1
7676 set selectedview 1
7677 set nextviewnum 2
7678 set viewname(1) "Command line"
7679 set viewfiles(1) $cmdline_files
7680 set viewargs(1) $revtreeargs
7681 set viewperm(1) 0
7682 addviewmenu 1
7683 .bar.view entryconf Edit* -state normal
7684 .bar.view entryconf Delete* -state normal
7687 if {[info exists permviews]} {
7688 foreach v $permviews {
7689 set n $nextviewnum
7690 incr nextviewnum
7691 set viewname($n) [lindex $v 0]
7692 set viewfiles($n) [lindex $v 1]
7693 set viewargs($n) [lindex $v 2]
7694 set viewperm($n) 1
7695 addviewmenu $n
7698 getcommits