[PATCH] gitk: Show an error and exit if no .git could be found
[git/git-bigfiles.git] / gitk
blob769c79ab07520d5bcdad9493ab383553d2031988
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 catch {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 if {[tk windowingsystem] == "win32"} {
827 bind . <MouseWheel> { windows_mousewheel_redirector %W %X %Y %D }
828 bind $ctext <MouseWheel> { windows_mousewheel_redirector %W %X %Y %D ; break }
829 } else {
830 bindall <ButtonRelease-4> "allcanvs yview scroll -5 units"
831 bindall <ButtonRelease-5> "allcanvs yview scroll 5 units"
833 bindall <2> "canvscan mark %W %x %y"
834 bindall <B2-Motion> "canvscan dragto %W %x %y"
835 bindkey <Home> selfirstline
836 bindkey <End> sellastline
837 bind . <Key-Up> "selnextline -1"
838 bind . <Key-Down> "selnextline 1"
839 bind . <Shift-Key-Up> "next_highlight -1"
840 bind . <Shift-Key-Down> "next_highlight 1"
841 bindkey <Key-Right> "goforw"
842 bindkey <Key-Left> "goback"
843 bind . <Key-Prior> "selnextpage -1"
844 bind . <Key-Next> "selnextpage 1"
845 bind . <$M1B-Home> "allcanvs yview moveto 0.0"
846 bind . <$M1B-End> "allcanvs yview moveto 1.0"
847 bind . <$M1B-Key-Up> "allcanvs yview scroll -1 units"
848 bind . <$M1B-Key-Down> "allcanvs yview scroll 1 units"
849 bind . <$M1B-Key-Prior> "allcanvs yview scroll -1 pages"
850 bind . <$M1B-Key-Next> "allcanvs yview scroll 1 pages"
851 bindkey <Key-Delete> "$ctext yview scroll -1 pages"
852 bindkey <Key-BackSpace> "$ctext yview scroll -1 pages"
853 bindkey <Key-space> "$ctext yview scroll 1 pages"
854 bindkey p "selnextline -1"
855 bindkey n "selnextline 1"
856 bindkey z "goback"
857 bindkey x "goforw"
858 bindkey i "selnextline -1"
859 bindkey k "selnextline 1"
860 bindkey j "goback"
861 bindkey l "goforw"
862 bindkey b "$ctext yview scroll -1 pages"
863 bindkey d "$ctext yview scroll 18 units"
864 bindkey u "$ctext yview scroll -18 units"
865 bindkey / {findnext 1}
866 bindkey <Key-Return> {findnext 0}
867 bindkey ? findprev
868 bindkey f nextfile
869 bindkey <F5> updatecommits
870 bind . <$M1B-q> doquit
871 bind . <$M1B-f> dofind
872 bind . <$M1B-g> {findnext 0}
873 bind . <$M1B-r> dosearchback
874 bind . <$M1B-s> dosearch
875 bind . <$M1B-equal> {incrfont 1}
876 bind . <$M1B-KP_Add> {incrfont 1}
877 bind . <$M1B-minus> {incrfont -1}
878 bind . <$M1B-KP_Subtract> {incrfont -1}
879 wm protocol . WM_DELETE_WINDOW doquit
880 bind . <Button-1> "click %W"
881 bind $fstring <Key-Return> dofind
882 bind $sha1entry <Key-Return> gotocommit
883 bind $sha1entry <<PasteSelection>> clearsha1
884 bind $cflist <1> {sel_flist %W %x %y; break}
885 bind $cflist <B1-Motion> {sel_flist %W %x %y; break}
886 bind $cflist <ButtonRelease-1> {treeclick %W %x %y}
887 bind $cflist <Button-3> {pop_flist_menu %W %X %Y %x %y}
889 set maincursor [. cget -cursor]
890 set textcursor [$ctext cget -cursor]
891 set curtextcursor $textcursor
893 set rowctxmenu .rowctxmenu
894 menu $rowctxmenu -tearoff 0
895 $rowctxmenu add command -label "Diff this -> selected" \
896 -command {diffvssel 0}
897 $rowctxmenu add command -label "Diff selected -> this" \
898 -command {diffvssel 1}
899 $rowctxmenu add command -label "Make patch" -command mkpatch
900 $rowctxmenu add command -label "Create tag" -command mktag
901 $rowctxmenu add command -label "Write commit to file" -command writecommit
902 $rowctxmenu add command -label "Create new branch" -command mkbranch
903 $rowctxmenu add command -label "Cherry-pick this commit" \
904 -command cherrypick
905 $rowctxmenu add command -label "Reset HEAD branch to here" \
906 -command resethead
908 set fakerowmenu .fakerowmenu
909 menu $fakerowmenu -tearoff 0
910 $fakerowmenu add command -label "Diff this -> selected" \
911 -command {diffvssel 0}
912 $fakerowmenu add command -label "Diff selected -> this" \
913 -command {diffvssel 1}
914 $fakerowmenu add command -label "Make patch" -command mkpatch
915 # $fakerowmenu add command -label "Commit" -command {mkcommit 0}
916 # $fakerowmenu add command -label "Commit all" -command {mkcommit 1}
917 # $fakerowmenu add command -label "Revert local changes" -command revertlocal
919 set headctxmenu .headctxmenu
920 menu $headctxmenu -tearoff 0
921 $headctxmenu add command -label "Check out this branch" \
922 -command cobranch
923 $headctxmenu add command -label "Remove this branch" \
924 -command rmbranch
926 global flist_menu
927 set flist_menu .flistctxmenu
928 menu $flist_menu -tearoff 0
929 $flist_menu add command -label "Highlight this too" \
930 -command {flist_hl 0}
931 $flist_menu add command -label "Highlight this only" \
932 -command {flist_hl 1}
935 # Windows sends all mouse wheel events to the current focused window, not
936 # the one where the mouse hovers, so bind those events here and redirect
937 # to the correct window
938 proc windows_mousewheel_redirector {W X Y D} {
939 global canv canv2 canv3
940 set w [winfo containing -displayof $W $X $Y]
941 if {$w ne ""} {
942 set u [expr {$D < 0 ? 5 : -5}]
943 if {$w == $canv || $w == $canv2 || $w == $canv3} {
944 allcanvs yview scroll $u units
945 } else {
946 catch {
947 $w yview scroll $u units
953 # mouse-2 makes all windows scan vertically, but only the one
954 # the cursor is in scans horizontally
955 proc canvscan {op w x y} {
956 global canv canv2 canv3
957 foreach c [list $canv $canv2 $canv3] {
958 if {$c == $w} {
959 $c scan $op $x $y
960 } else {
961 $c scan $op 0 $y
966 proc scrollcanv {cscroll f0 f1} {
967 $cscroll set $f0 $f1
968 drawfrac $f0 $f1
969 flushhighlights
972 # when we make a key binding for the toplevel, make sure
973 # it doesn't get triggered when that key is pressed in the
974 # find string entry widget.
975 proc bindkey {ev script} {
976 global entries
977 bind . $ev $script
978 set escript [bind Entry $ev]
979 if {$escript == {}} {
980 set escript [bind Entry <Key>]
982 foreach e $entries {
983 bind $e $ev "$escript; break"
987 # set the focus back to the toplevel for any click outside
988 # the entry widgets
989 proc click {w} {
990 global ctext entries
991 foreach e [concat $entries $ctext] {
992 if {$w == $e} return
994 focus .
997 proc savestuff {w} {
998 global canv canv2 canv3 ctext cflist mainfont textfont uifont tabstop
999 global stuffsaved findmergefiles maxgraphpct
1000 global maxwidth showneartags showlocalchanges
1001 global viewname viewfiles viewargs viewperm nextviewnum
1002 global cmitmode wrapcomment
1003 global colors bgcolor fgcolor diffcolors selectbgcolor
1005 if {$stuffsaved} return
1006 if {![winfo viewable .]} return
1007 catch {
1008 set f [open "~/.gitk-new" w]
1009 puts $f [list set mainfont $mainfont]
1010 puts $f [list set textfont $textfont]
1011 puts $f [list set uifont $uifont]
1012 puts $f [list set tabstop $tabstop]
1013 puts $f [list set findmergefiles $findmergefiles]
1014 puts $f [list set maxgraphpct $maxgraphpct]
1015 puts $f [list set maxwidth $maxwidth]
1016 puts $f [list set cmitmode $cmitmode]
1017 puts $f [list set wrapcomment $wrapcomment]
1018 puts $f [list set showneartags $showneartags]
1019 puts $f [list set showlocalchanges $showlocalchanges]
1020 puts $f [list set bgcolor $bgcolor]
1021 puts $f [list set fgcolor $fgcolor]
1022 puts $f [list set colors $colors]
1023 puts $f [list set diffcolors $diffcolors]
1024 puts $f [list set selectbgcolor $selectbgcolor]
1026 puts $f "set geometry(main) [wm geometry .]"
1027 puts $f "set geometry(topwidth) [winfo width .tf]"
1028 puts $f "set geometry(topheight) [winfo height .tf]"
1029 puts $f "set geometry(pwsash0) \"[.tf.histframe.pwclist sash coord 0]\""
1030 puts $f "set geometry(pwsash1) \"[.tf.histframe.pwclist sash coord 1]\""
1031 puts $f "set geometry(botwidth) [winfo width .bleft]"
1032 puts $f "set geometry(botheight) [winfo height .bleft]"
1034 puts -nonewline $f "set permviews {"
1035 for {set v 0} {$v < $nextviewnum} {incr v} {
1036 if {$viewperm($v)} {
1037 puts $f "{[list $viewname($v) $viewfiles($v) $viewargs($v)]}"
1040 puts $f "}"
1041 close $f
1042 file rename -force "~/.gitk-new" "~/.gitk"
1044 set stuffsaved 1
1047 proc resizeclistpanes {win w} {
1048 global oldwidth
1049 if {[info exists oldwidth($win)]} {
1050 set s0 [$win sash coord 0]
1051 set s1 [$win sash coord 1]
1052 if {$w < 60} {
1053 set sash0 [expr {int($w/2 - 2)}]
1054 set sash1 [expr {int($w*5/6 - 2)}]
1055 } else {
1056 set factor [expr {1.0 * $w / $oldwidth($win)}]
1057 set sash0 [expr {int($factor * [lindex $s0 0])}]
1058 set sash1 [expr {int($factor * [lindex $s1 0])}]
1059 if {$sash0 < 30} {
1060 set sash0 30
1062 if {$sash1 < $sash0 + 20} {
1063 set sash1 [expr {$sash0 + 20}]
1065 if {$sash1 > $w - 10} {
1066 set sash1 [expr {$w - 10}]
1067 if {$sash0 > $sash1 - 20} {
1068 set sash0 [expr {$sash1 - 20}]
1072 $win sash place 0 $sash0 [lindex $s0 1]
1073 $win sash place 1 $sash1 [lindex $s1 1]
1075 set oldwidth($win) $w
1078 proc resizecdetpanes {win w} {
1079 global oldwidth
1080 if {[info exists oldwidth($win)]} {
1081 set s0 [$win sash coord 0]
1082 if {$w < 60} {
1083 set sash0 [expr {int($w*3/4 - 2)}]
1084 } else {
1085 set factor [expr {1.0 * $w / $oldwidth($win)}]
1086 set sash0 [expr {int($factor * [lindex $s0 0])}]
1087 if {$sash0 < 45} {
1088 set sash0 45
1090 if {$sash0 > $w - 15} {
1091 set sash0 [expr {$w - 15}]
1094 $win sash place 0 $sash0 [lindex $s0 1]
1096 set oldwidth($win) $w
1099 proc allcanvs args {
1100 global canv canv2 canv3
1101 eval $canv $args
1102 eval $canv2 $args
1103 eval $canv3 $args
1106 proc bindall {event action} {
1107 global canv canv2 canv3
1108 bind $canv $event $action
1109 bind $canv2 $event $action
1110 bind $canv3 $event $action
1113 proc about {} {
1114 global uifont
1115 set w .about
1116 if {[winfo exists $w]} {
1117 raise $w
1118 return
1120 toplevel $w
1121 wm title $w "About gitk"
1122 message $w.m -text {
1123 Gitk - a commit viewer for git
1125 Copyright © 2005-2006 Paul Mackerras
1127 Use and redistribute under the terms of the GNU General Public License} \
1128 -justify center -aspect 400 -border 2 -bg white -relief groove
1129 pack $w.m -side top -fill x -padx 2 -pady 2
1130 $w.m configure -font $uifont
1131 button $w.ok -text Close -command "destroy $w" -default active
1132 pack $w.ok -side bottom
1133 $w.ok configure -font $uifont
1134 bind $w <Visibility> "focus $w.ok"
1135 bind $w <Key-Escape> "destroy $w"
1136 bind $w <Key-Return> "destroy $w"
1139 proc keys {} {
1140 global uifont
1141 set w .keys
1142 if {[winfo exists $w]} {
1143 raise $w
1144 return
1146 if {[tk windowingsystem] eq {aqua}} {
1147 set M1T Cmd
1148 } else {
1149 set M1T Ctrl
1151 toplevel $w
1152 wm title $w "Gitk key bindings"
1153 message $w.m -text "
1154 Gitk key bindings:
1156 <$M1T-Q> Quit
1157 <Home> Move to first commit
1158 <End> Move to last commit
1159 <Up>, p, i Move up one commit
1160 <Down>, n, k Move down one commit
1161 <Left>, z, j Go back in history list
1162 <Right>, x, l Go forward in history list
1163 <PageUp> Move up one page in commit list
1164 <PageDown> Move down one page in commit list
1165 <$M1T-Home> Scroll to top of commit list
1166 <$M1T-End> Scroll to bottom of commit list
1167 <$M1T-Up> Scroll commit list up one line
1168 <$M1T-Down> Scroll commit list down one line
1169 <$M1T-PageUp> Scroll commit list up one page
1170 <$M1T-PageDown> Scroll commit list down one page
1171 <Shift-Up> Move to previous highlighted line
1172 <Shift-Down> Move to next highlighted line
1173 <Delete>, b Scroll diff view up one page
1174 <Backspace> Scroll diff view up one page
1175 <Space> Scroll diff view down one page
1176 u Scroll diff view up 18 lines
1177 d Scroll diff view down 18 lines
1178 <$M1T-F> Find
1179 <$M1T-G> Move to next find hit
1180 <Return> Move to next find hit
1181 / Move to next find hit, or redo find
1182 ? Move to previous find hit
1183 f Scroll diff view to next file
1184 <$M1T-S> Search for next hit in diff view
1185 <$M1T-R> Search for previous hit in diff view
1186 <$M1T-KP+> Increase font size
1187 <$M1T-plus> Increase font size
1188 <$M1T-KP-> Decrease font size
1189 <$M1T-minus> Decrease font size
1190 <F5> Update
1192 -justify left -bg white -border 2 -relief groove
1193 pack $w.m -side top -fill both -padx 2 -pady 2
1194 $w.m configure -font $uifont
1195 button $w.ok -text Close -command "destroy $w" -default active
1196 pack $w.ok -side bottom
1197 $w.ok configure -font $uifont
1198 bind $w <Visibility> "focus $w.ok"
1199 bind $w <Key-Escape> "destroy $w"
1200 bind $w <Key-Return> "destroy $w"
1203 # Procedures for manipulating the file list window at the
1204 # bottom right of the overall window.
1206 proc treeview {w l openlevs} {
1207 global treecontents treediropen treeheight treeparent treeindex
1209 set ix 0
1210 set treeindex() 0
1211 set lev 0
1212 set prefix {}
1213 set prefixend -1
1214 set prefendstack {}
1215 set htstack {}
1216 set ht 0
1217 set treecontents() {}
1218 $w conf -state normal
1219 foreach f $l {
1220 while {[string range $f 0 $prefixend] ne $prefix} {
1221 if {$lev <= $openlevs} {
1222 $w mark set e:$treeindex($prefix) "end -1c"
1223 $w mark gravity e:$treeindex($prefix) left
1225 set treeheight($prefix) $ht
1226 incr ht [lindex $htstack end]
1227 set htstack [lreplace $htstack end end]
1228 set prefixend [lindex $prefendstack end]
1229 set prefendstack [lreplace $prefendstack end end]
1230 set prefix [string range $prefix 0 $prefixend]
1231 incr lev -1
1233 set tail [string range $f [expr {$prefixend+1}] end]
1234 while {[set slash [string first "/" $tail]] >= 0} {
1235 lappend htstack $ht
1236 set ht 0
1237 lappend prefendstack $prefixend
1238 incr prefixend [expr {$slash + 1}]
1239 set d [string range $tail 0 $slash]
1240 lappend treecontents($prefix) $d
1241 set oldprefix $prefix
1242 append prefix $d
1243 set treecontents($prefix) {}
1244 set treeindex($prefix) [incr ix]
1245 set treeparent($prefix) $oldprefix
1246 set tail [string range $tail [expr {$slash+1}] end]
1247 if {$lev <= $openlevs} {
1248 set ht 1
1249 set treediropen($prefix) [expr {$lev < $openlevs}]
1250 set bm [expr {$lev == $openlevs? "tri-rt": "tri-dn"}]
1251 $w mark set d:$ix "end -1c"
1252 $w mark gravity d:$ix left
1253 set str "\n"
1254 for {set i 0} {$i < $lev} {incr i} {append str "\t"}
1255 $w insert end $str
1256 $w image create end -align center -image $bm -padx 1 \
1257 -name a:$ix
1258 $w insert end $d [highlight_tag $prefix]
1259 $w mark set s:$ix "end -1c"
1260 $w mark gravity s:$ix left
1262 incr lev
1264 if {$tail ne {}} {
1265 if {$lev <= $openlevs} {
1266 incr ht
1267 set str "\n"
1268 for {set i 0} {$i < $lev} {incr i} {append str "\t"}
1269 $w insert end $str
1270 $w insert end $tail [highlight_tag $f]
1272 lappend treecontents($prefix) $tail
1275 while {$htstack ne {}} {
1276 set treeheight($prefix) $ht
1277 incr ht [lindex $htstack end]
1278 set htstack [lreplace $htstack end end]
1279 set prefixend [lindex $prefendstack end]
1280 set prefendstack [lreplace $prefendstack end end]
1281 set prefix [string range $prefix 0 $prefixend]
1283 $w conf -state disabled
1286 proc linetoelt {l} {
1287 global treeheight treecontents
1289 set y 2
1290 set prefix {}
1291 while {1} {
1292 foreach e $treecontents($prefix) {
1293 if {$y == $l} {
1294 return "$prefix$e"
1296 set n 1
1297 if {[string index $e end] eq "/"} {
1298 set n $treeheight($prefix$e)
1299 if {$y + $n > $l} {
1300 append prefix $e
1301 incr y
1302 break
1305 incr y $n
1310 proc highlight_tree {y prefix} {
1311 global treeheight treecontents cflist
1313 foreach e $treecontents($prefix) {
1314 set path $prefix$e
1315 if {[highlight_tag $path] ne {}} {
1316 $cflist tag add bold $y.0 "$y.0 lineend"
1318 incr y
1319 if {[string index $e end] eq "/" && $treeheight($path) > 1} {
1320 set y [highlight_tree $y $path]
1323 return $y
1326 proc treeclosedir {w dir} {
1327 global treediropen treeheight treeparent treeindex
1329 set ix $treeindex($dir)
1330 $w conf -state normal
1331 $w delete s:$ix e:$ix
1332 set treediropen($dir) 0
1333 $w image configure a:$ix -image tri-rt
1334 $w conf -state disabled
1335 set n [expr {1 - $treeheight($dir)}]
1336 while {$dir ne {}} {
1337 incr treeheight($dir) $n
1338 set dir $treeparent($dir)
1342 proc treeopendir {w dir} {
1343 global treediropen treeheight treeparent treecontents treeindex
1345 set ix $treeindex($dir)
1346 $w conf -state normal
1347 $w image configure a:$ix -image tri-dn
1348 $w mark set e:$ix s:$ix
1349 $w mark gravity e:$ix right
1350 set lev 0
1351 set str "\n"
1352 set n [llength $treecontents($dir)]
1353 for {set x $dir} {$x ne {}} {set x $treeparent($x)} {
1354 incr lev
1355 append str "\t"
1356 incr treeheight($x) $n
1358 foreach e $treecontents($dir) {
1359 set de $dir$e
1360 if {[string index $e end] eq "/"} {
1361 set iy $treeindex($de)
1362 $w mark set d:$iy e:$ix
1363 $w mark gravity d:$iy left
1364 $w insert e:$ix $str
1365 set treediropen($de) 0
1366 $w image create e:$ix -align center -image tri-rt -padx 1 \
1367 -name a:$iy
1368 $w insert e:$ix $e [highlight_tag $de]
1369 $w mark set s:$iy e:$ix
1370 $w mark gravity s:$iy left
1371 set treeheight($de) 1
1372 } else {
1373 $w insert e:$ix $str
1374 $w insert e:$ix $e [highlight_tag $de]
1377 $w mark gravity e:$ix left
1378 $w conf -state disabled
1379 set treediropen($dir) 1
1380 set top [lindex [split [$w index @0,0] .] 0]
1381 set ht [$w cget -height]
1382 set l [lindex [split [$w index s:$ix] .] 0]
1383 if {$l < $top} {
1384 $w yview $l.0
1385 } elseif {$l + $n + 1 > $top + $ht} {
1386 set top [expr {$l + $n + 2 - $ht}]
1387 if {$l < $top} {
1388 set top $l
1390 $w yview $top.0
1394 proc treeclick {w x y} {
1395 global treediropen cmitmode ctext cflist cflist_top
1397 if {$cmitmode ne "tree"} return
1398 if {![info exists cflist_top]} return
1399 set l [lindex [split [$w index "@$x,$y"] "."] 0]
1400 $cflist tag remove highlight $cflist_top.0 "$cflist_top.0 lineend"
1401 $cflist tag add highlight $l.0 "$l.0 lineend"
1402 set cflist_top $l
1403 if {$l == 1} {
1404 $ctext yview 1.0
1405 return
1407 set e [linetoelt $l]
1408 if {[string index $e end] ne "/"} {
1409 showfile $e
1410 } elseif {$treediropen($e)} {
1411 treeclosedir $w $e
1412 } else {
1413 treeopendir $w $e
1417 proc setfilelist {id} {
1418 global treefilelist cflist
1420 treeview $cflist $treefilelist($id) 0
1423 image create bitmap tri-rt -background black -foreground blue -data {
1424 #define tri-rt_width 13
1425 #define tri-rt_height 13
1426 static unsigned char tri-rt_bits[] = {
1427 0x00, 0x00, 0x00, 0x00, 0x10, 0x00, 0x30, 0x00, 0x70, 0x00, 0xf0, 0x00,
1428 0xf0, 0x01, 0xf0, 0x00, 0x70, 0x00, 0x30, 0x00, 0x10, 0x00, 0x00, 0x00,
1429 0x00, 0x00};
1430 } -maskdata {
1431 #define tri-rt-mask_width 13
1432 #define tri-rt-mask_height 13
1433 static unsigned char tri-rt-mask_bits[] = {
1434 0x08, 0x00, 0x18, 0x00, 0x38, 0x00, 0x78, 0x00, 0xf8, 0x00, 0xf8, 0x01,
1435 0xf8, 0x03, 0xf8, 0x01, 0xf8, 0x00, 0x78, 0x00, 0x38, 0x00, 0x18, 0x00,
1436 0x08, 0x00};
1438 image create bitmap tri-dn -background black -foreground blue -data {
1439 #define tri-dn_width 13
1440 #define tri-dn_height 13
1441 static unsigned char tri-dn_bits[] = {
1442 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0xfc, 0x07, 0xf8, 0x03,
1443 0xf0, 0x01, 0xe0, 0x00, 0x40, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
1444 0x00, 0x00};
1445 } -maskdata {
1446 #define tri-dn-mask_width 13
1447 #define tri-dn-mask_height 13
1448 static unsigned char tri-dn-mask_bits[] = {
1449 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0xff, 0x1f, 0xfe, 0x0f, 0xfc, 0x07,
1450 0xf8, 0x03, 0xf0, 0x01, 0xe0, 0x00, 0x40, 0x00, 0x00, 0x00, 0x00, 0x00,
1451 0x00, 0x00};
1454 proc init_flist {first} {
1455 global cflist cflist_top selectedline difffilestart
1457 $cflist conf -state normal
1458 $cflist delete 0.0 end
1459 if {$first ne {}} {
1460 $cflist insert end $first
1461 set cflist_top 1
1462 $cflist tag add highlight 1.0 "1.0 lineend"
1463 } else {
1464 catch {unset cflist_top}
1466 $cflist conf -state disabled
1467 set difffilestart {}
1470 proc highlight_tag {f} {
1471 global highlight_paths
1473 foreach p $highlight_paths {
1474 if {[string match $p $f]} {
1475 return "bold"
1478 return {}
1481 proc highlight_filelist {} {
1482 global cmitmode cflist
1484 $cflist conf -state normal
1485 if {$cmitmode ne "tree"} {
1486 set end [lindex [split [$cflist index end] .] 0]
1487 for {set l 2} {$l < $end} {incr l} {
1488 set line [$cflist get $l.0 "$l.0 lineend"]
1489 if {[highlight_tag $line] ne {}} {
1490 $cflist tag add bold $l.0 "$l.0 lineend"
1493 } else {
1494 highlight_tree 2 {}
1496 $cflist conf -state disabled
1499 proc unhighlight_filelist {} {
1500 global cflist
1502 $cflist conf -state normal
1503 $cflist tag remove bold 1.0 end
1504 $cflist conf -state disabled
1507 proc add_flist {fl} {
1508 global cflist
1510 $cflist conf -state normal
1511 foreach f $fl {
1512 $cflist insert end "\n"
1513 $cflist insert end $f [highlight_tag $f]
1515 $cflist conf -state disabled
1518 proc sel_flist {w x y} {
1519 global ctext difffilestart cflist cflist_top cmitmode
1521 if {$cmitmode eq "tree"} return
1522 if {![info exists cflist_top]} return
1523 set l [lindex [split [$w index "@$x,$y"] "."] 0]
1524 $cflist tag remove highlight $cflist_top.0 "$cflist_top.0 lineend"
1525 $cflist tag add highlight $l.0 "$l.0 lineend"
1526 set cflist_top $l
1527 if {$l == 1} {
1528 $ctext yview 1.0
1529 } else {
1530 catch {$ctext yview [lindex $difffilestart [expr {$l - 2}]]}
1534 proc pop_flist_menu {w X Y x y} {
1535 global ctext cflist cmitmode flist_menu flist_menu_file
1536 global treediffs diffids
1538 set l [lindex [split [$w index "@$x,$y"] "."] 0]
1539 if {$l <= 1} return
1540 if {$cmitmode eq "tree"} {
1541 set e [linetoelt $l]
1542 if {[string index $e end] eq "/"} return
1543 } else {
1544 set e [lindex $treediffs($diffids) [expr {$l-2}]]
1546 set flist_menu_file $e
1547 tk_popup $flist_menu $X $Y
1550 proc flist_hl {only} {
1551 global flist_menu_file highlight_files
1553 set x [shellquote $flist_menu_file]
1554 if {$only || $highlight_files eq {}} {
1555 set highlight_files $x
1556 } else {
1557 append highlight_files " " $x
1561 # Functions for adding and removing shell-type quoting
1563 proc shellquote {str} {
1564 if {![string match "*\['\"\\ \t]*" $str]} {
1565 return $str
1567 if {![string match "*\['\"\\]*" $str]} {
1568 return "\"$str\""
1570 if {![string match "*'*" $str]} {
1571 return "'$str'"
1573 return "\"[string map {\" \\\" \\ \\\\} $str]\""
1576 proc shellarglist {l} {
1577 set str {}
1578 foreach a $l {
1579 if {$str ne {}} {
1580 append str " "
1582 append str [shellquote $a]
1584 return $str
1587 proc shelldequote {str} {
1588 set ret {}
1589 set used -1
1590 while {1} {
1591 incr used
1592 if {![regexp -start $used -indices "\['\"\\\\ \t]" $str first]} {
1593 append ret [string range $str $used end]
1594 set used [string length $str]
1595 break
1597 set first [lindex $first 0]
1598 set ch [string index $str $first]
1599 if {$first > $used} {
1600 append ret [string range $str $used [expr {$first - 1}]]
1601 set used $first
1603 if {$ch eq " " || $ch eq "\t"} break
1604 incr used
1605 if {$ch eq "'"} {
1606 set first [string first "'" $str $used]
1607 if {$first < 0} {
1608 error "unmatched single-quote"
1610 append ret [string range $str $used [expr {$first - 1}]]
1611 set used $first
1612 continue
1614 if {$ch eq "\\"} {
1615 if {$used >= [string length $str]} {
1616 error "trailing backslash"
1618 append ret [string index $str $used]
1619 continue
1621 # here ch == "\""
1622 while {1} {
1623 if {![regexp -start $used -indices "\[\"\\\\]" $str first]} {
1624 error "unmatched double-quote"
1626 set first [lindex $first 0]
1627 set ch [string index $str $first]
1628 if {$first > $used} {
1629 append ret [string range $str $used [expr {$first - 1}]]
1630 set used $first
1632 if {$ch eq "\""} break
1633 incr used
1634 append ret [string index $str $used]
1635 incr used
1638 return [list $used $ret]
1641 proc shellsplit {str} {
1642 set l {}
1643 while {1} {
1644 set str [string trimleft $str]
1645 if {$str eq {}} break
1646 set dq [shelldequote $str]
1647 set n [lindex $dq 0]
1648 set word [lindex $dq 1]
1649 set str [string range $str $n end]
1650 lappend l $word
1652 return $l
1655 # Code to implement multiple views
1657 proc newview {ishighlight} {
1658 global nextviewnum newviewname newviewperm uifont newishighlight
1659 global newviewargs revtreeargs
1661 set newishighlight $ishighlight
1662 set top .gitkview
1663 if {[winfo exists $top]} {
1664 raise $top
1665 return
1667 set newviewname($nextviewnum) "View $nextviewnum"
1668 set newviewperm($nextviewnum) 0
1669 set newviewargs($nextviewnum) [shellarglist $revtreeargs]
1670 vieweditor $top $nextviewnum "Gitk view definition"
1673 proc editview {} {
1674 global curview
1675 global viewname viewperm newviewname newviewperm
1676 global viewargs newviewargs
1678 set top .gitkvedit-$curview
1679 if {[winfo exists $top]} {
1680 raise $top
1681 return
1683 set newviewname($curview) $viewname($curview)
1684 set newviewperm($curview) $viewperm($curview)
1685 set newviewargs($curview) [shellarglist $viewargs($curview)]
1686 vieweditor $top $curview "Gitk: edit view $viewname($curview)"
1689 proc vieweditor {top n title} {
1690 global newviewname newviewperm viewfiles
1691 global uifont
1693 toplevel $top
1694 wm title $top $title
1695 label $top.nl -text "Name" -font $uifont
1696 entry $top.name -width 20 -textvariable newviewname($n) -font $uifont
1697 grid $top.nl $top.name -sticky w -pady 5
1698 checkbutton $top.perm -text "Remember this view" -variable newviewperm($n) \
1699 -font $uifont
1700 grid $top.perm - -pady 5 -sticky w
1701 message $top.al -aspect 1000 -font $uifont \
1702 -text "Commits to include (arguments to git rev-list):"
1703 grid $top.al - -sticky w -pady 5
1704 entry $top.args -width 50 -textvariable newviewargs($n) \
1705 -background white -font $uifont
1706 grid $top.args - -sticky ew -padx 5
1707 message $top.l -aspect 1000 -font $uifont \
1708 -text "Enter files and directories to include, one per line:"
1709 grid $top.l - -sticky w
1710 text $top.t -width 40 -height 10 -background white -font $uifont
1711 if {[info exists viewfiles($n)]} {
1712 foreach f $viewfiles($n) {
1713 $top.t insert end $f
1714 $top.t insert end "\n"
1716 $top.t delete {end - 1c} end
1717 $top.t mark set insert 0.0
1719 grid $top.t - -sticky ew -padx 5
1720 frame $top.buts
1721 button $top.buts.ok -text "OK" -command [list newviewok $top $n] \
1722 -font $uifont
1723 button $top.buts.can -text "Cancel" -command [list destroy $top] \
1724 -font $uifont
1725 grid $top.buts.ok $top.buts.can
1726 grid columnconfigure $top.buts 0 -weight 1 -uniform a
1727 grid columnconfigure $top.buts 1 -weight 1 -uniform a
1728 grid $top.buts - -pady 10 -sticky ew
1729 focus $top.t
1732 proc doviewmenu {m first cmd op argv} {
1733 set nmenu [$m index end]
1734 for {set i $first} {$i <= $nmenu} {incr i} {
1735 if {[$m entrycget $i -command] eq $cmd} {
1736 eval $m $op $i $argv
1737 break
1742 proc allviewmenus {n op args} {
1743 global viewhlmenu
1745 doviewmenu .bar.view 5 [list showview $n] $op $args
1746 doviewmenu $viewhlmenu 1 [list addvhighlight $n] $op $args
1749 proc newviewok {top n} {
1750 global nextviewnum newviewperm newviewname newishighlight
1751 global viewname viewfiles viewperm selectedview curview
1752 global viewargs newviewargs viewhlmenu
1754 if {[catch {
1755 set newargs [shellsplit $newviewargs($n)]
1756 } err]} {
1757 error_popup "Error in commit selection arguments: $err"
1758 wm raise $top
1759 focus $top
1760 return
1762 set files {}
1763 foreach f [split [$top.t get 0.0 end] "\n"] {
1764 set ft [string trim $f]
1765 if {$ft ne {}} {
1766 lappend files $ft
1769 if {![info exists viewfiles($n)]} {
1770 # creating a new view
1771 incr nextviewnum
1772 set viewname($n) $newviewname($n)
1773 set viewperm($n) $newviewperm($n)
1774 set viewfiles($n) $files
1775 set viewargs($n) $newargs
1776 addviewmenu $n
1777 if {!$newishighlight} {
1778 run showview $n
1779 } else {
1780 run addvhighlight $n
1782 } else {
1783 # editing an existing view
1784 set viewperm($n) $newviewperm($n)
1785 if {$newviewname($n) ne $viewname($n)} {
1786 set viewname($n) $newviewname($n)
1787 doviewmenu .bar.view 5 [list showview $n] \
1788 entryconf [list -label $viewname($n)]
1789 doviewmenu $viewhlmenu 1 [list addvhighlight $n] \
1790 entryconf [list -label $viewname($n) -value $viewname($n)]
1792 if {$files ne $viewfiles($n) || $newargs ne $viewargs($n)} {
1793 set viewfiles($n) $files
1794 set viewargs($n) $newargs
1795 if {$curview == $n} {
1796 run updatecommits
1800 catch {destroy $top}
1803 proc delview {} {
1804 global curview viewdata viewperm hlview selectedhlview
1806 if {$curview == 0} return
1807 if {[info exists hlview] && $hlview == $curview} {
1808 set selectedhlview None
1809 unset hlview
1811 allviewmenus $curview delete
1812 set viewdata($curview) {}
1813 set viewperm($curview) 0
1814 showview 0
1817 proc addviewmenu {n} {
1818 global viewname viewhlmenu
1820 .bar.view add radiobutton -label $viewname($n) \
1821 -command [list showview $n] -variable selectedview -value $n
1822 $viewhlmenu add radiobutton -label $viewname($n) \
1823 -command [list addvhighlight $n] -variable selectedhlview
1826 proc flatten {var} {
1827 global $var
1829 set ret {}
1830 foreach i [array names $var] {
1831 lappend ret $i [set $var\($i\)]
1833 return $ret
1836 proc unflatten {var l} {
1837 global $var
1839 catch {unset $var}
1840 foreach {i v} $l {
1841 set $var\($i\) $v
1845 proc showview {n} {
1846 global curview viewdata viewfiles
1847 global displayorder parentlist rowidlist rowoffsets
1848 global colormap rowtextx commitrow nextcolor canvxmax
1849 global numcommits rowrangelist commitlisted idrowranges rowchk
1850 global selectedline currentid canv canvy0
1851 global treediffs
1852 global pending_select phase
1853 global commitidx rowlaidout rowoptim
1854 global commfd
1855 global selectedview selectfirst
1856 global vparentlist vdisporder vcmitlisted
1857 global hlview selectedhlview
1859 if {$n == $curview} return
1860 set selid {}
1861 if {[info exists selectedline]} {
1862 set selid $currentid
1863 set y [yc $selectedline]
1864 set ymax [lindex [$canv cget -scrollregion] 3]
1865 set span [$canv yview]
1866 set ytop [expr {[lindex $span 0] * $ymax}]
1867 set ybot [expr {[lindex $span 1] * $ymax}]
1868 if {$ytop < $y && $y < $ybot} {
1869 set yscreen [expr {$y - $ytop}]
1870 } else {
1871 set yscreen [expr {($ybot - $ytop) / 2}]
1873 } elseif {[info exists pending_select]} {
1874 set selid $pending_select
1875 unset pending_select
1877 unselectline
1878 normalline
1879 if {$curview >= 0} {
1880 set vparentlist($curview) $parentlist
1881 set vdisporder($curview) $displayorder
1882 set vcmitlisted($curview) $commitlisted
1883 if {$phase ne {}} {
1884 set viewdata($curview) \
1885 [list $phase $rowidlist $rowoffsets $rowrangelist \
1886 [flatten idrowranges] [flatten idinlist] \
1887 $rowlaidout $rowoptim $numcommits]
1888 } elseif {![info exists viewdata($curview)]
1889 || [lindex $viewdata($curview) 0] ne {}} {
1890 set viewdata($curview) \
1891 [list {} $rowidlist $rowoffsets $rowrangelist]
1894 catch {unset treediffs}
1895 clear_display
1896 if {[info exists hlview] && $hlview == $n} {
1897 unset hlview
1898 set selectedhlview None
1901 set curview $n
1902 set selectedview $n
1903 .bar.view entryconf Edit* -state [expr {$n == 0? "disabled": "normal"}]
1904 .bar.view entryconf Delete* -state [expr {$n == 0? "disabled": "normal"}]
1906 if {![info exists viewdata($n)]} {
1907 if {$selid ne {}} {
1908 set pending_select $selid
1910 getcommits
1911 return
1914 set v $viewdata($n)
1915 set phase [lindex $v 0]
1916 set displayorder $vdisporder($n)
1917 set parentlist $vparentlist($n)
1918 set commitlisted $vcmitlisted($n)
1919 set rowidlist [lindex $v 1]
1920 set rowoffsets [lindex $v 2]
1921 set rowrangelist [lindex $v 3]
1922 if {$phase eq {}} {
1923 set numcommits [llength $displayorder]
1924 catch {unset idrowranges}
1925 } else {
1926 unflatten idrowranges [lindex $v 4]
1927 unflatten idinlist [lindex $v 5]
1928 set rowlaidout [lindex $v 6]
1929 set rowoptim [lindex $v 7]
1930 set numcommits [lindex $v 8]
1931 catch {unset rowchk}
1934 catch {unset colormap}
1935 catch {unset rowtextx}
1936 set nextcolor 0
1937 set canvxmax [$canv cget -width]
1938 set curview $n
1939 set row 0
1940 setcanvscroll
1941 set yf 0
1942 set row {}
1943 set selectfirst 0
1944 if {$selid ne {} && [info exists commitrow($n,$selid)]} {
1945 set row $commitrow($n,$selid)
1946 # try to get the selected row in the same position on the screen
1947 set ymax [lindex [$canv cget -scrollregion] 3]
1948 set ytop [expr {[yc $row] - $yscreen}]
1949 if {$ytop < 0} {
1950 set ytop 0
1952 set yf [expr {$ytop * 1.0 / $ymax}]
1954 allcanvs yview moveto $yf
1955 drawvisible
1956 if {$row ne {}} {
1957 selectline $row 0
1958 } elseif {$selid ne {}} {
1959 set pending_select $selid
1960 } else {
1961 set row [first_real_row]
1962 if {$row < $numcommits} {
1963 selectline $row 0
1964 } else {
1965 set selectfirst 1
1968 if {$phase ne {}} {
1969 if {$phase eq "getcommits"} {
1970 show_status "Reading commits..."
1972 run chewcommits $n
1973 } elseif {$numcommits == 0} {
1974 show_status "No commits selected"
1978 # Stuff relating to the highlighting facility
1980 proc ishighlighted {row} {
1981 global vhighlights fhighlights nhighlights rhighlights
1983 if {[info exists nhighlights($row)] && $nhighlights($row) > 0} {
1984 return $nhighlights($row)
1986 if {[info exists vhighlights($row)] && $vhighlights($row) > 0} {
1987 return $vhighlights($row)
1989 if {[info exists fhighlights($row)] && $fhighlights($row) > 0} {
1990 return $fhighlights($row)
1992 if {[info exists rhighlights($row)] && $rhighlights($row) > 0} {
1993 return $rhighlights($row)
1995 return 0
1998 proc bolden {row font} {
1999 global canv linehtag selectedline boldrows
2001 lappend boldrows $row
2002 $canv itemconf $linehtag($row) -font $font
2003 if {[info exists selectedline] && $row == $selectedline} {
2004 $canv delete secsel
2005 set t [eval $canv create rect [$canv bbox $linehtag($row)] \
2006 -outline {{}} -tags secsel \
2007 -fill [$canv cget -selectbackground]]
2008 $canv lower $t
2012 proc bolden_name {row font} {
2013 global canv2 linentag selectedline boldnamerows
2015 lappend boldnamerows $row
2016 $canv2 itemconf $linentag($row) -font $font
2017 if {[info exists selectedline] && $row == $selectedline} {
2018 $canv2 delete secsel
2019 set t [eval $canv2 create rect [$canv2 bbox $linentag($row)] \
2020 -outline {{}} -tags secsel \
2021 -fill [$canv2 cget -selectbackground]]
2022 $canv2 lower $t
2026 proc unbolden {} {
2027 global mainfont boldrows
2029 set stillbold {}
2030 foreach row $boldrows {
2031 if {![ishighlighted $row]} {
2032 bolden $row $mainfont
2033 } else {
2034 lappend stillbold $row
2037 set boldrows $stillbold
2040 proc addvhighlight {n} {
2041 global hlview curview viewdata vhl_done vhighlights commitidx
2043 if {[info exists hlview]} {
2044 delvhighlight
2046 set hlview $n
2047 if {$n != $curview && ![info exists viewdata($n)]} {
2048 set viewdata($n) [list getcommits {{}} {{}} {} {} {} 0 0 0 {}]
2049 set vparentlist($n) {}
2050 set vdisporder($n) {}
2051 set vcmitlisted($n) {}
2052 start_rev_list $n
2054 set vhl_done $commitidx($hlview)
2055 if {$vhl_done > 0} {
2056 drawvisible
2060 proc delvhighlight {} {
2061 global hlview vhighlights
2063 if {![info exists hlview]} return
2064 unset hlview
2065 catch {unset vhighlights}
2066 unbolden
2069 proc vhighlightmore {} {
2070 global hlview vhl_done commitidx vhighlights
2071 global displayorder vdisporder curview mainfont
2073 set font [concat $mainfont bold]
2074 set max $commitidx($hlview)
2075 if {$hlview == $curview} {
2076 set disp $displayorder
2077 } else {
2078 set disp $vdisporder($hlview)
2080 set vr [visiblerows]
2081 set r0 [lindex $vr 0]
2082 set r1 [lindex $vr 1]
2083 for {set i $vhl_done} {$i < $max} {incr i} {
2084 set id [lindex $disp $i]
2085 if {[info exists commitrow($curview,$id)]} {
2086 set row $commitrow($curview,$id)
2087 if {$r0 <= $row && $row <= $r1} {
2088 if {![highlighted $row]} {
2089 bolden $row $font
2091 set vhighlights($row) 1
2095 set vhl_done $max
2098 proc askvhighlight {row id} {
2099 global hlview vhighlights commitrow iddrawn mainfont
2101 if {[info exists commitrow($hlview,$id)]} {
2102 if {[info exists iddrawn($id)] && ![ishighlighted $row]} {
2103 bolden $row [concat $mainfont bold]
2105 set vhighlights($row) 1
2106 } else {
2107 set vhighlights($row) 0
2111 proc hfiles_change {name ix op} {
2112 global highlight_files filehighlight fhighlights fh_serial
2113 global mainfont highlight_paths
2115 if {[info exists filehighlight]} {
2116 # delete previous highlights
2117 catch {close $filehighlight}
2118 unset filehighlight
2119 catch {unset fhighlights}
2120 unbolden
2121 unhighlight_filelist
2123 set highlight_paths {}
2124 after cancel do_file_hl $fh_serial
2125 incr fh_serial
2126 if {$highlight_files ne {}} {
2127 after 300 do_file_hl $fh_serial
2131 proc makepatterns {l} {
2132 set ret {}
2133 foreach e $l {
2134 set ee [string map {"*" "\\*" "?" "\\?" "\[" "\\\[" "\\" "\\\\"} $e]
2135 if {[string index $ee end] eq "/"} {
2136 lappend ret "$ee*"
2137 } else {
2138 lappend ret $ee
2139 lappend ret "$ee/*"
2142 return $ret
2145 proc do_file_hl {serial} {
2146 global highlight_files filehighlight highlight_paths gdttype fhl_list
2148 if {$gdttype eq "touching paths:"} {
2149 if {[catch {set paths [shellsplit $highlight_files]}]} return
2150 set highlight_paths [makepatterns $paths]
2151 highlight_filelist
2152 set gdtargs [concat -- $paths]
2153 } else {
2154 set gdtargs [list "-S$highlight_files"]
2156 set cmd [concat | git diff-tree -r -s --stdin $gdtargs]
2157 set filehighlight [open $cmd r+]
2158 fconfigure $filehighlight -blocking 0
2159 filerun $filehighlight readfhighlight
2160 set fhl_list {}
2161 drawvisible
2162 flushhighlights
2165 proc flushhighlights {} {
2166 global filehighlight fhl_list
2168 if {[info exists filehighlight]} {
2169 lappend fhl_list {}
2170 puts $filehighlight ""
2171 flush $filehighlight
2175 proc askfilehighlight {row id} {
2176 global filehighlight fhighlights fhl_list
2178 lappend fhl_list $id
2179 set fhighlights($row) -1
2180 puts $filehighlight $id
2183 proc readfhighlight {} {
2184 global filehighlight fhighlights commitrow curview mainfont iddrawn
2185 global fhl_list
2187 if {![info exists filehighlight]} {
2188 return 0
2190 set nr 0
2191 while {[incr nr] <= 100 && [gets $filehighlight line] >= 0} {
2192 set line [string trim $line]
2193 set i [lsearch -exact $fhl_list $line]
2194 if {$i < 0} continue
2195 for {set j 0} {$j < $i} {incr j} {
2196 set id [lindex $fhl_list $j]
2197 if {[info exists commitrow($curview,$id)]} {
2198 set fhighlights($commitrow($curview,$id)) 0
2201 set fhl_list [lrange $fhl_list [expr {$i+1}] end]
2202 if {$line eq {}} continue
2203 if {![info exists commitrow($curview,$line)]} continue
2204 set row $commitrow($curview,$line)
2205 if {[info exists iddrawn($line)] && ![ishighlighted $row]} {
2206 bolden $row [concat $mainfont bold]
2208 set fhighlights($row) 1
2210 if {[eof $filehighlight]} {
2211 # strange...
2212 puts "oops, git diff-tree died"
2213 catch {close $filehighlight}
2214 unset filehighlight
2215 return 0
2217 next_hlcont
2218 return 1
2221 proc find_change {name ix op} {
2222 global nhighlights mainfont boldnamerows
2223 global findstring findpattern findtype
2225 # delete previous highlights, if any
2226 foreach row $boldnamerows {
2227 bolden_name $row $mainfont
2229 set boldnamerows {}
2230 catch {unset nhighlights}
2231 unbolden
2232 unmarkmatches
2233 if {$findtype ne "Regexp"} {
2234 set e [string map {"*" "\\*" "?" "\\?" "\[" "\\\[" "\\" "\\\\"} \
2235 $findstring]
2236 set findpattern "*$e*"
2238 drawvisible
2241 proc doesmatch {f} {
2242 global findtype findstring findpattern
2244 if {$findtype eq "Regexp"} {
2245 return [regexp $findstring $f]
2246 } elseif {$findtype eq "IgnCase"} {
2247 return [string match -nocase $findpattern $f]
2248 } else {
2249 return [string match $findpattern $f]
2253 proc askfindhighlight {row id} {
2254 global nhighlights commitinfo iddrawn mainfont
2255 global findloc
2256 global markingmatches
2258 if {![info exists commitinfo($id)]} {
2259 getcommit $id
2261 set info $commitinfo($id)
2262 set isbold 0
2263 set fldtypes {Headline Author Date Committer CDate Comments}
2264 foreach f $info ty $fldtypes {
2265 if {($findloc eq "All fields" || $findloc eq $ty) &&
2266 [doesmatch $f]} {
2267 if {$ty eq "Author"} {
2268 set isbold 2
2269 break
2271 set isbold 1
2274 if {$isbold && [info exists iddrawn($id)]} {
2275 set f [concat $mainfont bold]
2276 if {![ishighlighted $row]} {
2277 bolden $row $f
2278 if {$isbold > 1} {
2279 bolden_name $row $f
2282 if {$markingmatches} {
2283 markrowmatches $row $id
2286 set nhighlights($row) $isbold
2289 proc markrowmatches {row id} {
2290 global canv canv2 linehtag linentag commitinfo findloc
2292 set headline [lindex $commitinfo($id) 0]
2293 set author [lindex $commitinfo($id) 1]
2294 $canv delete match$row
2295 $canv2 delete match$row
2296 if {$findloc eq "All fields" || $findloc eq "Headline"} {
2297 set m [findmatches $headline]
2298 if {$m ne {}} {
2299 markmatches $canv $row $headline $linehtag($row) $m \
2300 [$canv itemcget $linehtag($row) -font] $row
2303 if {$findloc eq "All fields" || $findloc eq "Author"} {
2304 set m [findmatches $author]
2305 if {$m ne {}} {
2306 markmatches $canv2 $row $author $linentag($row) $m \
2307 [$canv2 itemcget $linentag($row) -font] $row
2312 proc vrel_change {name ix op} {
2313 global highlight_related
2315 rhighlight_none
2316 if {$highlight_related ne "None"} {
2317 run drawvisible
2321 # prepare for testing whether commits are descendents or ancestors of a
2322 proc rhighlight_sel {a} {
2323 global descendent desc_todo ancestor anc_todo
2324 global highlight_related rhighlights
2326 catch {unset descendent}
2327 set desc_todo [list $a]
2328 catch {unset ancestor}
2329 set anc_todo [list $a]
2330 if {$highlight_related ne "None"} {
2331 rhighlight_none
2332 run drawvisible
2336 proc rhighlight_none {} {
2337 global rhighlights
2339 catch {unset rhighlights}
2340 unbolden
2343 proc is_descendent {a} {
2344 global curview children commitrow descendent desc_todo
2346 set v $curview
2347 set la $commitrow($v,$a)
2348 set todo $desc_todo
2349 set leftover {}
2350 set done 0
2351 for {set i 0} {$i < [llength $todo]} {incr i} {
2352 set do [lindex $todo $i]
2353 if {$commitrow($v,$do) < $la} {
2354 lappend leftover $do
2355 continue
2357 foreach nk $children($v,$do) {
2358 if {![info exists descendent($nk)]} {
2359 set descendent($nk) 1
2360 lappend todo $nk
2361 if {$nk eq $a} {
2362 set done 1
2366 if {$done} {
2367 set desc_todo [concat $leftover [lrange $todo [expr {$i+1}] end]]
2368 return
2371 set descendent($a) 0
2372 set desc_todo $leftover
2375 proc is_ancestor {a} {
2376 global curview parentlist commitrow ancestor anc_todo
2378 set v $curview
2379 set la $commitrow($v,$a)
2380 set todo $anc_todo
2381 set leftover {}
2382 set done 0
2383 for {set i 0} {$i < [llength $todo]} {incr i} {
2384 set do [lindex $todo $i]
2385 if {![info exists commitrow($v,$do)] || $commitrow($v,$do) > $la} {
2386 lappend leftover $do
2387 continue
2389 foreach np [lindex $parentlist $commitrow($v,$do)] {
2390 if {![info exists ancestor($np)]} {
2391 set ancestor($np) 1
2392 lappend todo $np
2393 if {$np eq $a} {
2394 set done 1
2398 if {$done} {
2399 set anc_todo [concat $leftover [lrange $todo [expr {$i+1}] end]]
2400 return
2403 set ancestor($a) 0
2404 set anc_todo $leftover
2407 proc askrelhighlight {row id} {
2408 global descendent highlight_related iddrawn mainfont rhighlights
2409 global selectedline ancestor
2411 if {![info exists selectedline]} return
2412 set isbold 0
2413 if {$highlight_related eq "Descendent" ||
2414 $highlight_related eq "Not descendent"} {
2415 if {![info exists descendent($id)]} {
2416 is_descendent $id
2418 if {$descendent($id) == ($highlight_related eq "Descendent")} {
2419 set isbold 1
2421 } elseif {$highlight_related eq "Ancestor" ||
2422 $highlight_related eq "Not ancestor"} {
2423 if {![info exists ancestor($id)]} {
2424 is_ancestor $id
2426 if {$ancestor($id) == ($highlight_related eq "Ancestor")} {
2427 set isbold 1
2430 if {[info exists iddrawn($id)]} {
2431 if {$isbold && ![ishighlighted $row]} {
2432 bolden $row [concat $mainfont bold]
2435 set rhighlights($row) $isbold
2438 proc next_hlcont {} {
2439 global fhl_row fhl_dirn displayorder numcommits
2440 global vhighlights fhighlights nhighlights rhighlights
2441 global hlview filehighlight findstring highlight_related
2443 if {![info exists fhl_dirn] || $fhl_dirn == 0} return
2444 set row $fhl_row
2445 while {1} {
2446 if {$row < 0 || $row >= $numcommits} {
2447 bell
2448 set fhl_dirn 0
2449 return
2451 set id [lindex $displayorder $row]
2452 if {[info exists hlview]} {
2453 if {![info exists vhighlights($row)]} {
2454 askvhighlight $row $id
2456 if {$vhighlights($row) > 0} break
2458 if {$findstring ne {}} {
2459 if {![info exists nhighlights($row)]} {
2460 askfindhighlight $row $id
2462 if {$nhighlights($row) > 0} break
2464 if {$highlight_related ne "None"} {
2465 if {![info exists rhighlights($row)]} {
2466 askrelhighlight $row $id
2468 if {$rhighlights($row) > 0} break
2470 if {[info exists filehighlight]} {
2471 if {![info exists fhighlights($row)]} {
2472 # ask for a few more while we're at it...
2473 set r $row
2474 for {set n 0} {$n < 100} {incr n} {
2475 if {![info exists fhighlights($r)]} {
2476 askfilehighlight $r [lindex $displayorder $r]
2478 incr r $fhl_dirn
2479 if {$r < 0 || $r >= $numcommits} break
2481 flushhighlights
2483 if {$fhighlights($row) < 0} {
2484 set fhl_row $row
2485 return
2487 if {$fhighlights($row) > 0} break
2489 incr row $fhl_dirn
2491 set fhl_dirn 0
2492 selectline $row 1
2495 proc next_highlight {dirn} {
2496 global selectedline fhl_row fhl_dirn
2497 global hlview filehighlight findstring highlight_related
2499 if {![info exists selectedline]} return
2500 if {!([info exists hlview] || $findstring ne {} ||
2501 $highlight_related ne "None" || [info exists filehighlight])} return
2502 set fhl_row [expr {$selectedline + $dirn}]
2503 set fhl_dirn $dirn
2504 next_hlcont
2507 proc cancel_next_highlight {} {
2508 global fhl_dirn
2510 set fhl_dirn 0
2513 # Graph layout functions
2515 proc shortids {ids} {
2516 set res {}
2517 foreach id $ids {
2518 if {[llength $id] > 1} {
2519 lappend res [shortids $id]
2520 } elseif {[regexp {^[0-9a-f]{40}$} $id]} {
2521 lappend res [string range $id 0 7]
2522 } else {
2523 lappend res $id
2526 return $res
2529 proc incrange {l x o} {
2530 set n [llength $l]
2531 while {$x < $n} {
2532 set e [lindex $l $x]
2533 if {$e ne {}} {
2534 lset l $x [expr {$e + $o}]
2536 incr x
2538 return $l
2541 proc ntimes {n o} {
2542 set ret {}
2543 for {} {$n > 0} {incr n -1} {
2544 lappend ret $o
2546 return $ret
2549 proc usedinrange {id l1 l2} {
2550 global children commitrow curview
2552 if {[info exists commitrow($curview,$id)]} {
2553 set r $commitrow($curview,$id)
2554 if {$l1 <= $r && $r <= $l2} {
2555 return [expr {$r - $l1 + 1}]
2558 set kids $children($curview,$id)
2559 foreach c $kids {
2560 set r $commitrow($curview,$c)
2561 if {$l1 <= $r && $r <= $l2} {
2562 return [expr {$r - $l1 + 1}]
2565 return 0
2568 proc sanity {row {full 0}} {
2569 global rowidlist rowoffsets
2571 set col -1
2572 set ids [lindex $rowidlist $row]
2573 foreach id $ids {
2574 incr col
2575 if {$id eq {}} continue
2576 if {$col < [llength $ids] - 1 &&
2577 [lsearch -exact -start [expr {$col+1}] $ids $id] >= 0} {
2578 puts "oops: [shortids $id] repeated in row $row col $col: {[shortids [lindex $rowidlist $row]]}"
2580 set o [lindex $rowoffsets $row $col]
2581 set y $row
2582 set x $col
2583 while {$o ne {}} {
2584 incr y -1
2585 incr x $o
2586 if {[lindex $rowidlist $y $x] != $id} {
2587 puts "oops: rowoffsets wrong at row [expr {$y+1}] col [expr {$x-$o}]"
2588 puts " id=[shortids $id] check started at row $row"
2589 for {set i $row} {$i >= $y} {incr i -1} {
2590 puts " row $i ids={[shortids [lindex $rowidlist $i]]} offs={[lindex $rowoffsets $i]}"
2592 break
2594 if {!$full} break
2595 set o [lindex $rowoffsets $y $x]
2600 proc makeuparrow {oid x y z} {
2601 global rowidlist rowoffsets uparrowlen idrowranges displayorder
2603 for {set i 1} {$i < $uparrowlen && $y > 1} {incr i} {
2604 incr y -1
2605 incr x $z
2606 set off0 [lindex $rowoffsets $y]
2607 for {set x0 $x} {1} {incr x0} {
2608 if {$x0 >= [llength $off0]} {
2609 set x0 [llength [lindex $rowoffsets [expr {$y-1}]]]
2610 break
2612 set z [lindex $off0 $x0]
2613 if {$z ne {}} {
2614 incr x0 $z
2615 break
2618 set z [expr {$x0 - $x}]
2619 lset rowidlist $y [linsert [lindex $rowidlist $y] $x $oid]
2620 lset rowoffsets $y [linsert [lindex $rowoffsets $y] $x $z]
2622 set tmp [lreplace [lindex $rowoffsets $y] $x $x {}]
2623 lset rowoffsets $y [incrange $tmp [expr {$x+1}] -1]
2624 lappend idrowranges($oid) [lindex $displayorder $y]
2627 proc initlayout {} {
2628 global rowidlist rowoffsets displayorder commitlisted
2629 global rowlaidout rowoptim
2630 global idinlist rowchk rowrangelist idrowranges
2631 global numcommits canvxmax canv
2632 global nextcolor
2633 global parentlist
2634 global colormap rowtextx
2635 global selectfirst
2637 set numcommits 0
2638 set displayorder {}
2639 set commitlisted {}
2640 set parentlist {}
2641 set rowrangelist {}
2642 set nextcolor 0
2643 set rowidlist {{}}
2644 set rowoffsets {{}}
2645 catch {unset idinlist}
2646 catch {unset rowchk}
2647 set rowlaidout 0
2648 set rowoptim 0
2649 set canvxmax [$canv cget -width]
2650 catch {unset colormap}
2651 catch {unset rowtextx}
2652 catch {unset idrowranges}
2653 set selectfirst 1
2656 proc setcanvscroll {} {
2657 global canv canv2 canv3 numcommits linespc canvxmax canvy0
2659 set ymax [expr {$canvy0 + ($numcommits - 0.5) * $linespc + 2}]
2660 $canv conf -scrollregion [list 0 0 $canvxmax $ymax]
2661 $canv2 conf -scrollregion [list 0 0 0 $ymax]
2662 $canv3 conf -scrollregion [list 0 0 0 $ymax]
2665 proc visiblerows {} {
2666 global canv numcommits linespc
2668 set ymax [lindex [$canv cget -scrollregion] 3]
2669 if {$ymax eq {} || $ymax == 0} return
2670 set f [$canv yview]
2671 set y0 [expr {int([lindex $f 0] * $ymax)}]
2672 set r0 [expr {int(($y0 - 3) / $linespc) - 1}]
2673 if {$r0 < 0} {
2674 set r0 0
2676 set y1 [expr {int([lindex $f 1] * $ymax)}]
2677 set r1 [expr {int(($y1 - 3) / $linespc) + 1}]
2678 if {$r1 >= $numcommits} {
2679 set r1 [expr {$numcommits - 1}]
2681 return [list $r0 $r1]
2684 proc layoutmore {tmax allread} {
2685 global rowlaidout rowoptim commitidx numcommits optim_delay
2686 global uparrowlen curview rowidlist idinlist
2688 set showlast 0
2689 set showdelay $optim_delay
2690 set optdelay [expr {$uparrowlen + 1}]
2691 while {1} {
2692 if {$rowoptim - $showdelay > $numcommits} {
2693 showstuff [expr {$rowoptim - $showdelay}] $showlast
2694 } elseif {$rowlaidout - $optdelay > $rowoptim} {
2695 set nr [expr {$rowlaidout - $optdelay - $rowoptim}]
2696 if {$nr > 100} {
2697 set nr 100
2699 optimize_rows $rowoptim 0 [expr {$rowoptim + $nr}]
2700 incr rowoptim $nr
2701 } elseif {$commitidx($curview) > $rowlaidout} {
2702 set nr [expr {$commitidx($curview) - $rowlaidout}]
2703 # may need to increase this threshold if uparrowlen or
2704 # mingaplen are increased...
2705 if {$nr > 150} {
2706 set nr 150
2708 set row $rowlaidout
2709 set rowlaidout [layoutrows $row [expr {$row + $nr}] $allread]
2710 if {$rowlaidout == $row} {
2711 return 0
2713 } elseif {$allread} {
2714 set optdelay 0
2715 set nrows $commitidx($curview)
2716 if {[lindex $rowidlist $nrows] ne {} ||
2717 [array names idinlist] ne {}} {
2718 layouttail
2719 set rowlaidout $commitidx($curview)
2720 } elseif {$rowoptim == $nrows} {
2721 set showdelay 0
2722 set showlast 1
2723 if {$numcommits == $nrows} {
2724 return 0
2727 } else {
2728 return 0
2730 if {$tmax ne {} && [clock clicks -milliseconds] >= $tmax} {
2731 return 1
2736 proc showstuff {canshow last} {
2737 global numcommits commitrow pending_select selectedline curview
2738 global lookingforhead mainheadid displayorder selectfirst
2739 global lastscrollset
2741 if {$numcommits == 0} {
2742 global phase
2743 set phase "incrdraw"
2744 allcanvs delete all
2746 set r0 $numcommits
2747 set prev $numcommits
2748 set numcommits $canshow
2749 set t [clock clicks -milliseconds]
2750 if {$prev < 100 || $last || $t - $lastscrollset > 500} {
2751 set lastscrollset $t
2752 setcanvscroll
2754 set rows [visiblerows]
2755 set r1 [lindex $rows 1]
2756 if {$r1 >= $canshow} {
2757 set r1 [expr {$canshow - 1}]
2759 if {$r0 <= $r1} {
2760 drawcommits $r0 $r1
2762 if {[info exists pending_select] &&
2763 [info exists commitrow($curview,$pending_select)] &&
2764 $commitrow($curview,$pending_select) < $numcommits} {
2765 selectline $commitrow($curview,$pending_select) 1
2767 if {$selectfirst} {
2768 if {[info exists selectedline] || [info exists pending_select]} {
2769 set selectfirst 0
2770 } else {
2771 set l [first_real_row]
2772 selectline $l 1
2773 set selectfirst 0
2776 if {$lookingforhead && [info exists commitrow($curview,$mainheadid)]
2777 && ($last || $commitrow($curview,$mainheadid) < $numcommits - 1)} {
2778 set lookingforhead 0
2779 dodiffindex
2783 proc doshowlocalchanges {} {
2784 global lookingforhead curview mainheadid phase commitrow
2786 if {[info exists commitrow($curview,$mainheadid)] &&
2787 ($phase eq {} || $commitrow($curview,$mainheadid) < $numcommits - 1)} {
2788 dodiffindex
2789 } elseif {$phase ne {}} {
2790 set lookingforhead 1
2794 proc dohidelocalchanges {} {
2795 global lookingforhead localfrow localirow lserial
2797 set lookingforhead 0
2798 if {$localfrow >= 0} {
2799 removerow $localfrow
2800 set localfrow -1
2801 if {$localirow > 0} {
2802 incr localirow -1
2805 if {$localirow >= 0} {
2806 removerow $localirow
2807 set localirow -1
2809 incr lserial
2812 # spawn off a process to do git diff-index --cached HEAD
2813 proc dodiffindex {} {
2814 global localirow localfrow lserial
2816 incr lserial
2817 set localfrow -1
2818 set localirow -1
2819 set fd [open "|git diff-index --cached HEAD" r]
2820 fconfigure $fd -blocking 0
2821 filerun $fd [list readdiffindex $fd $lserial]
2824 proc readdiffindex {fd serial} {
2825 global localirow commitrow mainheadid nullid2 curview
2826 global commitinfo commitdata lserial
2828 set isdiff 1
2829 if {[gets $fd line] < 0} {
2830 if {![eof $fd]} {
2831 return 1
2833 set isdiff 0
2835 # we only need to see one line and we don't really care what it says...
2836 close $fd
2838 # now see if there are any local changes not checked in to the index
2839 if {$serial == $lserial} {
2840 set fd [open "|git diff-files" r]
2841 fconfigure $fd -blocking 0
2842 filerun $fd [list readdifffiles $fd $serial]
2845 if {$isdiff && $serial == $lserial && $localirow == -1} {
2846 # add the line for the changes in the index to the graph
2847 set localirow $commitrow($curview,$mainheadid)
2848 set hl "Local changes checked in to index but not committed"
2849 set commitinfo($nullid2) [list $hl {} {} {} {} " $hl\n"]
2850 set commitdata($nullid2) "\n $hl\n"
2851 insertrow $localirow $nullid2
2853 return 0
2856 proc readdifffiles {fd serial} {
2857 global localirow localfrow commitrow mainheadid nullid curview
2858 global commitinfo commitdata lserial
2860 set isdiff 1
2861 if {[gets $fd line] < 0} {
2862 if {![eof $fd]} {
2863 return 1
2865 set isdiff 0
2867 # we only need to see one line and we don't really care what it says...
2868 close $fd
2870 if {$isdiff && $serial == $lserial && $localfrow == -1} {
2871 # add the line for the local diff to the graph
2872 if {$localirow >= 0} {
2873 set localfrow $localirow
2874 incr localirow
2875 } else {
2876 set localfrow $commitrow($curview,$mainheadid)
2878 set hl "Local uncommitted changes, not checked in to index"
2879 set commitinfo($nullid) [list $hl {} {} {} {} " $hl\n"]
2880 set commitdata($nullid) "\n $hl\n"
2881 insertrow $localfrow $nullid
2883 return 0
2886 proc layoutrows {row endrow last} {
2887 global rowidlist rowoffsets displayorder
2888 global uparrowlen downarrowlen maxwidth mingaplen
2889 global children parentlist
2890 global idrowranges
2891 global commitidx curview
2892 global idinlist rowchk rowrangelist
2894 set idlist [lindex $rowidlist $row]
2895 set offs [lindex $rowoffsets $row]
2896 while {$row < $endrow} {
2897 set id [lindex $displayorder $row]
2898 set oldolds {}
2899 set newolds {}
2900 foreach p [lindex $parentlist $row] {
2901 if {![info exists idinlist($p)]} {
2902 lappend newolds $p
2903 } elseif {!$idinlist($p)} {
2904 lappend oldolds $p
2906 set idinlist($p) 1
2908 set nev [expr {[llength $idlist] + [llength $newolds]
2909 + [llength $oldolds] - $maxwidth + 1}]
2910 if {$nev > 0} {
2911 if {!$last &&
2912 $row + $uparrowlen + $mingaplen >= $commitidx($curview)} break
2913 for {set x [llength $idlist]} {[incr x -1] >= 0} {} {
2914 set i [lindex $idlist $x]
2915 if {![info exists rowchk($i)] || $row >= $rowchk($i)} {
2916 set r [usedinrange $i [expr {$row - $downarrowlen}] \
2917 [expr {$row + $uparrowlen + $mingaplen}]]
2918 if {$r == 0} {
2919 set idlist [lreplace $idlist $x $x]
2920 set offs [lreplace $offs $x $x]
2921 set offs [incrange $offs $x 1]
2922 set idinlist($i) 0
2923 set rm1 [expr {$row - 1}]
2924 lappend idrowranges($i) [lindex $displayorder $rm1]
2925 if {[incr nev -1] <= 0} break
2926 continue
2928 set rowchk($id) [expr {$row + $r}]
2931 lset rowidlist $row $idlist
2932 lset rowoffsets $row $offs
2934 set col [lsearch -exact $idlist $id]
2935 if {$col < 0} {
2936 set col [llength $idlist]
2937 lappend idlist $id
2938 lset rowidlist $row $idlist
2939 set z {}
2940 if {$children($curview,$id) ne {}} {
2941 set z [expr {[llength [lindex $rowidlist [expr {$row-1}]]] - $col}]
2942 unset idinlist($id)
2944 lappend offs $z
2945 lset rowoffsets $row $offs
2946 if {$z ne {}} {
2947 makeuparrow $id $col $row $z
2949 } else {
2950 unset idinlist($id)
2952 set ranges {}
2953 if {[info exists idrowranges($id)]} {
2954 set ranges $idrowranges($id)
2955 lappend ranges $id
2956 unset idrowranges($id)
2958 lappend rowrangelist $ranges
2959 incr row
2960 set offs [ntimes [llength $idlist] 0]
2961 set l [llength $newolds]
2962 set idlist [eval lreplace \$idlist $col $col $newolds]
2963 set o 0
2964 if {$l != 1} {
2965 set offs [lrange $offs 0 [expr {$col - 1}]]
2966 foreach x $newolds {
2967 lappend offs {}
2968 incr o -1
2970 incr o
2971 set tmp [expr {[llength $idlist] - [llength $offs]}]
2972 if {$tmp > 0} {
2973 set offs [concat $offs [ntimes $tmp $o]]
2975 } else {
2976 lset offs $col {}
2978 foreach i $newolds {
2979 set idrowranges($i) $id
2981 incr col $l
2982 foreach oid $oldolds {
2983 set idlist [linsert $idlist $col $oid]
2984 set offs [linsert $offs $col $o]
2985 makeuparrow $oid $col $row $o
2986 incr col
2988 lappend rowidlist $idlist
2989 lappend rowoffsets $offs
2991 return $row
2994 proc addextraid {id row} {
2995 global displayorder commitrow commitinfo
2996 global commitidx commitlisted
2997 global parentlist children curview
2999 incr commitidx($curview)
3000 lappend displayorder $id
3001 lappend commitlisted 0
3002 lappend parentlist {}
3003 set commitrow($curview,$id) $row
3004 readcommit $id
3005 if {![info exists commitinfo($id)]} {
3006 set commitinfo($id) {"No commit information available"}
3008 if {![info exists children($curview,$id)]} {
3009 set children($curview,$id) {}
3013 proc layouttail {} {
3014 global rowidlist rowoffsets idinlist commitidx curview
3015 global idrowranges rowrangelist
3017 set row $commitidx($curview)
3018 set idlist [lindex $rowidlist $row]
3019 while {$idlist ne {}} {
3020 set col [expr {[llength $idlist] - 1}]
3021 set id [lindex $idlist $col]
3022 addextraid $id $row
3023 catch {unset idinlist($id)}
3024 lappend idrowranges($id) $id
3025 lappend rowrangelist $idrowranges($id)
3026 unset idrowranges($id)
3027 incr row
3028 set offs [ntimes $col 0]
3029 set idlist [lreplace $idlist $col $col]
3030 lappend rowidlist $idlist
3031 lappend rowoffsets $offs
3034 foreach id [array names idinlist] {
3035 unset idinlist($id)
3036 addextraid $id $row
3037 lset rowidlist $row [list $id]
3038 lset rowoffsets $row 0
3039 makeuparrow $id 0 $row 0
3040 lappend idrowranges($id) $id
3041 lappend rowrangelist $idrowranges($id)
3042 unset idrowranges($id)
3043 incr row
3044 lappend rowidlist {}
3045 lappend rowoffsets {}
3049 proc insert_pad {row col npad} {
3050 global rowidlist rowoffsets
3052 set pad [ntimes $npad {}]
3053 lset rowidlist $row [eval linsert [list [lindex $rowidlist $row]] $col $pad]
3054 set tmp [eval linsert [list [lindex $rowoffsets $row]] $col $pad]
3055 lset rowoffsets $row [incrange $tmp [expr {$col + $npad}] [expr {-$npad}]]
3058 proc optimize_rows {row col endrow} {
3059 global rowidlist rowoffsets displayorder
3061 for {} {$row < $endrow} {incr row} {
3062 set idlist [lindex $rowidlist $row]
3063 set offs [lindex $rowoffsets $row]
3064 set haspad 0
3065 for {} {$col < [llength $offs]} {incr col} {
3066 if {[lindex $idlist $col] eq {}} {
3067 set haspad 1
3068 continue
3070 set z [lindex $offs $col]
3071 if {$z eq {}} continue
3072 set isarrow 0
3073 set x0 [expr {$col + $z}]
3074 set y0 [expr {$row - 1}]
3075 set z0 [lindex $rowoffsets $y0 $x0]
3076 if {$z0 eq {}} {
3077 set id [lindex $idlist $col]
3078 set ranges [rowranges $id]
3079 if {$ranges ne {} && $y0 > [lindex $ranges 0]} {
3080 set isarrow 1
3083 # Looking at lines from this row to the previous row,
3084 # make them go straight up if they end in an arrow on
3085 # the previous row; otherwise make them go straight up
3086 # or at 45 degrees.
3087 if {$z < -1 || ($z < 0 && $isarrow)} {
3088 # Line currently goes left too much;
3089 # insert pads in the previous row, then optimize it
3090 set npad [expr {-1 - $z + $isarrow}]
3091 set offs [incrange $offs $col $npad]
3092 insert_pad $y0 $x0 $npad
3093 if {$y0 > 0} {
3094 optimize_rows $y0 $x0 $row
3096 set z [lindex $offs $col]
3097 set x0 [expr {$col + $z}]
3098 set z0 [lindex $rowoffsets $y0 $x0]
3099 } elseif {$z > 1 || ($z > 0 && $isarrow)} {
3100 # Line currently goes right too much;
3101 # insert pads in this line and adjust the next's rowoffsets
3102 set npad [expr {$z - 1 + $isarrow}]
3103 set y1 [expr {$row + 1}]
3104 set offs2 [lindex $rowoffsets $y1]
3105 set x1 -1
3106 foreach z $offs2 {
3107 incr x1
3108 if {$z eq {} || $x1 + $z < $col} continue
3109 if {$x1 + $z > $col} {
3110 incr npad
3112 lset rowoffsets $y1 [incrange $offs2 $x1 $npad]
3113 break
3115 set pad [ntimes $npad {}]
3116 set idlist [eval linsert \$idlist $col $pad]
3117 set tmp [eval linsert \$offs $col $pad]
3118 incr col $npad
3119 set offs [incrange $tmp $col [expr {-$npad}]]
3120 set z [lindex $offs $col]
3121 set haspad 1
3123 if {$z0 eq {} && !$isarrow} {
3124 # this line links to its first child on row $row-2
3125 set rm2 [expr {$row - 2}]
3126 set id [lindex $displayorder $rm2]
3127 set xc [lsearch -exact [lindex $rowidlist $rm2] $id]
3128 if {$xc >= 0} {
3129 set z0 [expr {$xc - $x0}]
3132 # avoid lines jigging left then immediately right
3133 if {$z0 ne {} && $z < 0 && $z0 > 0} {
3134 insert_pad $y0 $x0 1
3135 set offs [incrange $offs $col 1]
3136 optimize_rows $y0 [expr {$x0 + 1}] $row
3139 if {!$haspad} {
3140 set o {}
3141 # Find the first column that doesn't have a line going right
3142 for {set col [llength $idlist]} {[incr col -1] >= 0} {} {
3143 set o [lindex $offs $col]
3144 if {$o eq {}} {
3145 # check if this is the link to the first child
3146 set id [lindex $idlist $col]
3147 set ranges [rowranges $id]
3148 if {$ranges ne {} && $row == [lindex $ranges 0]} {
3149 # it is, work out offset to child
3150 set y0 [expr {$row - 1}]
3151 set id [lindex $displayorder $y0]
3152 set x0 [lsearch -exact [lindex $rowidlist $y0] $id]
3153 if {$x0 >= 0} {
3154 set o [expr {$x0 - $col}]
3158 if {$o eq {} || $o <= 0} break
3160 # Insert a pad at that column as long as it has a line and
3161 # isn't the last column, and adjust the next row' offsets
3162 if {$o ne {} && [incr col] < [llength $idlist]} {
3163 set y1 [expr {$row + 1}]
3164 set offs2 [lindex $rowoffsets $y1]
3165 set x1 -1
3166 foreach z $offs2 {
3167 incr x1
3168 if {$z eq {} || $x1 + $z < $col} continue
3169 lset rowoffsets $y1 [incrange $offs2 $x1 1]
3170 break
3172 set idlist [linsert $idlist $col {}]
3173 set tmp [linsert $offs $col {}]
3174 incr col
3175 set offs [incrange $tmp $col -1]
3178 lset rowidlist $row $idlist
3179 lset rowoffsets $row $offs
3180 set col 0
3184 proc xc {row col} {
3185 global canvx0 linespc
3186 return [expr {$canvx0 + $col * $linespc}]
3189 proc yc {row} {
3190 global canvy0 linespc
3191 return [expr {$canvy0 + $row * $linespc}]
3194 proc linewidth {id} {
3195 global thickerline lthickness
3197 set wid $lthickness
3198 if {[info exists thickerline] && $id eq $thickerline} {
3199 set wid [expr {2 * $lthickness}]
3201 return $wid
3204 proc rowranges {id} {
3205 global phase idrowranges commitrow rowlaidout rowrangelist curview
3207 set ranges {}
3208 if {$phase eq {} ||
3209 ([info exists commitrow($curview,$id)]
3210 && $commitrow($curview,$id) < $rowlaidout)} {
3211 set ranges [lindex $rowrangelist $commitrow($curview,$id)]
3212 } elseif {[info exists idrowranges($id)]} {
3213 set ranges $idrowranges($id)
3215 set linenos {}
3216 foreach rid $ranges {
3217 lappend linenos $commitrow($curview,$rid)
3219 if {$linenos ne {}} {
3220 lset linenos 0 [expr {[lindex $linenos 0] + 1}]
3222 return $linenos
3225 # work around tk8.4 refusal to draw arrows on diagonal segments
3226 proc adjarrowhigh {coords} {
3227 global linespc
3229 set x0 [lindex $coords 0]
3230 set x1 [lindex $coords 2]
3231 if {$x0 != $x1} {
3232 set y0 [lindex $coords 1]
3233 set y1 [lindex $coords 3]
3234 if {$y0 - $y1 <= 2 * $linespc && $x1 == [lindex $coords 4]} {
3235 # we have a nearby vertical segment, just trim off the diag bit
3236 set coords [lrange $coords 2 end]
3237 } else {
3238 set slope [expr {($x0 - $x1) / ($y0 - $y1)}]
3239 set xi [expr {$x0 - $slope * $linespc / 2}]
3240 set yi [expr {$y0 - $linespc / 2}]
3241 set coords [lreplace $coords 0 1 $xi $y0 $xi $yi]
3244 return $coords
3247 proc drawlineseg {id row endrow arrowlow} {
3248 global rowidlist displayorder iddrawn linesegs
3249 global canv colormap linespc curview maxlinelen
3251 set cols [list [lsearch -exact [lindex $rowidlist $row] $id]]
3252 set le [expr {$row + 1}]
3253 set arrowhigh 1
3254 while {1} {
3255 set c [lsearch -exact [lindex $rowidlist $le] $id]
3256 if {$c < 0} {
3257 incr le -1
3258 break
3260 lappend cols $c
3261 set x [lindex $displayorder $le]
3262 if {$x eq $id} {
3263 set arrowhigh 0
3264 break
3266 if {[info exists iddrawn($x)] || $le == $endrow} {
3267 set c [lsearch -exact [lindex $rowidlist [expr {$le+1}]] $id]
3268 if {$c >= 0} {
3269 lappend cols $c
3270 set arrowhigh 0
3272 break
3274 incr le
3276 if {$le <= $row} {
3277 return $row
3280 set lines {}
3281 set i 0
3282 set joinhigh 0
3283 if {[info exists linesegs($id)]} {
3284 set lines $linesegs($id)
3285 foreach li $lines {
3286 set r0 [lindex $li 0]
3287 if {$r0 > $row} {
3288 if {$r0 == $le && [lindex $li 1] - $row <= $maxlinelen} {
3289 set joinhigh 1
3291 break
3293 incr i
3296 set joinlow 0
3297 if {$i > 0} {
3298 set li [lindex $lines [expr {$i-1}]]
3299 set r1 [lindex $li 1]
3300 if {$r1 == $row && $le - [lindex $li 0] <= $maxlinelen} {
3301 set joinlow 1
3305 set x [lindex $cols [expr {$le - $row}]]
3306 set xp [lindex $cols [expr {$le - 1 - $row}]]
3307 set dir [expr {$xp - $x}]
3308 if {$joinhigh} {
3309 set ith [lindex $lines $i 2]
3310 set coords [$canv coords $ith]
3311 set ah [$canv itemcget $ith -arrow]
3312 set arrowhigh [expr {$ah eq "first" || $ah eq "both"}]
3313 set x2 [lindex $cols [expr {$le + 1 - $row}]]
3314 if {$x2 ne {} && $x - $x2 == $dir} {
3315 set coords [lrange $coords 0 end-2]
3317 } else {
3318 set coords [list [xc $le $x] [yc $le]]
3320 if {$joinlow} {
3321 set itl [lindex $lines [expr {$i-1}] 2]
3322 set al [$canv itemcget $itl -arrow]
3323 set arrowlow [expr {$al eq "last" || $al eq "both"}]
3324 } elseif {$arrowlow &&
3325 [lsearch -exact [lindex $rowidlist [expr {$row-1}]] $id] >= 0} {
3326 set arrowlow 0
3328 set arrow [lindex {none first last both} [expr {$arrowhigh + 2*$arrowlow}]]
3329 for {set y $le} {[incr y -1] > $row} {} {
3330 set x $xp
3331 set xp [lindex $cols [expr {$y - 1 - $row}]]
3332 set ndir [expr {$xp - $x}]
3333 if {$dir != $ndir || $xp < 0} {
3334 lappend coords [xc $y $x] [yc $y]
3336 set dir $ndir
3338 if {!$joinlow} {
3339 if {$xp < 0} {
3340 # join parent line to first child
3341 set ch [lindex $displayorder $row]
3342 set xc [lsearch -exact [lindex $rowidlist $row] $ch]
3343 if {$xc < 0} {
3344 puts "oops: drawlineseg: child $ch not on row $row"
3345 } else {
3346 if {$xc < $x - 1} {
3347 lappend coords [xc $row [expr {$x-1}]] [yc $row]
3348 } elseif {$xc > $x + 1} {
3349 lappend coords [xc $row [expr {$x+1}]] [yc $row]
3351 set x $xc
3353 lappend coords [xc $row $x] [yc $row]
3354 } else {
3355 set xn [xc $row $xp]
3356 set yn [yc $row]
3357 # work around tk8.4 refusal to draw arrows on diagonal segments
3358 if {$arrowlow && $xn != [lindex $coords end-1]} {
3359 if {[llength $coords] < 4 ||
3360 [lindex $coords end-3] != [lindex $coords end-1] ||
3361 [lindex $coords end] - $yn > 2 * $linespc} {
3362 set xn [xc $row [expr {$xp - 0.5 * $dir}]]
3363 set yo [yc [expr {$row + 0.5}]]
3364 lappend coords $xn $yo $xn $yn
3366 } else {
3367 lappend coords $xn $yn
3370 if {!$joinhigh} {
3371 if {$arrowhigh} {
3372 set coords [adjarrowhigh $coords]
3374 assigncolor $id
3375 set t [$canv create line $coords -width [linewidth $id] \
3376 -fill $colormap($id) -tags lines.$id -arrow $arrow]
3377 $canv lower $t
3378 bindline $t $id
3379 set lines [linsert $lines $i [list $row $le $t]]
3380 } else {
3381 $canv coords $ith $coords
3382 if {$arrow ne $ah} {
3383 $canv itemconf $ith -arrow $arrow
3385 lset lines $i 0 $row
3387 } else {
3388 set xo [lsearch -exact [lindex $rowidlist [expr {$row - 1}]] $id]
3389 set ndir [expr {$xo - $xp}]
3390 set clow [$canv coords $itl]
3391 if {$dir == $ndir} {
3392 set clow [lrange $clow 2 end]
3394 set coords [concat $coords $clow]
3395 if {!$joinhigh} {
3396 lset lines [expr {$i-1}] 1 $le
3397 if {$arrowhigh} {
3398 set coords [adjarrowhigh $coords]
3400 } else {
3401 # coalesce two pieces
3402 $canv delete $ith
3403 set b [lindex $lines [expr {$i-1}] 0]
3404 set e [lindex $lines $i 1]
3405 set lines [lreplace $lines [expr {$i-1}] $i [list $b $e $itl]]
3407 $canv coords $itl $coords
3408 if {$arrow ne $al} {
3409 $canv itemconf $itl -arrow $arrow
3413 set linesegs($id) $lines
3414 return $le
3417 proc drawparentlinks {id row} {
3418 global rowidlist canv colormap curview parentlist
3419 global idpos
3421 set rowids [lindex $rowidlist $row]
3422 set col [lsearch -exact $rowids $id]
3423 if {$col < 0} return
3424 set olds [lindex $parentlist $row]
3425 set row2 [expr {$row + 1}]
3426 set x [xc $row $col]
3427 set y [yc $row]
3428 set y2 [yc $row2]
3429 set ids [lindex $rowidlist $row2]
3430 # rmx = right-most X coord used
3431 set rmx 0
3432 foreach p $olds {
3433 set i [lsearch -exact $ids $p]
3434 if {$i < 0} {
3435 puts "oops, parent $p of $id not in list"
3436 continue
3438 set x2 [xc $row2 $i]
3439 if {$x2 > $rmx} {
3440 set rmx $x2
3442 if {[lsearch -exact $rowids $p] < 0} {
3443 # drawlineseg will do this one for us
3444 continue
3446 assigncolor $p
3447 # should handle duplicated parents here...
3448 set coords [list $x $y]
3449 if {$i < $col - 1} {
3450 lappend coords [xc $row [expr {$i + 1}]] $y
3451 } elseif {$i > $col + 1} {
3452 lappend coords [xc $row [expr {$i - 1}]] $y
3454 lappend coords $x2 $y2
3455 set t [$canv create line $coords -width [linewidth $p] \
3456 -fill $colormap($p) -tags lines.$p]
3457 $canv lower $t
3458 bindline $t $p
3460 if {$rmx > [lindex $idpos($id) 1]} {
3461 lset idpos($id) 1 $rmx
3462 redrawtags $id
3466 proc drawlines {id} {
3467 global canv
3469 $canv itemconf lines.$id -width [linewidth $id]
3472 proc drawcmittext {id row col} {
3473 global linespc canv canv2 canv3 canvy0 fgcolor curview
3474 global commitlisted commitinfo rowidlist parentlist
3475 global rowtextx idpos idtags idheads idotherrefs
3476 global linehtag linentag linedtag
3477 global mainfont canvxmax boldrows boldnamerows fgcolor nullid nullid2
3479 # listed is 0 for boundary, 1 for normal, 2 for left, 3 for right
3480 set listed [lindex $commitlisted $row]
3481 if {$id eq $nullid} {
3482 set ofill red
3483 } elseif {$id eq $nullid2} {
3484 set ofill green
3485 } else {
3486 set ofill [expr {$listed != 0? "blue": "white"}]
3488 set x [xc $row $col]
3489 set y [yc $row]
3490 set orad [expr {$linespc / 3}]
3491 if {$listed <= 1} {
3492 set t [$canv create oval [expr {$x - $orad}] [expr {$y - $orad}] \
3493 [expr {$x + $orad - 1}] [expr {$y + $orad - 1}] \
3494 -fill $ofill -outline $fgcolor -width 1 -tags circle]
3495 } elseif {$listed == 2} {
3496 # triangle pointing left for left-side commits
3497 set t [$canv create polygon \
3498 [expr {$x - $orad}] $y \
3499 [expr {$x + $orad - 1}] [expr {$y - $orad}] \
3500 [expr {$x + $orad - 1}] [expr {$y + $orad - 1}] \
3501 -fill $ofill -outline $fgcolor -width 1 -tags circle]
3502 } else {
3503 # triangle pointing right for right-side commits
3504 set t [$canv create polygon \
3505 [expr {$x + $orad - 1}] $y \
3506 [expr {$x - $orad}] [expr {$y - $orad}] \
3507 [expr {$x - $orad}] [expr {$y + $orad - 1}] \
3508 -fill $ofill -outline $fgcolor -width 1 -tags circle]
3510 $canv raise $t
3511 $canv bind $t <1> {selcanvline {} %x %y}
3512 set rmx [llength [lindex $rowidlist $row]]
3513 set olds [lindex $parentlist $row]
3514 if {$olds ne {}} {
3515 set nextids [lindex $rowidlist [expr {$row + 1}]]
3516 foreach p $olds {
3517 set i [lsearch -exact $nextids $p]
3518 if {$i > $rmx} {
3519 set rmx $i
3523 set xt [xc $row $rmx]
3524 set rowtextx($row) $xt
3525 set idpos($id) [list $x $xt $y]
3526 if {[info exists idtags($id)] || [info exists idheads($id)]
3527 || [info exists idotherrefs($id)]} {
3528 set xt [drawtags $id $x $xt $y]
3530 set headline [lindex $commitinfo($id) 0]
3531 set name [lindex $commitinfo($id) 1]
3532 set date [lindex $commitinfo($id) 2]
3533 set date [formatdate $date]
3534 set font $mainfont
3535 set nfont $mainfont
3536 set isbold [ishighlighted $row]
3537 if {$isbold > 0} {
3538 lappend boldrows $row
3539 lappend font bold
3540 if {$isbold > 1} {
3541 lappend boldnamerows $row
3542 lappend nfont bold
3545 set linehtag($row) [$canv create text $xt $y -anchor w -fill $fgcolor \
3546 -text $headline -font $font -tags text]
3547 $canv bind $linehtag($row) <Button-3> "rowmenu %X %Y $id"
3548 set linentag($row) [$canv2 create text 3 $y -anchor w -fill $fgcolor \
3549 -text $name -font $nfont -tags text]
3550 set linedtag($row) [$canv3 create text 3 $y -anchor w -fill $fgcolor \
3551 -text $date -font $mainfont -tags text]
3552 set xr [expr {$xt + [font measure $mainfont $headline]}]
3553 if {$xr > $canvxmax} {
3554 set canvxmax $xr
3555 setcanvscroll
3559 proc drawcmitrow {row} {
3560 global displayorder rowidlist
3561 global iddrawn markingmatches
3562 global commitinfo parentlist numcommits
3563 global filehighlight fhighlights findstring nhighlights
3564 global hlview vhighlights
3565 global highlight_related rhighlights
3567 if {$row >= $numcommits} return
3569 set id [lindex $displayorder $row]
3570 if {[info exists hlview] && ![info exists vhighlights($row)]} {
3571 askvhighlight $row $id
3573 if {[info exists filehighlight] && ![info exists fhighlights($row)]} {
3574 askfilehighlight $row $id
3576 if {$findstring ne {} && ![info exists nhighlights($row)]} {
3577 askfindhighlight $row $id
3579 if {$highlight_related ne "None" && ![info exists rhighlights($row)]} {
3580 askrelhighlight $row $id
3582 if {![info exists iddrawn($id)]} {
3583 set col [lsearch -exact [lindex $rowidlist $row] $id]
3584 if {$col < 0} {
3585 puts "oops, row $row id $id not in list"
3586 return
3588 if {![info exists commitinfo($id)]} {
3589 getcommit $id
3591 assigncolor $id
3592 drawcmittext $id $row $col
3593 set iddrawn($id) 1
3595 if {$markingmatches} {
3596 markrowmatches $row $id
3600 proc drawcommits {row {endrow {}}} {
3601 global numcommits iddrawn displayorder curview
3602 global parentlist rowidlist
3604 if {$row < 0} {
3605 set row 0
3607 if {$endrow eq {}} {
3608 set endrow $row
3610 if {$endrow >= $numcommits} {
3611 set endrow [expr {$numcommits - 1}]
3614 # make the lines join to already-drawn rows either side
3615 set r [expr {$row - 1}]
3616 if {$r < 0 || ![info exists iddrawn([lindex $displayorder $r])]} {
3617 set r $row
3619 set er [expr {$endrow + 1}]
3620 if {$er >= $numcommits ||
3621 ![info exists iddrawn([lindex $displayorder $er])]} {
3622 set er $endrow
3624 for {} {$r <= $er} {incr r} {
3625 set id [lindex $displayorder $r]
3626 set wasdrawn [info exists iddrawn($id)]
3627 drawcmitrow $r
3628 if {$r == $er} break
3629 set nextid [lindex $displayorder [expr {$r + 1}]]
3630 if {$wasdrawn && [info exists iddrawn($nextid)]} {
3631 catch {unset prevlines}
3632 continue
3634 drawparentlinks $id $r
3636 if {[info exists lineends($r)]} {
3637 foreach lid $lineends($r) {
3638 unset prevlines($lid)
3641 set rowids [lindex $rowidlist $r]
3642 foreach lid $rowids {
3643 if {$lid eq {}} continue
3644 if {$lid eq $id} {
3645 # see if this is the first child of any of its parents
3646 foreach p [lindex $parentlist $r] {
3647 if {[lsearch -exact $rowids $p] < 0} {
3648 # make this line extend up to the child
3649 set le [drawlineseg $p $r $er 0]
3650 lappend lineends($le) $p
3651 set prevlines($p) 1
3654 } elseif {![info exists prevlines($lid)]} {
3655 set le [drawlineseg $lid $r $er 1]
3656 lappend lineends($le) $lid
3657 set prevlines($lid) 1
3663 proc drawfrac {f0 f1} {
3664 global canv linespc
3666 set ymax [lindex [$canv cget -scrollregion] 3]
3667 if {$ymax eq {} || $ymax == 0} return
3668 set y0 [expr {int($f0 * $ymax)}]
3669 set row [expr {int(($y0 - 3) / $linespc) - 1}]
3670 set y1 [expr {int($f1 * $ymax)}]
3671 set endrow [expr {int(($y1 - 3) / $linespc) + 1}]
3672 drawcommits $row $endrow
3675 proc drawvisible {} {
3676 global canv
3677 eval drawfrac [$canv yview]
3680 proc clear_display {} {
3681 global iddrawn linesegs
3682 global vhighlights fhighlights nhighlights rhighlights
3684 allcanvs delete all
3685 catch {unset iddrawn}
3686 catch {unset linesegs}
3687 catch {unset vhighlights}
3688 catch {unset fhighlights}
3689 catch {unset nhighlights}
3690 catch {unset rhighlights}
3693 proc findcrossings {id} {
3694 global rowidlist parentlist numcommits rowoffsets displayorder
3696 set cross {}
3697 set ccross {}
3698 foreach {s e} [rowranges $id] {
3699 if {$e >= $numcommits} {
3700 set e [expr {$numcommits - 1}]
3702 if {$e <= $s} continue
3703 set x [lsearch -exact [lindex $rowidlist $e] $id]
3704 if {$x < 0} {
3705 puts "findcrossings: oops, no [shortids $id] in row $e"
3706 continue
3708 for {set row $e} {[incr row -1] >= $s} {} {
3709 set olds [lindex $parentlist $row]
3710 set kid [lindex $displayorder $row]
3711 set kidx [lsearch -exact [lindex $rowidlist $row] $kid]
3712 if {$kidx < 0} continue
3713 set nextrow [lindex $rowidlist [expr {$row + 1}]]
3714 foreach p $olds {
3715 set px [lsearch -exact $nextrow $p]
3716 if {$px < 0} continue
3717 if {($kidx < $x && $x < $px) || ($px < $x && $x < $kidx)} {
3718 if {[lsearch -exact $ccross $p] >= 0} continue
3719 if {$x == $px + ($kidx < $px? -1: 1)} {
3720 lappend ccross $p
3721 } elseif {[lsearch -exact $cross $p] < 0} {
3722 lappend cross $p
3726 set inc [lindex $rowoffsets $row $x]
3727 if {$inc eq {}} break
3728 incr x $inc
3731 return [concat $ccross {{}} $cross]
3734 proc assigncolor {id} {
3735 global colormap colors nextcolor
3736 global commitrow parentlist children children curview
3738 if {[info exists colormap($id)]} return
3739 set ncolors [llength $colors]
3740 if {[info exists children($curview,$id)]} {
3741 set kids $children($curview,$id)
3742 } else {
3743 set kids {}
3745 if {[llength $kids] == 1} {
3746 set child [lindex $kids 0]
3747 if {[info exists colormap($child)]
3748 && [llength [lindex $parentlist $commitrow($curview,$child)]] == 1} {
3749 set colormap($id) $colormap($child)
3750 return
3753 set badcolors {}
3754 set origbad {}
3755 foreach x [findcrossings $id] {
3756 if {$x eq {}} {
3757 # delimiter between corner crossings and other crossings
3758 if {[llength $badcolors] >= $ncolors - 1} break
3759 set origbad $badcolors
3761 if {[info exists colormap($x)]
3762 && [lsearch -exact $badcolors $colormap($x)] < 0} {
3763 lappend badcolors $colormap($x)
3766 if {[llength $badcolors] >= $ncolors} {
3767 set badcolors $origbad
3769 set origbad $badcolors
3770 if {[llength $badcolors] < $ncolors - 1} {
3771 foreach child $kids {
3772 if {[info exists colormap($child)]
3773 && [lsearch -exact $badcolors $colormap($child)] < 0} {
3774 lappend badcolors $colormap($child)
3776 foreach p [lindex $parentlist $commitrow($curview,$child)] {
3777 if {[info exists colormap($p)]
3778 && [lsearch -exact $badcolors $colormap($p)] < 0} {
3779 lappend badcolors $colormap($p)
3783 if {[llength $badcolors] >= $ncolors} {
3784 set badcolors $origbad
3787 for {set i 0} {$i <= $ncolors} {incr i} {
3788 set c [lindex $colors $nextcolor]
3789 if {[incr nextcolor] >= $ncolors} {
3790 set nextcolor 0
3792 if {[lsearch -exact $badcolors $c]} break
3794 set colormap($id) $c
3797 proc bindline {t id} {
3798 global canv
3800 $canv bind $t <Enter> "lineenter %x %y $id"
3801 $canv bind $t <Motion> "linemotion %x %y $id"
3802 $canv bind $t <Leave> "lineleave $id"
3803 $canv bind $t <Button-1> "lineclick %x %y $id 1"
3806 proc drawtags {id x xt y1} {
3807 global idtags idheads idotherrefs mainhead
3808 global linespc lthickness
3809 global canv mainfont commitrow rowtextx curview fgcolor bgcolor
3811 set marks {}
3812 set ntags 0
3813 set nheads 0
3814 if {[info exists idtags($id)]} {
3815 set marks $idtags($id)
3816 set ntags [llength $marks]
3818 if {[info exists idheads($id)]} {
3819 set marks [concat $marks $idheads($id)]
3820 set nheads [llength $idheads($id)]
3822 if {[info exists idotherrefs($id)]} {
3823 set marks [concat $marks $idotherrefs($id)]
3825 if {$marks eq {}} {
3826 return $xt
3829 set delta [expr {int(0.5 * ($linespc - $lthickness))}]
3830 set yt [expr {$y1 - 0.5 * $linespc}]
3831 set yb [expr {$yt + $linespc - 1}]
3832 set xvals {}
3833 set wvals {}
3834 set i -1
3835 foreach tag $marks {
3836 incr i
3837 if {$i >= $ntags && $i < $ntags + $nheads && $tag eq $mainhead} {
3838 set wid [font measure [concat $mainfont bold] $tag]
3839 } else {
3840 set wid [font measure $mainfont $tag]
3842 lappend xvals $xt
3843 lappend wvals $wid
3844 set xt [expr {$xt + $delta + $wid + $lthickness + $linespc}]
3846 set t [$canv create line $x $y1 [lindex $xvals end] $y1 \
3847 -width $lthickness -fill black -tags tag.$id]
3848 $canv lower $t
3849 foreach tag $marks x $xvals wid $wvals {
3850 set xl [expr {$x + $delta}]
3851 set xr [expr {$x + $delta + $wid + $lthickness}]
3852 set font $mainfont
3853 if {[incr ntags -1] >= 0} {
3854 # draw a tag
3855 set t [$canv create polygon $x [expr {$yt + $delta}] $xl $yt \
3856 $xr $yt $xr $yb $xl $yb $x [expr {$yb - $delta}] \
3857 -width 1 -outline black -fill yellow -tags tag.$id]
3858 $canv bind $t <1> [list showtag $tag 1]
3859 set rowtextx($commitrow($curview,$id)) [expr {$xr + $linespc}]
3860 } else {
3861 # draw a head or other ref
3862 if {[incr nheads -1] >= 0} {
3863 set col green
3864 if {$tag eq $mainhead} {
3865 lappend font bold
3867 } else {
3868 set col "#ddddff"
3870 set xl [expr {$xl - $delta/2}]
3871 $canv create polygon $x $yt $xr $yt $xr $yb $x $yb \
3872 -width 1 -outline black -fill $col -tags tag.$id
3873 if {[regexp {^(remotes/.*/|remotes/)} $tag match remoteprefix]} {
3874 set rwid [font measure $mainfont $remoteprefix]
3875 set xi [expr {$x + 1}]
3876 set yti [expr {$yt + 1}]
3877 set xri [expr {$x + $rwid}]
3878 $canv create polygon $xi $yti $xri $yti $xri $yb $xi $yb \
3879 -width 0 -fill "#ffddaa" -tags tag.$id
3882 set t [$canv create text $xl $y1 -anchor w -text $tag -fill $fgcolor \
3883 -font $font -tags [list tag.$id text]]
3884 if {$ntags >= 0} {
3885 $canv bind $t <1> [list showtag $tag 1]
3886 } elseif {$nheads >= 0} {
3887 $canv bind $t <Button-3> [list headmenu %X %Y $id $tag]
3890 return $xt
3893 proc xcoord {i level ln} {
3894 global canvx0 xspc1 xspc2
3896 set x [expr {$canvx0 + $i * $xspc1($ln)}]
3897 if {$i > 0 && $i == $level} {
3898 set x [expr {$x + 0.5 * ($xspc2 - $xspc1($ln))}]
3899 } elseif {$i > $level} {
3900 set x [expr {$x + $xspc2 - $xspc1($ln)}]
3902 return $x
3905 proc show_status {msg} {
3906 global canv mainfont fgcolor
3908 clear_display
3909 $canv create text 3 3 -anchor nw -text $msg -font $mainfont \
3910 -tags text -fill $fgcolor
3913 # Insert a new commit as the child of the commit on row $row.
3914 # The new commit will be displayed on row $row and the commits
3915 # on that row and below will move down one row.
3916 proc insertrow {row newcmit} {
3917 global displayorder parentlist commitlisted children
3918 global commitrow curview rowidlist rowoffsets numcommits
3919 global rowrangelist rowlaidout rowoptim numcommits
3920 global selectedline rowchk commitidx
3922 if {$row >= $numcommits} {
3923 puts "oops, inserting new row $row but only have $numcommits rows"
3924 return
3926 set p [lindex $displayorder $row]
3927 set displayorder [linsert $displayorder $row $newcmit]
3928 set parentlist [linsert $parentlist $row $p]
3929 set kids $children($curview,$p)
3930 lappend kids $newcmit
3931 set children($curview,$p) $kids
3932 set children($curview,$newcmit) {}
3933 set commitlisted [linsert $commitlisted $row 1]
3934 set l [llength $displayorder]
3935 for {set r $row} {$r < $l} {incr r} {
3936 set id [lindex $displayorder $r]
3937 set commitrow($curview,$id) $r
3939 incr commitidx($curview)
3941 set idlist [lindex $rowidlist $row]
3942 set offs [lindex $rowoffsets $row]
3943 set newoffs {}
3944 foreach x $idlist {
3945 if {$x eq {} || ($x eq $p && [llength $kids] == 1)} {
3946 lappend newoffs {}
3947 } else {
3948 lappend newoffs 0
3951 if {[llength $kids] == 1} {
3952 set col [lsearch -exact $idlist $p]
3953 lset idlist $col $newcmit
3954 } else {
3955 set col [llength $idlist]
3956 lappend idlist $newcmit
3957 lappend offs {}
3958 lset rowoffsets $row $offs
3960 set rowidlist [linsert $rowidlist $row $idlist]
3961 set rowoffsets [linsert $rowoffsets [expr {$row+1}] $newoffs]
3963 set rowrangelist [linsert $rowrangelist $row {}]
3964 if {[llength $kids] > 1} {
3965 set rp1 [expr {$row + 1}]
3966 set ranges [lindex $rowrangelist $rp1]
3967 if {$ranges eq {}} {
3968 set ranges [list $newcmit $p]
3969 } elseif {[lindex $ranges end-1] eq $p} {
3970 lset ranges end-1 $newcmit
3972 lset rowrangelist $rp1 $ranges
3975 catch {unset rowchk}
3977 incr rowlaidout
3978 incr rowoptim
3979 incr numcommits
3981 if {[info exists selectedline] && $selectedline >= $row} {
3982 incr selectedline
3984 redisplay
3987 # Remove a commit that was inserted with insertrow on row $row.
3988 proc removerow {row} {
3989 global displayorder parentlist commitlisted children
3990 global commitrow curview rowidlist rowoffsets numcommits
3991 global rowrangelist idrowranges rowlaidout rowoptim numcommits
3992 global linesegends selectedline rowchk commitidx
3994 if {$row >= $numcommits} {
3995 puts "oops, removing row $row but only have $numcommits rows"
3996 return
3998 set rp1 [expr {$row + 1}]
3999 set id [lindex $displayorder $row]
4000 set p [lindex $parentlist $row]
4001 set displayorder [lreplace $displayorder $row $row]
4002 set parentlist [lreplace $parentlist $row $row]
4003 set commitlisted [lreplace $commitlisted $row $row]
4004 set kids $children($curview,$p)
4005 set i [lsearch -exact $kids $id]
4006 if {$i >= 0} {
4007 set kids [lreplace $kids $i $i]
4008 set children($curview,$p) $kids
4010 set l [llength $displayorder]
4011 for {set r $row} {$r < $l} {incr r} {
4012 set id [lindex $displayorder $r]
4013 set commitrow($curview,$id) $r
4015 incr commitidx($curview) -1
4017 set rowidlist [lreplace $rowidlist $row $row]
4018 set rowoffsets [lreplace $rowoffsets $rp1 $rp1]
4019 if {$kids ne {}} {
4020 set offs [lindex $rowoffsets $row]
4021 set offs [lreplace $offs end end]
4022 lset rowoffsets $row $offs
4025 set rowrangelist [lreplace $rowrangelist $row $row]
4026 if {[llength $kids] > 0} {
4027 set ranges [lindex $rowrangelist $row]
4028 if {[lindex $ranges end-1] eq $id} {
4029 set ranges [lreplace $ranges end-1 end]
4030 lset rowrangelist $row $ranges
4034 catch {unset rowchk}
4036 incr rowlaidout -1
4037 incr rowoptim -1
4038 incr numcommits -1
4040 if {[info exists selectedline] && $selectedline > $row} {
4041 incr selectedline -1
4043 redisplay
4046 # Don't change the text pane cursor if it is currently the hand cursor,
4047 # showing that we are over a sha1 ID link.
4048 proc settextcursor {c} {
4049 global ctext curtextcursor
4051 if {[$ctext cget -cursor] == $curtextcursor} {
4052 $ctext config -cursor $c
4054 set curtextcursor $c
4057 proc nowbusy {what} {
4058 global isbusy
4060 if {[array names isbusy] eq {}} {
4061 . config -cursor watch
4062 settextcursor watch
4064 set isbusy($what) 1
4067 proc notbusy {what} {
4068 global isbusy maincursor textcursor
4070 catch {unset isbusy($what)}
4071 if {[array names isbusy] eq {}} {
4072 . config -cursor $maincursor
4073 settextcursor $textcursor
4077 proc findmatches {f} {
4078 global findtype findstring
4079 if {$findtype == "Regexp"} {
4080 set matches [regexp -indices -all -inline $findstring $f]
4081 } else {
4082 set fs $findstring
4083 if {$findtype == "IgnCase"} {
4084 set f [string tolower $f]
4085 set fs [string tolower $fs]
4087 set matches {}
4088 set i 0
4089 set l [string length $fs]
4090 while {[set j [string first $fs $f $i]] >= 0} {
4091 lappend matches [list $j [expr {$j+$l-1}]]
4092 set i [expr {$j + $l}]
4095 return $matches
4098 proc dofind {{rev 0}} {
4099 global findstring findstartline findcurline selectedline numcommits
4101 unmarkmatches
4102 cancel_next_highlight
4103 focus .
4104 if {$findstring eq {} || $numcommits == 0} return
4105 if {![info exists selectedline]} {
4106 set findstartline [lindex [visiblerows] $rev]
4107 } else {
4108 set findstartline $selectedline
4110 set findcurline $findstartline
4111 nowbusy finding
4112 if {!$rev} {
4113 run findmore
4114 } else {
4115 if {$findcurline == 0} {
4116 set findcurline $numcommits
4118 incr findcurline -1
4119 run findmorerev
4123 proc findnext {restart} {
4124 global findcurline
4125 if {![info exists findcurline]} {
4126 if {$restart} {
4127 dofind
4128 } else {
4129 bell
4131 } else {
4132 run findmore
4133 nowbusy finding
4137 proc findprev {} {
4138 global findcurline
4139 if {![info exists findcurline]} {
4140 dofind 1
4141 } else {
4142 run findmorerev
4143 nowbusy finding
4147 proc findmore {} {
4148 global commitdata commitinfo numcommits findstring findpattern findloc
4149 global findstartline findcurline displayorder
4151 set fldtypes {Headline Author Date Committer CDate Comments}
4152 set l [expr {$findcurline + 1}]
4153 if {$l >= $numcommits} {
4154 set l 0
4156 if {$l <= $findstartline} {
4157 set lim [expr {$findstartline + 1}]
4158 } else {
4159 set lim $numcommits
4161 if {$lim - $l > 500} {
4162 set lim [expr {$l + 500}]
4164 set last 0
4165 for {} {$l < $lim} {incr l} {
4166 set id [lindex $displayorder $l]
4167 # shouldn't happen unless git log doesn't give all the commits...
4168 if {![info exists commitdata($id)]} continue
4169 if {![doesmatch $commitdata($id)]} continue
4170 if {![info exists commitinfo($id)]} {
4171 getcommit $id
4173 set info $commitinfo($id)
4174 foreach f $info ty $fldtypes {
4175 if {($findloc eq "All fields" || $findloc eq $ty) &&
4176 [doesmatch $f]} {
4177 findselectline $l
4178 notbusy finding
4179 return 0
4183 if {$l == $findstartline + 1} {
4184 bell
4185 unset findcurline
4186 notbusy finding
4187 return 0
4189 set findcurline [expr {$l - 1}]
4190 return 1
4193 proc findmorerev {} {
4194 global commitdata commitinfo numcommits findstring findpattern findloc
4195 global findstartline findcurline displayorder
4197 set fldtypes {Headline Author Date Committer CDate Comments}
4198 set l $findcurline
4199 if {$l == 0} {
4200 set l $numcommits
4202 incr l -1
4203 if {$l >= $findstartline} {
4204 set lim [expr {$findstartline - 1}]
4205 } else {
4206 set lim -1
4208 if {$l - $lim > 500} {
4209 set lim [expr {$l - 500}]
4211 set last 0
4212 for {} {$l > $lim} {incr l -1} {
4213 set id [lindex $displayorder $l]
4214 if {![doesmatch $commitdata($id)]} continue
4215 if {![info exists commitinfo($id)]} {
4216 getcommit $id
4218 set info $commitinfo($id)
4219 foreach f $info ty $fldtypes {
4220 if {($findloc eq "All fields" || $findloc eq $ty) &&
4221 [doesmatch $f]} {
4222 findselectline $l
4223 notbusy finding
4224 return 0
4228 if {$l == -1} {
4229 bell
4230 unset findcurline
4231 notbusy finding
4232 return 0
4234 set findcurline [expr {$l + 1}]
4235 return 1
4238 proc findselectline {l} {
4239 global findloc commentend ctext findcurline markingmatches
4241 set markingmatches 1
4242 set findcurline $l
4243 selectline $l 1
4244 if {$findloc == "All fields" || $findloc == "Comments"} {
4245 # highlight the matches in the comments
4246 set f [$ctext get 1.0 $commentend]
4247 set matches [findmatches $f]
4248 foreach match $matches {
4249 set start [lindex $match 0]
4250 set end [expr {[lindex $match 1] + 1}]
4251 $ctext tag add found "1.0 + $start c" "1.0 + $end c"
4254 drawvisible
4257 # mark the bits of a headline or author that match a find string
4258 proc markmatches {canv l str tag matches font row} {
4259 global selectedline
4261 set bbox [$canv bbox $tag]
4262 set x0 [lindex $bbox 0]
4263 set y0 [lindex $bbox 1]
4264 set y1 [lindex $bbox 3]
4265 foreach match $matches {
4266 set start [lindex $match 0]
4267 set end [lindex $match 1]
4268 if {$start > $end} continue
4269 set xoff [font measure $font [string range $str 0 [expr {$start-1}]]]
4270 set xlen [font measure $font [string range $str 0 [expr {$end}]]]
4271 set t [$canv create rect [expr {$x0+$xoff}] $y0 \
4272 [expr {$x0+$xlen+2}] $y1 \
4273 -outline {} -tags [list match$l matches] -fill yellow]
4274 $canv lower $t
4275 if {[info exists selectedline] && $row == $selectedline} {
4276 $canv raise $t secsel
4281 proc unmarkmatches {} {
4282 global findids markingmatches findcurline
4284 allcanvs delete matches
4285 catch {unset findids}
4286 set markingmatches 0
4287 catch {unset findcurline}
4290 proc selcanvline {w x y} {
4291 global canv canvy0 ctext linespc
4292 global rowtextx
4293 set ymax [lindex [$canv cget -scrollregion] 3]
4294 if {$ymax == {}} return
4295 set yfrac [lindex [$canv yview] 0]
4296 set y [expr {$y + $yfrac * $ymax}]
4297 set l [expr {int(($y - $canvy0) / $linespc + 0.5)}]
4298 if {$l < 0} {
4299 set l 0
4301 if {$w eq $canv} {
4302 if {![info exists rowtextx($l)] || $x < $rowtextx($l)} return
4304 unmarkmatches
4305 selectline $l 1
4308 proc commit_descriptor {p} {
4309 global commitinfo
4310 if {![info exists commitinfo($p)]} {
4311 getcommit $p
4313 set l "..."
4314 if {[llength $commitinfo($p)] > 1} {
4315 set l [lindex $commitinfo($p) 0]
4317 return "$p ($l)\n"
4320 # append some text to the ctext widget, and make any SHA1 ID
4321 # that we know about be a clickable link.
4322 proc appendwithlinks {text tags} {
4323 global ctext commitrow linknum curview
4325 set start [$ctext index "end - 1c"]
4326 $ctext insert end $text $tags
4327 set links [regexp -indices -all -inline {[0-9a-f]{40}} $text]
4328 foreach l $links {
4329 set s [lindex $l 0]
4330 set e [lindex $l 1]
4331 set linkid [string range $text $s $e]
4332 if {![info exists commitrow($curview,$linkid)]} continue
4333 incr e
4334 $ctext tag add link "$start + $s c" "$start + $e c"
4335 $ctext tag add link$linknum "$start + $s c" "$start + $e c"
4336 $ctext tag bind link$linknum <1> \
4337 [list selectline $commitrow($curview,$linkid) 1]
4338 incr linknum
4340 $ctext tag conf link -foreground blue -underline 1
4341 $ctext tag bind link <Enter> { %W configure -cursor hand2 }
4342 $ctext tag bind link <Leave> { %W configure -cursor $curtextcursor }
4345 proc viewnextline {dir} {
4346 global canv linespc
4348 $canv delete hover
4349 set ymax [lindex [$canv cget -scrollregion] 3]
4350 set wnow [$canv yview]
4351 set wtop [expr {[lindex $wnow 0] * $ymax}]
4352 set newtop [expr {$wtop + $dir * $linespc}]
4353 if {$newtop < 0} {
4354 set newtop 0
4355 } elseif {$newtop > $ymax} {
4356 set newtop $ymax
4358 allcanvs yview moveto [expr {$newtop * 1.0 / $ymax}]
4361 # add a list of tag or branch names at position pos
4362 # returns the number of names inserted
4363 proc appendrefs {pos ids var} {
4364 global ctext commitrow linknum curview $var maxrefs
4366 if {[catch {$ctext index $pos}]} {
4367 return 0
4369 $ctext conf -state normal
4370 $ctext delete $pos "$pos lineend"
4371 set tags {}
4372 foreach id $ids {
4373 foreach tag [set $var\($id\)] {
4374 lappend tags [list $tag $id]
4377 if {[llength $tags] > $maxrefs} {
4378 $ctext insert $pos "many ([llength $tags])"
4379 } else {
4380 set tags [lsort -index 0 -decreasing $tags]
4381 set sep {}
4382 foreach ti $tags {
4383 set id [lindex $ti 1]
4384 set lk link$linknum
4385 incr linknum
4386 $ctext tag delete $lk
4387 $ctext insert $pos $sep
4388 $ctext insert $pos [lindex $ti 0] $lk
4389 if {[info exists commitrow($curview,$id)]} {
4390 $ctext tag conf $lk -foreground blue
4391 $ctext tag bind $lk <1> \
4392 [list selectline $commitrow($curview,$id) 1]
4393 $ctext tag conf $lk -underline 1
4394 $ctext tag bind $lk <Enter> { %W configure -cursor hand2 }
4395 $ctext tag bind $lk <Leave> \
4396 { %W configure -cursor $curtextcursor }
4398 set sep ", "
4401 $ctext conf -state disabled
4402 return [llength $tags]
4405 # called when we have finished computing the nearby tags
4406 proc dispneartags {delay} {
4407 global selectedline currentid showneartags tagphase
4409 if {![info exists selectedline] || !$showneartags} return
4410 after cancel dispnexttag
4411 if {$delay} {
4412 after 200 dispnexttag
4413 set tagphase -1
4414 } else {
4415 after idle dispnexttag
4416 set tagphase 0
4420 proc dispnexttag {} {
4421 global selectedline currentid showneartags tagphase ctext
4423 if {![info exists selectedline] || !$showneartags} return
4424 switch -- $tagphase {
4426 set dtags [desctags $currentid]
4427 if {$dtags ne {}} {
4428 appendrefs precedes $dtags idtags
4432 set atags [anctags $currentid]
4433 if {$atags ne {}} {
4434 appendrefs follows $atags idtags
4438 set dheads [descheads $currentid]
4439 if {$dheads ne {}} {
4440 if {[appendrefs branch $dheads idheads] > 1
4441 && [$ctext get "branch -3c"] eq "h"} {
4442 # turn "Branch" into "Branches"
4443 $ctext conf -state normal
4444 $ctext insert "branch -2c" "es"
4445 $ctext conf -state disabled
4450 if {[incr tagphase] <= 2} {
4451 after idle dispnexttag
4455 proc selectline {l isnew} {
4456 global canv canv2 canv3 ctext commitinfo selectedline
4457 global displayorder linehtag linentag linedtag
4458 global canvy0 linespc parentlist children curview
4459 global currentid sha1entry
4460 global commentend idtags linknum
4461 global mergemax numcommits pending_select
4462 global cmitmode showneartags allcommits
4464 catch {unset pending_select}
4465 $canv delete hover
4466 normalline
4467 cancel_next_highlight
4468 if {$l < 0 || $l >= $numcommits} return
4469 set y [expr {$canvy0 + $l * $linespc}]
4470 set ymax [lindex [$canv cget -scrollregion] 3]
4471 set ytop [expr {$y - $linespc - 1}]
4472 set ybot [expr {$y + $linespc + 1}]
4473 set wnow [$canv yview]
4474 set wtop [expr {[lindex $wnow 0] * $ymax}]
4475 set wbot [expr {[lindex $wnow 1] * $ymax}]
4476 set wh [expr {$wbot - $wtop}]
4477 set newtop $wtop
4478 if {$ytop < $wtop} {
4479 if {$ybot < $wtop} {
4480 set newtop [expr {$y - $wh / 2.0}]
4481 } else {
4482 set newtop $ytop
4483 if {$newtop > $wtop - $linespc} {
4484 set newtop [expr {$wtop - $linespc}]
4487 } elseif {$ybot > $wbot} {
4488 if {$ytop > $wbot} {
4489 set newtop [expr {$y - $wh / 2.0}]
4490 } else {
4491 set newtop [expr {$ybot - $wh}]
4492 if {$newtop < $wtop + $linespc} {
4493 set newtop [expr {$wtop + $linespc}]
4497 if {$newtop != $wtop} {
4498 if {$newtop < 0} {
4499 set newtop 0
4501 allcanvs yview moveto [expr {$newtop * 1.0 / $ymax}]
4502 drawvisible
4505 if {![info exists linehtag($l)]} return
4506 $canv delete secsel
4507 set t [eval $canv create rect [$canv bbox $linehtag($l)] -outline {{}} \
4508 -tags secsel -fill [$canv cget -selectbackground]]
4509 $canv lower $t
4510 $canv2 delete secsel
4511 set t [eval $canv2 create rect [$canv2 bbox $linentag($l)] -outline {{}} \
4512 -tags secsel -fill [$canv2 cget -selectbackground]]
4513 $canv2 lower $t
4514 $canv3 delete secsel
4515 set t [eval $canv3 create rect [$canv3 bbox $linedtag($l)] -outline {{}} \
4516 -tags secsel -fill [$canv3 cget -selectbackground]]
4517 $canv3 lower $t
4519 if {$isnew} {
4520 addtohistory [list selectline $l 0]
4523 set selectedline $l
4525 set id [lindex $displayorder $l]
4526 set currentid $id
4527 $sha1entry delete 0 end
4528 $sha1entry insert 0 $id
4529 $sha1entry selection from 0
4530 $sha1entry selection to end
4531 rhighlight_sel $id
4533 $ctext conf -state normal
4534 clear_ctext
4535 set linknum 0
4536 set info $commitinfo($id)
4537 set date [formatdate [lindex $info 2]]
4538 $ctext insert end "Author: [lindex $info 1] $date\n"
4539 set date [formatdate [lindex $info 4]]
4540 $ctext insert end "Committer: [lindex $info 3] $date\n"
4541 if {[info exists idtags($id)]} {
4542 $ctext insert end "Tags:"
4543 foreach tag $idtags($id) {
4544 $ctext insert end " $tag"
4546 $ctext insert end "\n"
4549 set headers {}
4550 set olds [lindex $parentlist $l]
4551 if {[llength $olds] > 1} {
4552 set np 0
4553 foreach p $olds {
4554 if {$np >= $mergemax} {
4555 set tag mmax
4556 } else {
4557 set tag m$np
4559 $ctext insert end "Parent: " $tag
4560 appendwithlinks [commit_descriptor $p] {}
4561 incr np
4563 } else {
4564 foreach p $olds {
4565 append headers "Parent: [commit_descriptor $p]"
4569 foreach c $children($curview,$id) {
4570 append headers "Child: [commit_descriptor $c]"
4573 # make anything that looks like a SHA1 ID be a clickable link
4574 appendwithlinks $headers {}
4575 if {$showneartags} {
4576 if {![info exists allcommits]} {
4577 getallcommits
4579 $ctext insert end "Branch: "
4580 $ctext mark set branch "end -1c"
4581 $ctext mark gravity branch left
4582 $ctext insert end "\nFollows: "
4583 $ctext mark set follows "end -1c"
4584 $ctext mark gravity follows left
4585 $ctext insert end "\nPrecedes: "
4586 $ctext mark set precedes "end -1c"
4587 $ctext mark gravity precedes left
4588 $ctext insert end "\n"
4589 dispneartags 1
4591 $ctext insert end "\n"
4592 set comment [lindex $info 5]
4593 if {[string first "\r" $comment] >= 0} {
4594 set comment [string map {"\r" "\n "} $comment]
4596 appendwithlinks $comment {comment}
4598 $ctext tag remove found 1.0 end
4599 $ctext conf -state disabled
4600 set commentend [$ctext index "end - 1c"]
4602 init_flist "Comments"
4603 if {$cmitmode eq "tree"} {
4604 gettree $id
4605 } elseif {[llength $olds] <= 1} {
4606 startdiff $id
4607 } else {
4608 mergediff $id $l
4612 proc selfirstline {} {
4613 unmarkmatches
4614 selectline 0 1
4617 proc sellastline {} {
4618 global numcommits
4619 unmarkmatches
4620 set l [expr {$numcommits - 1}]
4621 selectline $l 1
4624 proc selnextline {dir} {
4625 global selectedline
4626 focus .
4627 if {![info exists selectedline]} return
4628 set l [expr {$selectedline + $dir}]
4629 unmarkmatches
4630 selectline $l 1
4633 proc selnextpage {dir} {
4634 global canv linespc selectedline numcommits
4636 set lpp [expr {([winfo height $canv] - 2) / $linespc}]
4637 if {$lpp < 1} {
4638 set lpp 1
4640 allcanvs yview scroll [expr {$dir * $lpp}] units
4641 drawvisible
4642 if {![info exists selectedline]} return
4643 set l [expr {$selectedline + $dir * $lpp}]
4644 if {$l < 0} {
4645 set l 0
4646 } elseif {$l >= $numcommits} {
4647 set l [expr $numcommits - 1]
4649 unmarkmatches
4650 selectline $l 1
4653 proc unselectline {} {
4654 global selectedline currentid
4656 catch {unset selectedline}
4657 catch {unset currentid}
4658 allcanvs delete secsel
4659 rhighlight_none
4660 cancel_next_highlight
4663 proc reselectline {} {
4664 global selectedline
4666 if {[info exists selectedline]} {
4667 selectline $selectedline 0
4671 proc addtohistory {cmd} {
4672 global history historyindex curview
4674 set elt [list $curview $cmd]
4675 if {$historyindex > 0
4676 && [lindex $history [expr {$historyindex - 1}]] == $elt} {
4677 return
4680 if {$historyindex < [llength $history]} {
4681 set history [lreplace $history $historyindex end $elt]
4682 } else {
4683 lappend history $elt
4685 incr historyindex
4686 if {$historyindex > 1} {
4687 .tf.bar.leftbut conf -state normal
4688 } else {
4689 .tf.bar.leftbut conf -state disabled
4691 .tf.bar.rightbut conf -state disabled
4694 proc godo {elt} {
4695 global curview
4697 set view [lindex $elt 0]
4698 set cmd [lindex $elt 1]
4699 if {$curview != $view} {
4700 showview $view
4702 eval $cmd
4705 proc goback {} {
4706 global history historyindex
4707 focus .
4709 if {$historyindex > 1} {
4710 incr historyindex -1
4711 godo [lindex $history [expr {$historyindex - 1}]]
4712 .tf.bar.rightbut conf -state normal
4714 if {$historyindex <= 1} {
4715 .tf.bar.leftbut conf -state disabled
4719 proc goforw {} {
4720 global history historyindex
4721 focus .
4723 if {$historyindex < [llength $history]} {
4724 set cmd [lindex $history $historyindex]
4725 incr historyindex
4726 godo $cmd
4727 .tf.bar.leftbut conf -state normal
4729 if {$historyindex >= [llength $history]} {
4730 .tf.bar.rightbut conf -state disabled
4734 proc gettree {id} {
4735 global treefilelist treeidlist diffids diffmergeid treepending
4736 global nullid nullid2
4738 set diffids $id
4739 catch {unset diffmergeid}
4740 if {![info exists treefilelist($id)]} {
4741 if {![info exists treepending]} {
4742 if {$id eq $nullid} {
4743 set cmd [list | git ls-files]
4744 } elseif {$id eq $nullid2} {
4745 set cmd [list | git ls-files --stage -t]
4746 } else {
4747 set cmd [list | git ls-tree -r $id]
4749 if {[catch {set gtf [open $cmd r]}]} {
4750 return
4752 set treepending $id
4753 set treefilelist($id) {}
4754 set treeidlist($id) {}
4755 fconfigure $gtf -blocking 0
4756 filerun $gtf [list gettreeline $gtf $id]
4758 } else {
4759 setfilelist $id
4763 proc gettreeline {gtf id} {
4764 global treefilelist treeidlist treepending cmitmode diffids nullid nullid2
4766 set nl 0
4767 while {[incr nl] <= 1000 && [gets $gtf line] >= 0} {
4768 if {$diffids eq $nullid} {
4769 set fname $line
4770 } else {
4771 if {$diffids ne $nullid2 && [lindex $line 1] ne "blob"} continue
4772 set i [string first "\t" $line]
4773 if {$i < 0} continue
4774 set sha1 [lindex $line 2]
4775 set fname [string range $line [expr {$i+1}] end]
4776 if {[string index $fname 0] eq "\""} {
4777 set fname [lindex $fname 0]
4779 lappend treeidlist($id) $sha1
4781 lappend treefilelist($id) $fname
4783 if {![eof $gtf]} {
4784 return [expr {$nl >= 1000? 2: 1}]
4786 close $gtf
4787 unset treepending
4788 if {$cmitmode ne "tree"} {
4789 if {![info exists diffmergeid]} {
4790 gettreediffs $diffids
4792 } elseif {$id ne $diffids} {
4793 gettree $diffids
4794 } else {
4795 setfilelist $id
4797 return 0
4800 proc showfile {f} {
4801 global treefilelist treeidlist diffids nullid nullid2
4802 global ctext commentend
4804 set i [lsearch -exact $treefilelist($diffids) $f]
4805 if {$i < 0} {
4806 puts "oops, $f not in list for id $diffids"
4807 return
4809 if {$diffids eq $nullid} {
4810 if {[catch {set bf [open $f r]} err]} {
4811 puts "oops, can't read $f: $err"
4812 return
4814 } else {
4815 set blob [lindex $treeidlist($diffids) $i]
4816 if {[catch {set bf [open [concat | git cat-file blob $blob] r]} err]} {
4817 puts "oops, error reading blob $blob: $err"
4818 return
4821 fconfigure $bf -blocking 0
4822 filerun $bf [list getblobline $bf $diffids]
4823 $ctext config -state normal
4824 clear_ctext $commentend
4825 $ctext insert end "\n"
4826 $ctext insert end "$f\n" filesep
4827 $ctext config -state disabled
4828 $ctext yview $commentend
4831 proc getblobline {bf id} {
4832 global diffids cmitmode ctext
4834 if {$id ne $diffids || $cmitmode ne "tree"} {
4835 catch {close $bf}
4836 return 0
4838 $ctext config -state normal
4839 set nl 0
4840 while {[incr nl] <= 1000 && [gets $bf line] >= 0} {
4841 $ctext insert end "$line\n"
4843 if {[eof $bf]} {
4844 # delete last newline
4845 $ctext delete "end - 2c" "end - 1c"
4846 close $bf
4847 return 0
4849 $ctext config -state disabled
4850 return [expr {$nl >= 1000? 2: 1}]
4853 proc mergediff {id l} {
4854 global diffmergeid diffopts mdifffd
4855 global diffids
4856 global parentlist
4858 set diffmergeid $id
4859 set diffids $id
4860 # this doesn't seem to actually affect anything...
4861 set env(GIT_DIFF_OPTS) $diffopts
4862 set cmd [concat | git diff-tree --no-commit-id --cc $id]
4863 if {[catch {set mdf [open $cmd r]} err]} {
4864 error_popup "Error getting merge diffs: $err"
4865 return
4867 fconfigure $mdf -blocking 0
4868 set mdifffd($id) $mdf
4869 set np [llength [lindex $parentlist $l]]
4870 filerun $mdf [list getmergediffline $mdf $id $np]
4873 proc getmergediffline {mdf id np} {
4874 global diffmergeid ctext cflist mergemax
4875 global difffilestart mdifffd
4877 $ctext conf -state normal
4878 set nr 0
4879 while {[incr nr] <= 1000 && [gets $mdf line] >= 0} {
4880 if {![info exists diffmergeid] || $id != $diffmergeid
4881 || $mdf != $mdifffd($id)} {
4882 close $mdf
4883 return 0
4885 if {[regexp {^diff --cc (.*)} $line match fname]} {
4886 # start of a new file
4887 $ctext insert end "\n"
4888 set here [$ctext index "end - 1c"]
4889 lappend difffilestart $here
4890 add_flist [list $fname]
4891 set l [expr {(78 - [string length $fname]) / 2}]
4892 set pad [string range "----------------------------------------" 1 $l]
4893 $ctext insert end "$pad $fname $pad\n" filesep
4894 } elseif {[regexp {^@@} $line]} {
4895 $ctext insert end "$line\n" hunksep
4896 } elseif {[regexp {^[0-9a-f]{40}$} $line] || [regexp {^index} $line]} {
4897 # do nothing
4898 } else {
4899 # parse the prefix - one ' ', '-' or '+' for each parent
4900 set spaces {}
4901 set minuses {}
4902 set pluses {}
4903 set isbad 0
4904 for {set j 0} {$j < $np} {incr j} {
4905 set c [string range $line $j $j]
4906 if {$c == " "} {
4907 lappend spaces $j
4908 } elseif {$c == "-"} {
4909 lappend minuses $j
4910 } elseif {$c == "+"} {
4911 lappend pluses $j
4912 } else {
4913 set isbad 1
4914 break
4917 set tags {}
4918 set num {}
4919 if {!$isbad && $minuses ne {} && $pluses eq {}} {
4920 # line doesn't appear in result, parents in $minuses have the line
4921 set num [lindex $minuses 0]
4922 } elseif {!$isbad && $pluses ne {} && $minuses eq {}} {
4923 # line appears in result, parents in $pluses don't have the line
4924 lappend tags mresult
4925 set num [lindex $spaces 0]
4927 if {$num ne {}} {
4928 if {$num >= $mergemax} {
4929 set num "max"
4931 lappend tags m$num
4933 $ctext insert end "$line\n" $tags
4936 $ctext conf -state disabled
4937 if {[eof $mdf]} {
4938 close $mdf
4939 return 0
4941 return [expr {$nr >= 1000? 2: 1}]
4944 proc startdiff {ids} {
4945 global treediffs diffids treepending diffmergeid nullid nullid2
4947 set diffids $ids
4948 catch {unset diffmergeid}
4949 if {![info exists treediffs($ids)] ||
4950 [lsearch -exact $ids $nullid] >= 0 ||
4951 [lsearch -exact $ids $nullid2] >= 0} {
4952 if {![info exists treepending]} {
4953 gettreediffs $ids
4955 } else {
4956 addtocflist $ids
4960 proc addtocflist {ids} {
4961 global treediffs cflist
4962 add_flist $treediffs($ids)
4963 getblobdiffs $ids
4966 proc diffcmd {ids flags} {
4967 global nullid nullid2
4969 set i [lsearch -exact $ids $nullid]
4970 set j [lsearch -exact $ids $nullid2]
4971 if {$i >= 0} {
4972 if {[llength $ids] > 1 && $j < 0} {
4973 # comparing working directory with some specific revision
4974 set cmd [concat | git diff-index $flags]
4975 if {$i == 0} {
4976 lappend cmd -R [lindex $ids 1]
4977 } else {
4978 lappend cmd [lindex $ids 0]
4980 } else {
4981 # comparing working directory with index
4982 set cmd [concat | git diff-files $flags]
4983 if {$j == 1} {
4984 lappend cmd -R
4987 } elseif {$j >= 0} {
4988 set cmd [concat | git diff-index --cached $flags]
4989 if {[llength $ids] > 1} {
4990 # comparing index with specific revision
4991 if {$i == 0} {
4992 lappend cmd -R [lindex $ids 1]
4993 } else {
4994 lappend cmd [lindex $ids 0]
4996 } else {
4997 # comparing index with HEAD
4998 lappend cmd HEAD
5000 } else {
5001 set cmd [concat | git diff-tree -r $flags $ids]
5003 return $cmd
5006 proc gettreediffs {ids} {
5007 global treediff treepending
5009 set treepending $ids
5010 set treediff {}
5011 if {[catch {set gdtf [open [diffcmd $ids {--no-commit-id}] r]}]} return
5012 fconfigure $gdtf -blocking 0
5013 filerun $gdtf [list gettreediffline $gdtf $ids]
5016 proc gettreediffline {gdtf ids} {
5017 global treediff treediffs treepending diffids diffmergeid
5018 global cmitmode
5020 set nr 0
5021 while {[incr nr] <= 1000 && [gets $gdtf line] >= 0} {
5022 set i [string first "\t" $line]
5023 if {$i >= 0} {
5024 set file [string range $line [expr {$i+1}] end]
5025 if {[string index $file 0] eq "\""} {
5026 set file [lindex $file 0]
5028 lappend treediff $file
5031 if {![eof $gdtf]} {
5032 return [expr {$nr >= 1000? 2: 1}]
5034 close $gdtf
5035 set treediffs($ids) $treediff
5036 unset treepending
5037 if {$cmitmode eq "tree"} {
5038 gettree $diffids
5039 } elseif {$ids != $diffids} {
5040 if {![info exists diffmergeid]} {
5041 gettreediffs $diffids
5043 } else {
5044 addtocflist $ids
5046 return 0
5049 proc getblobdiffs {ids} {
5050 global diffopts blobdifffd diffids env
5051 global diffinhdr treediffs
5053 set env(GIT_DIFF_OPTS) $diffopts
5054 if {[catch {set bdf [open [diffcmd $ids {-p -C --no-commit-id}] r]} err]} {
5055 puts "error getting diffs: $err"
5056 return
5058 set diffinhdr 0
5059 fconfigure $bdf -blocking 0
5060 set blobdifffd($ids) $bdf
5061 filerun $bdf [list getblobdiffline $bdf $diffids]
5064 proc setinlist {var i val} {
5065 global $var
5067 while {[llength [set $var]] < $i} {
5068 lappend $var {}
5070 if {[llength [set $var]] == $i} {
5071 lappend $var $val
5072 } else {
5073 lset $var $i $val
5077 proc makediffhdr {fname ids} {
5078 global ctext curdiffstart treediffs
5080 set i [lsearch -exact $treediffs($ids) $fname]
5081 if {$i >= 0} {
5082 setinlist difffilestart $i $curdiffstart
5084 set l [expr {(78 - [string length $fname]) / 2}]
5085 set pad [string range "----------------------------------------" 1 $l]
5086 $ctext insert $curdiffstart "$pad $fname $pad" filesep
5089 proc getblobdiffline {bdf ids} {
5090 global diffids blobdifffd ctext curdiffstart
5091 global diffnexthead diffnextnote difffilestart
5092 global diffinhdr treediffs
5094 set nr 0
5095 $ctext conf -state normal
5096 while {[incr nr] <= 1000 && [gets $bdf line] >= 0} {
5097 if {$ids != $diffids || $bdf != $blobdifffd($ids)} {
5098 close $bdf
5099 return 0
5101 if {![string compare -length 11 "diff --git " $line]} {
5102 # trim off "diff --git "
5103 set line [string range $line 11 end]
5104 set diffinhdr 1
5105 # start of a new file
5106 $ctext insert end "\n"
5107 set curdiffstart [$ctext index "end - 1c"]
5108 $ctext insert end "\n" filesep
5109 # If the name hasn't changed the length will be odd,
5110 # the middle char will be a space, and the two bits either
5111 # side will be a/name and b/name, or "a/name" and "b/name".
5112 # If the name has changed we'll get "rename from" and
5113 # "rename to" lines following this, and we'll use them
5114 # to get the filenames.
5115 # This complexity is necessary because spaces in the filename(s)
5116 # don't get escaped.
5117 set l [string length $line]
5118 set i [expr {$l / 2}]
5119 if {!(($l & 1) && [string index $line $i] eq " " &&
5120 [string range $line 2 [expr {$i - 1}]] eq \
5121 [string range $line [expr {$i + 3}] end])} {
5122 continue
5124 # unescape if quoted and chop off the a/ from the front
5125 if {[string index $line 0] eq "\""} {
5126 set fname [string range [lindex $line 0] 2 end]
5127 } else {
5128 set fname [string range $line 2 [expr {$i - 1}]]
5130 makediffhdr $fname $ids
5132 } elseif {[regexp {^@@ -([0-9]+)(,[0-9]+)? \+([0-9]+)(,[0-9]+)? @@(.*)} \
5133 $line match f1l f1c f2l f2c rest]} {
5134 $ctext insert end "$line\n" hunksep
5135 set diffinhdr 0
5137 } elseif {$diffinhdr} {
5138 if {![string compare -length 12 "rename from " $line]} {
5139 set fname [string range $line 12 end]
5140 if {[string index $fname 0] eq "\""} {
5141 set fname [lindex $fname 0]
5143 set i [lsearch -exact $treediffs($ids) $fname]
5144 if {$i >= 0} {
5145 setinlist difffilestart $i $curdiffstart
5147 } elseif {![string compare -length 10 $line "rename to "]} {
5148 set fname [string range $line 10 end]
5149 if {[string index $fname 0] eq "\""} {
5150 set fname [lindex $fname 0]
5152 makediffhdr $fname $ids
5153 } elseif {[string compare -length 3 $line "---"] == 0} {
5154 # do nothing
5155 continue
5156 } elseif {[string compare -length 3 $line "+++"] == 0} {
5157 set diffinhdr 0
5158 continue
5160 $ctext insert end "$line\n" filesep
5162 } else {
5163 set x [string range $line 0 0]
5164 if {$x == "-" || $x == "+"} {
5165 set tag [expr {$x == "+"}]
5166 $ctext insert end "$line\n" d$tag
5167 } elseif {$x == " "} {
5168 $ctext insert end "$line\n"
5169 } else {
5170 # "\ No newline at end of file",
5171 # or something else we don't recognize
5172 $ctext insert end "$line\n" hunksep
5176 $ctext conf -state disabled
5177 if {[eof $bdf]} {
5178 close $bdf
5179 return 0
5181 return [expr {$nr >= 1000? 2: 1}]
5184 proc changediffdisp {} {
5185 global ctext diffelide
5187 $ctext tag conf d0 -elide [lindex $diffelide 0]
5188 $ctext tag conf d1 -elide [lindex $diffelide 1]
5191 proc prevfile {} {
5192 global difffilestart ctext
5193 set prev [lindex $difffilestart 0]
5194 set here [$ctext index @0,0]
5195 foreach loc $difffilestart {
5196 if {[$ctext compare $loc >= $here]} {
5197 $ctext yview $prev
5198 return
5200 set prev $loc
5202 $ctext yview $prev
5205 proc nextfile {} {
5206 global difffilestart ctext
5207 set here [$ctext index @0,0]
5208 foreach loc $difffilestart {
5209 if {[$ctext compare $loc > $here]} {
5210 $ctext yview $loc
5211 return
5216 proc clear_ctext {{first 1.0}} {
5217 global ctext smarktop smarkbot
5219 set l [lindex [split $first .] 0]
5220 if {![info exists smarktop] || [$ctext compare $first < $smarktop.0]} {
5221 set smarktop $l
5223 if {![info exists smarkbot] || [$ctext compare $first < $smarkbot.0]} {
5224 set smarkbot $l
5226 $ctext delete $first end
5229 proc incrsearch {name ix op} {
5230 global ctext searchstring searchdirn
5232 $ctext tag remove found 1.0 end
5233 if {[catch {$ctext index anchor}]} {
5234 # no anchor set, use start of selection, or of visible area
5235 set sel [$ctext tag ranges sel]
5236 if {$sel ne {}} {
5237 $ctext mark set anchor [lindex $sel 0]
5238 } elseif {$searchdirn eq "-forwards"} {
5239 $ctext mark set anchor @0,0
5240 } else {
5241 $ctext mark set anchor @0,[winfo height $ctext]
5244 if {$searchstring ne {}} {
5245 set here [$ctext search $searchdirn -- $searchstring anchor]
5246 if {$here ne {}} {
5247 $ctext see $here
5249 searchmarkvisible 1
5253 proc dosearch {} {
5254 global sstring ctext searchstring searchdirn
5256 focus $sstring
5257 $sstring icursor end
5258 set searchdirn -forwards
5259 if {$searchstring ne {}} {
5260 set sel [$ctext tag ranges sel]
5261 if {$sel ne {}} {
5262 set start "[lindex $sel 0] + 1c"
5263 } elseif {[catch {set start [$ctext index anchor]}]} {
5264 set start "@0,0"
5266 set match [$ctext search -count mlen -- $searchstring $start]
5267 $ctext tag remove sel 1.0 end
5268 if {$match eq {}} {
5269 bell
5270 return
5272 $ctext see $match
5273 set mend "$match + $mlen c"
5274 $ctext tag add sel $match $mend
5275 $ctext mark unset anchor
5279 proc dosearchback {} {
5280 global sstring ctext searchstring searchdirn
5282 focus $sstring
5283 $sstring icursor end
5284 set searchdirn -backwards
5285 if {$searchstring ne {}} {
5286 set sel [$ctext tag ranges sel]
5287 if {$sel ne {}} {
5288 set start [lindex $sel 0]
5289 } elseif {[catch {set start [$ctext index anchor]}]} {
5290 set start @0,[winfo height $ctext]
5292 set match [$ctext search -backwards -count ml -- $searchstring $start]
5293 $ctext tag remove sel 1.0 end
5294 if {$match eq {}} {
5295 bell
5296 return
5298 $ctext see $match
5299 set mend "$match + $ml c"
5300 $ctext tag add sel $match $mend
5301 $ctext mark unset anchor
5305 proc searchmark {first last} {
5306 global ctext searchstring
5308 set mend $first.0
5309 while {1} {
5310 set match [$ctext search -count mlen -- $searchstring $mend $last.end]
5311 if {$match eq {}} break
5312 set mend "$match + $mlen c"
5313 $ctext tag add found $match $mend
5317 proc searchmarkvisible {doall} {
5318 global ctext smarktop smarkbot
5320 set topline [lindex [split [$ctext index @0,0] .] 0]
5321 set botline [lindex [split [$ctext index @0,[winfo height $ctext]] .] 0]
5322 if {$doall || $botline < $smarktop || $topline > $smarkbot} {
5323 # no overlap with previous
5324 searchmark $topline $botline
5325 set smarktop $topline
5326 set smarkbot $botline
5327 } else {
5328 if {$topline < $smarktop} {
5329 searchmark $topline [expr {$smarktop-1}]
5330 set smarktop $topline
5332 if {$botline > $smarkbot} {
5333 searchmark [expr {$smarkbot+1}] $botline
5334 set smarkbot $botline
5339 proc scrolltext {f0 f1} {
5340 global searchstring
5342 .bleft.sb set $f0 $f1
5343 if {$searchstring ne {}} {
5344 searchmarkvisible 0
5348 proc setcoords {} {
5349 global linespc charspc canvx0 canvy0 mainfont
5350 global xspc1 xspc2 lthickness
5352 set linespc [font metrics $mainfont -linespace]
5353 set charspc [font measure $mainfont "m"]
5354 set canvy0 [expr {int(3 + 0.5 * $linespc)}]
5355 set canvx0 [expr {int(3 + 0.5 * $linespc)}]
5356 set lthickness [expr {int($linespc / 9) + 1}]
5357 set xspc1(0) $linespc
5358 set xspc2 $linespc
5361 proc redisplay {} {
5362 global canv
5363 global selectedline
5365 set ymax [lindex [$canv cget -scrollregion] 3]
5366 if {$ymax eq {} || $ymax == 0} return
5367 set span [$canv yview]
5368 clear_display
5369 setcanvscroll
5370 allcanvs yview moveto [lindex $span 0]
5371 drawvisible
5372 if {[info exists selectedline]} {
5373 selectline $selectedline 0
5374 allcanvs yview moveto [lindex $span 0]
5378 proc incrfont {inc} {
5379 global mainfont textfont ctext canv phase cflist
5380 global charspc tabstop
5381 global stopped entries
5382 unmarkmatches
5383 set mainfont [lreplace $mainfont 1 1 [expr {[lindex $mainfont 1] + $inc}]]
5384 set textfont [lreplace $textfont 1 1 [expr {[lindex $textfont 1] + $inc}]]
5385 setcoords
5386 $ctext conf -font $textfont -tabs "[expr {$tabstop * $charspc}]"
5387 $cflist conf -font $textfont
5388 $ctext tag conf filesep -font [concat $textfont bold]
5389 foreach e $entries {
5390 $e conf -font $mainfont
5392 if {$phase eq "getcommits"} {
5393 $canv itemconf textitems -font $mainfont
5395 redisplay
5398 proc clearsha1 {} {
5399 global sha1entry sha1string
5400 if {[string length $sha1string] == 40} {
5401 $sha1entry delete 0 end
5405 proc sha1change {n1 n2 op} {
5406 global sha1string currentid sha1but
5407 if {$sha1string == {}
5408 || ([info exists currentid] && $sha1string == $currentid)} {
5409 set state disabled
5410 } else {
5411 set state normal
5413 if {[$sha1but cget -state] == $state} return
5414 if {$state == "normal"} {
5415 $sha1but conf -state normal -relief raised -text "Goto: "
5416 } else {
5417 $sha1but conf -state disabled -relief flat -text "SHA1 ID: "
5421 proc gotocommit {} {
5422 global sha1string currentid commitrow tagids headids
5423 global displayorder numcommits curview
5425 if {$sha1string == {}
5426 || ([info exists currentid] && $sha1string == $currentid)} return
5427 if {[info exists tagids($sha1string)]} {
5428 set id $tagids($sha1string)
5429 } elseif {[info exists headids($sha1string)]} {
5430 set id $headids($sha1string)
5431 } else {
5432 set id [string tolower $sha1string]
5433 if {[regexp {^[0-9a-f]{4,39}$} $id]} {
5434 set matches {}
5435 foreach i $displayorder {
5436 if {[string match $id* $i]} {
5437 lappend matches $i
5440 if {$matches ne {}} {
5441 if {[llength $matches] > 1} {
5442 error_popup "Short SHA1 id $id is ambiguous"
5443 return
5445 set id [lindex $matches 0]
5449 if {[info exists commitrow($curview,$id)]} {
5450 selectline $commitrow($curview,$id) 1
5451 return
5453 if {[regexp {^[0-9a-fA-F]{4,}$} $sha1string]} {
5454 set type "SHA1 id"
5455 } else {
5456 set type "Tag/Head"
5458 error_popup "$type $sha1string is not known"
5461 proc lineenter {x y id} {
5462 global hoverx hovery hoverid hovertimer
5463 global commitinfo canv
5465 if {![info exists commitinfo($id)] && ![getcommit $id]} return
5466 set hoverx $x
5467 set hovery $y
5468 set hoverid $id
5469 if {[info exists hovertimer]} {
5470 after cancel $hovertimer
5472 set hovertimer [after 500 linehover]
5473 $canv delete hover
5476 proc linemotion {x y id} {
5477 global hoverx hovery hoverid hovertimer
5479 if {[info exists hoverid] && $id == $hoverid} {
5480 set hoverx $x
5481 set hovery $y
5482 if {[info exists hovertimer]} {
5483 after cancel $hovertimer
5485 set hovertimer [after 500 linehover]
5489 proc lineleave {id} {
5490 global hoverid hovertimer canv
5492 if {[info exists hoverid] && $id == $hoverid} {
5493 $canv delete hover
5494 if {[info exists hovertimer]} {
5495 after cancel $hovertimer
5496 unset hovertimer
5498 unset hoverid
5502 proc linehover {} {
5503 global hoverx hovery hoverid hovertimer
5504 global canv linespc lthickness
5505 global commitinfo mainfont
5507 set text [lindex $commitinfo($hoverid) 0]
5508 set ymax [lindex [$canv cget -scrollregion] 3]
5509 if {$ymax == {}} return
5510 set yfrac [lindex [$canv yview] 0]
5511 set x [expr {$hoverx + 2 * $linespc}]
5512 set y [expr {$hovery + $yfrac * $ymax - $linespc / 2}]
5513 set x0 [expr {$x - 2 * $lthickness}]
5514 set y0 [expr {$y - 2 * $lthickness}]
5515 set x1 [expr {$x + [font measure $mainfont $text] + 2 * $lthickness}]
5516 set y1 [expr {$y + $linespc + 2 * $lthickness}]
5517 set t [$canv create rectangle $x0 $y0 $x1 $y1 \
5518 -fill \#ffff80 -outline black -width 1 -tags hover]
5519 $canv raise $t
5520 set t [$canv create text $x $y -anchor nw -text $text -tags hover \
5521 -font $mainfont]
5522 $canv raise $t
5525 proc clickisonarrow {id y} {
5526 global lthickness
5528 set ranges [rowranges $id]
5529 set thresh [expr {2 * $lthickness + 6}]
5530 set n [expr {[llength $ranges] - 1}]
5531 for {set i 1} {$i < $n} {incr i} {
5532 set row [lindex $ranges $i]
5533 if {abs([yc $row] - $y) < $thresh} {
5534 return $i
5537 return {}
5540 proc arrowjump {id n y} {
5541 global canv
5543 # 1 <-> 2, 3 <-> 4, etc...
5544 set n [expr {(($n - 1) ^ 1) + 1}]
5545 set row [lindex [rowranges $id] $n]
5546 set yt [yc $row]
5547 set ymax [lindex [$canv cget -scrollregion] 3]
5548 if {$ymax eq {} || $ymax <= 0} return
5549 set view [$canv yview]
5550 set yspan [expr {[lindex $view 1] - [lindex $view 0]}]
5551 set yfrac [expr {$yt / $ymax - $yspan / 2}]
5552 if {$yfrac < 0} {
5553 set yfrac 0
5555 allcanvs yview moveto $yfrac
5558 proc lineclick {x y id isnew} {
5559 global ctext commitinfo children canv thickerline curview
5561 if {![info exists commitinfo($id)] && ![getcommit $id]} return
5562 unmarkmatches
5563 unselectline
5564 normalline
5565 $canv delete hover
5566 # draw this line thicker than normal
5567 set thickerline $id
5568 drawlines $id
5569 if {$isnew} {
5570 set ymax [lindex [$canv cget -scrollregion] 3]
5571 if {$ymax eq {}} return
5572 set yfrac [lindex [$canv yview] 0]
5573 set y [expr {$y + $yfrac * $ymax}]
5575 set dirn [clickisonarrow $id $y]
5576 if {$dirn ne {}} {
5577 arrowjump $id $dirn $y
5578 return
5581 if {$isnew} {
5582 addtohistory [list lineclick $x $y $id 0]
5584 # fill the details pane with info about this line
5585 $ctext conf -state normal
5586 clear_ctext
5587 $ctext tag conf link -foreground blue -underline 1
5588 $ctext tag bind link <Enter> { %W configure -cursor hand2 }
5589 $ctext tag bind link <Leave> { %W configure -cursor $curtextcursor }
5590 $ctext insert end "Parent:\t"
5591 $ctext insert end $id [list link link0]
5592 $ctext tag bind link0 <1> [list selbyid $id]
5593 set info $commitinfo($id)
5594 $ctext insert end "\n\t[lindex $info 0]\n"
5595 $ctext insert end "\tAuthor:\t[lindex $info 1]\n"
5596 set date [formatdate [lindex $info 2]]
5597 $ctext insert end "\tDate:\t$date\n"
5598 set kids $children($curview,$id)
5599 if {$kids ne {}} {
5600 $ctext insert end "\nChildren:"
5601 set i 0
5602 foreach child $kids {
5603 incr i
5604 if {![info exists commitinfo($child)] && ![getcommit $child]} continue
5605 set info $commitinfo($child)
5606 $ctext insert end "\n\t"
5607 $ctext insert end $child [list link link$i]
5608 $ctext tag bind link$i <1> [list selbyid $child]
5609 $ctext insert end "\n\t[lindex $info 0]"
5610 $ctext insert end "\n\tAuthor:\t[lindex $info 1]"
5611 set date [formatdate [lindex $info 2]]
5612 $ctext insert end "\n\tDate:\t$date\n"
5615 $ctext conf -state disabled
5616 init_flist {}
5619 proc normalline {} {
5620 global thickerline
5621 if {[info exists thickerline]} {
5622 set id $thickerline
5623 unset thickerline
5624 drawlines $id
5628 proc selbyid {id} {
5629 global commitrow curview
5630 if {[info exists commitrow($curview,$id)]} {
5631 selectline $commitrow($curview,$id) 1
5635 proc mstime {} {
5636 global startmstime
5637 if {![info exists startmstime]} {
5638 set startmstime [clock clicks -milliseconds]
5640 return [format "%.3f" [expr {([clock click -milliseconds] - $startmstime) / 1000.0}]]
5643 proc rowmenu {x y id} {
5644 global rowctxmenu commitrow selectedline rowmenuid curview
5645 global nullid nullid2 fakerowmenu mainhead
5647 set rowmenuid $id
5648 if {![info exists selectedline]
5649 || $commitrow($curview,$id) eq $selectedline} {
5650 set state disabled
5651 } else {
5652 set state normal
5654 if {$id ne $nullid && $id ne $nullid2} {
5655 set menu $rowctxmenu
5656 $menu entryconfigure 7 -label "Reset $mainhead branch to here"
5657 } else {
5658 set menu $fakerowmenu
5660 $menu entryconfigure "Diff this*" -state $state
5661 $menu entryconfigure "Diff selected*" -state $state
5662 $menu entryconfigure "Make patch" -state $state
5663 tk_popup $menu $x $y
5666 proc diffvssel {dirn} {
5667 global rowmenuid selectedline displayorder
5669 if {![info exists selectedline]} return
5670 if {$dirn} {
5671 set oldid [lindex $displayorder $selectedline]
5672 set newid $rowmenuid
5673 } else {
5674 set oldid $rowmenuid
5675 set newid [lindex $displayorder $selectedline]
5677 addtohistory [list doseldiff $oldid $newid]
5678 doseldiff $oldid $newid
5681 proc doseldiff {oldid newid} {
5682 global ctext
5683 global commitinfo
5685 $ctext conf -state normal
5686 clear_ctext
5687 init_flist "Top"
5688 $ctext insert end "From "
5689 $ctext tag conf link -foreground blue -underline 1
5690 $ctext tag bind link <Enter> { %W configure -cursor hand2 }
5691 $ctext tag bind link <Leave> { %W configure -cursor $curtextcursor }
5692 $ctext tag bind link0 <1> [list selbyid $oldid]
5693 $ctext insert end $oldid [list link link0]
5694 $ctext insert end "\n "
5695 $ctext insert end [lindex $commitinfo($oldid) 0]
5696 $ctext insert end "\n\nTo "
5697 $ctext tag bind link1 <1> [list selbyid $newid]
5698 $ctext insert end $newid [list link link1]
5699 $ctext insert end "\n "
5700 $ctext insert end [lindex $commitinfo($newid) 0]
5701 $ctext insert end "\n"
5702 $ctext conf -state disabled
5703 $ctext tag remove found 1.0 end
5704 startdiff [list $oldid $newid]
5707 proc mkpatch {} {
5708 global rowmenuid currentid commitinfo patchtop patchnum
5710 if {![info exists currentid]} return
5711 set oldid $currentid
5712 set oldhead [lindex $commitinfo($oldid) 0]
5713 set newid $rowmenuid
5714 set newhead [lindex $commitinfo($newid) 0]
5715 set top .patch
5716 set patchtop $top
5717 catch {destroy $top}
5718 toplevel $top
5719 label $top.title -text "Generate patch"
5720 grid $top.title - -pady 10
5721 label $top.from -text "From:"
5722 entry $top.fromsha1 -width 40 -relief flat
5723 $top.fromsha1 insert 0 $oldid
5724 $top.fromsha1 conf -state readonly
5725 grid $top.from $top.fromsha1 -sticky w
5726 entry $top.fromhead -width 60 -relief flat
5727 $top.fromhead insert 0 $oldhead
5728 $top.fromhead conf -state readonly
5729 grid x $top.fromhead -sticky w
5730 label $top.to -text "To:"
5731 entry $top.tosha1 -width 40 -relief flat
5732 $top.tosha1 insert 0 $newid
5733 $top.tosha1 conf -state readonly
5734 grid $top.to $top.tosha1 -sticky w
5735 entry $top.tohead -width 60 -relief flat
5736 $top.tohead insert 0 $newhead
5737 $top.tohead conf -state readonly
5738 grid x $top.tohead -sticky w
5739 button $top.rev -text "Reverse" -command mkpatchrev -padx 5
5740 grid $top.rev x -pady 10
5741 label $top.flab -text "Output file:"
5742 entry $top.fname -width 60
5743 $top.fname insert 0 [file normalize "patch$patchnum.patch"]
5744 incr patchnum
5745 grid $top.flab $top.fname -sticky w
5746 frame $top.buts
5747 button $top.buts.gen -text "Generate" -command mkpatchgo
5748 button $top.buts.can -text "Cancel" -command mkpatchcan
5749 grid $top.buts.gen $top.buts.can
5750 grid columnconfigure $top.buts 0 -weight 1 -uniform a
5751 grid columnconfigure $top.buts 1 -weight 1 -uniform a
5752 grid $top.buts - -pady 10 -sticky ew
5753 focus $top.fname
5756 proc mkpatchrev {} {
5757 global patchtop
5759 set oldid [$patchtop.fromsha1 get]
5760 set oldhead [$patchtop.fromhead get]
5761 set newid [$patchtop.tosha1 get]
5762 set newhead [$patchtop.tohead get]
5763 foreach e [list fromsha1 fromhead tosha1 tohead] \
5764 v [list $newid $newhead $oldid $oldhead] {
5765 $patchtop.$e conf -state normal
5766 $patchtop.$e delete 0 end
5767 $patchtop.$e insert 0 $v
5768 $patchtop.$e conf -state readonly
5772 proc mkpatchgo {} {
5773 global patchtop nullid nullid2
5775 set oldid [$patchtop.fromsha1 get]
5776 set newid [$patchtop.tosha1 get]
5777 set fname [$patchtop.fname get]
5778 set cmd [diffcmd [list $oldid $newid] -p]
5779 lappend cmd >$fname &
5780 if {[catch {eval exec $cmd} err]} {
5781 error_popup "Error creating patch: $err"
5783 catch {destroy $patchtop}
5784 unset patchtop
5787 proc mkpatchcan {} {
5788 global patchtop
5790 catch {destroy $patchtop}
5791 unset patchtop
5794 proc mktag {} {
5795 global rowmenuid mktagtop commitinfo
5797 set top .maketag
5798 set mktagtop $top
5799 catch {destroy $top}
5800 toplevel $top
5801 label $top.title -text "Create tag"
5802 grid $top.title - -pady 10
5803 label $top.id -text "ID:"
5804 entry $top.sha1 -width 40 -relief flat
5805 $top.sha1 insert 0 $rowmenuid
5806 $top.sha1 conf -state readonly
5807 grid $top.id $top.sha1 -sticky w
5808 entry $top.head -width 60 -relief flat
5809 $top.head insert 0 [lindex $commitinfo($rowmenuid) 0]
5810 $top.head conf -state readonly
5811 grid x $top.head -sticky w
5812 label $top.tlab -text "Tag name:"
5813 entry $top.tag -width 60
5814 grid $top.tlab $top.tag -sticky w
5815 frame $top.buts
5816 button $top.buts.gen -text "Create" -command mktaggo
5817 button $top.buts.can -text "Cancel" -command mktagcan
5818 grid $top.buts.gen $top.buts.can
5819 grid columnconfigure $top.buts 0 -weight 1 -uniform a
5820 grid columnconfigure $top.buts 1 -weight 1 -uniform a
5821 grid $top.buts - -pady 10 -sticky ew
5822 focus $top.tag
5825 proc domktag {} {
5826 global mktagtop env tagids idtags
5828 set id [$mktagtop.sha1 get]
5829 set tag [$mktagtop.tag get]
5830 if {$tag == {}} {
5831 error_popup "No tag name specified"
5832 return
5834 if {[info exists tagids($tag)]} {
5835 error_popup "Tag \"$tag\" already exists"
5836 return
5838 if {[catch {
5839 set dir [gitdir]
5840 set fname [file join $dir "refs/tags" $tag]
5841 set f [open $fname w]
5842 puts $f $id
5843 close $f
5844 } err]} {
5845 error_popup "Error creating tag: $err"
5846 return
5849 set tagids($tag) $id
5850 lappend idtags($id) $tag
5851 redrawtags $id
5852 addedtag $id
5855 proc redrawtags {id} {
5856 global canv linehtag commitrow idpos selectedline curview
5857 global mainfont canvxmax iddrawn
5859 if {![info exists commitrow($curview,$id)]} return
5860 if {![info exists iddrawn($id)]} return
5861 drawcommits $commitrow($curview,$id)
5862 $canv delete tag.$id
5863 set xt [eval drawtags $id $idpos($id)]
5864 $canv coords $linehtag($commitrow($curview,$id)) $xt [lindex $idpos($id) 2]
5865 set text [$canv itemcget $linehtag($commitrow($curview,$id)) -text]
5866 set xr [expr {$xt + [font measure $mainfont $text]}]
5867 if {$xr > $canvxmax} {
5868 set canvxmax $xr
5869 setcanvscroll
5871 if {[info exists selectedline]
5872 && $selectedline == $commitrow($curview,$id)} {
5873 selectline $selectedline 0
5877 proc mktagcan {} {
5878 global mktagtop
5880 catch {destroy $mktagtop}
5881 unset mktagtop
5884 proc mktaggo {} {
5885 domktag
5886 mktagcan
5889 proc writecommit {} {
5890 global rowmenuid wrcomtop commitinfo wrcomcmd
5892 set top .writecommit
5893 set wrcomtop $top
5894 catch {destroy $top}
5895 toplevel $top
5896 label $top.title -text "Write commit to file"
5897 grid $top.title - -pady 10
5898 label $top.id -text "ID:"
5899 entry $top.sha1 -width 40 -relief flat
5900 $top.sha1 insert 0 $rowmenuid
5901 $top.sha1 conf -state readonly
5902 grid $top.id $top.sha1 -sticky w
5903 entry $top.head -width 60 -relief flat
5904 $top.head insert 0 [lindex $commitinfo($rowmenuid) 0]
5905 $top.head conf -state readonly
5906 grid x $top.head -sticky w
5907 label $top.clab -text "Command:"
5908 entry $top.cmd -width 60 -textvariable wrcomcmd
5909 grid $top.clab $top.cmd -sticky w -pady 10
5910 label $top.flab -text "Output file:"
5911 entry $top.fname -width 60
5912 $top.fname insert 0 [file normalize "commit-[string range $rowmenuid 0 6]"]
5913 grid $top.flab $top.fname -sticky w
5914 frame $top.buts
5915 button $top.buts.gen -text "Write" -command wrcomgo
5916 button $top.buts.can -text "Cancel" -command wrcomcan
5917 grid $top.buts.gen $top.buts.can
5918 grid columnconfigure $top.buts 0 -weight 1 -uniform a
5919 grid columnconfigure $top.buts 1 -weight 1 -uniform a
5920 grid $top.buts - -pady 10 -sticky ew
5921 focus $top.fname
5924 proc wrcomgo {} {
5925 global wrcomtop
5927 set id [$wrcomtop.sha1 get]
5928 set cmd "echo $id | [$wrcomtop.cmd get]"
5929 set fname [$wrcomtop.fname get]
5930 if {[catch {exec sh -c $cmd >$fname &} err]} {
5931 error_popup "Error writing commit: $err"
5933 catch {destroy $wrcomtop}
5934 unset wrcomtop
5937 proc wrcomcan {} {
5938 global wrcomtop
5940 catch {destroy $wrcomtop}
5941 unset wrcomtop
5944 proc mkbranch {} {
5945 global rowmenuid mkbrtop
5947 set top .makebranch
5948 catch {destroy $top}
5949 toplevel $top
5950 label $top.title -text "Create new branch"
5951 grid $top.title - -pady 10
5952 label $top.id -text "ID:"
5953 entry $top.sha1 -width 40 -relief flat
5954 $top.sha1 insert 0 $rowmenuid
5955 $top.sha1 conf -state readonly
5956 grid $top.id $top.sha1 -sticky w
5957 label $top.nlab -text "Name:"
5958 entry $top.name -width 40
5959 grid $top.nlab $top.name -sticky w
5960 frame $top.buts
5961 button $top.buts.go -text "Create" -command [list mkbrgo $top]
5962 button $top.buts.can -text "Cancel" -command "catch {destroy $top}"
5963 grid $top.buts.go $top.buts.can
5964 grid columnconfigure $top.buts 0 -weight 1 -uniform a
5965 grid columnconfigure $top.buts 1 -weight 1 -uniform a
5966 grid $top.buts - -pady 10 -sticky ew
5967 focus $top.name
5970 proc mkbrgo {top} {
5971 global headids idheads
5973 set name [$top.name get]
5974 set id [$top.sha1 get]
5975 if {$name eq {}} {
5976 error_popup "Please specify a name for the new branch"
5977 return
5979 catch {destroy $top}
5980 nowbusy newbranch
5981 update
5982 if {[catch {
5983 exec git branch $name $id
5984 } err]} {
5985 notbusy newbranch
5986 error_popup $err
5987 } else {
5988 set headids($name) $id
5989 lappend idheads($id) $name
5990 addedhead $id $name
5991 notbusy newbranch
5992 redrawtags $id
5993 dispneartags 0
5997 proc cherrypick {} {
5998 global rowmenuid curview commitrow
5999 global mainhead
6001 set oldhead [exec git rev-parse HEAD]
6002 set dheads [descheads $rowmenuid]
6003 if {$dheads ne {} && [lsearch -exact $dheads $oldhead] >= 0} {
6004 set ok [confirm_popup "Commit [string range $rowmenuid 0 7] is already\
6005 included in branch $mainhead -- really re-apply it?"]
6006 if {!$ok} return
6008 nowbusy cherrypick
6009 update
6010 # Unfortunately git-cherry-pick writes stuff to stderr even when
6011 # no error occurs, and exec takes that as an indication of error...
6012 if {[catch {exec sh -c "git cherry-pick -r $rowmenuid 2>&1"} err]} {
6013 notbusy cherrypick
6014 error_popup $err
6015 return
6017 set newhead [exec git rev-parse HEAD]
6018 if {$newhead eq $oldhead} {
6019 notbusy cherrypick
6020 error_popup "No changes committed"
6021 return
6023 addnewchild $newhead $oldhead
6024 if {[info exists commitrow($curview,$oldhead)]} {
6025 insertrow $commitrow($curview,$oldhead) $newhead
6026 if {$mainhead ne {}} {
6027 movehead $newhead $mainhead
6028 movedhead $newhead $mainhead
6030 redrawtags $oldhead
6031 redrawtags $newhead
6033 notbusy cherrypick
6036 proc resethead {} {
6037 global mainheadid mainhead rowmenuid confirm_ok resettype
6038 global showlocalchanges
6040 set confirm_ok 0
6041 set w ".confirmreset"
6042 toplevel $w
6043 wm transient $w .
6044 wm title $w "Confirm reset"
6045 message $w.m -text \
6046 "Reset branch $mainhead to [string range $rowmenuid 0 7]?" \
6047 -justify center -aspect 1000
6048 pack $w.m -side top -fill x -padx 20 -pady 20
6049 frame $w.f -relief sunken -border 2
6050 message $w.f.rt -text "Reset type:" -aspect 1000
6051 grid $w.f.rt -sticky w
6052 set resettype mixed
6053 radiobutton $w.f.soft -value soft -variable resettype -justify left \
6054 -text "Soft: Leave working tree and index untouched"
6055 grid $w.f.soft -sticky w
6056 radiobutton $w.f.mixed -value mixed -variable resettype -justify left \
6057 -text "Mixed: Leave working tree untouched, reset index"
6058 grid $w.f.mixed -sticky w
6059 radiobutton $w.f.hard -value hard -variable resettype -justify left \
6060 -text "Hard: Reset working tree and index\n(discard ALL local changes)"
6061 grid $w.f.hard -sticky w
6062 pack $w.f -side top -fill x
6063 button $w.ok -text OK -command "set confirm_ok 1; destroy $w"
6064 pack $w.ok -side left -fill x -padx 20 -pady 20
6065 button $w.cancel -text Cancel -command "destroy $w"
6066 pack $w.cancel -side right -fill x -padx 20 -pady 20
6067 bind $w <Visibility> "grab $w; focus $w"
6068 tkwait window $w
6069 if {!$confirm_ok} return
6070 if {[catch {set fd [open \
6071 [list | sh -c "git reset --$resettype $rowmenuid 2>&1"] r]} err]} {
6072 error_popup $err
6073 } else {
6074 dohidelocalchanges
6075 set w ".resetprogress"
6076 filerun $fd [list readresetstat $fd $w]
6077 toplevel $w
6078 wm transient $w
6079 wm title $w "Reset progress"
6080 message $w.m -text "Reset in progress, please wait..." \
6081 -justify center -aspect 1000
6082 pack $w.m -side top -fill x -padx 20 -pady 5
6083 canvas $w.c -width 150 -height 20 -bg white
6084 $w.c create rect 0 0 0 20 -fill green -tags rect
6085 pack $w.c -side top -fill x -padx 20 -pady 5 -expand 1
6086 nowbusy reset
6090 proc readresetstat {fd w} {
6091 global mainhead mainheadid showlocalchanges
6093 if {[gets $fd line] >= 0} {
6094 if {[regexp {([0-9]+)% \(([0-9]+)/([0-9]+)\)} $line match p m n]} {
6095 set x [expr {($m * 150) / $n}]
6096 $w.c coords rect 0 0 $x 20
6098 return 1
6100 destroy $w
6101 notbusy reset
6102 if {[catch {close $fd} err]} {
6103 error_popup $err
6105 set oldhead $mainheadid
6106 set newhead [exec git rev-parse HEAD]
6107 if {$newhead ne $oldhead} {
6108 movehead $newhead $mainhead
6109 movedhead $newhead $mainhead
6110 set mainheadid $newhead
6111 redrawtags $oldhead
6112 redrawtags $newhead
6114 if {$showlocalchanges} {
6115 doshowlocalchanges
6117 return 0
6120 # context menu for a head
6121 proc headmenu {x y id head} {
6122 global headmenuid headmenuhead headctxmenu mainhead
6124 set headmenuid $id
6125 set headmenuhead $head
6126 set state normal
6127 if {$head eq $mainhead} {
6128 set state disabled
6130 $headctxmenu entryconfigure 0 -state $state
6131 $headctxmenu entryconfigure 1 -state $state
6132 tk_popup $headctxmenu $x $y
6135 proc cobranch {} {
6136 global headmenuid headmenuhead mainhead headids
6137 global showlocalchanges mainheadid
6139 # check the tree is clean first??
6140 set oldmainhead $mainhead
6141 nowbusy checkout
6142 update
6143 dohidelocalchanges
6144 if {[catch {
6145 exec git checkout -q $headmenuhead
6146 } err]} {
6147 notbusy checkout
6148 error_popup $err
6149 } else {
6150 notbusy checkout
6151 set mainhead $headmenuhead
6152 set mainheadid $headmenuid
6153 if {[info exists headids($oldmainhead)]} {
6154 redrawtags $headids($oldmainhead)
6156 redrawtags $headmenuid
6158 if {$showlocalchanges} {
6159 dodiffindex
6163 proc rmbranch {} {
6164 global headmenuid headmenuhead mainhead
6165 global headids idheads
6167 set head $headmenuhead
6168 set id $headmenuid
6169 # this check shouldn't be needed any more...
6170 if {$head eq $mainhead} {
6171 error_popup "Cannot delete the currently checked-out branch"
6172 return
6174 set dheads [descheads $id]
6175 if {$dheads eq $headids($head)} {
6176 # the stuff on this branch isn't on any other branch
6177 if {![confirm_popup "The commits on branch $head aren't on any other\
6178 branch.\nReally delete branch $head?"]} return
6180 nowbusy rmbranch
6181 update
6182 if {[catch {exec git branch -D $head} err]} {
6183 notbusy rmbranch
6184 error_popup $err
6185 return
6187 removehead $id $head
6188 removedhead $id $head
6189 redrawtags $id
6190 notbusy rmbranch
6191 dispneartags 0
6194 # Stuff for finding nearby tags
6195 proc getallcommits {} {
6196 global allcommits allids nbmp nextarc seeds
6198 set allids {}
6199 set nbmp 0
6200 set nextarc 0
6201 set allcommits 0
6202 set seeds {}
6203 regetallcommits
6206 # Called when the graph might have changed
6207 proc regetallcommits {} {
6208 global allcommits seeds
6210 set cmd [concat | git rev-list --all --parents]
6211 foreach id $seeds {
6212 lappend cmd "^$id"
6214 set fd [open $cmd r]
6215 fconfigure $fd -blocking 0
6216 incr allcommits
6217 nowbusy allcommits
6218 filerun $fd [list getallclines $fd]
6221 # Since most commits have 1 parent and 1 child, we group strings of
6222 # such commits into "arcs" joining branch/merge points (BMPs), which
6223 # are commits that either don't have 1 parent or don't have 1 child.
6225 # arcnos(id) - incoming arcs for BMP, arc we're on for other nodes
6226 # arcout(id) - outgoing arcs for BMP
6227 # arcids(a) - list of IDs on arc including end but not start
6228 # arcstart(a) - BMP ID at start of arc
6229 # arcend(a) - BMP ID at end of arc
6230 # growing(a) - arc a is still growing
6231 # arctags(a) - IDs out of arcids (excluding end) that have tags
6232 # archeads(a) - IDs out of arcids (excluding end) that have heads
6233 # The start of an arc is at the descendent end, so "incoming" means
6234 # coming from descendents, and "outgoing" means going towards ancestors.
6236 proc getallclines {fd} {
6237 global allids allparents allchildren idtags idheads nextarc nbmp
6238 global arcnos arcids arctags arcout arcend arcstart archeads growing
6239 global seeds allcommits
6241 set nid 0
6242 while {[incr nid] <= 1000 && [gets $fd line] >= 0} {
6243 set id [lindex $line 0]
6244 if {[info exists allparents($id)]} {
6245 # seen it already
6246 continue
6248 lappend allids $id
6249 set olds [lrange $line 1 end]
6250 set allparents($id) $olds
6251 if {![info exists allchildren($id)]} {
6252 set allchildren($id) {}
6253 set arcnos($id) {}
6254 lappend seeds $id
6255 } else {
6256 set a $arcnos($id)
6257 if {[llength $olds] == 1 && [llength $a] == 1} {
6258 lappend arcids($a) $id
6259 if {[info exists idtags($id)]} {
6260 lappend arctags($a) $id
6262 if {[info exists idheads($id)]} {
6263 lappend archeads($a) $id
6265 if {[info exists allparents($olds)]} {
6266 # seen parent already
6267 if {![info exists arcout($olds)]} {
6268 splitarc $olds
6270 lappend arcids($a) $olds
6271 set arcend($a) $olds
6272 unset growing($a)
6274 lappend allchildren($olds) $id
6275 lappend arcnos($olds) $a
6276 continue
6279 incr nbmp
6280 foreach a $arcnos($id) {
6281 lappend arcids($a) $id
6282 set arcend($a) $id
6283 unset growing($a)
6286 set ao {}
6287 foreach p $olds {
6288 lappend allchildren($p) $id
6289 set a [incr nextarc]
6290 set arcstart($a) $id
6291 set archeads($a) {}
6292 set arctags($a) {}
6293 set archeads($a) {}
6294 set arcids($a) {}
6295 lappend ao $a
6296 set growing($a) 1
6297 if {[info exists allparents($p)]} {
6298 # seen it already, may need to make a new branch
6299 if {![info exists arcout($p)]} {
6300 splitarc $p
6302 lappend arcids($a) $p
6303 set arcend($a) $p
6304 unset growing($a)
6306 lappend arcnos($p) $a
6308 set arcout($id) $ao
6310 if {$nid > 0} {
6311 global cached_dheads cached_dtags cached_atags
6312 catch {unset cached_dheads}
6313 catch {unset cached_dtags}
6314 catch {unset cached_atags}
6316 if {![eof $fd]} {
6317 return [expr {$nid >= 1000? 2: 1}]
6319 close $fd
6320 if {[incr allcommits -1] == 0} {
6321 notbusy allcommits
6323 dispneartags 0
6324 return 0
6327 proc recalcarc {a} {
6328 global arctags archeads arcids idtags idheads
6330 set at {}
6331 set ah {}
6332 foreach id [lrange $arcids($a) 0 end-1] {
6333 if {[info exists idtags($id)]} {
6334 lappend at $id
6336 if {[info exists idheads($id)]} {
6337 lappend ah $id
6340 set arctags($a) $at
6341 set archeads($a) $ah
6344 proc splitarc {p} {
6345 global arcnos arcids nextarc nbmp arctags archeads idtags idheads
6346 global arcstart arcend arcout allparents growing
6348 set a $arcnos($p)
6349 if {[llength $a] != 1} {
6350 puts "oops splitarc called but [llength $a] arcs already"
6351 return
6353 set a [lindex $a 0]
6354 set i [lsearch -exact $arcids($a) $p]
6355 if {$i < 0} {
6356 puts "oops splitarc $p not in arc $a"
6357 return
6359 set na [incr nextarc]
6360 if {[info exists arcend($a)]} {
6361 set arcend($na) $arcend($a)
6362 } else {
6363 set l [lindex $allparents([lindex $arcids($a) end]) 0]
6364 set j [lsearch -exact $arcnos($l) $a]
6365 set arcnos($l) [lreplace $arcnos($l) $j $j $na]
6367 set tail [lrange $arcids($a) [expr {$i+1}] end]
6368 set arcids($a) [lrange $arcids($a) 0 $i]
6369 set arcend($a) $p
6370 set arcstart($na) $p
6371 set arcout($p) $na
6372 set arcids($na) $tail
6373 if {[info exists growing($a)]} {
6374 set growing($na) 1
6375 unset growing($a)
6377 incr nbmp
6379 foreach id $tail {
6380 if {[llength $arcnos($id)] == 1} {
6381 set arcnos($id) $na
6382 } else {
6383 set j [lsearch -exact $arcnos($id) $a]
6384 set arcnos($id) [lreplace $arcnos($id) $j $j $na]
6388 # reconstruct tags and heads lists
6389 if {$arctags($a) ne {} || $archeads($a) ne {}} {
6390 recalcarc $a
6391 recalcarc $na
6392 } else {
6393 set arctags($na) {}
6394 set archeads($na) {}
6398 # Update things for a new commit added that is a child of one
6399 # existing commit. Used when cherry-picking.
6400 proc addnewchild {id p} {
6401 global allids allparents allchildren idtags nextarc nbmp
6402 global arcnos arcids arctags arcout arcend arcstart archeads growing
6403 global seeds
6405 lappend allids $id
6406 set allparents($id) [list $p]
6407 set allchildren($id) {}
6408 set arcnos($id) {}
6409 lappend seeds $id
6410 incr nbmp
6411 lappend allchildren($p) $id
6412 set a [incr nextarc]
6413 set arcstart($a) $id
6414 set archeads($a) {}
6415 set arctags($a) {}
6416 set arcids($a) [list $p]
6417 set arcend($a) $p
6418 if {![info exists arcout($p)]} {
6419 splitarc $p
6421 lappend arcnos($p) $a
6422 set arcout($id) [list $a]
6425 # Returns 1 if a is an ancestor of b, -1 if b is an ancestor of a,
6426 # or 0 if neither is true.
6427 proc anc_or_desc {a b} {
6428 global arcout arcstart arcend arcnos cached_isanc
6430 if {$arcnos($a) eq $arcnos($b)} {
6431 # Both are on the same arc(s); either both are the same BMP,
6432 # or if one is not a BMP, the other is also not a BMP or is
6433 # the BMP at end of the arc (and it only has 1 incoming arc).
6434 # Or both can be BMPs with no incoming arcs.
6435 if {$a eq $b || $arcnos($a) eq {}} {
6436 return 0
6438 # assert {[llength $arcnos($a)] == 1}
6439 set arc [lindex $arcnos($a) 0]
6440 set i [lsearch -exact $arcids($arc) $a]
6441 set j [lsearch -exact $arcids($arc) $b]
6442 if {$i < 0 || $i > $j} {
6443 return 1
6444 } else {
6445 return -1
6449 if {![info exists arcout($a)]} {
6450 set arc [lindex $arcnos($a) 0]
6451 if {[info exists arcend($arc)]} {
6452 set aend $arcend($arc)
6453 } else {
6454 set aend {}
6456 set a $arcstart($arc)
6457 } else {
6458 set aend $a
6460 if {![info exists arcout($b)]} {
6461 set arc [lindex $arcnos($b) 0]
6462 if {[info exists arcend($arc)]} {
6463 set bend $arcend($arc)
6464 } else {
6465 set bend {}
6467 set b $arcstart($arc)
6468 } else {
6469 set bend $b
6471 if {$a eq $bend} {
6472 return 1
6474 if {$b eq $aend} {
6475 return -1
6477 if {[info exists cached_isanc($a,$bend)]} {
6478 if {$cached_isanc($a,$bend)} {
6479 return 1
6482 if {[info exists cached_isanc($b,$aend)]} {
6483 if {$cached_isanc($b,$aend)} {
6484 return -1
6486 if {[info exists cached_isanc($a,$bend)]} {
6487 return 0
6491 set todo [list $a $b]
6492 set anc($a) a
6493 set anc($b) b
6494 for {set i 0} {$i < [llength $todo]} {incr i} {
6495 set x [lindex $todo $i]
6496 if {$anc($x) eq {}} {
6497 continue
6499 foreach arc $arcnos($x) {
6500 set xd $arcstart($arc)
6501 if {$xd eq $bend} {
6502 set cached_isanc($a,$bend) 1
6503 set cached_isanc($b,$aend) 0
6504 return 1
6505 } elseif {$xd eq $aend} {
6506 set cached_isanc($b,$aend) 1
6507 set cached_isanc($a,$bend) 0
6508 return -1
6510 if {![info exists anc($xd)]} {
6511 set anc($xd) $anc($x)
6512 lappend todo $xd
6513 } elseif {$anc($xd) ne $anc($x)} {
6514 set anc($xd) {}
6518 set cached_isanc($a,$bend) 0
6519 set cached_isanc($b,$aend) 0
6520 return 0
6523 # This identifies whether $desc has an ancestor that is
6524 # a growing tip of the graph and which is not an ancestor of $anc
6525 # and returns 0 if so and 1 if not.
6526 # If we subsequently discover a tag on such a growing tip, and that
6527 # turns out to be a descendent of $anc (which it could, since we
6528 # don't necessarily see children before parents), then $desc
6529 # isn't a good choice to display as a descendent tag of
6530 # $anc (since it is the descendent of another tag which is
6531 # a descendent of $anc). Similarly, $anc isn't a good choice to
6532 # display as a ancestor tag of $desc.
6534 proc is_certain {desc anc} {
6535 global arcnos arcout arcstart arcend growing problems
6537 set certain {}
6538 if {[llength $arcnos($anc)] == 1} {
6539 # tags on the same arc are certain
6540 if {$arcnos($desc) eq $arcnos($anc)} {
6541 return 1
6543 if {![info exists arcout($anc)]} {
6544 # if $anc is partway along an arc, use the start of the arc instead
6545 set a [lindex $arcnos($anc) 0]
6546 set anc $arcstart($a)
6549 if {[llength $arcnos($desc)] > 1 || [info exists arcout($desc)]} {
6550 set x $desc
6551 } else {
6552 set a [lindex $arcnos($desc) 0]
6553 set x $arcend($a)
6555 if {$x == $anc} {
6556 return 1
6558 set anclist [list $x]
6559 set dl($x) 1
6560 set nnh 1
6561 set ngrowanc 0
6562 for {set i 0} {$i < [llength $anclist] && ($nnh > 0 || $ngrowanc > 0)} {incr i} {
6563 set x [lindex $anclist $i]
6564 if {$dl($x)} {
6565 incr nnh -1
6567 set done($x) 1
6568 foreach a $arcout($x) {
6569 if {[info exists growing($a)]} {
6570 if {![info exists growanc($x)] && $dl($x)} {
6571 set growanc($x) 1
6572 incr ngrowanc
6574 } else {
6575 set y $arcend($a)
6576 if {[info exists dl($y)]} {
6577 if {$dl($y)} {
6578 if {!$dl($x)} {
6579 set dl($y) 0
6580 if {![info exists done($y)]} {
6581 incr nnh -1
6583 if {[info exists growanc($x)]} {
6584 incr ngrowanc -1
6586 set xl [list $y]
6587 for {set k 0} {$k < [llength $xl]} {incr k} {
6588 set z [lindex $xl $k]
6589 foreach c $arcout($z) {
6590 if {[info exists arcend($c)]} {
6591 set v $arcend($c)
6592 if {[info exists dl($v)] && $dl($v)} {
6593 set dl($v) 0
6594 if {![info exists done($v)]} {
6595 incr nnh -1
6597 if {[info exists growanc($v)]} {
6598 incr ngrowanc -1
6600 lappend xl $v
6607 } elseif {$y eq $anc || !$dl($x)} {
6608 set dl($y) 0
6609 lappend anclist $y
6610 } else {
6611 set dl($y) 1
6612 lappend anclist $y
6613 incr nnh
6618 foreach x [array names growanc] {
6619 if {$dl($x)} {
6620 return 0
6622 return 0
6624 return 1
6627 proc validate_arctags {a} {
6628 global arctags idtags
6630 set i -1
6631 set na $arctags($a)
6632 foreach id $arctags($a) {
6633 incr i
6634 if {![info exists idtags($id)]} {
6635 set na [lreplace $na $i $i]
6636 incr i -1
6639 set arctags($a) $na
6642 proc validate_archeads {a} {
6643 global archeads idheads
6645 set i -1
6646 set na $archeads($a)
6647 foreach id $archeads($a) {
6648 incr i
6649 if {![info exists idheads($id)]} {
6650 set na [lreplace $na $i $i]
6651 incr i -1
6654 set archeads($a) $na
6657 # Return the list of IDs that have tags that are descendents of id,
6658 # ignoring IDs that are descendents of IDs already reported.
6659 proc desctags {id} {
6660 global arcnos arcstart arcids arctags idtags allparents
6661 global growing cached_dtags
6663 if {![info exists allparents($id)]} {
6664 return {}
6666 set t1 [clock clicks -milliseconds]
6667 set argid $id
6668 if {[llength $arcnos($id)] == 1 && [llength $allparents($id)] == 1} {
6669 # part-way along an arc; check that arc first
6670 set a [lindex $arcnos($id) 0]
6671 if {$arctags($a) ne {}} {
6672 validate_arctags $a
6673 set i [lsearch -exact $arcids($a) $id]
6674 set tid {}
6675 foreach t $arctags($a) {
6676 set j [lsearch -exact $arcids($a) $t]
6677 if {$j >= $i} break
6678 set tid $t
6680 if {$tid ne {}} {
6681 return $tid
6684 set id $arcstart($a)
6685 if {[info exists idtags($id)]} {
6686 return $id
6689 if {[info exists cached_dtags($id)]} {
6690 return $cached_dtags($id)
6693 set origid $id
6694 set todo [list $id]
6695 set queued($id) 1
6696 set nc 1
6697 for {set i 0} {$i < [llength $todo] && $nc > 0} {incr i} {
6698 set id [lindex $todo $i]
6699 set done($id) 1
6700 set ta [info exists hastaggedancestor($id)]
6701 if {!$ta} {
6702 incr nc -1
6704 # ignore tags on starting node
6705 if {!$ta && $i > 0} {
6706 if {[info exists idtags($id)]} {
6707 set tagloc($id) $id
6708 set ta 1
6709 } elseif {[info exists cached_dtags($id)]} {
6710 set tagloc($id) $cached_dtags($id)
6711 set ta 1
6714 foreach a $arcnos($id) {
6715 set d $arcstart($a)
6716 if {!$ta && $arctags($a) ne {}} {
6717 validate_arctags $a
6718 if {$arctags($a) ne {}} {
6719 lappend tagloc($id) [lindex $arctags($a) end]
6722 if {$ta || $arctags($a) ne {}} {
6723 set tomark [list $d]
6724 for {set j 0} {$j < [llength $tomark]} {incr j} {
6725 set dd [lindex $tomark $j]
6726 if {![info exists hastaggedancestor($dd)]} {
6727 if {[info exists done($dd)]} {
6728 foreach b $arcnos($dd) {
6729 lappend tomark $arcstart($b)
6731 if {[info exists tagloc($dd)]} {
6732 unset tagloc($dd)
6734 } elseif {[info exists queued($dd)]} {
6735 incr nc -1
6737 set hastaggedancestor($dd) 1
6741 if {![info exists queued($d)]} {
6742 lappend todo $d
6743 set queued($d) 1
6744 if {![info exists hastaggedancestor($d)]} {
6745 incr nc
6750 set tags {}
6751 foreach id [array names tagloc] {
6752 if {![info exists hastaggedancestor($id)]} {
6753 foreach t $tagloc($id) {
6754 if {[lsearch -exact $tags $t] < 0} {
6755 lappend tags $t
6760 set t2 [clock clicks -milliseconds]
6761 set loopix $i
6763 # remove tags that are descendents of other tags
6764 for {set i 0} {$i < [llength $tags]} {incr i} {
6765 set a [lindex $tags $i]
6766 for {set j 0} {$j < $i} {incr j} {
6767 set b [lindex $tags $j]
6768 set r [anc_or_desc $a $b]
6769 if {$r == 1} {
6770 set tags [lreplace $tags $j $j]
6771 incr j -1
6772 incr i -1
6773 } elseif {$r == -1} {
6774 set tags [lreplace $tags $i $i]
6775 incr i -1
6776 break
6781 if {[array names growing] ne {}} {
6782 # graph isn't finished, need to check if any tag could get
6783 # eclipsed by another tag coming later. Simply ignore any
6784 # tags that could later get eclipsed.
6785 set ctags {}
6786 foreach t $tags {
6787 if {[is_certain $t $origid]} {
6788 lappend ctags $t
6791 if {$tags eq $ctags} {
6792 set cached_dtags($origid) $tags
6793 } else {
6794 set tags $ctags
6796 } else {
6797 set cached_dtags($origid) $tags
6799 set t3 [clock clicks -milliseconds]
6800 if {0 && $t3 - $t1 >= 100} {
6801 puts "iterating descendents ($loopix/[llength $todo] nodes) took\
6802 [expr {$t2-$t1}]+[expr {$t3-$t2}]ms, $nc candidates left"
6804 return $tags
6807 proc anctags {id} {
6808 global arcnos arcids arcout arcend arctags idtags allparents
6809 global growing cached_atags
6811 if {![info exists allparents($id)]} {
6812 return {}
6814 set t1 [clock clicks -milliseconds]
6815 set argid $id
6816 if {[llength $arcnos($id)] == 1 && [llength $allparents($id)] == 1} {
6817 # part-way along an arc; check that arc first
6818 set a [lindex $arcnos($id) 0]
6819 if {$arctags($a) ne {}} {
6820 validate_arctags $a
6821 set i [lsearch -exact $arcids($a) $id]
6822 foreach t $arctags($a) {
6823 set j [lsearch -exact $arcids($a) $t]
6824 if {$j > $i} {
6825 return $t
6829 if {![info exists arcend($a)]} {
6830 return {}
6832 set id $arcend($a)
6833 if {[info exists idtags($id)]} {
6834 return $id
6837 if {[info exists cached_atags($id)]} {
6838 return $cached_atags($id)
6841 set origid $id
6842 set todo [list $id]
6843 set queued($id) 1
6844 set taglist {}
6845 set nc 1
6846 for {set i 0} {$i < [llength $todo] && $nc > 0} {incr i} {
6847 set id [lindex $todo $i]
6848 set done($id) 1
6849 set td [info exists hastaggeddescendent($id)]
6850 if {!$td} {
6851 incr nc -1
6853 # ignore tags on starting node
6854 if {!$td && $i > 0} {
6855 if {[info exists idtags($id)]} {
6856 set tagloc($id) $id
6857 set td 1
6858 } elseif {[info exists cached_atags($id)]} {
6859 set tagloc($id) $cached_atags($id)
6860 set td 1
6863 foreach a $arcout($id) {
6864 if {!$td && $arctags($a) ne {}} {
6865 validate_arctags $a
6866 if {$arctags($a) ne {}} {
6867 lappend tagloc($id) [lindex $arctags($a) 0]
6870 if {![info exists arcend($a)]} continue
6871 set d $arcend($a)
6872 if {$td || $arctags($a) ne {}} {
6873 set tomark [list $d]
6874 for {set j 0} {$j < [llength $tomark]} {incr j} {
6875 set dd [lindex $tomark $j]
6876 if {![info exists hastaggeddescendent($dd)]} {
6877 if {[info exists done($dd)]} {
6878 foreach b $arcout($dd) {
6879 if {[info exists arcend($b)]} {
6880 lappend tomark $arcend($b)
6883 if {[info exists tagloc($dd)]} {
6884 unset tagloc($dd)
6886 } elseif {[info exists queued($dd)]} {
6887 incr nc -1
6889 set hastaggeddescendent($dd) 1
6893 if {![info exists queued($d)]} {
6894 lappend todo $d
6895 set queued($d) 1
6896 if {![info exists hastaggeddescendent($d)]} {
6897 incr nc
6902 set t2 [clock clicks -milliseconds]
6903 set loopix $i
6904 set tags {}
6905 foreach id [array names tagloc] {
6906 if {![info exists hastaggeddescendent($id)]} {
6907 foreach t $tagloc($id) {
6908 if {[lsearch -exact $tags $t] < 0} {
6909 lappend tags $t
6915 # remove tags that are ancestors of other tags
6916 for {set i 0} {$i < [llength $tags]} {incr i} {
6917 set a [lindex $tags $i]
6918 for {set j 0} {$j < $i} {incr j} {
6919 set b [lindex $tags $j]
6920 set r [anc_or_desc $a $b]
6921 if {$r == -1} {
6922 set tags [lreplace $tags $j $j]
6923 incr j -1
6924 incr i -1
6925 } elseif {$r == 1} {
6926 set tags [lreplace $tags $i $i]
6927 incr i -1
6928 break
6933 if {[array names growing] ne {}} {
6934 # graph isn't finished, need to check if any tag could get
6935 # eclipsed by another tag coming later. Simply ignore any
6936 # tags that could later get eclipsed.
6937 set ctags {}
6938 foreach t $tags {
6939 if {[is_certain $origid $t]} {
6940 lappend ctags $t
6943 if {$tags eq $ctags} {
6944 set cached_atags($origid) $tags
6945 } else {
6946 set tags $ctags
6948 } else {
6949 set cached_atags($origid) $tags
6951 set t3 [clock clicks -milliseconds]
6952 if {0 && $t3 - $t1 >= 100} {
6953 puts "iterating ancestors ($loopix/[llength $todo] nodes) took\
6954 [expr {$t2-$t1}]+[expr {$t3-$t2}]ms, $nc candidates left"
6956 return $tags
6959 # Return the list of IDs that have heads that are descendents of id,
6960 # including id itself if it has a head.
6961 proc descheads {id} {
6962 global arcnos arcstart arcids archeads idheads cached_dheads
6963 global allparents
6965 if {![info exists allparents($id)]} {
6966 return {}
6968 set aret {}
6969 if {[llength $arcnos($id)] == 1 && [llength $allparents($id)] == 1} {
6970 # part-way along an arc; check it first
6971 set a [lindex $arcnos($id) 0]
6972 if {$archeads($a) ne {}} {
6973 validate_archeads $a
6974 set i [lsearch -exact $arcids($a) $id]
6975 foreach t $archeads($a) {
6976 set j [lsearch -exact $arcids($a) $t]
6977 if {$j > $i} break
6978 lappend aret $t
6981 set id $arcstart($a)
6983 set origid $id
6984 set todo [list $id]
6985 set seen($id) 1
6986 set ret {}
6987 for {set i 0} {$i < [llength $todo]} {incr i} {
6988 set id [lindex $todo $i]
6989 if {[info exists cached_dheads($id)]} {
6990 set ret [concat $ret $cached_dheads($id)]
6991 } else {
6992 if {[info exists idheads($id)]} {
6993 lappend ret $id
6995 foreach a $arcnos($id) {
6996 if {$archeads($a) ne {}} {
6997 validate_archeads $a
6998 if {$archeads($a) ne {}} {
6999 set ret [concat $ret $archeads($a)]
7002 set d $arcstart($a)
7003 if {![info exists seen($d)]} {
7004 lappend todo $d
7005 set seen($d) 1
7010 set ret [lsort -unique $ret]
7011 set cached_dheads($origid) $ret
7012 return [concat $ret $aret]
7015 proc addedtag {id} {
7016 global arcnos arcout cached_dtags cached_atags
7018 if {![info exists arcnos($id)]} return
7019 if {![info exists arcout($id)]} {
7020 recalcarc [lindex $arcnos($id) 0]
7022 catch {unset cached_dtags}
7023 catch {unset cached_atags}
7026 proc addedhead {hid head} {
7027 global arcnos arcout cached_dheads
7029 if {![info exists arcnos($hid)]} return
7030 if {![info exists arcout($hid)]} {
7031 recalcarc [lindex $arcnos($hid) 0]
7033 catch {unset cached_dheads}
7036 proc removedhead {hid head} {
7037 global cached_dheads
7039 catch {unset cached_dheads}
7042 proc movedhead {hid head} {
7043 global arcnos arcout cached_dheads
7045 if {![info exists arcnos($hid)]} return
7046 if {![info exists arcout($hid)]} {
7047 recalcarc [lindex $arcnos($hid) 0]
7049 catch {unset cached_dheads}
7052 proc changedrefs {} {
7053 global cached_dheads cached_dtags cached_atags
7054 global arctags archeads arcnos arcout idheads idtags
7056 foreach id [concat [array names idheads] [array names idtags]] {
7057 if {[info exists arcnos($id)] && ![info exists arcout($id)]} {
7058 set a [lindex $arcnos($id) 0]
7059 if {![info exists donearc($a)]} {
7060 recalcarc $a
7061 set donearc($a) 1
7065 catch {unset cached_dtags}
7066 catch {unset cached_atags}
7067 catch {unset cached_dheads}
7070 proc rereadrefs {} {
7071 global idtags idheads idotherrefs mainhead
7073 set refids [concat [array names idtags] \
7074 [array names idheads] [array names idotherrefs]]
7075 foreach id $refids {
7076 if {![info exists ref($id)]} {
7077 set ref($id) [listrefs $id]
7080 set oldmainhead $mainhead
7081 readrefs
7082 changedrefs
7083 set refids [lsort -unique [concat $refids [array names idtags] \
7084 [array names idheads] [array names idotherrefs]]]
7085 foreach id $refids {
7086 set v [listrefs $id]
7087 if {![info exists ref($id)] || $ref($id) != $v ||
7088 ($id eq $oldmainhead && $id ne $mainhead) ||
7089 ($id eq $mainhead && $id ne $oldmainhead)} {
7090 redrawtags $id
7095 proc listrefs {id} {
7096 global idtags idheads idotherrefs
7098 set x {}
7099 if {[info exists idtags($id)]} {
7100 set x $idtags($id)
7102 set y {}
7103 if {[info exists idheads($id)]} {
7104 set y $idheads($id)
7106 set z {}
7107 if {[info exists idotherrefs($id)]} {
7108 set z $idotherrefs($id)
7110 return [list $x $y $z]
7113 proc showtag {tag isnew} {
7114 global ctext tagcontents tagids linknum tagobjid
7116 if {$isnew} {
7117 addtohistory [list showtag $tag 0]
7119 $ctext conf -state normal
7120 clear_ctext
7121 set linknum 0
7122 if {![info exists tagcontents($tag)]} {
7123 catch {
7124 set tagcontents($tag) [exec git cat-file tag $tagobjid($tag)]
7127 if {[info exists tagcontents($tag)]} {
7128 set text $tagcontents($tag)
7129 } else {
7130 set text "Tag: $tag\nId: $tagids($tag)"
7132 appendwithlinks $text {}
7133 $ctext conf -state disabled
7134 init_flist {}
7137 proc doquit {} {
7138 global stopped
7139 set stopped 100
7140 savestuff .
7141 destroy .
7144 proc doprefs {} {
7145 global maxwidth maxgraphpct diffopts
7146 global oldprefs prefstop showneartags showlocalchanges
7147 global bgcolor fgcolor ctext diffcolors selectbgcolor
7148 global uifont tabstop
7150 set top .gitkprefs
7151 set prefstop $top
7152 if {[winfo exists $top]} {
7153 raise $top
7154 return
7156 foreach v {maxwidth maxgraphpct diffopts showneartags showlocalchanges} {
7157 set oldprefs($v) [set $v]
7159 toplevel $top
7160 wm title $top "Gitk preferences"
7161 label $top.ldisp -text "Commit list display options"
7162 $top.ldisp configure -font $uifont
7163 grid $top.ldisp - -sticky w -pady 10
7164 label $top.spacer -text " "
7165 label $top.maxwidthl -text "Maximum graph width (lines)" \
7166 -font optionfont
7167 spinbox $top.maxwidth -from 0 -to 100 -width 4 -textvariable maxwidth
7168 grid $top.spacer $top.maxwidthl $top.maxwidth -sticky w
7169 label $top.maxpctl -text "Maximum graph width (% of pane)" \
7170 -font optionfont
7171 spinbox $top.maxpct -from 1 -to 100 -width 4 -textvariable maxgraphpct
7172 grid x $top.maxpctl $top.maxpct -sticky w
7173 frame $top.showlocal
7174 label $top.showlocal.l -text "Show local changes" -font optionfont
7175 checkbutton $top.showlocal.b -variable showlocalchanges
7176 pack $top.showlocal.b $top.showlocal.l -side left
7177 grid x $top.showlocal -sticky w
7179 label $top.ddisp -text "Diff display options"
7180 $top.ddisp configure -font $uifont
7181 grid $top.ddisp - -sticky w -pady 10
7182 label $top.diffoptl -text "Options for diff program" \
7183 -font optionfont
7184 entry $top.diffopt -width 20 -textvariable diffopts
7185 grid x $top.diffoptl $top.diffopt -sticky w
7186 frame $top.ntag
7187 label $top.ntag.l -text "Display nearby tags" -font optionfont
7188 checkbutton $top.ntag.b -variable showneartags
7189 pack $top.ntag.b $top.ntag.l -side left
7190 grid x $top.ntag -sticky w
7191 label $top.tabstopl -text "tabstop" -font optionfont
7192 spinbox $top.tabstop -from 1 -to 20 -width 4 -textvariable tabstop
7193 grid x $top.tabstopl $top.tabstop -sticky w
7195 label $top.cdisp -text "Colors: press to choose"
7196 $top.cdisp configure -font $uifont
7197 grid $top.cdisp - -sticky w -pady 10
7198 label $top.bg -padx 40 -relief sunk -background $bgcolor
7199 button $top.bgbut -text "Background" -font optionfont \
7200 -command [list choosecolor bgcolor 0 $top.bg background setbg]
7201 grid x $top.bgbut $top.bg -sticky w
7202 label $top.fg -padx 40 -relief sunk -background $fgcolor
7203 button $top.fgbut -text "Foreground" -font optionfont \
7204 -command [list choosecolor fgcolor 0 $top.fg foreground setfg]
7205 grid x $top.fgbut $top.fg -sticky w
7206 label $top.diffold -padx 40 -relief sunk -background [lindex $diffcolors 0]
7207 button $top.diffoldbut -text "Diff: old lines" -font optionfont \
7208 -command [list choosecolor diffcolors 0 $top.diffold "diff old lines" \
7209 [list $ctext tag conf d0 -foreground]]
7210 grid x $top.diffoldbut $top.diffold -sticky w
7211 label $top.diffnew -padx 40 -relief sunk -background [lindex $diffcolors 1]
7212 button $top.diffnewbut -text "Diff: new lines" -font optionfont \
7213 -command [list choosecolor diffcolors 1 $top.diffnew "diff new lines" \
7214 [list $ctext tag conf d1 -foreground]]
7215 grid x $top.diffnewbut $top.diffnew -sticky w
7216 label $top.hunksep -padx 40 -relief sunk -background [lindex $diffcolors 2]
7217 button $top.hunksepbut -text "Diff: hunk header" -font optionfont \
7218 -command [list choosecolor diffcolors 2 $top.hunksep \
7219 "diff hunk header" \
7220 [list $ctext tag conf hunksep -foreground]]
7221 grid x $top.hunksepbut $top.hunksep -sticky w
7222 label $top.selbgsep -padx 40 -relief sunk -background $selectbgcolor
7223 button $top.selbgbut -text "Select bg" -font optionfont \
7224 -command [list choosecolor selectbgcolor 0 $top.selbgsep background setselbg]
7225 grid x $top.selbgbut $top.selbgsep -sticky w
7227 frame $top.buts
7228 button $top.buts.ok -text "OK" -command prefsok -default active
7229 $top.buts.ok configure -font $uifont
7230 button $top.buts.can -text "Cancel" -command prefscan -default normal
7231 $top.buts.can configure -font $uifont
7232 grid $top.buts.ok $top.buts.can
7233 grid columnconfigure $top.buts 0 -weight 1 -uniform a
7234 grid columnconfigure $top.buts 1 -weight 1 -uniform a
7235 grid $top.buts - - -pady 10 -sticky ew
7236 bind $top <Visibility> "focus $top.buts.ok"
7239 proc choosecolor {v vi w x cmd} {
7240 global $v
7242 set c [tk_chooseColor -initialcolor [lindex [set $v] $vi] \
7243 -title "Gitk: choose color for $x"]
7244 if {$c eq {}} return
7245 $w conf -background $c
7246 lset $v $vi $c
7247 eval $cmd $c
7250 proc setselbg {c} {
7251 global bglist cflist
7252 foreach w $bglist {
7253 $w configure -selectbackground $c
7255 $cflist tag configure highlight \
7256 -background [$cflist cget -selectbackground]
7257 allcanvs itemconf secsel -fill $c
7260 proc setbg {c} {
7261 global bglist
7263 foreach w $bglist {
7264 $w conf -background $c
7268 proc setfg {c} {
7269 global fglist canv
7271 foreach w $fglist {
7272 $w conf -foreground $c
7274 allcanvs itemconf text -fill $c
7275 $canv itemconf circle -outline $c
7278 proc prefscan {} {
7279 global maxwidth maxgraphpct diffopts
7280 global oldprefs prefstop showneartags showlocalchanges
7282 foreach v {maxwidth maxgraphpct diffopts showneartags showlocalchanges} {
7283 set $v $oldprefs($v)
7285 catch {destroy $prefstop}
7286 unset prefstop
7289 proc prefsok {} {
7290 global maxwidth maxgraphpct
7291 global oldprefs prefstop showneartags showlocalchanges
7292 global charspc ctext tabstop
7294 catch {destroy $prefstop}
7295 unset prefstop
7296 $ctext configure -tabs "[expr {$tabstop * $charspc}]"
7297 if {$showlocalchanges != $oldprefs(showlocalchanges)} {
7298 if {$showlocalchanges} {
7299 doshowlocalchanges
7300 } else {
7301 dohidelocalchanges
7304 if {$maxwidth != $oldprefs(maxwidth)
7305 || $maxgraphpct != $oldprefs(maxgraphpct)} {
7306 redisplay
7307 } elseif {$showneartags != $oldprefs(showneartags)} {
7308 reselectline
7312 proc formatdate {d} {
7313 if {$d ne {}} {
7314 set d [clock format $d -format "%Y-%m-%d %H:%M:%S"]
7316 return $d
7319 # This list of encoding names and aliases is distilled from
7320 # http://www.iana.org/assignments/character-sets.
7321 # Not all of them are supported by Tcl.
7322 set encoding_aliases {
7323 { ANSI_X3.4-1968 iso-ir-6 ANSI_X3.4-1986 ISO_646.irv:1991 ASCII
7324 ISO646-US US-ASCII us IBM367 cp367 csASCII }
7325 { ISO-10646-UTF-1 csISO10646UTF1 }
7326 { ISO_646.basic:1983 ref csISO646basic1983 }
7327 { INVARIANT csINVARIANT }
7328 { ISO_646.irv:1983 iso-ir-2 irv csISO2IntlRefVersion }
7329 { BS_4730 iso-ir-4 ISO646-GB gb uk csISO4UnitedKingdom }
7330 { NATS-SEFI iso-ir-8-1 csNATSSEFI }
7331 { NATS-SEFI-ADD iso-ir-8-2 csNATSSEFIADD }
7332 { NATS-DANO iso-ir-9-1 csNATSDANO }
7333 { NATS-DANO-ADD iso-ir-9-2 csNATSDANOADD }
7334 { SEN_850200_B iso-ir-10 FI ISO646-FI ISO646-SE se csISO10Swedish }
7335 { SEN_850200_C iso-ir-11 ISO646-SE2 se2 csISO11SwedishForNames }
7336 { KS_C_5601-1987 iso-ir-149 KS_C_5601-1989 KSC_5601 korean csKSC56011987 }
7337 { ISO-2022-KR csISO2022KR }
7338 { EUC-KR csEUCKR }
7339 { ISO-2022-JP csISO2022JP }
7340 { ISO-2022-JP-2 csISO2022JP2 }
7341 { JIS_C6220-1969-jp JIS_C6220-1969 iso-ir-13 katakana x0201-7
7342 csISO13JISC6220jp }
7343 { JIS_C6220-1969-ro iso-ir-14 jp ISO646-JP csISO14JISC6220ro }
7344 { IT iso-ir-15 ISO646-IT csISO15Italian }
7345 { PT iso-ir-16 ISO646-PT csISO16Portuguese }
7346 { ES iso-ir-17 ISO646-ES csISO17Spanish }
7347 { greek7-old iso-ir-18 csISO18Greek7Old }
7348 { latin-greek iso-ir-19 csISO19LatinGreek }
7349 { DIN_66003 iso-ir-21 de ISO646-DE csISO21German }
7350 { NF_Z_62-010_(1973) iso-ir-25 ISO646-FR1 csISO25French }
7351 { Latin-greek-1 iso-ir-27 csISO27LatinGreek1 }
7352 { ISO_5427 iso-ir-37 csISO5427Cyrillic }
7353 { JIS_C6226-1978 iso-ir-42 csISO42JISC62261978 }
7354 { BS_viewdata iso-ir-47 csISO47BSViewdata }
7355 { INIS iso-ir-49 csISO49INIS }
7356 { INIS-8 iso-ir-50 csISO50INIS8 }
7357 { INIS-cyrillic iso-ir-51 csISO51INISCyrillic }
7358 { ISO_5427:1981 iso-ir-54 ISO5427Cyrillic1981 }
7359 { ISO_5428:1980 iso-ir-55 csISO5428Greek }
7360 { GB_1988-80 iso-ir-57 cn ISO646-CN csISO57GB1988 }
7361 { GB_2312-80 iso-ir-58 chinese csISO58GB231280 }
7362 { NS_4551-1 iso-ir-60 ISO646-NO no csISO60DanishNorwegian
7363 csISO60Norwegian1 }
7364 { NS_4551-2 ISO646-NO2 iso-ir-61 no2 csISO61Norwegian2 }
7365 { NF_Z_62-010 iso-ir-69 ISO646-FR fr csISO69French }
7366 { videotex-suppl iso-ir-70 csISO70VideotexSupp1 }
7367 { PT2 iso-ir-84 ISO646-PT2 csISO84Portuguese2 }
7368 { ES2 iso-ir-85 ISO646-ES2 csISO85Spanish2 }
7369 { MSZ_7795.3 iso-ir-86 ISO646-HU hu csISO86Hungarian }
7370 { JIS_C6226-1983 iso-ir-87 x0208 JIS_X0208-1983 csISO87JISX0208 }
7371 { greek7 iso-ir-88 csISO88Greek7 }
7372 { ASMO_449 ISO_9036 arabic7 iso-ir-89 csISO89ASMO449 }
7373 { iso-ir-90 csISO90 }
7374 { JIS_C6229-1984-a iso-ir-91 jp-ocr-a csISO91JISC62291984a }
7375 { JIS_C6229-1984-b iso-ir-92 ISO646-JP-OCR-B jp-ocr-b
7376 csISO92JISC62991984b }
7377 { JIS_C6229-1984-b-add iso-ir-93 jp-ocr-b-add csISO93JIS62291984badd }
7378 { JIS_C6229-1984-hand iso-ir-94 jp-ocr-hand csISO94JIS62291984hand }
7379 { JIS_C6229-1984-hand-add iso-ir-95 jp-ocr-hand-add
7380 csISO95JIS62291984handadd }
7381 { JIS_C6229-1984-kana iso-ir-96 csISO96JISC62291984kana }
7382 { ISO_2033-1983 iso-ir-98 e13b csISO2033 }
7383 { ANSI_X3.110-1983 iso-ir-99 CSA_T500-1983 NAPLPS csISO99NAPLPS }
7384 { ISO_8859-1:1987 iso-ir-100 ISO_8859-1 ISO-8859-1 latin1 l1 IBM819
7385 CP819 csISOLatin1 }
7386 { ISO_8859-2:1987 iso-ir-101 ISO_8859-2 ISO-8859-2 latin2 l2 csISOLatin2 }
7387 { T.61-7bit iso-ir-102 csISO102T617bit }
7388 { T.61-8bit T.61 iso-ir-103 csISO103T618bit }
7389 { ISO_8859-3:1988 iso-ir-109 ISO_8859-3 ISO-8859-3 latin3 l3 csISOLatin3 }
7390 { ISO_8859-4:1988 iso-ir-110 ISO_8859-4 ISO-8859-4 latin4 l4 csISOLatin4 }
7391 { ECMA-cyrillic iso-ir-111 KOI8-E csISO111ECMACyrillic }
7392 { CSA_Z243.4-1985-1 iso-ir-121 ISO646-CA csa7-1 ca csISO121Canadian1 }
7393 { CSA_Z243.4-1985-2 iso-ir-122 ISO646-CA2 csa7-2 csISO122Canadian2 }
7394 { CSA_Z243.4-1985-gr iso-ir-123 csISO123CSAZ24341985gr }
7395 { ISO_8859-6:1987 iso-ir-127 ISO_8859-6 ISO-8859-6 ECMA-114 ASMO-708
7396 arabic csISOLatinArabic }
7397 { ISO_8859-6-E csISO88596E ISO-8859-6-E }
7398 { ISO_8859-6-I csISO88596I ISO-8859-6-I }
7399 { ISO_8859-7:1987 iso-ir-126 ISO_8859-7 ISO-8859-7 ELOT_928 ECMA-118
7400 greek greek8 csISOLatinGreek }
7401 { T.101-G2 iso-ir-128 csISO128T101G2 }
7402 { ISO_8859-8:1988 iso-ir-138 ISO_8859-8 ISO-8859-8 hebrew
7403 csISOLatinHebrew }
7404 { ISO_8859-8-E csISO88598E ISO-8859-8-E }
7405 { ISO_8859-8-I csISO88598I ISO-8859-8-I }
7406 { CSN_369103 iso-ir-139 csISO139CSN369103 }
7407 { JUS_I.B1.002 iso-ir-141 ISO646-YU js yu csISO141JUSIB1002 }
7408 { ISO_6937-2-add iso-ir-142 csISOTextComm }
7409 { IEC_P27-1 iso-ir-143 csISO143IECP271 }
7410 { ISO_8859-5:1988 iso-ir-144 ISO_8859-5 ISO-8859-5 cyrillic
7411 csISOLatinCyrillic }
7412 { JUS_I.B1.003-serb iso-ir-146 serbian csISO146Serbian }
7413 { JUS_I.B1.003-mac macedonian iso-ir-147 csISO147Macedonian }
7414 { ISO_8859-9:1989 iso-ir-148 ISO_8859-9 ISO-8859-9 latin5 l5 csISOLatin5 }
7415 { greek-ccitt iso-ir-150 csISO150 csISO150GreekCCITT }
7416 { NC_NC00-10:81 cuba iso-ir-151 ISO646-CU csISO151Cuba }
7417 { ISO_6937-2-25 iso-ir-152 csISO6937Add }
7418 { GOST_19768-74 ST_SEV_358-88 iso-ir-153 csISO153GOST1976874 }
7419 { ISO_8859-supp iso-ir-154 latin1-2-5 csISO8859Supp }
7420 { ISO_10367-box iso-ir-155 csISO10367Box }
7421 { ISO-8859-10 iso-ir-157 l6 ISO_8859-10:1992 csISOLatin6 latin6 }
7422 { latin-lap lap iso-ir-158 csISO158Lap }
7423 { JIS_X0212-1990 x0212 iso-ir-159 csISO159JISX02121990 }
7424 { DS_2089 DS2089 ISO646-DK dk csISO646Danish }
7425 { us-dk csUSDK }
7426 { dk-us csDKUS }
7427 { JIS_X0201 X0201 csHalfWidthKatakana }
7428 { KSC5636 ISO646-KR csKSC5636 }
7429 { ISO-10646-UCS-2 csUnicode }
7430 { ISO-10646-UCS-4 csUCS4 }
7431 { DEC-MCS dec csDECMCS }
7432 { hp-roman8 roman8 r8 csHPRoman8 }
7433 { macintosh mac csMacintosh }
7434 { IBM037 cp037 ebcdic-cp-us ebcdic-cp-ca ebcdic-cp-wt ebcdic-cp-nl
7435 csIBM037 }
7436 { IBM038 EBCDIC-INT cp038 csIBM038 }
7437 { IBM273 CP273 csIBM273 }
7438 { IBM274 EBCDIC-BE CP274 csIBM274 }
7439 { IBM275 EBCDIC-BR cp275 csIBM275 }
7440 { IBM277 EBCDIC-CP-DK EBCDIC-CP-NO csIBM277 }
7441 { IBM278 CP278 ebcdic-cp-fi ebcdic-cp-se csIBM278 }
7442 { IBM280 CP280 ebcdic-cp-it csIBM280 }
7443 { IBM281 EBCDIC-JP-E cp281 csIBM281 }
7444 { IBM284 CP284 ebcdic-cp-es csIBM284 }
7445 { IBM285 CP285 ebcdic-cp-gb csIBM285 }
7446 { IBM290 cp290 EBCDIC-JP-kana csIBM290 }
7447 { IBM297 cp297 ebcdic-cp-fr csIBM297 }
7448 { IBM420 cp420 ebcdic-cp-ar1 csIBM420 }
7449 { IBM423 cp423 ebcdic-cp-gr csIBM423 }
7450 { IBM424 cp424 ebcdic-cp-he csIBM424 }
7451 { IBM437 cp437 437 csPC8CodePage437 }
7452 { IBM500 CP500 ebcdic-cp-be ebcdic-cp-ch csIBM500 }
7453 { IBM775 cp775 csPC775Baltic }
7454 { IBM850 cp850 850 csPC850Multilingual }
7455 { IBM851 cp851 851 csIBM851 }
7456 { IBM852 cp852 852 csPCp852 }
7457 { IBM855 cp855 855 csIBM855 }
7458 { IBM857 cp857 857 csIBM857 }
7459 { IBM860 cp860 860 csIBM860 }
7460 { IBM861 cp861 861 cp-is csIBM861 }
7461 { IBM862 cp862 862 csPC862LatinHebrew }
7462 { IBM863 cp863 863 csIBM863 }
7463 { IBM864 cp864 csIBM864 }
7464 { IBM865 cp865 865 csIBM865 }
7465 { IBM866 cp866 866 csIBM866 }
7466 { IBM868 CP868 cp-ar csIBM868 }
7467 { IBM869 cp869 869 cp-gr csIBM869 }
7468 { IBM870 CP870 ebcdic-cp-roece ebcdic-cp-yu csIBM870 }
7469 { IBM871 CP871 ebcdic-cp-is csIBM871 }
7470 { IBM880 cp880 EBCDIC-Cyrillic csIBM880 }
7471 { IBM891 cp891 csIBM891 }
7472 { IBM903 cp903 csIBM903 }
7473 { IBM904 cp904 904 csIBBM904 }
7474 { IBM905 CP905 ebcdic-cp-tr csIBM905 }
7475 { IBM918 CP918 ebcdic-cp-ar2 csIBM918 }
7476 { IBM1026 CP1026 csIBM1026 }
7477 { EBCDIC-AT-DE csIBMEBCDICATDE }
7478 { EBCDIC-AT-DE-A csEBCDICATDEA }
7479 { EBCDIC-CA-FR csEBCDICCAFR }
7480 { EBCDIC-DK-NO csEBCDICDKNO }
7481 { EBCDIC-DK-NO-A csEBCDICDKNOA }
7482 { EBCDIC-FI-SE csEBCDICFISE }
7483 { EBCDIC-FI-SE-A csEBCDICFISEA }
7484 { EBCDIC-FR csEBCDICFR }
7485 { EBCDIC-IT csEBCDICIT }
7486 { EBCDIC-PT csEBCDICPT }
7487 { EBCDIC-ES csEBCDICES }
7488 { EBCDIC-ES-A csEBCDICESA }
7489 { EBCDIC-ES-S csEBCDICESS }
7490 { EBCDIC-UK csEBCDICUK }
7491 { EBCDIC-US csEBCDICUS }
7492 { UNKNOWN-8BIT csUnknown8BiT }
7493 { MNEMONIC csMnemonic }
7494 { MNEM csMnem }
7495 { VISCII csVISCII }
7496 { VIQR csVIQR }
7497 { KOI8-R csKOI8R }
7498 { IBM00858 CCSID00858 CP00858 PC-Multilingual-850+euro }
7499 { IBM00924 CCSID00924 CP00924 ebcdic-Latin9--euro }
7500 { IBM01140 CCSID01140 CP01140 ebcdic-us-37+euro }
7501 { IBM01141 CCSID01141 CP01141 ebcdic-de-273+euro }
7502 { IBM01142 CCSID01142 CP01142 ebcdic-dk-277+euro ebcdic-no-277+euro }
7503 { IBM01143 CCSID01143 CP01143 ebcdic-fi-278+euro ebcdic-se-278+euro }
7504 { IBM01144 CCSID01144 CP01144 ebcdic-it-280+euro }
7505 { IBM01145 CCSID01145 CP01145 ebcdic-es-284+euro }
7506 { IBM01146 CCSID01146 CP01146 ebcdic-gb-285+euro }
7507 { IBM01147 CCSID01147 CP01147 ebcdic-fr-297+euro }
7508 { IBM01148 CCSID01148 CP01148 ebcdic-international-500+euro }
7509 { IBM01149 CCSID01149 CP01149 ebcdic-is-871+euro }
7510 { IBM1047 IBM-1047 }
7511 { PTCP154 csPTCP154 PT154 CP154 Cyrillic-Asian }
7512 { Amiga-1251 Ami1251 Amiga1251 Ami-1251 }
7513 { UNICODE-1-1 csUnicode11 }
7514 { CESU-8 csCESU-8 }
7515 { BOCU-1 csBOCU-1 }
7516 { UNICODE-1-1-UTF-7 csUnicode11UTF7 }
7517 { ISO-8859-14 iso-ir-199 ISO_8859-14:1998 ISO_8859-14 latin8 iso-celtic
7518 l8 }
7519 { ISO-8859-15 ISO_8859-15 Latin-9 }
7520 { ISO-8859-16 iso-ir-226 ISO_8859-16:2001 ISO_8859-16 latin10 l10 }
7521 { GBK CP936 MS936 windows-936 }
7522 { JIS_Encoding csJISEncoding }
7523 { Shift_JIS MS_Kanji csShiftJIS }
7524 { Extended_UNIX_Code_Packed_Format_for_Japanese csEUCPkdFmtJapanese
7525 EUC-JP }
7526 { Extended_UNIX_Code_Fixed_Width_for_Japanese csEUCFixWidJapanese }
7527 { ISO-10646-UCS-Basic csUnicodeASCII }
7528 { ISO-10646-Unicode-Latin1 csUnicodeLatin1 ISO-10646 }
7529 { ISO-Unicode-IBM-1261 csUnicodeIBM1261 }
7530 { ISO-Unicode-IBM-1268 csUnicodeIBM1268 }
7531 { ISO-Unicode-IBM-1276 csUnicodeIBM1276 }
7532 { ISO-Unicode-IBM-1264 csUnicodeIBM1264 }
7533 { ISO-Unicode-IBM-1265 csUnicodeIBM1265 }
7534 { ISO-8859-1-Windows-3.0-Latin-1 csWindows30Latin1 }
7535 { ISO-8859-1-Windows-3.1-Latin-1 csWindows31Latin1 }
7536 { ISO-8859-2-Windows-Latin-2 csWindows31Latin2 }
7537 { ISO-8859-9-Windows-Latin-5 csWindows31Latin5 }
7538 { Adobe-Standard-Encoding csAdobeStandardEncoding }
7539 { Ventura-US csVenturaUS }
7540 { Ventura-International csVenturaInternational }
7541 { PC8-Danish-Norwegian csPC8DanishNorwegian }
7542 { PC8-Turkish csPC8Turkish }
7543 { IBM-Symbols csIBMSymbols }
7544 { IBM-Thai csIBMThai }
7545 { HP-Legal csHPLegal }
7546 { HP-Pi-font csHPPiFont }
7547 { HP-Math8 csHPMath8 }
7548 { Adobe-Symbol-Encoding csHPPSMath }
7549 { HP-DeskTop csHPDesktop }
7550 { Ventura-Math csVenturaMath }
7551 { Microsoft-Publishing csMicrosoftPublishing }
7552 { Windows-31J csWindows31J }
7553 { GB2312 csGB2312 }
7554 { Big5 csBig5 }
7557 proc tcl_encoding {enc} {
7558 global encoding_aliases
7559 set names [encoding names]
7560 set lcnames [string tolower $names]
7561 set enc [string tolower $enc]
7562 set i [lsearch -exact $lcnames $enc]
7563 if {$i < 0} {
7564 # look for "isonnn" instead of "iso-nnn" or "iso_nnn"
7565 if {[regsub {^iso[-_]} $enc iso encx]} {
7566 set i [lsearch -exact $lcnames $encx]
7569 if {$i < 0} {
7570 foreach l $encoding_aliases {
7571 set ll [string tolower $l]
7572 if {[lsearch -exact $ll $enc] < 0} continue
7573 # look through the aliases for one that tcl knows about
7574 foreach e $ll {
7575 set i [lsearch -exact $lcnames $e]
7576 if {$i < 0} {
7577 if {[regsub {^iso[-_]} $e iso ex]} {
7578 set i [lsearch -exact $lcnames $ex]
7581 if {$i >= 0} break
7583 break
7586 if {$i >= 0} {
7587 return [lindex $names $i]
7589 return {}
7592 # defaults...
7593 set datemode 0
7594 set diffopts "-U 5 -p"
7595 set wrcomcmd "git diff-tree --stdin -p --pretty"
7597 set gitencoding {}
7598 catch {
7599 set gitencoding [exec git config --get i18n.commitencoding]
7601 if {$gitencoding == ""} {
7602 set gitencoding "utf-8"
7604 set tclencoding [tcl_encoding $gitencoding]
7605 if {$tclencoding == {}} {
7606 puts stderr "Warning: encoding $gitencoding is not supported by Tcl/Tk"
7609 set mainfont {Helvetica 9}
7610 set textfont {Courier 9}
7611 set uifont {Helvetica 9 bold}
7612 set tabstop 8
7613 set findmergefiles 0
7614 set maxgraphpct 50
7615 set maxwidth 16
7616 set revlistorder 0
7617 set fastdate 0
7618 set uparrowlen 7
7619 set downarrowlen 7
7620 set mingaplen 30
7621 set cmitmode "patch"
7622 set wrapcomment "none"
7623 set showneartags 1
7624 set maxrefs 20
7625 set maxlinelen 200
7626 set showlocalchanges 1
7628 set colors {green red blue magenta darkgrey brown orange}
7629 set bgcolor white
7630 set fgcolor black
7631 set diffcolors {red "#00a000" blue}
7632 set selectbgcolor gray85
7634 catch {source ~/.gitk}
7636 font create optionfont -family sans-serif -size -12
7638 # check that we can find a .git directory somewhere...
7639 if {[catch {set gitdir [gitdir]}]} {
7640 show_error {} . "Cannot find a git repository here."
7641 exit 1
7643 if {![file isdirectory $gitdir]} {
7644 show_error {} . "Cannot find the git directory \"$gitdir\"."
7645 exit 1
7648 set revtreeargs {}
7649 set cmdline_files {}
7650 set i 0
7651 foreach arg $argv {
7652 switch -- $arg {
7653 "" { }
7654 "-d" { set datemode 1 }
7655 "--" {
7656 set cmdline_files [lrange $argv [expr {$i + 1}] end]
7657 break
7659 default {
7660 lappend revtreeargs $arg
7663 incr i
7666 if {$i >= [llength $argv] && $revtreeargs ne {}} {
7667 # no -- on command line, but some arguments (other than -d)
7668 if {[catch {
7669 set f [eval exec git rev-parse --no-revs --no-flags $revtreeargs]
7670 set cmdline_files [split $f "\n"]
7671 set n [llength $cmdline_files]
7672 set revtreeargs [lrange $revtreeargs 0 end-$n]
7673 # Unfortunately git rev-parse doesn't produce an error when
7674 # something is both a revision and a filename. To be consistent
7675 # with git log and git rev-list, check revtreeargs for filenames.
7676 foreach arg $revtreeargs {
7677 if {[file exists $arg]} {
7678 show_error {} . "Ambiguous argument '$arg': both revision\
7679 and filename"
7680 exit 1
7683 } err]} {
7684 # unfortunately we get both stdout and stderr in $err,
7685 # so look for "fatal:".
7686 set i [string first "fatal:" $err]
7687 if {$i > 0} {
7688 set err [string range $err [expr {$i + 6}] end]
7690 show_error {} . "Bad arguments to gitk:\n$err"
7691 exit 1
7695 set nullid "0000000000000000000000000000000000000000"
7696 set nullid2 "0000000000000000000000000000000000000001"
7699 set runq {}
7700 set history {}
7701 set historyindex 0
7702 set fh_serial 0
7703 set nhl_names {}
7704 set highlight_paths {}
7705 set searchdirn -forwards
7706 set boldrows {}
7707 set boldnamerows {}
7708 set diffelide {0 0}
7709 set markingmatches 0
7711 set optim_delay 16
7713 set nextviewnum 1
7714 set curview 0
7715 set selectedview 0
7716 set selectedhlview None
7717 set viewfiles(0) {}
7718 set viewperm(0) 0
7719 set viewargs(0) {}
7721 set cmdlineok 0
7722 set stopped 0
7723 set stuffsaved 0
7724 set patchnum 0
7725 set lookingforhead 0
7726 set localirow -1
7727 set localfrow -1
7728 set lserial 0
7729 setcoords
7730 makewindow
7731 # wait for the window to become visible
7732 tkwait visibility .
7733 wm title . "[file tail $argv0]: [file tail [pwd]]"
7734 readrefs
7736 if {$cmdline_files ne {} || $revtreeargs ne {}} {
7737 # create a view for the files/dirs specified on the command line
7738 set curview 1
7739 set selectedview 1
7740 set nextviewnum 2
7741 set viewname(1) "Command line"
7742 set viewfiles(1) $cmdline_files
7743 set viewargs(1) $revtreeargs
7744 set viewperm(1) 0
7745 addviewmenu 1
7746 .bar.view entryconf Edit* -state normal
7747 .bar.view entryconf Delete* -state normal
7750 if {[info exists permviews]} {
7751 foreach v $permviews {
7752 set n $nextviewnum
7753 incr nextviewnum
7754 set viewname($n) [lindex $v 0]
7755 set viewfiles($n) [lindex $v 1]
7756 set viewargs($n) [lindex $v 2]
7757 set viewperm($n) 1
7758 addviewmenu $n
7761 getcommits