gitk: New algorithm for drawing the graph lines
[git/libgit-gsoc.git] / gitk
blob21eefc40a8d11758a4b1233fb04ec4eea8a0e02b
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
87 set startmsecs [clock clicks -milliseconds]
88 set commitidx($view) 0
89 set args $viewargs($view)
90 if {$viewfiles($view) ne {}} {
91 set args [concat $args "--" $viewfiles($view)]
93 set order "--topo-order"
94 if {$datemode} {
95 set order "--date-order"
97 if {[catch {
98 set fd [open [concat | git rev-list --header $order \
99 --parents --boundary --default HEAD $args] r]
100 } err]} {
101 puts stderr "Error executing git rev-list: $err"
102 exit 1
104 set commfd($view) $fd
105 set leftover($view) {}
106 fconfigure $fd -blocking 0 -translation lf
107 if {$tclencoding != {}} {
108 fconfigure $fd -encoding $tclencoding
110 filerun $fd [list getcommitlines $fd $view]
111 nowbusy $view
114 proc stop_rev_list {} {
115 global commfd curview
117 if {![info exists commfd($curview)]} return
118 set fd $commfd($curview)
119 catch {
120 set pid [pid $fd]
121 exec kill $pid
123 catch {close $fd}
124 unset commfd($curview)
127 proc getcommits {} {
128 global phase canv mainfont curview
130 set phase getcommits
131 initlayout
132 start_rev_list $curview
133 show_status "Reading commits..."
136 proc getcommitlines {fd view} {
137 global commitlisted
138 global leftover commfd
139 global displayorder commitidx commitrow commitdata
140 global parentlist childlist children curview hlview
141 global vparentlist vchildlist vdisporder vcmitlisted
143 set stuff [read $fd 500000]
144 if {$stuff == {}} {
145 if {![eof $fd]} {
146 return 1
148 global viewname
149 unset commfd($view)
150 notbusy $view
151 # set it blocking so we wait for the process to terminate
152 fconfigure $fd -blocking 1
153 if {[catch {close $fd} err]} {
154 set fv {}
155 if {$view != $curview} {
156 set fv " for the \"$viewname($view)\" view"
158 if {[string range $err 0 4] == "usage"} {
159 set err "Gitk: error reading commits$fv:\
160 bad arguments to git rev-list."
161 if {$viewname($view) eq "Command line"} {
162 append err \
163 " (Note: arguments to gitk are passed to git rev-list\
164 to allow selection of commits to be displayed.)"
166 } else {
167 set err "Error reading commits$fv: $err"
169 error_popup $err
171 if {$view == $curview} {
172 run chewcommits $view
174 return 0
176 set start 0
177 set gotsome 0
178 while 1 {
179 set i [string first "\0" $stuff $start]
180 if {$i < 0} {
181 append leftover($view) [string range $stuff $start end]
182 break
184 if {$start == 0} {
185 set cmit $leftover($view)
186 append cmit [string range $stuff 0 [expr {$i - 1}]]
187 set leftover($view) {}
188 } else {
189 set cmit [string range $stuff $start [expr {$i - 1}]]
191 set start [expr {$i + 1}]
192 set j [string first "\n" $cmit]
193 set ok 0
194 set listed 1
195 if {$j >= 0} {
196 set ids [string range $cmit 0 [expr {$j - 1}]]
197 if {[string range $ids 0 0] == "-"} {
198 set listed 0
199 set ids [string range $ids 1 end]
201 set ok 1
202 foreach id $ids {
203 if {[string length $id] != 40} {
204 set ok 0
205 break
209 if {!$ok} {
210 set shortcmit $cmit
211 if {[string length $shortcmit] > 80} {
212 set shortcmit "[string range $shortcmit 0 80]..."
214 error_popup "Can't parse git rev-list output: {$shortcmit}"
215 exit 1
217 set id [lindex $ids 0]
218 if {$listed} {
219 set olds [lrange $ids 1 end]
220 set i 0
221 foreach p $olds {
222 if {$i == 0 || [lsearch -exact $olds $p] >= $i} {
223 lappend children($view,$p) $id
225 incr i
227 } else {
228 set olds {}
230 if {![info exists children($view,$id)]} {
231 set children($view,$id) {}
233 set commitdata($id) [string range $cmit [expr {$j + 1}] end]
234 set commitrow($view,$id) $commitidx($view)
235 incr commitidx($view)
236 if {$view == $curview} {
237 lappend parentlist $olds
238 lappend childlist $children($view,$id)
239 lappend displayorder $id
240 lappend commitlisted $listed
241 } else {
242 lappend vparentlist($view) $olds
243 lappend vchildlist($view) $children($view,$id)
244 lappend vdisporder($view) $id
245 lappend vcmitlisted($view) $listed
247 set gotsome 1
249 if {$gotsome} {
250 run chewcommits $view
252 return 2
255 proc chewcommits {view} {
256 global curview hlview commfd
257 global selectedline pending_select
259 set more 0
260 if {$view == $curview} {
261 set allread [expr {![info exists commfd($view)]}]
262 set tlimit [expr {[clock clicks -milliseconds] + 50}]
263 set more [layoutmore $tlimit $allread]
264 if {$allread && !$more} {
265 global displayorder commitidx phase
266 global numcommits startmsecs
268 if {[info exists pending_select]} {
269 set row [expr {[lindex $displayorder 0] eq $nullid}]
270 selectline $row 1
272 if {$commitidx($curview) > 0} {
273 #set ms [expr {[clock clicks -milliseconds] - $startmsecs}]
274 #puts "overall $ms ms for $numcommits commits"
275 } else {
276 show_status "No commits selected"
278 notbusy layout
279 set phase {}
282 if {[info exists hlview] && $view == $hlview} {
283 vhighlightmore
285 return $more
288 proc readcommit {id} {
289 if {[catch {set contents [exec git cat-file commit $id]}]} return
290 parsecommit $id $contents 0
293 proc updatecommits {} {
294 global viewdata curview phase displayorder
295 global children commitrow selectedline thickerline
297 if {$phase ne {}} {
298 stop_rev_list
299 set phase {}
301 set n $curview
302 foreach id $displayorder {
303 catch {unset children($n,$id)}
304 catch {unset commitrow($n,$id)}
306 set curview -1
307 catch {unset selectedline}
308 catch {unset thickerline}
309 catch {unset viewdata($n)}
310 readrefs
311 changedrefs
312 regetallcommits
313 showview $n
316 proc parsecommit {id contents listed} {
317 global commitinfo cdate
319 set inhdr 1
320 set comment {}
321 set headline {}
322 set auname {}
323 set audate {}
324 set comname {}
325 set comdate {}
326 set hdrend [string first "\n\n" $contents]
327 if {$hdrend < 0} {
328 # should never happen...
329 set hdrend [string length $contents]
331 set header [string range $contents 0 [expr {$hdrend - 1}]]
332 set comment [string range $contents [expr {$hdrend + 2}] end]
333 foreach line [split $header "\n"] {
334 set tag [lindex $line 0]
335 if {$tag == "author"} {
336 set audate [lindex $line end-1]
337 set auname [lrange $line 1 end-2]
338 } elseif {$tag == "committer"} {
339 set comdate [lindex $line end-1]
340 set comname [lrange $line 1 end-2]
343 set headline {}
344 # take the first non-blank line of the comment as the headline
345 set headline [string trimleft $comment]
346 set i [string first "\n" $headline]
347 if {$i >= 0} {
348 set headline [string range $headline 0 $i]
350 set headline [string trimright $headline]
351 set i [string first "\r" $headline]
352 if {$i >= 0} {
353 set headline [string trimright [string range $headline 0 $i]]
355 if {!$listed} {
356 # git rev-list indents the comment by 4 spaces;
357 # if we got this via git cat-file, add the indentation
358 set newcomment {}
359 foreach line [split $comment "\n"] {
360 append newcomment " "
361 append newcomment $line
362 append newcomment "\n"
364 set comment $newcomment
366 if {$comdate != {}} {
367 set cdate($id) $comdate
369 set commitinfo($id) [list $headline $auname $audate \
370 $comname $comdate $comment]
373 proc getcommit {id} {
374 global commitdata commitinfo
376 if {[info exists commitdata($id)]} {
377 parsecommit $id $commitdata($id) 1
378 } else {
379 readcommit $id
380 if {![info exists commitinfo($id)]} {
381 set commitinfo($id) {"No commit information available"}
384 return 1
387 proc readrefs {} {
388 global tagids idtags headids idheads tagcontents
389 global otherrefids idotherrefs mainhead
391 foreach v {tagids idtags headids idheads otherrefids idotherrefs} {
392 catch {unset $v}
394 set refd [open [list | git show-ref] r]
395 while {0 <= [set n [gets $refd line]]} {
396 if {![regexp {^([0-9a-f]{40}) refs/([^^]*)$} $line \
397 match id path]} {
398 continue
400 if {[regexp {^remotes/.*/HEAD$} $path match]} {
401 continue
403 if {![regexp {^(tags|heads)/(.*)$} $path match type name]} {
404 set type others
405 set name $path
407 if {[regexp {^remotes/} $path match]} {
408 set type heads
410 if {$type == "tags"} {
411 set tagids($name) $id
412 lappend idtags($id) $name
413 set obj {}
414 set type {}
415 set tag {}
416 catch {
417 set commit [exec git rev-parse "$id^0"]
418 if {$commit != $id} {
419 set tagids($name) $commit
420 lappend idtags($commit) $name
423 catch {
424 set tagcontents($name) [exec git cat-file tag $id]
426 } elseif { $type == "heads" } {
427 set headids($name) $id
428 lappend idheads($id) $name
429 } else {
430 set otherrefids($name) $id
431 lappend idotherrefs($id) $name
434 close $refd
435 set mainhead {}
436 catch {
437 set thehead [exec git symbolic-ref HEAD]
438 if {[string match "refs/heads/*" $thehead]} {
439 set mainhead [string range $thehead 11 end]
444 # update things for a head moved to a child of its previous location
445 proc movehead {id name} {
446 global headids idheads
448 removehead $headids($name) $name
449 set headids($name) $id
450 lappend idheads($id) $name
453 # update things when a head has been removed
454 proc removehead {id name} {
455 global headids idheads
457 if {$idheads($id) eq $name} {
458 unset idheads($id)
459 } else {
460 set i [lsearch -exact $idheads($id) $name]
461 if {$i >= 0} {
462 set idheads($id) [lreplace $idheads($id) $i $i]
465 unset headids($name)
468 proc show_error {w top msg} {
469 message $w.m -text $msg -justify center -aspect 400
470 pack $w.m -side top -fill x -padx 20 -pady 20
471 button $w.ok -text OK -command "destroy $top"
472 pack $w.ok -side bottom -fill x
473 bind $top <Visibility> "grab $top; focus $top"
474 bind $top <Key-Return> "destroy $top"
475 tkwait window $top
478 proc error_popup msg {
479 set w .error
480 toplevel $w
481 wm transient $w .
482 show_error $w $w $msg
485 proc confirm_popup msg {
486 global confirm_ok
487 set confirm_ok 0
488 set w .confirm
489 toplevel $w
490 wm transient $w .
491 message $w.m -text $msg -justify center -aspect 400
492 pack $w.m -side top -fill x -padx 20 -pady 20
493 button $w.ok -text OK -command "set confirm_ok 1; destroy $w"
494 pack $w.ok -side left -fill x
495 button $w.cancel -text Cancel -command "destroy $w"
496 pack $w.cancel -side right -fill x
497 bind $w <Visibility> "grab $w; focus $w"
498 tkwait window $w
499 return $confirm_ok
502 proc makewindow {} {
503 global canv canv2 canv3 linespc charspc ctext cflist
504 global textfont mainfont uifont tabstop
505 global findtype findtypemenu findloc findstring fstring geometry
506 global entries sha1entry sha1string sha1but
507 global maincursor textcursor curtextcursor
508 global rowctxmenu mergemax wrapcomment
509 global highlight_files gdttype
510 global searchstring sstring
511 global bgcolor fgcolor bglist fglist diffcolors selectbgcolor
512 global headctxmenu
514 menu .bar
515 .bar add cascade -label "File" -menu .bar.file
516 .bar configure -font $uifont
517 menu .bar.file
518 .bar.file add command -label "Update" -command updatecommits
519 .bar.file add command -label "Reread references" -command rereadrefs
520 .bar.file add command -label "Quit" -command doquit
521 .bar.file configure -font $uifont
522 menu .bar.edit
523 .bar add cascade -label "Edit" -menu .bar.edit
524 .bar.edit add command -label "Preferences" -command doprefs
525 .bar.edit configure -font $uifont
527 menu .bar.view -font $uifont
528 .bar add cascade -label "View" -menu .bar.view
529 .bar.view add command -label "New view..." -command {newview 0}
530 .bar.view add command -label "Edit view..." -command editview \
531 -state disabled
532 .bar.view add command -label "Delete view" -command delview -state disabled
533 .bar.view add separator
534 .bar.view add radiobutton -label "All files" -command {showview 0} \
535 -variable selectedview -value 0
537 menu .bar.help
538 .bar add cascade -label "Help" -menu .bar.help
539 .bar.help add command -label "About gitk" -command about
540 .bar.help add command -label "Key bindings" -command keys
541 .bar.help configure -font $uifont
542 . configure -menu .bar
544 # the gui has upper and lower half, parts of a paned window.
545 panedwindow .ctop -orient vertical
547 # possibly use assumed geometry
548 if {![info exists geometry(pwsash0)]} {
549 set geometry(topheight) [expr {15 * $linespc}]
550 set geometry(topwidth) [expr {80 * $charspc}]
551 set geometry(botheight) [expr {15 * $linespc}]
552 set geometry(botwidth) [expr {50 * $charspc}]
553 set geometry(pwsash0) "[expr {40 * $charspc}] 2"
554 set geometry(pwsash1) "[expr {60 * $charspc}] 2"
557 # the upper half will have a paned window, a scroll bar to the right, and some stuff below
558 frame .tf -height $geometry(topheight) -width $geometry(topwidth)
559 frame .tf.histframe
560 panedwindow .tf.histframe.pwclist -orient horizontal -sashpad 0 -handlesize 4
562 # create three canvases
563 set cscroll .tf.histframe.csb
564 set canv .tf.histframe.pwclist.canv
565 canvas $canv \
566 -selectbackground $selectbgcolor \
567 -background $bgcolor -bd 0 \
568 -yscrollincr $linespc -yscrollcommand "scrollcanv $cscroll"
569 .tf.histframe.pwclist add $canv
570 set canv2 .tf.histframe.pwclist.canv2
571 canvas $canv2 \
572 -selectbackground $selectbgcolor \
573 -background $bgcolor -bd 0 -yscrollincr $linespc
574 .tf.histframe.pwclist add $canv2
575 set canv3 .tf.histframe.pwclist.canv3
576 canvas $canv3 \
577 -selectbackground $selectbgcolor \
578 -background $bgcolor -bd 0 -yscrollincr $linespc
579 .tf.histframe.pwclist add $canv3
580 eval .tf.histframe.pwclist sash place 0 $geometry(pwsash0)
581 eval .tf.histframe.pwclist sash place 1 $geometry(pwsash1)
583 # a scroll bar to rule them
584 scrollbar $cscroll -command {allcanvs yview} -highlightthickness 0
585 pack $cscroll -side right -fill y
586 bind .tf.histframe.pwclist <Configure> {resizeclistpanes %W %w}
587 lappend bglist $canv $canv2 $canv3
588 pack .tf.histframe.pwclist -fill both -expand 1 -side left
590 # we have two button bars at bottom of top frame. Bar 1
591 frame .tf.bar
592 frame .tf.lbar -height 15
594 set sha1entry .tf.bar.sha1
595 set entries $sha1entry
596 set sha1but .tf.bar.sha1label
597 button $sha1but -text "SHA1 ID: " -state disabled -relief flat \
598 -command gotocommit -width 8 -font $uifont
599 $sha1but conf -disabledforeground [$sha1but cget -foreground]
600 pack .tf.bar.sha1label -side left
601 entry $sha1entry -width 40 -font $textfont -textvariable sha1string
602 trace add variable sha1string write sha1change
603 pack $sha1entry -side left -pady 2
605 image create bitmap bm-left -data {
606 #define left_width 16
607 #define left_height 16
608 static unsigned char left_bits[] = {
609 0x00, 0x00, 0xc0, 0x01, 0xe0, 0x00, 0x70, 0x00, 0x38, 0x00, 0x1c, 0x00,
610 0x0e, 0x00, 0xff, 0x7f, 0xff, 0x7f, 0xff, 0x7f, 0x0e, 0x00, 0x1c, 0x00,
611 0x38, 0x00, 0x70, 0x00, 0xe0, 0x00, 0xc0, 0x01};
613 image create bitmap bm-right -data {
614 #define right_width 16
615 #define right_height 16
616 static unsigned char right_bits[] = {
617 0x00, 0x00, 0xc0, 0x01, 0x80, 0x03, 0x00, 0x07, 0x00, 0x0e, 0x00, 0x1c,
618 0x00, 0x38, 0xff, 0x7f, 0xff, 0x7f, 0xff, 0x7f, 0x00, 0x38, 0x00, 0x1c,
619 0x00, 0x0e, 0x00, 0x07, 0x80, 0x03, 0xc0, 0x01};
621 button .tf.bar.leftbut -image bm-left -command goback \
622 -state disabled -width 26
623 pack .tf.bar.leftbut -side left -fill y
624 button .tf.bar.rightbut -image bm-right -command goforw \
625 -state disabled -width 26
626 pack .tf.bar.rightbut -side left -fill y
628 button .tf.bar.findbut -text "Find" -command dofind -font $uifont
629 pack .tf.bar.findbut -side left
630 set findstring {}
631 set fstring .tf.bar.findstring
632 lappend entries $fstring
633 entry $fstring -width 30 -font $textfont -textvariable findstring
634 trace add variable findstring write find_change
635 pack $fstring -side left -expand 1 -fill x -in .tf.bar
636 set findtype Exact
637 set findtypemenu [tk_optionMenu .tf.bar.findtype \
638 findtype Exact IgnCase Regexp]
639 trace add variable findtype write find_change
640 .tf.bar.findtype configure -font $uifont
641 .tf.bar.findtype.menu configure -font $uifont
642 set findloc "All fields"
643 tk_optionMenu .tf.bar.findloc findloc "All fields" Headline \
644 Comments Author Committer
645 trace add variable findloc write find_change
646 .tf.bar.findloc configure -font $uifont
647 .tf.bar.findloc.menu configure -font $uifont
648 pack .tf.bar.findloc -side right
649 pack .tf.bar.findtype -side right
651 # build up the bottom bar of upper window
652 label .tf.lbar.flabel -text "Highlight: Commits " \
653 -font $uifont
654 pack .tf.lbar.flabel -side left -fill y
655 set gdttype "touching paths:"
656 set gm [tk_optionMenu .tf.lbar.gdttype gdttype "touching paths:" \
657 "adding/removing string:"]
658 trace add variable gdttype write hfiles_change
659 $gm conf -font $uifont
660 .tf.lbar.gdttype conf -font $uifont
661 pack .tf.lbar.gdttype -side left -fill y
662 entry .tf.lbar.fent -width 25 -font $textfont \
663 -textvariable highlight_files
664 trace add variable highlight_files write hfiles_change
665 lappend entries .tf.lbar.fent
666 pack .tf.lbar.fent -side left -fill x -expand 1
667 label .tf.lbar.vlabel -text " OR in view" -font $uifont
668 pack .tf.lbar.vlabel -side left -fill y
669 global viewhlmenu selectedhlview
670 set viewhlmenu [tk_optionMenu .tf.lbar.vhl selectedhlview None]
671 $viewhlmenu entryconf None -command delvhighlight
672 $viewhlmenu conf -font $uifont
673 .tf.lbar.vhl conf -font $uifont
674 pack .tf.lbar.vhl -side left -fill y
675 label .tf.lbar.rlabel -text " OR " -font $uifont
676 pack .tf.lbar.rlabel -side left -fill y
677 global highlight_related
678 set m [tk_optionMenu .tf.lbar.relm highlight_related None \
679 "Descendent" "Not descendent" "Ancestor" "Not ancestor"]
680 $m conf -font $uifont
681 .tf.lbar.relm conf -font $uifont
682 trace add variable highlight_related write vrel_change
683 pack .tf.lbar.relm -side left -fill y
685 # Finish putting the upper half of the viewer together
686 pack .tf.lbar -in .tf -side bottom -fill x
687 pack .tf.bar -in .tf -side bottom -fill x
688 pack .tf.histframe -fill both -side top -expand 1
689 .ctop add .tf
690 .ctop paneconfigure .tf -height $geometry(topheight)
691 .ctop paneconfigure .tf -width $geometry(topwidth)
693 # now build up the bottom
694 panedwindow .pwbottom -orient horizontal
696 # lower left, a text box over search bar, scroll bar to the right
697 # if we know window height, then that will set the lower text height, otherwise
698 # we set lower text height which will drive window height
699 if {[info exists geometry(main)]} {
700 frame .bleft -width $geometry(botwidth)
701 } else {
702 frame .bleft -width $geometry(botwidth) -height $geometry(botheight)
704 frame .bleft.top
705 frame .bleft.mid
707 button .bleft.top.search -text "Search" -command dosearch \
708 -font $uifont
709 pack .bleft.top.search -side left -padx 5
710 set sstring .bleft.top.sstring
711 entry $sstring -width 20 -font $textfont -textvariable searchstring
712 lappend entries $sstring
713 trace add variable searchstring write incrsearch
714 pack $sstring -side left -expand 1 -fill x
715 radiobutton .bleft.mid.diff -text "Diff" \
716 -command changediffdisp -variable diffelide -value {0 0}
717 radiobutton .bleft.mid.old -text "Old version" \
718 -command changediffdisp -variable diffelide -value {0 1}
719 radiobutton .bleft.mid.new -text "New version" \
720 -command changediffdisp -variable diffelide -value {1 0}
721 pack .bleft.mid.diff .bleft.mid.old .bleft.mid.new -side left
722 set ctext .bleft.ctext
723 text $ctext -background $bgcolor -foreground $fgcolor \
724 -tabs "[expr {$tabstop * $charspc}]" \
725 -state disabled -font $textfont \
726 -yscrollcommand scrolltext -wrap none
727 scrollbar .bleft.sb -command "$ctext yview"
728 pack .bleft.top -side top -fill x
729 pack .bleft.mid -side top -fill x
730 pack .bleft.sb -side right -fill y
731 pack $ctext -side left -fill both -expand 1
732 lappend bglist $ctext
733 lappend fglist $ctext
735 $ctext tag conf comment -wrap $wrapcomment
736 $ctext tag conf filesep -font [concat $textfont bold] -back "#aaaaaa"
737 $ctext tag conf hunksep -fore [lindex $diffcolors 2]
738 $ctext tag conf d0 -fore [lindex $diffcolors 0]
739 $ctext tag conf d1 -fore [lindex $diffcolors 1]
740 $ctext tag conf m0 -fore red
741 $ctext tag conf m1 -fore blue
742 $ctext tag conf m2 -fore green
743 $ctext tag conf m3 -fore purple
744 $ctext tag conf m4 -fore brown
745 $ctext tag conf m5 -fore "#009090"
746 $ctext tag conf m6 -fore magenta
747 $ctext tag conf m7 -fore "#808000"
748 $ctext tag conf m8 -fore "#009000"
749 $ctext tag conf m9 -fore "#ff0080"
750 $ctext tag conf m10 -fore cyan
751 $ctext tag conf m11 -fore "#b07070"
752 $ctext tag conf m12 -fore "#70b0f0"
753 $ctext tag conf m13 -fore "#70f0b0"
754 $ctext tag conf m14 -fore "#f0b070"
755 $ctext tag conf m15 -fore "#ff70b0"
756 $ctext tag conf mmax -fore darkgrey
757 set mergemax 16
758 $ctext tag conf mresult -font [concat $textfont bold]
759 $ctext tag conf msep -font [concat $textfont bold]
760 $ctext tag conf found -back yellow
762 .pwbottom add .bleft
763 .pwbottom paneconfigure .bleft -width $geometry(botwidth)
765 # lower right
766 frame .bright
767 frame .bright.mode
768 radiobutton .bright.mode.patch -text "Patch" \
769 -command reselectline -variable cmitmode -value "patch"
770 .bright.mode.patch configure -font $uifont
771 radiobutton .bright.mode.tree -text "Tree" \
772 -command reselectline -variable cmitmode -value "tree"
773 .bright.mode.tree configure -font $uifont
774 grid .bright.mode.patch .bright.mode.tree -sticky ew
775 pack .bright.mode -side top -fill x
776 set cflist .bright.cfiles
777 set indent [font measure $mainfont "nn"]
778 text $cflist \
779 -selectbackground $selectbgcolor \
780 -background $bgcolor -foreground $fgcolor \
781 -font $mainfont \
782 -tabs [list $indent [expr {2 * $indent}]] \
783 -yscrollcommand ".bright.sb set" \
784 -cursor [. cget -cursor] \
785 -spacing1 1 -spacing3 1
786 lappend bglist $cflist
787 lappend fglist $cflist
788 scrollbar .bright.sb -command "$cflist yview"
789 pack .bright.sb -side right -fill y
790 pack $cflist -side left -fill both -expand 1
791 $cflist tag configure highlight \
792 -background [$cflist cget -selectbackground]
793 $cflist tag configure bold -font [concat $mainfont bold]
795 .pwbottom add .bright
796 .ctop add .pwbottom
798 # restore window position if known
799 if {[info exists geometry(main)]} {
800 wm geometry . "$geometry(main)"
803 bind .pwbottom <Configure> {resizecdetpanes %W %w}
804 pack .ctop -fill both -expand 1
805 bindall <1> {selcanvline %W %x %y}
806 #bindall <B1-Motion> {selcanvline %W %x %y}
807 bindall <ButtonRelease-4> "allcanvs yview scroll -5 units"
808 bindall <ButtonRelease-5> "allcanvs yview scroll 5 units"
809 bindall <2> "canvscan mark %W %x %y"
810 bindall <B2-Motion> "canvscan dragto %W %x %y"
811 bindkey <Home> selfirstline
812 bindkey <End> sellastline
813 bind . <Key-Up> "selnextline -1"
814 bind . <Key-Down> "selnextline 1"
815 bind . <Shift-Key-Up> "next_highlight -1"
816 bind . <Shift-Key-Down> "next_highlight 1"
817 bindkey <Key-Right> "goforw"
818 bindkey <Key-Left> "goback"
819 bind . <Key-Prior> "selnextpage -1"
820 bind . <Key-Next> "selnextpage 1"
821 bind . <Control-Home> "allcanvs yview moveto 0.0"
822 bind . <Control-End> "allcanvs yview moveto 1.0"
823 bind . <Control-Key-Up> "allcanvs yview scroll -1 units"
824 bind . <Control-Key-Down> "allcanvs yview scroll 1 units"
825 bind . <Control-Key-Prior> "allcanvs yview scroll -1 pages"
826 bind . <Control-Key-Next> "allcanvs yview scroll 1 pages"
827 bindkey <Key-Delete> "$ctext yview scroll -1 pages"
828 bindkey <Key-BackSpace> "$ctext yview scroll -1 pages"
829 bindkey <Key-space> "$ctext yview scroll 1 pages"
830 bindkey p "selnextline -1"
831 bindkey n "selnextline 1"
832 bindkey z "goback"
833 bindkey x "goforw"
834 bindkey i "selnextline -1"
835 bindkey k "selnextline 1"
836 bindkey j "goback"
837 bindkey l "goforw"
838 bindkey b "$ctext yview scroll -1 pages"
839 bindkey d "$ctext yview scroll 18 units"
840 bindkey u "$ctext yview scroll -18 units"
841 bindkey / {findnext 1}
842 bindkey <Key-Return> {findnext 0}
843 bindkey ? findprev
844 bindkey f nextfile
845 bindkey <F5> updatecommits
846 bind . <Control-q> doquit
847 bind . <Control-f> dofind
848 bind . <Control-g> {findnext 0}
849 bind . <Control-r> dosearchback
850 bind . <Control-s> dosearch
851 bind . <Control-equal> {incrfont 1}
852 bind . <Control-KP_Add> {incrfont 1}
853 bind . <Control-minus> {incrfont -1}
854 bind . <Control-KP_Subtract> {incrfont -1}
855 wm protocol . WM_DELETE_WINDOW doquit
856 bind . <Button-1> "click %W"
857 bind $fstring <Key-Return> dofind
858 bind $sha1entry <Key-Return> gotocommit
859 bind $sha1entry <<PasteSelection>> clearsha1
860 bind $cflist <1> {sel_flist %W %x %y; break}
861 bind $cflist <B1-Motion> {sel_flist %W %x %y; break}
862 bind $cflist <ButtonRelease-1> {treeclick %W %x %y}
864 set maincursor [. cget -cursor]
865 set textcursor [$ctext cget -cursor]
866 set curtextcursor $textcursor
868 set rowctxmenu .rowctxmenu
869 menu $rowctxmenu -tearoff 0
870 $rowctxmenu add command -label "Diff this -> selected" \
871 -command {diffvssel 0}
872 $rowctxmenu add command -label "Diff selected -> this" \
873 -command {diffvssel 1}
874 $rowctxmenu add command -label "Make patch" -command mkpatch
875 $rowctxmenu add command -label "Create tag" -command mktag
876 $rowctxmenu add command -label "Write commit to file" -command writecommit
877 $rowctxmenu add command -label "Create new branch" -command mkbranch
878 $rowctxmenu add command -label "Cherry-pick this commit" \
879 -command cherrypick
881 set headctxmenu .headctxmenu
882 menu $headctxmenu -tearoff 0
883 $headctxmenu add command -label "Check out this branch" \
884 -command cobranch
885 $headctxmenu add command -label "Remove this branch" \
886 -command rmbranch
889 # mouse-2 makes all windows scan vertically, but only the one
890 # the cursor is in scans horizontally
891 proc canvscan {op w x y} {
892 global canv canv2 canv3
893 foreach c [list $canv $canv2 $canv3] {
894 if {$c == $w} {
895 $c scan $op $x $y
896 } else {
897 $c scan $op 0 $y
902 proc scrollcanv {cscroll f0 f1} {
903 $cscroll set $f0 $f1
904 drawfrac $f0 $f1
905 flushhighlights
908 # when we make a key binding for the toplevel, make sure
909 # it doesn't get triggered when that key is pressed in the
910 # find string entry widget.
911 proc bindkey {ev script} {
912 global entries
913 bind . $ev $script
914 set escript [bind Entry $ev]
915 if {$escript == {}} {
916 set escript [bind Entry <Key>]
918 foreach e $entries {
919 bind $e $ev "$escript; break"
923 # set the focus back to the toplevel for any click outside
924 # the entry widgets
925 proc click {w} {
926 global entries
927 foreach e $entries {
928 if {$w == $e} return
930 focus .
933 proc savestuff {w} {
934 global canv canv2 canv3 ctext cflist mainfont textfont uifont tabstop
935 global stuffsaved findmergefiles maxgraphpct
936 global maxwidth showneartags
937 global viewname viewfiles viewargs viewperm nextviewnum
938 global cmitmode wrapcomment
939 global colors bgcolor fgcolor diffcolors selectbgcolor
941 if {$stuffsaved} return
942 if {![winfo viewable .]} return
943 catch {
944 set f [open "~/.gitk-new" w]
945 puts $f [list set mainfont $mainfont]
946 puts $f [list set textfont $textfont]
947 puts $f [list set uifont $uifont]
948 puts $f [list set tabstop $tabstop]
949 puts $f [list set findmergefiles $findmergefiles]
950 puts $f [list set maxgraphpct $maxgraphpct]
951 puts $f [list set maxwidth $maxwidth]
952 puts $f [list set cmitmode $cmitmode]
953 puts $f [list set wrapcomment $wrapcomment]
954 puts $f [list set showneartags $showneartags]
955 puts $f [list set bgcolor $bgcolor]
956 puts $f [list set fgcolor $fgcolor]
957 puts $f [list set colors $colors]
958 puts $f [list set diffcolors $diffcolors]
959 puts $f [list set selectbgcolor $selectbgcolor]
961 puts $f "set geometry(main) [wm geometry .]"
962 puts $f "set geometry(topwidth) [winfo width .tf]"
963 puts $f "set geometry(topheight) [winfo height .tf]"
964 puts $f "set geometry(pwsash0) \"[.tf.histframe.pwclist sash coord 0]\""
965 puts $f "set geometry(pwsash1) \"[.tf.histframe.pwclist sash coord 1]\""
966 puts $f "set geometry(botwidth) [winfo width .bleft]"
967 puts $f "set geometry(botheight) [winfo height .bleft]"
969 puts -nonewline $f "set permviews {"
970 for {set v 0} {$v < $nextviewnum} {incr v} {
971 if {$viewperm($v)} {
972 puts $f "{[list $viewname($v) $viewfiles($v) $viewargs($v)]}"
975 puts $f "}"
976 close $f
977 file rename -force "~/.gitk-new" "~/.gitk"
979 set stuffsaved 1
982 proc resizeclistpanes {win w} {
983 global oldwidth
984 if {[info exists oldwidth($win)]} {
985 set s0 [$win sash coord 0]
986 set s1 [$win sash coord 1]
987 if {$w < 60} {
988 set sash0 [expr {int($w/2 - 2)}]
989 set sash1 [expr {int($w*5/6 - 2)}]
990 } else {
991 set factor [expr {1.0 * $w / $oldwidth($win)}]
992 set sash0 [expr {int($factor * [lindex $s0 0])}]
993 set sash1 [expr {int($factor * [lindex $s1 0])}]
994 if {$sash0 < 30} {
995 set sash0 30
997 if {$sash1 < $sash0 + 20} {
998 set sash1 [expr {$sash0 + 20}]
1000 if {$sash1 > $w - 10} {
1001 set sash1 [expr {$w - 10}]
1002 if {$sash0 > $sash1 - 20} {
1003 set sash0 [expr {$sash1 - 20}]
1007 $win sash place 0 $sash0 [lindex $s0 1]
1008 $win sash place 1 $sash1 [lindex $s1 1]
1010 set oldwidth($win) $w
1013 proc resizecdetpanes {win w} {
1014 global oldwidth
1015 if {[info exists oldwidth($win)]} {
1016 set s0 [$win sash coord 0]
1017 if {$w < 60} {
1018 set sash0 [expr {int($w*3/4 - 2)}]
1019 } else {
1020 set factor [expr {1.0 * $w / $oldwidth($win)}]
1021 set sash0 [expr {int($factor * [lindex $s0 0])}]
1022 if {$sash0 < 45} {
1023 set sash0 45
1025 if {$sash0 > $w - 15} {
1026 set sash0 [expr {$w - 15}]
1029 $win sash place 0 $sash0 [lindex $s0 1]
1031 set oldwidth($win) $w
1034 proc allcanvs args {
1035 global canv canv2 canv3
1036 eval $canv $args
1037 eval $canv2 $args
1038 eval $canv3 $args
1041 proc bindall {event action} {
1042 global canv canv2 canv3
1043 bind $canv $event $action
1044 bind $canv2 $event $action
1045 bind $canv3 $event $action
1048 proc about {} {
1049 global uifont
1050 set w .about
1051 if {[winfo exists $w]} {
1052 raise $w
1053 return
1055 toplevel $w
1056 wm title $w "About gitk"
1057 message $w.m -text {
1058 Gitk - a commit viewer for git
1060 Copyright © 2005-2006 Paul Mackerras
1062 Use and redistribute under the terms of the GNU General Public License} \
1063 -justify center -aspect 400 -border 2 -bg white -relief groove
1064 pack $w.m -side top -fill x -padx 2 -pady 2
1065 $w.m configure -font $uifont
1066 button $w.ok -text Close -command "destroy $w" -default active
1067 pack $w.ok -side bottom
1068 $w.ok configure -font $uifont
1069 bind $w <Visibility> "focus $w.ok"
1070 bind $w <Key-Escape> "destroy $w"
1071 bind $w <Key-Return> "destroy $w"
1074 proc keys {} {
1075 global uifont
1076 set w .keys
1077 if {[winfo exists $w]} {
1078 raise $w
1079 return
1081 toplevel $w
1082 wm title $w "Gitk key bindings"
1083 message $w.m -text {
1084 Gitk key bindings:
1086 <Ctrl-Q> Quit
1087 <Home> Move to first commit
1088 <End> Move to last commit
1089 <Up>, p, i Move up one commit
1090 <Down>, n, k Move down one commit
1091 <Left>, z, j Go back in history list
1092 <Right>, x, l Go forward in history list
1093 <PageUp> Move up one page in commit list
1094 <PageDown> Move down one page in commit list
1095 <Ctrl-Home> Scroll to top of commit list
1096 <Ctrl-End> Scroll to bottom of commit list
1097 <Ctrl-Up> Scroll commit list up one line
1098 <Ctrl-Down> Scroll commit list down one line
1099 <Ctrl-PageUp> Scroll commit list up one page
1100 <Ctrl-PageDown> Scroll commit list down one page
1101 <Shift-Up> Move to previous highlighted line
1102 <Shift-Down> Move to next highlighted line
1103 <Delete>, b Scroll diff view up one page
1104 <Backspace> Scroll diff view up one page
1105 <Space> Scroll diff view down one page
1106 u Scroll diff view up 18 lines
1107 d Scroll diff view down 18 lines
1108 <Ctrl-F> Find
1109 <Ctrl-G> Move to next find hit
1110 <Return> Move to next find hit
1111 / Move to next find hit, or redo find
1112 ? Move to previous find hit
1113 f Scroll diff view to next file
1114 <Ctrl-S> Search for next hit in diff view
1115 <Ctrl-R> Search for previous hit in diff view
1116 <Ctrl-KP+> Increase font size
1117 <Ctrl-plus> Increase font size
1118 <Ctrl-KP-> Decrease font size
1119 <Ctrl-minus> Decrease font size
1120 <F5> Update
1122 -justify left -bg white -border 2 -relief groove
1123 pack $w.m -side top -fill both -padx 2 -pady 2
1124 $w.m configure -font $uifont
1125 button $w.ok -text Close -command "destroy $w" -default active
1126 pack $w.ok -side bottom
1127 $w.ok configure -font $uifont
1128 bind $w <Visibility> "focus $w.ok"
1129 bind $w <Key-Escape> "destroy $w"
1130 bind $w <Key-Return> "destroy $w"
1133 # Procedures for manipulating the file list window at the
1134 # bottom right of the overall window.
1136 proc treeview {w l openlevs} {
1137 global treecontents treediropen treeheight treeparent treeindex
1139 set ix 0
1140 set treeindex() 0
1141 set lev 0
1142 set prefix {}
1143 set prefixend -1
1144 set prefendstack {}
1145 set htstack {}
1146 set ht 0
1147 set treecontents() {}
1148 $w conf -state normal
1149 foreach f $l {
1150 while {[string range $f 0 $prefixend] ne $prefix} {
1151 if {$lev <= $openlevs} {
1152 $w mark set e:$treeindex($prefix) "end -1c"
1153 $w mark gravity e:$treeindex($prefix) left
1155 set treeheight($prefix) $ht
1156 incr ht [lindex $htstack end]
1157 set htstack [lreplace $htstack end end]
1158 set prefixend [lindex $prefendstack end]
1159 set prefendstack [lreplace $prefendstack end end]
1160 set prefix [string range $prefix 0 $prefixend]
1161 incr lev -1
1163 set tail [string range $f [expr {$prefixend+1}] end]
1164 while {[set slash [string first "/" $tail]] >= 0} {
1165 lappend htstack $ht
1166 set ht 0
1167 lappend prefendstack $prefixend
1168 incr prefixend [expr {$slash + 1}]
1169 set d [string range $tail 0 $slash]
1170 lappend treecontents($prefix) $d
1171 set oldprefix $prefix
1172 append prefix $d
1173 set treecontents($prefix) {}
1174 set treeindex($prefix) [incr ix]
1175 set treeparent($prefix) $oldprefix
1176 set tail [string range $tail [expr {$slash+1}] end]
1177 if {$lev <= $openlevs} {
1178 set ht 1
1179 set treediropen($prefix) [expr {$lev < $openlevs}]
1180 set bm [expr {$lev == $openlevs? "tri-rt": "tri-dn"}]
1181 $w mark set d:$ix "end -1c"
1182 $w mark gravity d:$ix left
1183 set str "\n"
1184 for {set i 0} {$i < $lev} {incr i} {append str "\t"}
1185 $w insert end $str
1186 $w image create end -align center -image $bm -padx 1 \
1187 -name a:$ix
1188 $w insert end $d [highlight_tag $prefix]
1189 $w mark set s:$ix "end -1c"
1190 $w mark gravity s:$ix left
1192 incr lev
1194 if {$tail ne {}} {
1195 if {$lev <= $openlevs} {
1196 incr ht
1197 set str "\n"
1198 for {set i 0} {$i < $lev} {incr i} {append str "\t"}
1199 $w insert end $str
1200 $w insert end $tail [highlight_tag $f]
1202 lappend treecontents($prefix) $tail
1205 while {$htstack ne {}} {
1206 set treeheight($prefix) $ht
1207 incr ht [lindex $htstack end]
1208 set htstack [lreplace $htstack end end]
1210 $w conf -state disabled
1213 proc linetoelt {l} {
1214 global treeheight treecontents
1216 set y 2
1217 set prefix {}
1218 while {1} {
1219 foreach e $treecontents($prefix) {
1220 if {$y == $l} {
1221 return "$prefix$e"
1223 set n 1
1224 if {[string index $e end] eq "/"} {
1225 set n $treeheight($prefix$e)
1226 if {$y + $n > $l} {
1227 append prefix $e
1228 incr y
1229 break
1232 incr y $n
1237 proc highlight_tree {y prefix} {
1238 global treeheight treecontents cflist
1240 foreach e $treecontents($prefix) {
1241 set path $prefix$e
1242 if {[highlight_tag $path] ne {}} {
1243 $cflist tag add bold $y.0 "$y.0 lineend"
1245 incr y
1246 if {[string index $e end] eq "/" && $treeheight($path) > 1} {
1247 set y [highlight_tree $y $path]
1250 return $y
1253 proc treeclosedir {w dir} {
1254 global treediropen treeheight treeparent treeindex
1256 set ix $treeindex($dir)
1257 $w conf -state normal
1258 $w delete s:$ix e:$ix
1259 set treediropen($dir) 0
1260 $w image configure a:$ix -image tri-rt
1261 $w conf -state disabled
1262 set n [expr {1 - $treeheight($dir)}]
1263 while {$dir ne {}} {
1264 incr treeheight($dir) $n
1265 set dir $treeparent($dir)
1269 proc treeopendir {w dir} {
1270 global treediropen treeheight treeparent treecontents treeindex
1272 set ix $treeindex($dir)
1273 $w conf -state normal
1274 $w image configure a:$ix -image tri-dn
1275 $w mark set e:$ix s:$ix
1276 $w mark gravity e:$ix right
1277 set lev 0
1278 set str "\n"
1279 set n [llength $treecontents($dir)]
1280 for {set x $dir} {$x ne {}} {set x $treeparent($x)} {
1281 incr lev
1282 append str "\t"
1283 incr treeheight($x) $n
1285 foreach e $treecontents($dir) {
1286 set de $dir$e
1287 if {[string index $e end] eq "/"} {
1288 set iy $treeindex($de)
1289 $w mark set d:$iy e:$ix
1290 $w mark gravity d:$iy left
1291 $w insert e:$ix $str
1292 set treediropen($de) 0
1293 $w image create e:$ix -align center -image tri-rt -padx 1 \
1294 -name a:$iy
1295 $w insert e:$ix $e [highlight_tag $de]
1296 $w mark set s:$iy e:$ix
1297 $w mark gravity s:$iy left
1298 set treeheight($de) 1
1299 } else {
1300 $w insert e:$ix $str
1301 $w insert e:$ix $e [highlight_tag $de]
1304 $w mark gravity e:$ix left
1305 $w conf -state disabled
1306 set treediropen($dir) 1
1307 set top [lindex [split [$w index @0,0] .] 0]
1308 set ht [$w cget -height]
1309 set l [lindex [split [$w index s:$ix] .] 0]
1310 if {$l < $top} {
1311 $w yview $l.0
1312 } elseif {$l + $n + 1 > $top + $ht} {
1313 set top [expr {$l + $n + 2 - $ht}]
1314 if {$l < $top} {
1315 set top $l
1317 $w yview $top.0
1321 proc treeclick {w x y} {
1322 global treediropen cmitmode ctext cflist cflist_top
1324 if {$cmitmode ne "tree"} return
1325 if {![info exists cflist_top]} return
1326 set l [lindex [split [$w index "@$x,$y"] "."] 0]
1327 $cflist tag remove highlight $cflist_top.0 "$cflist_top.0 lineend"
1328 $cflist tag add highlight $l.0 "$l.0 lineend"
1329 set cflist_top $l
1330 if {$l == 1} {
1331 $ctext yview 1.0
1332 return
1334 set e [linetoelt $l]
1335 if {[string index $e end] ne "/"} {
1336 showfile $e
1337 } elseif {$treediropen($e)} {
1338 treeclosedir $w $e
1339 } else {
1340 treeopendir $w $e
1344 proc setfilelist {id} {
1345 global treefilelist cflist
1347 treeview $cflist $treefilelist($id) 0
1350 image create bitmap tri-rt -background black -foreground blue -data {
1351 #define tri-rt_width 13
1352 #define tri-rt_height 13
1353 static unsigned char tri-rt_bits[] = {
1354 0x00, 0x00, 0x00, 0x00, 0x10, 0x00, 0x30, 0x00, 0x70, 0x00, 0xf0, 0x00,
1355 0xf0, 0x01, 0xf0, 0x00, 0x70, 0x00, 0x30, 0x00, 0x10, 0x00, 0x00, 0x00,
1356 0x00, 0x00};
1357 } -maskdata {
1358 #define tri-rt-mask_width 13
1359 #define tri-rt-mask_height 13
1360 static unsigned char tri-rt-mask_bits[] = {
1361 0x08, 0x00, 0x18, 0x00, 0x38, 0x00, 0x78, 0x00, 0xf8, 0x00, 0xf8, 0x01,
1362 0xf8, 0x03, 0xf8, 0x01, 0xf8, 0x00, 0x78, 0x00, 0x38, 0x00, 0x18, 0x00,
1363 0x08, 0x00};
1365 image create bitmap tri-dn -background black -foreground blue -data {
1366 #define tri-dn_width 13
1367 #define tri-dn_height 13
1368 static unsigned char tri-dn_bits[] = {
1369 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0xfc, 0x07, 0xf8, 0x03,
1370 0xf0, 0x01, 0xe0, 0x00, 0x40, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
1371 0x00, 0x00};
1372 } -maskdata {
1373 #define tri-dn-mask_width 13
1374 #define tri-dn-mask_height 13
1375 static unsigned char tri-dn-mask_bits[] = {
1376 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0xff, 0x1f, 0xfe, 0x0f, 0xfc, 0x07,
1377 0xf8, 0x03, 0xf0, 0x01, 0xe0, 0x00, 0x40, 0x00, 0x00, 0x00, 0x00, 0x00,
1378 0x00, 0x00};
1381 proc init_flist {first} {
1382 global cflist cflist_top selectedline difffilestart
1384 $cflist conf -state normal
1385 $cflist delete 0.0 end
1386 if {$first ne {}} {
1387 $cflist insert end $first
1388 set cflist_top 1
1389 $cflist tag add highlight 1.0 "1.0 lineend"
1390 } else {
1391 catch {unset cflist_top}
1393 $cflist conf -state disabled
1394 set difffilestart {}
1397 proc highlight_tag {f} {
1398 global highlight_paths
1400 foreach p $highlight_paths {
1401 if {[string match $p $f]} {
1402 return "bold"
1405 return {}
1408 proc highlight_filelist {} {
1409 global cmitmode cflist
1411 $cflist conf -state normal
1412 if {$cmitmode ne "tree"} {
1413 set end [lindex [split [$cflist index end] .] 0]
1414 for {set l 2} {$l < $end} {incr l} {
1415 set line [$cflist get $l.0 "$l.0 lineend"]
1416 if {[highlight_tag $line] ne {}} {
1417 $cflist tag add bold $l.0 "$l.0 lineend"
1420 } else {
1421 highlight_tree 2 {}
1423 $cflist conf -state disabled
1426 proc unhighlight_filelist {} {
1427 global cflist
1429 $cflist conf -state normal
1430 $cflist tag remove bold 1.0 end
1431 $cflist conf -state disabled
1434 proc add_flist {fl} {
1435 global cflist
1437 $cflist conf -state normal
1438 foreach f $fl {
1439 $cflist insert end "\n"
1440 $cflist insert end $f [highlight_tag $f]
1442 $cflist conf -state disabled
1445 proc sel_flist {w x y} {
1446 global ctext difffilestart cflist cflist_top cmitmode
1448 if {$cmitmode eq "tree"} return
1449 if {![info exists cflist_top]} return
1450 set l [lindex [split [$w index "@$x,$y"] "."] 0]
1451 $cflist tag remove highlight $cflist_top.0 "$cflist_top.0 lineend"
1452 $cflist tag add highlight $l.0 "$l.0 lineend"
1453 set cflist_top $l
1454 if {$l == 1} {
1455 $ctext yview 1.0
1456 } else {
1457 catch {$ctext yview [lindex $difffilestart [expr {$l - 2}]]}
1461 # Functions for adding and removing shell-type quoting
1463 proc shellquote {str} {
1464 if {![string match "*\['\"\\ \t]*" $str]} {
1465 return $str
1467 if {![string match "*\['\"\\]*" $str]} {
1468 return "\"$str\""
1470 if {![string match "*'*" $str]} {
1471 return "'$str'"
1473 return "\"[string map {\" \\\" \\ \\\\} $str]\""
1476 proc shellarglist {l} {
1477 set str {}
1478 foreach a $l {
1479 if {$str ne {}} {
1480 append str " "
1482 append str [shellquote $a]
1484 return $str
1487 proc shelldequote {str} {
1488 set ret {}
1489 set used -1
1490 while {1} {
1491 incr used
1492 if {![regexp -start $used -indices "\['\"\\\\ \t]" $str first]} {
1493 append ret [string range $str $used end]
1494 set used [string length $str]
1495 break
1497 set first [lindex $first 0]
1498 set ch [string index $str $first]
1499 if {$first > $used} {
1500 append ret [string range $str $used [expr {$first - 1}]]
1501 set used $first
1503 if {$ch eq " " || $ch eq "\t"} break
1504 incr used
1505 if {$ch eq "'"} {
1506 set first [string first "'" $str $used]
1507 if {$first < 0} {
1508 error "unmatched single-quote"
1510 append ret [string range $str $used [expr {$first - 1}]]
1511 set used $first
1512 continue
1514 if {$ch eq "\\"} {
1515 if {$used >= [string length $str]} {
1516 error "trailing backslash"
1518 append ret [string index $str $used]
1519 continue
1521 # here ch == "\""
1522 while {1} {
1523 if {![regexp -start $used -indices "\[\"\\\\]" $str first]} {
1524 error "unmatched double-quote"
1526 set first [lindex $first 0]
1527 set ch [string index $str $first]
1528 if {$first > $used} {
1529 append ret [string range $str $used [expr {$first - 1}]]
1530 set used $first
1532 if {$ch eq "\""} break
1533 incr used
1534 append ret [string index $str $used]
1535 incr used
1538 return [list $used $ret]
1541 proc shellsplit {str} {
1542 set l {}
1543 while {1} {
1544 set str [string trimleft $str]
1545 if {$str eq {}} break
1546 set dq [shelldequote $str]
1547 set n [lindex $dq 0]
1548 set word [lindex $dq 1]
1549 set str [string range $str $n end]
1550 lappend l $word
1552 return $l
1555 # Code to implement multiple views
1557 proc newview {ishighlight} {
1558 global nextviewnum newviewname newviewperm uifont newishighlight
1559 global newviewargs revtreeargs
1561 set newishighlight $ishighlight
1562 set top .gitkview
1563 if {[winfo exists $top]} {
1564 raise $top
1565 return
1567 set newviewname($nextviewnum) "View $nextviewnum"
1568 set newviewperm($nextviewnum) 0
1569 set newviewargs($nextviewnum) [shellarglist $revtreeargs]
1570 vieweditor $top $nextviewnum "Gitk view definition"
1573 proc editview {} {
1574 global curview
1575 global viewname viewperm newviewname newviewperm
1576 global viewargs newviewargs
1578 set top .gitkvedit-$curview
1579 if {[winfo exists $top]} {
1580 raise $top
1581 return
1583 set newviewname($curview) $viewname($curview)
1584 set newviewperm($curview) $viewperm($curview)
1585 set newviewargs($curview) [shellarglist $viewargs($curview)]
1586 vieweditor $top $curview "Gitk: edit view $viewname($curview)"
1589 proc vieweditor {top n title} {
1590 global newviewname newviewperm viewfiles
1591 global uifont
1593 toplevel $top
1594 wm title $top $title
1595 label $top.nl -text "Name" -font $uifont
1596 entry $top.name -width 20 -textvariable newviewname($n) -font $uifont
1597 grid $top.nl $top.name -sticky w -pady 5
1598 checkbutton $top.perm -text "Remember this view" -variable newviewperm($n) \
1599 -font $uifont
1600 grid $top.perm - -pady 5 -sticky w
1601 message $top.al -aspect 1000 -font $uifont \
1602 -text "Commits to include (arguments to git rev-list):"
1603 grid $top.al - -sticky w -pady 5
1604 entry $top.args -width 50 -textvariable newviewargs($n) \
1605 -background white -font $uifont
1606 grid $top.args - -sticky ew -padx 5
1607 message $top.l -aspect 1000 -font $uifont \
1608 -text "Enter files and directories to include, one per line:"
1609 grid $top.l - -sticky w
1610 text $top.t -width 40 -height 10 -background white -font $uifont
1611 if {[info exists viewfiles($n)]} {
1612 foreach f $viewfiles($n) {
1613 $top.t insert end $f
1614 $top.t insert end "\n"
1616 $top.t delete {end - 1c} end
1617 $top.t mark set insert 0.0
1619 grid $top.t - -sticky ew -padx 5
1620 frame $top.buts
1621 button $top.buts.ok -text "OK" -command [list newviewok $top $n] \
1622 -font $uifont
1623 button $top.buts.can -text "Cancel" -command [list destroy $top] \
1624 -font $uifont
1625 grid $top.buts.ok $top.buts.can
1626 grid columnconfigure $top.buts 0 -weight 1 -uniform a
1627 grid columnconfigure $top.buts 1 -weight 1 -uniform a
1628 grid $top.buts - -pady 10 -sticky ew
1629 focus $top.t
1632 proc doviewmenu {m first cmd op argv} {
1633 set nmenu [$m index end]
1634 for {set i $first} {$i <= $nmenu} {incr i} {
1635 if {[$m entrycget $i -command] eq $cmd} {
1636 eval $m $op $i $argv
1637 break
1642 proc allviewmenus {n op args} {
1643 global viewhlmenu
1645 doviewmenu .bar.view 5 [list showview $n] $op $args
1646 doviewmenu $viewhlmenu 1 [list addvhighlight $n] $op $args
1649 proc newviewok {top n} {
1650 global nextviewnum newviewperm newviewname newishighlight
1651 global viewname viewfiles viewperm selectedview curview
1652 global viewargs newviewargs viewhlmenu
1654 if {[catch {
1655 set newargs [shellsplit $newviewargs($n)]
1656 } err]} {
1657 error_popup "Error in commit selection arguments: $err"
1658 wm raise $top
1659 focus $top
1660 return
1662 set files {}
1663 foreach f [split [$top.t get 0.0 end] "\n"] {
1664 set ft [string trim $f]
1665 if {$ft ne {}} {
1666 lappend files $ft
1669 if {![info exists viewfiles($n)]} {
1670 # creating a new view
1671 incr nextviewnum
1672 set viewname($n) $newviewname($n)
1673 set viewperm($n) $newviewperm($n)
1674 set viewfiles($n) $files
1675 set viewargs($n) $newargs
1676 addviewmenu $n
1677 if {!$newishighlight} {
1678 run showview $n
1679 } else {
1680 run addvhighlight $n
1682 } else {
1683 # editing an existing view
1684 set viewperm($n) $newviewperm($n)
1685 if {$newviewname($n) ne $viewname($n)} {
1686 set viewname($n) $newviewname($n)
1687 doviewmenu .bar.view 5 [list showview $n] \
1688 entryconf [list -label $viewname($n)]
1689 doviewmenu $viewhlmenu 1 [list addvhighlight $n] \
1690 entryconf [list -label $viewname($n) -value $viewname($n)]
1692 if {$files ne $viewfiles($n) || $newargs ne $viewargs($n)} {
1693 set viewfiles($n) $files
1694 set viewargs($n) $newargs
1695 if {$curview == $n} {
1696 run updatecommits
1700 catch {destroy $top}
1703 proc delview {} {
1704 global curview viewdata viewperm hlview selectedhlview
1706 if {$curview == 0} return
1707 if {[info exists hlview] && $hlview == $curview} {
1708 set selectedhlview None
1709 unset hlview
1711 allviewmenus $curview delete
1712 set viewdata($curview) {}
1713 set viewperm($curview) 0
1714 showview 0
1717 proc addviewmenu {n} {
1718 global viewname viewhlmenu
1720 .bar.view add radiobutton -label $viewname($n) \
1721 -command [list showview $n] -variable selectedview -value $n
1722 $viewhlmenu add radiobutton -label $viewname($n) \
1723 -command [list addvhighlight $n] -variable selectedhlview
1726 proc flatten {var} {
1727 global $var
1729 set ret {}
1730 foreach i [array names $var] {
1731 lappend ret $i [set $var\($i\)]
1733 return $ret
1736 proc unflatten {var l} {
1737 global $var
1739 catch {unset $var}
1740 foreach {i v} $l {
1741 set $var\($i\) $v
1745 proc showview {n} {
1746 global curview viewdata viewfiles
1747 global displayorder parentlist childlist rowidlist rowoffsets
1748 global colormap rowtextx commitrow nextcolor canvxmax
1749 global numcommits rowrangelist commitlisted idrowranges
1750 global selectedline currentid canv canvy0
1751 global matchinglines treediffs
1752 global pending_select phase
1753 global commitidx rowlaidout rowoptim
1754 global commfd
1755 global selectedview selectfirst
1756 global vparentlist vchildlist vdisporder vcmitlisted
1757 global hlview selectedhlview
1759 if {$n == $curview} return
1760 set selid {}
1761 if {[info exists selectedline]} {
1762 set selid $currentid
1763 set y [yc $selectedline]
1764 set ymax [lindex [$canv cget -scrollregion] 3]
1765 set span [$canv yview]
1766 set ytop [expr {[lindex $span 0] * $ymax}]
1767 set ybot [expr {[lindex $span 1] * $ymax}]
1768 if {$ytop < $y && $y < $ybot} {
1769 set yscreen [expr {$y - $ytop}]
1770 } else {
1771 set yscreen [expr {($ybot - $ytop) / 2}]
1773 } elseif {[info exists pending_select]} {
1774 set selid $pending_select
1775 unset pending_select
1777 unselectline
1778 normalline
1779 stopfindproc
1780 if {$curview >= 0} {
1781 set vparentlist($curview) $parentlist
1782 set vchildlist($curview) $childlist
1783 set vdisporder($curview) $displayorder
1784 set vcmitlisted($curview) $commitlisted
1785 if {$phase ne {}} {
1786 set viewdata($curview) \
1787 [list $phase $rowidlist $rowoffsets $rowrangelist \
1788 [flatten idrowranges] [flatten idinlist] \
1789 $rowlaidout $rowoptim $numcommits]
1790 } elseif {![info exists viewdata($curview)]
1791 || [lindex $viewdata($curview) 0] ne {}} {
1792 set viewdata($curview) \
1793 [list {} $rowidlist $rowoffsets $rowrangelist]
1796 catch {unset matchinglines}
1797 catch {unset treediffs}
1798 clear_display
1799 if {[info exists hlview] && $hlview == $n} {
1800 unset hlview
1801 set selectedhlview None
1804 set curview $n
1805 set selectedview $n
1806 .bar.view entryconf Edit* -state [expr {$n == 0? "disabled": "normal"}]
1807 .bar.view entryconf Delete* -state [expr {$n == 0? "disabled": "normal"}]
1809 if {![info exists viewdata($n)]} {
1810 if {$selid ne {}} {
1811 set pending_select $selid
1813 getcommits
1814 return
1817 set v $viewdata($n)
1818 set phase [lindex $v 0]
1819 set displayorder $vdisporder($n)
1820 set parentlist $vparentlist($n)
1821 set childlist $vchildlist($n)
1822 set commitlisted $vcmitlisted($n)
1823 set rowidlist [lindex $v 1]
1824 set rowoffsets [lindex $v 2]
1825 set rowrangelist [lindex $v 3]
1826 if {$phase eq {}} {
1827 set numcommits [llength $displayorder]
1828 catch {unset idrowranges}
1829 } else {
1830 unflatten idrowranges [lindex $v 4]
1831 unflatten idinlist [lindex $v 5]
1832 set rowlaidout [lindex $v 6]
1833 set rowoptim [lindex $v 7]
1834 set numcommits [lindex $v 8]
1837 catch {unset colormap}
1838 catch {unset rowtextx}
1839 set nextcolor 0
1840 set canvxmax [$canv cget -width]
1841 set curview $n
1842 set row 0
1843 setcanvscroll
1844 set yf 0
1845 set row {}
1846 set selectfirst 0
1847 if {$selid ne {} && [info exists commitrow($n,$selid)]} {
1848 set row $commitrow($n,$selid)
1849 # try to get the selected row in the same position on the screen
1850 set ymax [lindex [$canv cget -scrollregion] 3]
1851 set ytop [expr {[yc $row] - $yscreen}]
1852 if {$ytop < 0} {
1853 set ytop 0
1855 set yf [expr {$ytop * 1.0 / $ymax}]
1857 allcanvs yview moveto $yf
1858 drawvisible
1859 if {$row ne {}} {
1860 selectline $row 0
1861 } elseif {$selid ne {}} {
1862 set pending_select $selid
1863 } else {
1864 if {$numcommits > 0} {
1865 selectline 0 0
1866 } else {
1867 set selectfirst 1
1870 if {$phase ne {}} {
1871 if {$phase eq "getcommits"} {
1872 show_status "Reading commits..."
1874 run chewcommits $n
1875 } elseif {$numcommits == 0} {
1876 show_status "No commits selected"
1880 # Stuff relating to the highlighting facility
1882 proc ishighlighted {row} {
1883 global vhighlights fhighlights nhighlights rhighlights
1885 if {[info exists nhighlights($row)] && $nhighlights($row) > 0} {
1886 return $nhighlights($row)
1888 if {[info exists vhighlights($row)] && $vhighlights($row) > 0} {
1889 return $vhighlights($row)
1891 if {[info exists fhighlights($row)] && $fhighlights($row) > 0} {
1892 return $fhighlights($row)
1894 if {[info exists rhighlights($row)] && $rhighlights($row) > 0} {
1895 return $rhighlights($row)
1897 return 0
1900 proc bolden {row font} {
1901 global canv linehtag selectedline boldrows
1903 lappend boldrows $row
1904 $canv itemconf $linehtag($row) -font $font
1905 if {[info exists selectedline] && $row == $selectedline} {
1906 $canv delete secsel
1907 set t [eval $canv create rect [$canv bbox $linehtag($row)] \
1908 -outline {{}} -tags secsel \
1909 -fill [$canv cget -selectbackground]]
1910 $canv lower $t
1914 proc bolden_name {row font} {
1915 global canv2 linentag selectedline boldnamerows
1917 lappend boldnamerows $row
1918 $canv2 itemconf $linentag($row) -font $font
1919 if {[info exists selectedline] && $row == $selectedline} {
1920 $canv2 delete secsel
1921 set t [eval $canv2 create rect [$canv2 bbox $linentag($row)] \
1922 -outline {{}} -tags secsel \
1923 -fill [$canv2 cget -selectbackground]]
1924 $canv2 lower $t
1928 proc unbolden {} {
1929 global mainfont boldrows
1931 set stillbold {}
1932 foreach row $boldrows {
1933 if {![ishighlighted $row]} {
1934 bolden $row $mainfont
1935 } else {
1936 lappend stillbold $row
1939 set boldrows $stillbold
1942 proc addvhighlight {n} {
1943 global hlview curview viewdata vhl_done vhighlights commitidx
1945 if {[info exists hlview]} {
1946 delvhighlight
1948 set hlview $n
1949 if {$n != $curview && ![info exists viewdata($n)]} {
1950 set viewdata($n) [list getcommits {{}} {{}} {} {} {} 0 0 0 {}]
1951 set vparentlist($n) {}
1952 set vchildlist($n) {}
1953 set vdisporder($n) {}
1954 set vcmitlisted($n) {}
1955 start_rev_list $n
1957 set vhl_done $commitidx($hlview)
1958 if {$vhl_done > 0} {
1959 drawvisible
1963 proc delvhighlight {} {
1964 global hlview vhighlights
1966 if {![info exists hlview]} return
1967 unset hlview
1968 catch {unset vhighlights}
1969 unbolden
1972 proc vhighlightmore {} {
1973 global hlview vhl_done commitidx vhighlights
1974 global displayorder vdisporder curview mainfont
1976 set font [concat $mainfont bold]
1977 set max $commitidx($hlview)
1978 if {$hlview == $curview} {
1979 set disp $displayorder
1980 } else {
1981 set disp $vdisporder($hlview)
1983 set vr [visiblerows]
1984 set r0 [lindex $vr 0]
1985 set r1 [lindex $vr 1]
1986 for {set i $vhl_done} {$i < $max} {incr i} {
1987 set id [lindex $disp $i]
1988 if {[info exists commitrow($curview,$id)]} {
1989 set row $commitrow($curview,$id)
1990 if {$r0 <= $row && $row <= $r1} {
1991 if {![highlighted $row]} {
1992 bolden $row $font
1994 set vhighlights($row) 1
1998 set vhl_done $max
2001 proc askvhighlight {row id} {
2002 global hlview vhighlights commitrow iddrawn mainfont
2004 if {[info exists commitrow($hlview,$id)]} {
2005 if {[info exists iddrawn($id)] && ![ishighlighted $row]} {
2006 bolden $row [concat $mainfont bold]
2008 set vhighlights($row) 1
2009 } else {
2010 set vhighlights($row) 0
2014 proc hfiles_change {name ix op} {
2015 global highlight_files filehighlight fhighlights fh_serial
2016 global mainfont highlight_paths
2018 if {[info exists filehighlight]} {
2019 # delete previous highlights
2020 catch {close $filehighlight}
2021 unset filehighlight
2022 catch {unset fhighlights}
2023 unbolden
2024 unhighlight_filelist
2026 set highlight_paths {}
2027 after cancel do_file_hl $fh_serial
2028 incr fh_serial
2029 if {$highlight_files ne {}} {
2030 after 300 do_file_hl $fh_serial
2034 proc makepatterns {l} {
2035 set ret {}
2036 foreach e $l {
2037 set ee [string map {"*" "\\*" "?" "\\?" "\[" "\\\[" "\\" "\\\\"} $e]
2038 if {[string index $ee end] eq "/"} {
2039 lappend ret "$ee*"
2040 } else {
2041 lappend ret $ee
2042 lappend ret "$ee/*"
2045 return $ret
2048 proc do_file_hl {serial} {
2049 global highlight_files filehighlight highlight_paths gdttype fhl_list
2051 if {$gdttype eq "touching paths:"} {
2052 if {[catch {set paths [shellsplit $highlight_files]}]} return
2053 set highlight_paths [makepatterns $paths]
2054 highlight_filelist
2055 set gdtargs [concat -- $paths]
2056 } else {
2057 set gdtargs [list "-S$highlight_files"]
2059 set cmd [concat | git diff-tree -r -s --stdin $gdtargs]
2060 set filehighlight [open $cmd r+]
2061 fconfigure $filehighlight -blocking 0
2062 filerun $filehighlight readfhighlight
2063 set fhl_list {}
2064 drawvisible
2065 flushhighlights
2068 proc flushhighlights {} {
2069 global filehighlight fhl_list
2071 if {[info exists filehighlight]} {
2072 lappend fhl_list {}
2073 puts $filehighlight ""
2074 flush $filehighlight
2078 proc askfilehighlight {row id} {
2079 global filehighlight fhighlights fhl_list
2081 lappend fhl_list $id
2082 set fhighlights($row) -1
2083 puts $filehighlight $id
2086 proc readfhighlight {} {
2087 global filehighlight fhighlights commitrow curview mainfont iddrawn
2088 global fhl_list
2090 if {![info exists filehighlight]} {
2091 return 0
2093 set nr 0
2094 while {[incr nr] <= 100 && [gets $filehighlight line] >= 0} {
2095 set line [string trim $line]
2096 set i [lsearch -exact $fhl_list $line]
2097 if {$i < 0} continue
2098 for {set j 0} {$j < $i} {incr j} {
2099 set id [lindex $fhl_list $j]
2100 if {[info exists commitrow($curview,$id)]} {
2101 set fhighlights($commitrow($curview,$id)) 0
2104 set fhl_list [lrange $fhl_list [expr {$i+1}] end]
2105 if {$line eq {}} continue
2106 if {![info exists commitrow($curview,$line)]} continue
2107 set row $commitrow($curview,$line)
2108 if {[info exists iddrawn($line)] && ![ishighlighted $row]} {
2109 bolden $row [concat $mainfont bold]
2111 set fhighlights($row) 1
2113 if {[eof $filehighlight]} {
2114 # strange...
2115 puts "oops, git diff-tree died"
2116 catch {close $filehighlight}
2117 unset filehighlight
2118 return 0
2120 next_hlcont
2121 return 1
2124 proc find_change {name ix op} {
2125 global nhighlights mainfont boldnamerows
2126 global findstring findpattern findtype
2128 # delete previous highlights, if any
2129 foreach row $boldnamerows {
2130 bolden_name $row $mainfont
2132 set boldnamerows {}
2133 catch {unset nhighlights}
2134 unbolden
2135 if {$findtype ne "Regexp"} {
2136 set e [string map {"*" "\\*" "?" "\\?" "\[" "\\\[" "\\" "\\\\"} \
2137 $findstring]
2138 set findpattern "*$e*"
2140 drawvisible
2143 proc askfindhighlight {row id} {
2144 global nhighlights commitinfo iddrawn mainfont
2145 global findstring findtype findloc findpattern
2147 if {![info exists commitinfo($id)]} {
2148 getcommit $id
2150 set info $commitinfo($id)
2151 set isbold 0
2152 set fldtypes {Headline Author Date Committer CDate Comments}
2153 foreach f $info ty $fldtypes {
2154 if {$findloc ne "All fields" && $findloc ne $ty} {
2155 continue
2157 if {$findtype eq "Regexp"} {
2158 set doesmatch [regexp $findstring $f]
2159 } elseif {$findtype eq "IgnCase"} {
2160 set doesmatch [string match -nocase $findpattern $f]
2161 } else {
2162 set doesmatch [string match $findpattern $f]
2164 if {$doesmatch} {
2165 if {$ty eq "Author"} {
2166 set isbold 2
2167 } else {
2168 set isbold 1
2172 if {[info exists iddrawn($id)]} {
2173 if {$isbold && ![ishighlighted $row]} {
2174 bolden $row [concat $mainfont bold]
2176 if {$isbold >= 2} {
2177 bolden_name $row [concat $mainfont bold]
2180 set nhighlights($row) $isbold
2183 proc vrel_change {name ix op} {
2184 global highlight_related
2186 rhighlight_none
2187 if {$highlight_related ne "None"} {
2188 run drawvisible
2192 # prepare for testing whether commits are descendents or ancestors of a
2193 proc rhighlight_sel {a} {
2194 global descendent desc_todo ancestor anc_todo
2195 global highlight_related rhighlights
2197 catch {unset descendent}
2198 set desc_todo [list $a]
2199 catch {unset ancestor}
2200 set anc_todo [list $a]
2201 if {$highlight_related ne "None"} {
2202 rhighlight_none
2203 run drawvisible
2207 proc rhighlight_none {} {
2208 global rhighlights
2210 catch {unset rhighlights}
2211 unbolden
2214 proc is_descendent {a} {
2215 global curview children commitrow descendent desc_todo
2217 set v $curview
2218 set la $commitrow($v,$a)
2219 set todo $desc_todo
2220 set leftover {}
2221 set done 0
2222 for {set i 0} {$i < [llength $todo]} {incr i} {
2223 set do [lindex $todo $i]
2224 if {$commitrow($v,$do) < $la} {
2225 lappend leftover $do
2226 continue
2228 foreach nk $children($v,$do) {
2229 if {![info exists descendent($nk)]} {
2230 set descendent($nk) 1
2231 lappend todo $nk
2232 if {$nk eq $a} {
2233 set done 1
2237 if {$done} {
2238 set desc_todo [concat $leftover [lrange $todo [expr {$i+1}] end]]
2239 return
2242 set descendent($a) 0
2243 set desc_todo $leftover
2246 proc is_ancestor {a} {
2247 global curview parentlist commitrow ancestor anc_todo
2249 set v $curview
2250 set la $commitrow($v,$a)
2251 set todo $anc_todo
2252 set leftover {}
2253 set done 0
2254 for {set i 0} {$i < [llength $todo]} {incr i} {
2255 set do [lindex $todo $i]
2256 if {![info exists commitrow($v,$do)] || $commitrow($v,$do) > $la} {
2257 lappend leftover $do
2258 continue
2260 foreach np [lindex $parentlist $commitrow($v,$do)] {
2261 if {![info exists ancestor($np)]} {
2262 set ancestor($np) 1
2263 lappend todo $np
2264 if {$np eq $a} {
2265 set done 1
2269 if {$done} {
2270 set anc_todo [concat $leftover [lrange $todo [expr {$i+1}] end]]
2271 return
2274 set ancestor($a) 0
2275 set anc_todo $leftover
2278 proc askrelhighlight {row id} {
2279 global descendent highlight_related iddrawn mainfont rhighlights
2280 global selectedline ancestor
2282 if {![info exists selectedline]} return
2283 set isbold 0
2284 if {$highlight_related eq "Descendent" ||
2285 $highlight_related eq "Not descendent"} {
2286 if {![info exists descendent($id)]} {
2287 is_descendent $id
2289 if {$descendent($id) == ($highlight_related eq "Descendent")} {
2290 set isbold 1
2292 } elseif {$highlight_related eq "Ancestor" ||
2293 $highlight_related eq "Not ancestor"} {
2294 if {![info exists ancestor($id)]} {
2295 is_ancestor $id
2297 if {$ancestor($id) == ($highlight_related eq "Ancestor")} {
2298 set isbold 1
2301 if {[info exists iddrawn($id)]} {
2302 if {$isbold && ![ishighlighted $row]} {
2303 bolden $row [concat $mainfont bold]
2306 set rhighlights($row) $isbold
2309 proc next_hlcont {} {
2310 global fhl_row fhl_dirn displayorder numcommits
2311 global vhighlights fhighlights nhighlights rhighlights
2312 global hlview filehighlight findstring highlight_related
2314 if {![info exists fhl_dirn] || $fhl_dirn == 0} return
2315 set row $fhl_row
2316 while {1} {
2317 if {$row < 0 || $row >= $numcommits} {
2318 bell
2319 set fhl_dirn 0
2320 return
2322 set id [lindex $displayorder $row]
2323 if {[info exists hlview]} {
2324 if {![info exists vhighlights($row)]} {
2325 askvhighlight $row $id
2327 if {$vhighlights($row) > 0} break
2329 if {$findstring ne {}} {
2330 if {![info exists nhighlights($row)]} {
2331 askfindhighlight $row $id
2333 if {$nhighlights($row) > 0} break
2335 if {$highlight_related ne "None"} {
2336 if {![info exists rhighlights($row)]} {
2337 askrelhighlight $row $id
2339 if {$rhighlights($row) > 0} break
2341 if {[info exists filehighlight]} {
2342 if {![info exists fhighlights($row)]} {
2343 # ask for a few more while we're at it...
2344 set r $row
2345 for {set n 0} {$n < 100} {incr n} {
2346 if {![info exists fhighlights($r)]} {
2347 askfilehighlight $r [lindex $displayorder $r]
2349 incr r $fhl_dirn
2350 if {$r < 0 || $r >= $numcommits} break
2352 flushhighlights
2354 if {$fhighlights($row) < 0} {
2355 set fhl_row $row
2356 return
2358 if {$fhighlights($row) > 0} break
2360 incr row $fhl_dirn
2362 set fhl_dirn 0
2363 selectline $row 1
2366 proc next_highlight {dirn} {
2367 global selectedline fhl_row fhl_dirn
2368 global hlview filehighlight findstring highlight_related
2370 if {![info exists selectedline]} return
2371 if {!([info exists hlview] || $findstring ne {} ||
2372 $highlight_related ne "None" || [info exists filehighlight])} return
2373 set fhl_row [expr {$selectedline + $dirn}]
2374 set fhl_dirn $dirn
2375 next_hlcont
2378 proc cancel_next_highlight {} {
2379 global fhl_dirn
2381 set fhl_dirn 0
2384 # Graph layout functions
2386 proc shortids {ids} {
2387 set res {}
2388 foreach id $ids {
2389 if {[llength $id] > 1} {
2390 lappend res [shortids $id]
2391 } elseif {[regexp {^[0-9a-f]{40}$} $id]} {
2392 lappend res [string range $id 0 7]
2393 } else {
2394 lappend res $id
2397 return $res
2400 proc incrange {l x o} {
2401 set n [llength $l]
2402 while {$x < $n} {
2403 set e [lindex $l $x]
2404 if {$e ne {}} {
2405 lset l $x [expr {$e + $o}]
2407 incr x
2409 return $l
2412 proc ntimes {n o} {
2413 set ret {}
2414 for {} {$n > 0} {incr n -1} {
2415 lappend ret $o
2417 return $ret
2420 proc usedinrange {id l1 l2} {
2421 global children commitrow childlist curview
2423 if {[info exists commitrow($curview,$id)]} {
2424 set r $commitrow($curview,$id)
2425 if {$l1 <= $r && $r <= $l2} {
2426 return [expr {$r - $l1 + 1}]
2428 set kids [lindex $childlist $r]
2429 } else {
2430 set kids $children($curview,$id)
2432 foreach c $kids {
2433 set r $commitrow($curview,$c)
2434 if {$l1 <= $r && $r <= $l2} {
2435 return [expr {$r - $l1 + 1}]
2438 return 0
2441 proc sanity {row {full 0}} {
2442 global rowidlist rowoffsets
2444 set col -1
2445 set ids [lindex $rowidlist $row]
2446 foreach id $ids {
2447 incr col
2448 if {$id eq {}} continue
2449 if {$col < [llength $ids] - 1 &&
2450 [lsearch -exact -start [expr {$col+1}] $ids $id] >= 0} {
2451 puts "oops: [shortids $id] repeated in row $row col $col: {[shortids [lindex $rowidlist $row]]}"
2453 set o [lindex $rowoffsets $row $col]
2454 set y $row
2455 set x $col
2456 while {$o ne {}} {
2457 incr y -1
2458 incr x $o
2459 if {[lindex $rowidlist $y $x] != $id} {
2460 puts "oops: rowoffsets wrong at row [expr {$y+1}] col [expr {$x-$o}]"
2461 puts " id=[shortids $id] check started at row $row"
2462 for {set i $row} {$i >= $y} {incr i -1} {
2463 puts " row $i ids={[shortids [lindex $rowidlist $i]]} offs={[lindex $rowoffsets $i]}"
2465 break
2467 if {!$full} break
2468 set o [lindex $rowoffsets $y $x]
2473 proc makeuparrow {oid x y z} {
2474 global rowidlist rowoffsets uparrowlen idrowranges displayorder
2476 for {set i 1} {$i < $uparrowlen && $y > 1} {incr i} {
2477 incr y -1
2478 incr x $z
2479 set off0 [lindex $rowoffsets $y]
2480 for {set x0 $x} {1} {incr x0} {
2481 if {$x0 >= [llength $off0]} {
2482 set x0 [llength [lindex $rowoffsets [expr {$y-1}]]]
2483 break
2485 set z [lindex $off0 $x0]
2486 if {$z ne {}} {
2487 incr x0 $z
2488 break
2491 set z [expr {$x0 - $x}]
2492 lset rowidlist $y [linsert [lindex $rowidlist $y] $x $oid]
2493 lset rowoffsets $y [linsert [lindex $rowoffsets $y] $x $z]
2495 set tmp [lreplace [lindex $rowoffsets $y] $x $x {}]
2496 lset rowoffsets $y [incrange $tmp [expr {$x+1}] -1]
2497 lappend idrowranges($oid) [lindex $displayorder $y]
2500 proc initlayout {} {
2501 global rowidlist rowoffsets displayorder commitlisted
2502 global rowlaidout rowoptim
2503 global idinlist rowchk rowrangelist idrowranges
2504 global numcommits canvxmax canv
2505 global nextcolor
2506 global parentlist childlist children
2507 global colormap rowtextx
2508 global selectfirst
2510 set numcommits 0
2511 set displayorder {}
2512 set commitlisted {}
2513 set parentlist {}
2514 set childlist {}
2515 set rowrangelist {}
2516 set nextcolor 0
2517 set rowidlist {{}}
2518 set rowoffsets {{}}
2519 catch {unset idinlist}
2520 catch {unset rowchk}
2521 set rowlaidout 0
2522 set rowoptim 0
2523 set canvxmax [$canv cget -width]
2524 catch {unset colormap}
2525 catch {unset rowtextx}
2526 catch {unset idrowranges}
2527 set selectfirst 1
2530 proc setcanvscroll {} {
2531 global canv canv2 canv3 numcommits linespc canvxmax canvy0
2533 set ymax [expr {$canvy0 + ($numcommits - 0.5) * $linespc + 2}]
2534 $canv conf -scrollregion [list 0 0 $canvxmax $ymax]
2535 $canv2 conf -scrollregion [list 0 0 0 $ymax]
2536 $canv3 conf -scrollregion [list 0 0 0 $ymax]
2539 proc visiblerows {} {
2540 global canv numcommits linespc
2542 set ymax [lindex [$canv cget -scrollregion] 3]
2543 if {$ymax eq {} || $ymax == 0} return
2544 set f [$canv yview]
2545 set y0 [expr {int([lindex $f 0] * $ymax)}]
2546 set r0 [expr {int(($y0 - 3) / $linespc) - 1}]
2547 if {$r0 < 0} {
2548 set r0 0
2550 set y1 [expr {int([lindex $f 1] * $ymax)}]
2551 set r1 [expr {int(($y1 - 3) / $linespc) + 1}]
2552 if {$r1 >= $numcommits} {
2553 set r1 [expr {$numcommits - 1}]
2555 return [list $r0 $r1]
2558 proc layoutmore {tmax allread} {
2559 global rowlaidout rowoptim commitidx numcommits optim_delay
2560 global uparrowlen curview rowidlist idinlist
2562 set showdelay $optim_delay
2563 set optdelay [expr {$uparrowlen + 1}]
2564 while {1} {
2565 if {$rowoptim - $showdelay > $numcommits} {
2566 showstuff [expr {$rowoptim - $showdelay}]
2567 } elseif {$rowlaidout - $optdelay > $rowoptim} {
2568 set nr [expr {$rowlaidout - $optdelay - $rowoptim}]
2569 if {$nr > 100} {
2570 set nr 100
2572 optimize_rows $rowoptim 0 [expr {$rowoptim + $nr}]
2573 incr rowoptim $nr
2574 } elseif {$commitidx($curview) > $rowlaidout} {
2575 set nr [expr {$commitidx($curview) - $rowlaidout}]
2576 # may need to increase this threshold if uparrowlen or
2577 # mingaplen are increased...
2578 if {$nr > 150} {
2579 set nr 150
2581 set row $rowlaidout
2582 set rowlaidout [layoutrows $row [expr {$row + $nr}] $allread]
2583 if {$rowlaidout == $row} {
2584 return 0
2586 } elseif {$allread} {
2587 set optdelay 0
2588 set nrows $commitidx($curview)
2589 if {[lindex $rowidlist $nrows] ne {} ||
2590 [array names idinlist] ne {}} {
2591 layouttail
2592 set rowlaidout $commitidx($curview)
2593 } elseif {$rowoptim == $nrows} {
2594 set showdelay 0
2595 if {$numcommits == $nrows} {
2596 return 0
2599 } else {
2600 return 0
2602 if {$tmax ne {} && [clock clicks -milliseconds] >= $tmax} {
2603 return 1
2608 proc showstuff {canshow} {
2609 global numcommits commitrow pending_select selectedline curview
2610 global displayorder selectfirst
2612 if {$numcommits == 0} {
2613 global phase
2614 set phase "incrdraw"
2615 allcanvs delete all
2617 set r0 $numcommits
2618 set numcommits $canshow
2619 setcanvscroll
2620 set rows [visiblerows]
2621 set r1 [lindex $rows 1]
2622 if {$r1 >= $canshow} {
2623 set r1 [expr {$canshow - 1}]
2625 if {$r0 <= $r1} {
2626 drawcommits $r0 $r1
2628 if {[info exists pending_select] &&
2629 [info exists commitrow($curview,$pending_select)] &&
2630 $commitrow($curview,$pending_select) < $numcommits} {
2631 selectline $commitrow($curview,$pending_select) 1
2633 if {$selectfirst} {
2634 if {[info exists selectedline] || [info exists pending_select]} {
2635 set selectfirst 0
2636 } else {
2637 selectline 0 1
2638 set selectfirst 0
2643 proc layoutrows {row endrow last} {
2644 global rowidlist rowoffsets displayorder
2645 global uparrowlen downarrowlen maxwidth mingaplen
2646 global childlist parentlist
2647 global idrowranges
2648 global commitidx curview
2649 global idinlist rowchk rowrangelist
2651 set idlist [lindex $rowidlist $row]
2652 set offs [lindex $rowoffsets $row]
2653 while {$row < $endrow} {
2654 set id [lindex $displayorder $row]
2655 set oldolds {}
2656 set newolds {}
2657 foreach p [lindex $parentlist $row] {
2658 if {![info exists idinlist($p)]} {
2659 lappend newolds $p
2660 } elseif {!$idinlist($p)} {
2661 lappend oldolds $p
2664 set nev [expr {[llength $idlist] + [llength $newolds]
2665 + [llength $oldolds] - $maxwidth + 1}]
2666 if {$nev > 0} {
2667 if {!$last &&
2668 $row + $uparrowlen + $mingaplen >= $commitidx($curview)} break
2669 for {set x [llength $idlist]} {[incr x -1] >= 0} {} {
2670 set i [lindex $idlist $x]
2671 if {![info exists rowchk($i)] || $row >= $rowchk($i)} {
2672 set r [usedinrange $i [expr {$row - $downarrowlen}] \
2673 [expr {$row + $uparrowlen + $mingaplen}]]
2674 if {$r == 0} {
2675 set idlist [lreplace $idlist $x $x]
2676 set offs [lreplace $offs $x $x]
2677 set offs [incrange $offs $x 1]
2678 set idinlist($i) 0
2679 set rm1 [expr {$row - 1}]
2680 lappend idrowranges($i) [lindex $displayorder $rm1]
2681 if {[incr nev -1] <= 0} break
2682 continue
2684 set rowchk($id) [expr {$row + $r}]
2687 lset rowidlist $row $idlist
2688 lset rowoffsets $row $offs
2690 set col [lsearch -exact $idlist $id]
2691 if {$col < 0} {
2692 set col [llength $idlist]
2693 lappend idlist $id
2694 lset rowidlist $row $idlist
2695 set z {}
2696 if {[lindex $childlist $row] ne {}} {
2697 set z [expr {[llength [lindex $rowidlist [expr {$row-1}]]] - $col}]
2698 unset idinlist($id)
2700 lappend offs $z
2701 lset rowoffsets $row $offs
2702 if {$z ne {}} {
2703 makeuparrow $id $col $row $z
2705 } else {
2706 unset idinlist($id)
2708 set ranges {}
2709 if {[info exists idrowranges($id)]} {
2710 set ranges $idrowranges($id)
2711 lappend ranges $id
2712 unset idrowranges($id)
2714 lappend rowrangelist $ranges
2715 incr row
2716 set offs [ntimes [llength $idlist] 0]
2717 set l [llength $newolds]
2718 set idlist [eval lreplace \$idlist $col $col $newolds]
2719 set o 0
2720 if {$l != 1} {
2721 set offs [lrange $offs 0 [expr {$col - 1}]]
2722 foreach x $newolds {
2723 lappend offs {}
2724 incr o -1
2726 incr o
2727 set tmp [expr {[llength $idlist] - [llength $offs]}]
2728 if {$tmp > 0} {
2729 set offs [concat $offs [ntimes $tmp $o]]
2731 } else {
2732 lset offs $col {}
2734 foreach i $newolds {
2735 set idinlist($i) 1
2736 set idrowranges($i) $id
2738 incr col $l
2739 foreach oid $oldolds {
2740 set idinlist($oid) 1
2741 set idlist [linsert $idlist $col $oid]
2742 set offs [linsert $offs $col $o]
2743 makeuparrow $oid $col $row $o
2744 incr col
2746 lappend rowidlist $idlist
2747 lappend rowoffsets $offs
2749 return $row
2752 proc addextraid {id row} {
2753 global displayorder commitrow commitinfo
2754 global commitidx commitlisted
2755 global parentlist childlist children curview
2757 incr commitidx($curview)
2758 lappend displayorder $id
2759 lappend commitlisted 0
2760 lappend parentlist {}
2761 set commitrow($curview,$id) $row
2762 readcommit $id
2763 if {![info exists commitinfo($id)]} {
2764 set commitinfo($id) {"No commit information available"}
2766 if {![info exists children($curview,$id)]} {
2767 set children($curview,$id) {}
2769 lappend childlist $children($curview,$id)
2772 proc layouttail {} {
2773 global rowidlist rowoffsets idinlist commitidx curview
2774 global idrowranges rowrangelist
2776 set row $commitidx($curview)
2777 set idlist [lindex $rowidlist $row]
2778 while {$idlist ne {}} {
2779 set col [expr {[llength $idlist] - 1}]
2780 set id [lindex $idlist $col]
2781 addextraid $id $row
2782 unset idinlist($id)
2783 lappend idrowranges($id) $row
2784 lappend rowrangelist $idrowranges($id)
2785 unset idrowranges($id)
2786 incr row
2787 set offs [ntimes $col 0]
2788 set idlist [lreplace $idlist $col $col]
2789 lappend rowidlist $idlist
2790 lappend rowoffsets $offs
2793 foreach id [array names idinlist] {
2794 unset idinlist($id)
2795 addextraid $id $row
2796 lset rowidlist $row [list $id]
2797 lset rowoffsets $row 0
2798 makeuparrow $id 0 $row 0
2799 lappend idrowranges($id) $row
2800 lappend rowrangelist $idrowranges($id)
2801 unset idrowranges($id)
2802 incr row
2803 lappend rowidlist {}
2804 lappend rowoffsets {}
2808 proc insert_pad {row col npad} {
2809 global rowidlist rowoffsets
2811 set pad [ntimes $npad {}]
2812 lset rowidlist $row [eval linsert [list [lindex $rowidlist $row]] $col $pad]
2813 set tmp [eval linsert [list [lindex $rowoffsets $row]] $col $pad]
2814 lset rowoffsets $row [incrange $tmp [expr {$col + $npad}] [expr {-$npad}]]
2817 proc optimize_rows {row col endrow} {
2818 global rowidlist rowoffsets idrowranges displayorder
2820 for {} {$row < $endrow} {incr row} {
2821 set idlist [lindex $rowidlist $row]
2822 set offs [lindex $rowoffsets $row]
2823 set haspad 0
2824 for {} {$col < [llength $offs]} {incr col} {
2825 if {[lindex $idlist $col] eq {}} {
2826 set haspad 1
2827 continue
2829 set z [lindex $offs $col]
2830 if {$z eq {}} continue
2831 set isarrow 0
2832 set x0 [expr {$col + $z}]
2833 set y0 [expr {$row - 1}]
2834 set z0 [lindex $rowoffsets $y0 $x0]
2835 if {$z0 eq {}} {
2836 set id [lindex $idlist $col]
2837 set ranges [rowranges $id]
2838 if {$ranges ne {} && $y0 > [lindex $ranges 0]} {
2839 set isarrow 1
2842 # Looking at lines from this row to the previous row,
2843 # make them go straight up if they end in an arrow on
2844 # the previous row; otherwise make them go straight up
2845 # or at 45 degrees.
2846 if {$z < -1 || ($z < 0 && $isarrow)} {
2847 # Line currently goes left too much;
2848 # insert pads in the previous row, then optimize it
2849 set npad [expr {-1 - $z + $isarrow}]
2850 set offs [incrange $offs $col $npad]
2851 insert_pad $y0 $x0 $npad
2852 if {$y0 > 0} {
2853 optimize_rows $y0 $x0 $row
2855 set z [lindex $offs $col]
2856 set x0 [expr {$col + $z}]
2857 set z0 [lindex $rowoffsets $y0 $x0]
2858 } elseif {$z > 1 || ($z > 0 && $isarrow)} {
2859 # Line currently goes right too much;
2860 # insert pads in this line and adjust the next's rowoffsets
2861 set npad [expr {$z - 1 + $isarrow}]
2862 set y1 [expr {$row + 1}]
2863 set offs2 [lindex $rowoffsets $y1]
2864 set x1 -1
2865 foreach z $offs2 {
2866 incr x1
2867 if {$z eq {} || $x1 + $z < $col} continue
2868 if {$x1 + $z > $col} {
2869 incr npad
2871 lset rowoffsets $y1 [incrange $offs2 $x1 $npad]
2872 break
2874 set pad [ntimes $npad {}]
2875 set idlist [eval linsert \$idlist $col $pad]
2876 set tmp [eval linsert \$offs $col $pad]
2877 incr col $npad
2878 set offs [incrange $tmp $col [expr {-$npad}]]
2879 set z [lindex $offs $col]
2880 set haspad 1
2882 if {$z0 eq {} && !$isarrow} {
2883 # this line links to its first child on row $row-2
2884 set rm2 [expr {$row - 2}]
2885 set id [lindex $displayorder $rm2]
2886 set xc [lsearch -exact [lindex $rowidlist $rm2] $id]
2887 if {$xc >= 0} {
2888 set z0 [expr {$xc - $x0}]
2891 # avoid lines jigging left then immediately right
2892 if {$z0 ne {} && $z < 0 && $z0 > 0} {
2893 insert_pad $y0 $x0 1
2894 set offs [incrange $offs $col 1]
2895 optimize_rows $y0 [expr {$x0 + 1}] $row
2898 if {!$haspad} {
2899 set o {}
2900 # Find the first column that doesn't have a line going right
2901 for {set col [llength $idlist]} {[incr col -1] >= 0} {} {
2902 set o [lindex $offs $col]
2903 if {$o eq {}} {
2904 # check if this is the link to the first child
2905 set id [lindex $idlist $col]
2906 set ranges [rowranges $id]
2907 if {$ranges ne {} && $row == [lindex $ranges 0]} {
2908 # it is, work out offset to child
2909 set y0 [expr {$row - 1}]
2910 set id [lindex $displayorder $y0]
2911 set x0 [lsearch -exact [lindex $rowidlist $y0] $id]
2912 if {$x0 >= 0} {
2913 set o [expr {$x0 - $col}]
2917 if {$o eq {} || $o <= 0} break
2919 # Insert a pad at that column as long as it has a line and
2920 # isn't the last column, and adjust the next row' offsets
2921 if {$o ne {} && [incr col] < [llength $idlist]} {
2922 set y1 [expr {$row + 1}]
2923 set offs2 [lindex $rowoffsets $y1]
2924 set x1 -1
2925 foreach z $offs2 {
2926 incr x1
2927 if {$z eq {} || $x1 + $z < $col} continue
2928 lset rowoffsets $y1 [incrange $offs2 $x1 1]
2929 break
2931 set idlist [linsert $idlist $col {}]
2932 set tmp [linsert $offs $col {}]
2933 incr col
2934 set offs [incrange $tmp $col -1]
2937 lset rowidlist $row $idlist
2938 lset rowoffsets $row $offs
2939 set col 0
2943 proc xc {row col} {
2944 global canvx0 linespc
2945 return [expr {$canvx0 + $col * $linespc}]
2948 proc yc {row} {
2949 global canvy0 linespc
2950 return [expr {$canvy0 + $row * $linespc}]
2953 proc linewidth {id} {
2954 global thickerline lthickness
2956 set wid $lthickness
2957 if {[info exists thickerline] && $id eq $thickerline} {
2958 set wid [expr {2 * $lthickness}]
2960 return $wid
2963 proc rowranges {id} {
2964 global phase idrowranges commitrow rowlaidout rowrangelist curview
2966 set ranges {}
2967 if {$phase eq {} ||
2968 ([info exists commitrow($curview,$id)]
2969 && $commitrow($curview,$id) < $rowlaidout)} {
2970 set ranges [lindex $rowrangelist $commitrow($curview,$id)]
2971 } elseif {[info exists idrowranges($id)]} {
2972 set ranges $idrowranges($id)
2974 set linenos {}
2975 foreach rid $ranges {
2976 lappend linenos $commitrow($curview,$rid)
2978 if {$linenos ne {}} {
2979 lset linenos 0 [expr {[lindex $linenos 0] + 1}]
2981 return $linenos
2984 # work around tk8.4 refusal to draw arrows on diagonal segments
2985 proc adjarrowhigh {coords} {
2986 global linespc
2988 set x0 [lindex $coords 0]
2989 set x1 [lindex $coords 2]
2990 if {$x0 != $x1} {
2991 set y0 [lindex $coords 1]
2992 set y1 [lindex $coords 3]
2993 if {$y0 - $y1 <= 2 * $linespc && $x1 == [lindex $coords 4]} {
2994 # we have a nearby vertical segment, just trim off the diag bit
2995 set coords [lrange $coords 2 end]
2996 } else {
2997 set slope [expr {($x0 - $x1) / ($y0 - $y1)}]
2998 set xi [expr {$x0 - $slope * $linespc / 2}]
2999 set yi [expr {$y0 - $linespc / 2}]
3000 set coords [lreplace $coords 0 1 $xi $y0 $xi $yi]
3003 return $coords
3006 proc drawlineseg {id row endrow arrowlow} {
3007 global rowidlist displayorder iddrawn linesegs
3008 global canv colormap linespc curview maxlinelen
3010 set cols [list [lsearch -exact [lindex $rowidlist $row] $id]]
3011 set le [expr {$row + 1}]
3012 set arrowhigh 1
3013 while {1} {
3014 set c [lsearch -exact [lindex $rowidlist $le] $id]
3015 if {$c < 0} {
3016 incr le -1
3017 break
3019 lappend cols $c
3020 set x [lindex $displayorder $le]
3021 if {$x eq $id} {
3022 set arrowhigh 0
3023 break
3025 if {[info exists iddrawn($x)] || $le == $endrow} {
3026 set c [lsearch -exact [lindex $rowidlist [expr {$le+1}]] $id]
3027 if {$c >= 0} {
3028 lappend cols $c
3029 set arrowhigh 0
3031 break
3033 incr le
3035 if {$le <= $row} {
3036 return $row
3039 set lines {}
3040 set i 0
3041 set joinhigh 0
3042 if {[info exists linesegs($id)]} {
3043 set lines $linesegs($id)
3044 foreach li $lines {
3045 set r0 [lindex $li 0]
3046 if {$r0 > $row} {
3047 if {$r0 == $le && [lindex $li 1] - $row <= $maxlinelen} {
3048 set joinhigh 1
3050 break
3052 incr i
3055 set joinlow 0
3056 if {$i > 0} {
3057 set li [lindex $lines [expr {$i-1}]]
3058 set r1 [lindex $li 1]
3059 if {$r1 == $row && $le - [lindex $li 0] <= $maxlinelen} {
3060 set joinlow 1
3064 set x [lindex $cols [expr {$le - $row}]]
3065 set xp [lindex $cols [expr {$le - 1 - $row}]]
3066 set dir [expr {$xp - $x}]
3067 if {$joinhigh} {
3068 set ith [lindex $lines $i 2]
3069 set coords [$canv coords $ith]
3070 set ah [$canv itemcget $ith -arrow]
3071 set arrowhigh [expr {$ah eq "first" || $ah eq "both"}]
3072 set x2 [lindex $cols [expr {$le + 1 - $row}]]
3073 if {$x2 ne {} && $x - $x2 == $dir} {
3074 set coords [lrange $coords 0 end-2]
3076 } else {
3077 set coords [list [xc $le $x] [yc $le]]
3079 if {$joinlow} {
3080 set itl [lindex $lines [expr {$i-1}] 2]
3081 set al [$canv itemcget $itl -arrow]
3082 set arrowlow [expr {$al eq "last" || $al eq "both"}]
3083 } elseif {$arrowlow &&
3084 [lsearch -exact [lindex $rowidlist [expr {$row-1}]] $id] >= 0} {
3085 set arrowlow 0
3087 set arrow [lindex {none first last both} [expr {$arrowhigh + 2*$arrowlow}]]
3088 for {set y $le} {[incr y -1] > $row} {} {
3089 set x $xp
3090 set xp [lindex $cols [expr {$y - 1 - $row}]]
3091 set ndir [expr {$xp - $x}]
3092 if {$dir != $ndir || $xp < 0} {
3093 lappend coords [xc $y $x] [yc $y]
3095 set dir $ndir
3097 if {!$joinlow} {
3098 if {$xp < 0} {
3099 # join parent line to first child
3100 set ch [lindex $displayorder $row]
3101 set xc [lsearch -exact [lindex $rowidlist $row] $ch]
3102 if {$xc < 0} {
3103 puts "oops: drawlineseg: child $ch not on row $row"
3104 } else {
3105 if {$xc < $x - 1} {
3106 lappend coords [xc $row [expr {$x-1}]] [yc $row]
3107 } elseif {$xc > $x + 1} {
3108 lappend coords [xc $row [expr {$x+1}]] [yc $row]
3110 set x $xc
3112 lappend coords [xc $row $x] [yc $row]
3113 } else {
3114 set xn [xc $row $xp]
3115 set yn [yc $row]
3116 # work around tk8.4 refusal to draw arrows on diagonal segments
3117 if {$arrowlow && $xn != [lindex $coords end-1]} {
3118 if {[llength $coords] < 4 ||
3119 [lindex $coords end-3] != [lindex $coords end-1] ||
3120 [lindex $coords end] - $yn > 2 * $linespc} {
3121 set xn [xc $row [expr {$xp - 0.5 * $dir}]]
3122 set yo [yc [expr {$row + 0.5}]]
3123 lappend coords $xn $yo $xn $yn
3125 } else {
3126 lappend coords $xn $yn
3129 if {!$joinhigh} {
3130 if {$arrowhigh} {
3131 set coords [adjarrowhigh $coords]
3133 assigncolor $id
3134 set t [$canv create line $coords -width [linewidth $id] \
3135 -fill $colormap($id) -tags lines.$id -arrow $arrow]
3136 $canv lower $t
3137 bindline $t $id
3138 set lines [linsert $lines $i [list $row $le $t]]
3139 } else {
3140 $canv coords $ith $coords
3141 if {$arrow ne $ah} {
3142 $canv itemconf $ith -arrow $arrow
3144 lset lines $i 0 $row
3146 } else {
3147 set xo [lsearch -exact [lindex $rowidlist [expr {$row - 1}]] $id]
3148 set ndir [expr {$xo - $xp}]
3149 set clow [$canv coords $itl]
3150 if {$dir == $ndir} {
3151 set clow [lrange $clow 2 end]
3153 set coords [concat $coords $clow]
3154 if {!$joinhigh} {
3155 lset lines [expr {$i-1}] 1 $le
3156 if {$arrowhigh} {
3157 set coords [adjarrowhigh $coords]
3159 } else {
3160 # coalesce two pieces
3161 $canv delete $ith
3162 set b [lindex $lines [expr {$i-1}] 0]
3163 set e [lindex $lines $i 1]
3164 set lines [lreplace $lines [expr {$i-1}] $i [list $b $e $itl]]
3166 $canv coords $itl $coords
3167 if {$arrow ne $al} {
3168 $canv itemconf $itl -arrow $arrow
3172 set linesegs($id) $lines
3173 return $le
3176 proc drawparentlinks {id row} {
3177 global rowidlist canv colormap curview parentlist
3178 global idpos
3180 set rowids [lindex $rowidlist $row]
3181 set col [lsearch -exact $rowids $id]
3182 if {$col < 0} return
3183 set olds [lindex $parentlist $row]
3184 set row2 [expr {$row + 1}]
3185 set x [xc $row $col]
3186 set y [yc $row]
3187 set y2 [yc $row2]
3188 set ids [lindex $rowidlist $row2]
3189 # rmx = right-most X coord used
3190 set rmx 0
3191 foreach p $olds {
3192 set i [lsearch -exact $ids $p]
3193 if {$i < 0} {
3194 puts "oops, parent $p of $id not in list"
3195 continue
3197 set x2 [xc $row2 $i]
3198 if {$x2 > $rmx} {
3199 set rmx $x2
3201 if {[lsearch -exact $rowids $p] < 0} {
3202 # drawlineseg will do this one for us
3203 continue
3205 assigncolor $p
3206 # should handle duplicated parents here...
3207 set coords [list $x $y]
3208 if {$i < $col - 1} {
3209 lappend coords [xc $row [expr {$i + 1}]] $y
3210 } elseif {$i > $col + 1} {
3211 lappend coords [xc $row [expr {$i - 1}]] $y
3213 lappend coords $x2 $y2
3214 set t [$canv create line $coords -width [linewidth $p] \
3215 -fill $colormap($p) -tags lines.$p]
3216 $canv lower $t
3217 bindline $t $p
3219 if {$rmx > [lindex $idpos($id) 1]} {
3220 lset idpos($id) 1 $rmx
3221 redrawtags $id
3225 proc drawlines {id} {
3226 global canv
3228 $canv itemconf lines.$id -width [linewidth $id]
3231 proc drawcmittext {id row col} {
3232 global linespc canv canv2 canv3 canvy0 fgcolor
3233 global commitlisted commitinfo rowidlist parentlist
3234 global rowtextx idpos idtags idheads idotherrefs
3235 global linehtag linentag linedtag
3236 global mainfont canvxmax boldrows boldnamerows fgcolor
3238 set ofill [expr {[lindex $commitlisted $row]? "blue": "white"}]
3239 set x [xc $row $col]
3240 set y [yc $row]
3241 set orad [expr {$linespc / 3}]
3242 set t [$canv create oval [expr {$x - $orad}] [expr {$y - $orad}] \
3243 [expr {$x + $orad - 1}] [expr {$y + $orad - 1}] \
3244 -fill $ofill -outline $fgcolor -width 1 -tags circle]
3245 $canv raise $t
3246 $canv bind $t <1> {selcanvline {} %x %y}
3247 set rmx [llength [lindex $rowidlist $row]]
3248 set olds [lindex $parentlist $row]
3249 if {$olds ne {}} {
3250 set nextids [lindex $rowidlist [expr {$row + 1}]]
3251 foreach p $olds {
3252 set i [lsearch -exact $nextids $p]
3253 if {$i > $rmx} {
3254 set rmx $i
3258 set xt [xc $row $rmx]
3259 set rowtextx($row) $xt
3260 set idpos($id) [list $x $xt $y]
3261 if {[info exists idtags($id)] || [info exists idheads($id)]
3262 || [info exists idotherrefs($id)]} {
3263 set xt [drawtags $id $x $xt $y]
3265 set headline [lindex $commitinfo($id) 0]
3266 set name [lindex $commitinfo($id) 1]
3267 set date [lindex $commitinfo($id) 2]
3268 set date [formatdate $date]
3269 set font $mainfont
3270 set nfont $mainfont
3271 set isbold [ishighlighted $row]
3272 if {$isbold > 0} {
3273 lappend boldrows $row
3274 lappend font bold
3275 if {$isbold > 1} {
3276 lappend boldnamerows $row
3277 lappend nfont bold
3280 set linehtag($row) [$canv create text $xt $y -anchor w -fill $fgcolor \
3281 -text $headline -font $font -tags text]
3282 $canv bind $linehtag($row) <Button-3> "rowmenu %X %Y $id"
3283 set linentag($row) [$canv2 create text 3 $y -anchor w -fill $fgcolor \
3284 -text $name -font $nfont -tags text]
3285 set linedtag($row) [$canv3 create text 3 $y -anchor w -fill $fgcolor \
3286 -text $date -font $mainfont -tags text]
3287 set xr [expr {$xt + [font measure $mainfont $headline]}]
3288 if {$xr > $canvxmax} {
3289 set canvxmax $xr
3290 setcanvscroll
3294 proc drawcmitrow {row} {
3295 global displayorder rowidlist
3296 global iddrawn
3297 global commitinfo parentlist numcommits
3298 global filehighlight fhighlights findstring nhighlights
3299 global hlview vhighlights
3300 global highlight_related rhighlights
3302 if {$row >= $numcommits} return
3304 set id [lindex $displayorder $row]
3305 if {[info exists hlview] && ![info exists vhighlights($row)]} {
3306 askvhighlight $row $id
3308 if {[info exists filehighlight] && ![info exists fhighlights($row)]} {
3309 askfilehighlight $row $id
3311 if {$findstring ne {} && ![info exists nhighlights($row)]} {
3312 askfindhighlight $row $id
3314 if {$highlight_related ne "None" && ![info exists rhighlights($row)]} {
3315 askrelhighlight $row $id
3317 if {[info exists iddrawn($id)]} return
3318 set col [lsearch -exact [lindex $rowidlist $row] $id]
3319 if {$col < 0} {
3320 puts "oops, row $row id $id not in list"
3321 return
3323 if {![info exists commitinfo($id)]} {
3324 getcommit $id
3326 assigncolor $id
3327 drawcmittext $id $row $col
3328 set iddrawn($id) 1
3331 proc drawcommits {row {endrow {}}} {
3332 global numcommits iddrawn displayorder curview
3333 global parentlist rowidlist
3335 if {$row < 0} {
3336 set row 0
3338 if {$endrow eq {}} {
3339 set endrow $row
3341 if {$endrow >= $numcommits} {
3342 set endrow [expr {$numcommits - 1}]
3345 # make the lines join to already-drawn rows either side
3346 set r [expr {$row - 1}]
3347 if {$r < 0 || ![info exists iddrawn([lindex $displayorder $r])]} {
3348 set r $row
3350 set er [expr {$endrow + 1}]
3351 if {$er >= $numcommits ||
3352 ![info exists iddrawn([lindex $displayorder $er])]} {
3353 set er $endrow
3355 for {} {$r <= $er} {incr r} {
3356 set id [lindex $displayorder $r]
3357 set wasdrawn [info exists iddrawn($id)]
3358 if {!$wasdrawn} {
3359 drawcmitrow $r
3361 if {$r == $er} break
3362 set nextid [lindex $displayorder [expr {$r + 1}]]
3363 if {$wasdrawn && [info exists iddrawn($nextid)]} {
3364 catch {unset prevlines}
3365 continue
3367 drawparentlinks $id $r
3369 if {[info exists lineends($r)]} {
3370 foreach lid $lineends($r) {
3371 unset prevlines($lid)
3374 set rowids [lindex $rowidlist $r]
3375 foreach lid $rowids {
3376 if {$lid eq {}} continue
3377 if {$lid eq $id} {
3378 # see if this is the first child of any of its parents
3379 foreach p [lindex $parentlist $r] {
3380 if {[lsearch -exact $rowids $p] < 0} {
3381 # make this line extend up to the child
3382 set le [drawlineseg $p $r $er 0]
3383 lappend lineends($le) $p
3384 set prevlines($p) 1
3387 } elseif {![info exists prevlines($lid)]} {
3388 set le [drawlineseg $lid $r $er 1]
3389 lappend lineends($le) $lid
3390 set prevlines($lid) 1
3396 proc drawfrac {f0 f1} {
3397 global canv linespc
3399 set ymax [lindex [$canv cget -scrollregion] 3]
3400 if {$ymax eq {} || $ymax == 0} return
3401 set y0 [expr {int($f0 * $ymax)}]
3402 set row [expr {int(($y0 - 3) / $linespc) - 1}]
3403 set y1 [expr {int($f1 * $ymax)}]
3404 set endrow [expr {int(($y1 - 3) / $linespc) + 1}]
3405 drawcommits $row $endrow
3408 proc drawvisible {} {
3409 global canv
3410 eval drawfrac [$canv yview]
3413 proc clear_display {} {
3414 global iddrawn linesegs
3415 global vhighlights fhighlights nhighlights rhighlights
3417 allcanvs delete all
3418 catch {unset iddrawn}
3419 catch {unset linesegs}
3420 catch {unset vhighlights}
3421 catch {unset fhighlights}
3422 catch {unset nhighlights}
3423 catch {unset rhighlights}
3426 proc findcrossings {id} {
3427 global rowidlist parentlist numcommits rowoffsets displayorder
3429 set cross {}
3430 set ccross {}
3431 foreach {s e} [rowranges $id] {
3432 if {$e >= $numcommits} {
3433 set e [expr {$numcommits - 1}]
3435 if {$e <= $s} continue
3436 set x [lsearch -exact [lindex $rowidlist $e] $id]
3437 if {$x < 0} {
3438 puts "findcrossings: oops, no [shortids $id] in row $e"
3439 continue
3441 for {set row $e} {[incr row -1] >= $s} {} {
3442 set olds [lindex $parentlist $row]
3443 set kid [lindex $displayorder $row]
3444 set kidx [lsearch -exact [lindex $rowidlist $row] $kid]
3445 if {$kidx < 0} continue
3446 set nextrow [lindex $rowidlist [expr {$row + 1}]]
3447 foreach p $olds {
3448 set px [lsearch -exact $nextrow $p]
3449 if {$px < 0} continue
3450 if {($kidx < $x && $x < $px) || ($px < $x && $x < $kidx)} {
3451 if {[lsearch -exact $ccross $p] >= 0} continue
3452 if {$x == $px + ($kidx < $px? -1: 1)} {
3453 lappend ccross $p
3454 } elseif {[lsearch -exact $cross $p] < 0} {
3455 lappend cross $p
3459 set inc [lindex $rowoffsets $row $x]
3460 if {$inc eq {}} break
3461 incr x $inc
3464 return [concat $ccross {{}} $cross]
3467 proc assigncolor {id} {
3468 global colormap colors nextcolor
3469 global commitrow parentlist children children curview
3471 if {[info exists colormap($id)]} return
3472 set ncolors [llength $colors]
3473 if {[info exists children($curview,$id)]} {
3474 set kids $children($curview,$id)
3475 } else {
3476 set kids {}
3478 if {[llength $kids] == 1} {
3479 set child [lindex $kids 0]
3480 if {[info exists colormap($child)]
3481 && [llength [lindex $parentlist $commitrow($curview,$child)]] == 1} {
3482 set colormap($id) $colormap($child)
3483 return
3486 set badcolors {}
3487 set origbad {}
3488 foreach x [findcrossings $id] {
3489 if {$x eq {}} {
3490 # delimiter between corner crossings and other crossings
3491 if {[llength $badcolors] >= $ncolors - 1} break
3492 set origbad $badcolors
3494 if {[info exists colormap($x)]
3495 && [lsearch -exact $badcolors $colormap($x)] < 0} {
3496 lappend badcolors $colormap($x)
3499 if {[llength $badcolors] >= $ncolors} {
3500 set badcolors $origbad
3502 set origbad $badcolors
3503 if {[llength $badcolors] < $ncolors - 1} {
3504 foreach child $kids {
3505 if {[info exists colormap($child)]
3506 && [lsearch -exact $badcolors $colormap($child)] < 0} {
3507 lappend badcolors $colormap($child)
3509 foreach p [lindex $parentlist $commitrow($curview,$child)] {
3510 if {[info exists colormap($p)]
3511 && [lsearch -exact $badcolors $colormap($p)] < 0} {
3512 lappend badcolors $colormap($p)
3516 if {[llength $badcolors] >= $ncolors} {
3517 set badcolors $origbad
3520 for {set i 0} {$i <= $ncolors} {incr i} {
3521 set c [lindex $colors $nextcolor]
3522 if {[incr nextcolor] >= $ncolors} {
3523 set nextcolor 0
3525 if {[lsearch -exact $badcolors $c]} break
3527 set colormap($id) $c
3530 proc bindline {t id} {
3531 global canv
3533 $canv bind $t <Enter> "lineenter %x %y $id"
3534 $canv bind $t <Motion> "linemotion %x %y $id"
3535 $canv bind $t <Leave> "lineleave $id"
3536 $canv bind $t <Button-1> "lineclick %x %y $id 1"
3539 proc drawtags {id x xt y1} {
3540 global idtags idheads idotherrefs mainhead
3541 global linespc lthickness
3542 global canv mainfont commitrow rowtextx curview fgcolor bgcolor
3544 set marks {}
3545 set ntags 0
3546 set nheads 0
3547 if {[info exists idtags($id)]} {
3548 set marks $idtags($id)
3549 set ntags [llength $marks]
3551 if {[info exists idheads($id)]} {
3552 set marks [concat $marks $idheads($id)]
3553 set nheads [llength $idheads($id)]
3555 if {[info exists idotherrefs($id)]} {
3556 set marks [concat $marks $idotherrefs($id)]
3558 if {$marks eq {}} {
3559 return $xt
3562 set delta [expr {int(0.5 * ($linespc - $lthickness))}]
3563 set yt [expr {$y1 - 0.5 * $linespc}]
3564 set yb [expr {$yt + $linespc - 1}]
3565 set xvals {}
3566 set wvals {}
3567 set i -1
3568 foreach tag $marks {
3569 incr i
3570 if {$i >= $ntags && $i < $ntags + $nheads && $tag eq $mainhead} {
3571 set wid [font measure [concat $mainfont bold] $tag]
3572 } else {
3573 set wid [font measure $mainfont $tag]
3575 lappend xvals $xt
3576 lappend wvals $wid
3577 set xt [expr {$xt + $delta + $wid + $lthickness + $linespc}]
3579 set t [$canv create line $x $y1 [lindex $xvals end] $y1 \
3580 -width $lthickness -fill black -tags tag.$id]
3581 $canv lower $t
3582 foreach tag $marks x $xvals wid $wvals {
3583 set xl [expr {$x + $delta}]
3584 set xr [expr {$x + $delta + $wid + $lthickness}]
3585 set font $mainfont
3586 if {[incr ntags -1] >= 0} {
3587 # draw a tag
3588 set t [$canv create polygon $x [expr {$yt + $delta}] $xl $yt \
3589 $xr $yt $xr $yb $xl $yb $x [expr {$yb - $delta}] \
3590 -width 1 -outline black -fill yellow -tags tag.$id]
3591 $canv bind $t <1> [list showtag $tag 1]
3592 set rowtextx($commitrow($curview,$id)) [expr {$xr + $linespc}]
3593 } else {
3594 # draw a head or other ref
3595 if {[incr nheads -1] >= 0} {
3596 set col green
3597 if {$tag eq $mainhead} {
3598 lappend font bold
3600 } else {
3601 set col "#ddddff"
3603 set xl [expr {$xl - $delta/2}]
3604 $canv create polygon $x $yt $xr $yt $xr $yb $x $yb \
3605 -width 1 -outline black -fill $col -tags tag.$id
3606 if {[regexp {^(remotes/.*/|remotes/)} $tag match remoteprefix]} {
3607 set rwid [font measure $mainfont $remoteprefix]
3608 set xi [expr {$x + 1}]
3609 set yti [expr {$yt + 1}]
3610 set xri [expr {$x + $rwid}]
3611 $canv create polygon $xi $yti $xri $yti $xri $yb $xi $yb \
3612 -width 0 -fill "#ffddaa" -tags tag.$id
3615 set t [$canv create text $xl $y1 -anchor w -text $tag -fill $fgcolor \
3616 -font $font -tags [list tag.$id text]]
3617 if {$ntags >= 0} {
3618 $canv bind $t <1> [list showtag $tag 1]
3619 } elseif {$nheads >= 0} {
3620 $canv bind $t <Button-3> [list headmenu %X %Y $id $tag]
3623 return $xt
3626 proc xcoord {i level ln} {
3627 global canvx0 xspc1 xspc2
3629 set x [expr {$canvx0 + $i * $xspc1($ln)}]
3630 if {$i > 0 && $i == $level} {
3631 set x [expr {$x + 0.5 * ($xspc2 - $xspc1($ln))}]
3632 } elseif {$i > $level} {
3633 set x [expr {$x + $xspc2 - $xspc1($ln)}]
3635 return $x
3638 proc show_status {msg} {
3639 global canv mainfont fgcolor
3641 clear_display
3642 $canv create text 3 3 -anchor nw -text $msg -font $mainfont \
3643 -tags text -fill $fgcolor
3646 # Insert a new commit as the child of the commit on row $row.
3647 # The new commit will be displayed on row $row and the commits
3648 # on that row and below will move down one row.
3649 proc insertrow {row newcmit} {
3650 global displayorder parentlist childlist commitlisted
3651 global commitrow curview rowidlist rowoffsets numcommits
3652 global rowrangelist rowlaidout rowoptim numcommits
3653 global selectedline
3655 if {$row >= $numcommits} {
3656 puts "oops, inserting new row $row but only have $numcommits rows"
3657 return
3659 set p [lindex $displayorder $row]
3660 set displayorder [linsert $displayorder $row $newcmit]
3661 set parentlist [linsert $parentlist $row $p]
3662 set kids [lindex $childlist $row]
3663 lappend kids $newcmit
3664 lset childlist $row $kids
3665 set childlist [linsert $childlist $row {}]
3666 set commitlisted [linsert $commitlisted $row 1]
3667 set l [llength $displayorder]
3668 for {set r $row} {$r < $l} {incr r} {
3669 set id [lindex $displayorder $r]
3670 set commitrow($curview,$id) $r
3673 set idlist [lindex $rowidlist $row]
3674 set offs [lindex $rowoffsets $row]
3675 set newoffs {}
3676 foreach x $idlist {
3677 if {$x eq {} || ($x eq $p && [llength $kids] == 1)} {
3678 lappend newoffs {}
3679 } else {
3680 lappend newoffs 0
3683 if {[llength $kids] == 1} {
3684 set col [lsearch -exact $idlist $p]
3685 lset idlist $col $newcmit
3686 } else {
3687 set col [llength $idlist]
3688 lappend idlist $newcmit
3689 lappend offs {}
3690 lset rowoffsets $row $offs
3692 set rowidlist [linsert $rowidlist $row $idlist]
3693 set rowoffsets [linsert $rowoffsets [expr {$row+1}] $newoffs]
3695 set rowrangelist [linsert $rowrangelist $row {}]
3696 if {[llength $kids] > 1} {
3697 set rp1 [expr {$row + 1}]
3698 set ranges [lindex $rowrangelist $rp1]
3699 if {$ranges eq {}} {
3700 set ranges [list $newcmit $p]
3701 } elseif {[lindex $ranges end-1] eq $p} {
3702 lset ranges end-1 $newcmit
3704 lset rowrangelist $rp1 $ranges
3707 incr rowlaidout
3708 incr rowoptim
3709 incr numcommits
3711 if {[info exists selectedline] && $selectedline >= $row} {
3712 incr selectedline
3714 redisplay
3717 # Don't change the text pane cursor if it is currently the hand cursor,
3718 # showing that we are over a sha1 ID link.
3719 proc settextcursor {c} {
3720 global ctext curtextcursor
3722 if {[$ctext cget -cursor] == $curtextcursor} {
3723 $ctext config -cursor $c
3725 set curtextcursor $c
3728 proc nowbusy {what} {
3729 global isbusy
3731 if {[array names isbusy] eq {}} {
3732 . config -cursor watch
3733 settextcursor watch
3735 set isbusy($what) 1
3738 proc notbusy {what} {
3739 global isbusy maincursor textcursor
3741 catch {unset isbusy($what)}
3742 if {[array names isbusy] eq {}} {
3743 . config -cursor $maincursor
3744 settextcursor $textcursor
3748 proc findmatches {f} {
3749 global findtype foundstring foundstrlen
3750 if {$findtype == "Regexp"} {
3751 set matches [regexp -indices -all -inline $foundstring $f]
3752 } else {
3753 if {$findtype == "IgnCase"} {
3754 set str [string tolower $f]
3755 } else {
3756 set str $f
3758 set matches {}
3759 set i 0
3760 while {[set j [string first $foundstring $str $i]] >= 0} {
3761 lappend matches [list $j [expr {$j+$foundstrlen-1}]]
3762 set i [expr {$j + $foundstrlen}]
3765 return $matches
3768 proc dofind {} {
3769 global findtype findloc findstring markedmatches commitinfo
3770 global numcommits displayorder linehtag linentag linedtag
3771 global mainfont canv canv2 canv3 selectedline
3772 global matchinglines foundstring foundstrlen matchstring
3773 global commitdata
3775 stopfindproc
3776 unmarkmatches
3777 cancel_next_highlight
3778 focus .
3779 set matchinglines {}
3780 if {$findtype == "IgnCase"} {
3781 set foundstring [string tolower $findstring]
3782 } else {
3783 set foundstring $findstring
3785 set foundstrlen [string length $findstring]
3786 if {$foundstrlen == 0} return
3787 regsub -all {[*?\[\\]} $foundstring {\\&} matchstring
3788 set matchstring "*$matchstring*"
3789 if {![info exists selectedline]} {
3790 set oldsel -1
3791 } else {
3792 set oldsel $selectedline
3794 set didsel 0
3795 set fldtypes {Headline Author Date Committer CDate Comments}
3796 set l -1
3797 foreach id $displayorder {
3798 set d $commitdata($id)
3799 incr l
3800 if {$findtype == "Regexp"} {
3801 set doesmatch [regexp $foundstring $d]
3802 } elseif {$findtype == "IgnCase"} {
3803 set doesmatch [string match -nocase $matchstring $d]
3804 } else {
3805 set doesmatch [string match $matchstring $d]
3807 if {!$doesmatch} continue
3808 if {![info exists commitinfo($id)]} {
3809 getcommit $id
3811 set info $commitinfo($id)
3812 set doesmatch 0
3813 foreach f $info ty $fldtypes {
3814 if {$findloc != "All fields" && $findloc != $ty} {
3815 continue
3817 set matches [findmatches $f]
3818 if {$matches == {}} continue
3819 set doesmatch 1
3820 if {$ty == "Headline"} {
3821 drawcommits $l
3822 markmatches $canv $l $f $linehtag($l) $matches $mainfont
3823 } elseif {$ty == "Author"} {
3824 drawcommits $l
3825 markmatches $canv2 $l $f $linentag($l) $matches $mainfont
3826 } elseif {$ty == "Date"} {
3827 drawcommits $l
3828 markmatches $canv3 $l $f $linedtag($l) $matches $mainfont
3831 if {$doesmatch} {
3832 lappend matchinglines $l
3833 if {!$didsel && $l > $oldsel} {
3834 findselectline $l
3835 set didsel 1
3839 if {$matchinglines == {}} {
3840 bell
3841 } elseif {!$didsel} {
3842 findselectline [lindex $matchinglines 0]
3846 proc findselectline {l} {
3847 global findloc commentend ctext
3848 selectline $l 1
3849 if {$findloc == "All fields" || $findloc == "Comments"} {
3850 # highlight the matches in the comments
3851 set f [$ctext get 1.0 $commentend]
3852 set matches [findmatches $f]
3853 foreach match $matches {
3854 set start [lindex $match 0]
3855 set end [expr {[lindex $match 1] + 1}]
3856 $ctext tag add found "1.0 + $start c" "1.0 + $end c"
3861 proc findnext {restart} {
3862 global matchinglines selectedline
3863 if {![info exists matchinglines]} {
3864 if {$restart} {
3865 dofind
3867 return
3869 if {![info exists selectedline]} return
3870 foreach l $matchinglines {
3871 if {$l > $selectedline} {
3872 findselectline $l
3873 return
3876 bell
3879 proc findprev {} {
3880 global matchinglines selectedline
3881 if {![info exists matchinglines]} {
3882 dofind
3883 return
3885 if {![info exists selectedline]} return
3886 set prev {}
3887 foreach l $matchinglines {
3888 if {$l >= $selectedline} break
3889 set prev $l
3891 if {$prev != {}} {
3892 findselectline $prev
3893 } else {
3894 bell
3898 proc stopfindproc {{done 0}} {
3899 global findprocpid findprocfile findids
3900 global ctext findoldcursor phase maincursor textcursor
3901 global findinprogress
3903 catch {unset findids}
3904 if {[info exists findprocpid]} {
3905 if {!$done} {
3906 catch {exec kill $findprocpid}
3908 catch {close $findprocfile}
3909 unset findprocpid
3911 catch {unset findinprogress}
3912 notbusy find
3915 # mark a commit as matching by putting a yellow background
3916 # behind the headline
3917 proc markheadline {l id} {
3918 global canv mainfont linehtag
3920 drawcommits $l
3921 set bbox [$canv bbox $linehtag($l)]
3922 set t [$canv create rect $bbox -outline {} -tags matches -fill yellow]
3923 $canv lower $t
3926 # mark the bits of a headline, author or date that match a find string
3927 proc markmatches {canv l str tag matches font} {
3928 set bbox [$canv bbox $tag]
3929 set x0 [lindex $bbox 0]
3930 set y0 [lindex $bbox 1]
3931 set y1 [lindex $bbox 3]
3932 foreach match $matches {
3933 set start [lindex $match 0]
3934 set end [lindex $match 1]
3935 if {$start > $end} continue
3936 set xoff [font measure $font [string range $str 0 [expr {$start-1}]]]
3937 set xlen [font measure $font [string range $str 0 [expr {$end}]]]
3938 set t [$canv create rect [expr {$x0+$xoff}] $y0 \
3939 [expr {$x0+$xlen+2}] $y1 \
3940 -outline {} -tags matches -fill yellow]
3941 $canv lower $t
3945 proc unmarkmatches {} {
3946 global matchinglines findids
3947 allcanvs delete matches
3948 catch {unset matchinglines}
3949 catch {unset findids}
3952 proc selcanvline {w x y} {
3953 global canv canvy0 ctext linespc
3954 global rowtextx
3955 set ymax [lindex [$canv cget -scrollregion] 3]
3956 if {$ymax == {}} return
3957 set yfrac [lindex [$canv yview] 0]
3958 set y [expr {$y + $yfrac * $ymax}]
3959 set l [expr {int(($y - $canvy0) / $linespc + 0.5)}]
3960 if {$l < 0} {
3961 set l 0
3963 if {$w eq $canv} {
3964 if {![info exists rowtextx($l)] || $x < $rowtextx($l)} return
3966 unmarkmatches
3967 selectline $l 1
3970 proc commit_descriptor {p} {
3971 global commitinfo
3972 if {![info exists commitinfo($p)]} {
3973 getcommit $p
3975 set l "..."
3976 if {[llength $commitinfo($p)] > 1} {
3977 set l [lindex $commitinfo($p) 0]
3979 return "$p ($l)\n"
3982 # append some text to the ctext widget, and make any SHA1 ID
3983 # that we know about be a clickable link.
3984 proc appendwithlinks {text tags} {
3985 global ctext commitrow linknum curview
3987 set start [$ctext index "end - 1c"]
3988 $ctext insert end $text $tags
3989 set links [regexp -indices -all -inline {[0-9a-f]{40}} $text]
3990 foreach l $links {
3991 set s [lindex $l 0]
3992 set e [lindex $l 1]
3993 set linkid [string range $text $s $e]
3994 if {![info exists commitrow($curview,$linkid)]} continue
3995 incr e
3996 $ctext tag add link "$start + $s c" "$start + $e c"
3997 $ctext tag add link$linknum "$start + $s c" "$start + $e c"
3998 $ctext tag bind link$linknum <1> \
3999 [list selectline $commitrow($curview,$linkid) 1]
4000 incr linknum
4002 $ctext tag conf link -foreground blue -underline 1
4003 $ctext tag bind link <Enter> { %W configure -cursor hand2 }
4004 $ctext tag bind link <Leave> { %W configure -cursor $curtextcursor }
4007 proc viewnextline {dir} {
4008 global canv linespc
4010 $canv delete hover
4011 set ymax [lindex [$canv cget -scrollregion] 3]
4012 set wnow [$canv yview]
4013 set wtop [expr {[lindex $wnow 0] * $ymax}]
4014 set newtop [expr {$wtop + $dir * $linespc}]
4015 if {$newtop < 0} {
4016 set newtop 0
4017 } elseif {$newtop > $ymax} {
4018 set newtop $ymax
4020 allcanvs yview moveto [expr {$newtop * 1.0 / $ymax}]
4023 # add a list of tag or branch names at position pos
4024 # returns the number of names inserted
4025 proc appendrefs {pos ids var} {
4026 global ctext commitrow linknum curview $var maxrefs
4028 if {[catch {$ctext index $pos}]} {
4029 return 0
4031 $ctext conf -state normal
4032 $ctext delete $pos "$pos lineend"
4033 set tags {}
4034 foreach id $ids {
4035 foreach tag [set $var\($id\)] {
4036 lappend tags [list $tag $id]
4039 if {[llength $tags] > $maxrefs} {
4040 $ctext insert $pos "many ([llength $tags])"
4041 } else {
4042 set tags [lsort -index 0 -decreasing $tags]
4043 set sep {}
4044 foreach ti $tags {
4045 set id [lindex $ti 1]
4046 set lk link$linknum
4047 incr linknum
4048 $ctext tag delete $lk
4049 $ctext insert $pos $sep
4050 $ctext insert $pos [lindex $ti 0] $lk
4051 if {[info exists commitrow($curview,$id)]} {
4052 $ctext tag conf $lk -foreground blue
4053 $ctext tag bind $lk <1> \
4054 [list selectline $commitrow($curview,$id) 1]
4055 $ctext tag conf $lk -underline 1
4056 $ctext tag bind $lk <Enter> { %W configure -cursor hand2 }
4057 $ctext tag bind $lk <Leave> \
4058 { %W configure -cursor $curtextcursor }
4060 set sep ", "
4063 $ctext conf -state disabled
4064 return [llength $tags]
4067 # called when we have finished computing the nearby tags
4068 proc dispneartags {delay} {
4069 global selectedline currentid showneartags tagphase
4071 if {![info exists selectedline] || !$showneartags} return
4072 after cancel dispnexttag
4073 if {$delay} {
4074 after 200 dispnexttag
4075 set tagphase -1
4076 } else {
4077 after idle dispnexttag
4078 set tagphase 0
4082 proc dispnexttag {} {
4083 global selectedline currentid showneartags tagphase ctext
4085 if {![info exists selectedline] || !$showneartags} return
4086 switch -- $tagphase {
4088 set dtags [desctags $currentid]
4089 if {$dtags ne {}} {
4090 appendrefs precedes $dtags idtags
4094 set atags [anctags $currentid]
4095 if {$atags ne {}} {
4096 appendrefs follows $atags idtags
4100 set dheads [descheads $currentid]
4101 if {$dheads ne {}} {
4102 if {[appendrefs branch $dheads idheads] > 1
4103 && [$ctext get "branch -3c"] eq "h"} {
4104 # turn "Branch" into "Branches"
4105 $ctext conf -state normal
4106 $ctext insert "branch -2c" "es"
4107 $ctext conf -state disabled
4112 if {[incr tagphase] <= 2} {
4113 after idle dispnexttag
4117 proc selectline {l isnew} {
4118 global canv canv2 canv3 ctext commitinfo selectedline
4119 global displayorder linehtag linentag linedtag
4120 global canvy0 linespc parentlist childlist
4121 global currentid sha1entry
4122 global commentend idtags linknum
4123 global mergemax numcommits pending_select
4124 global cmitmode showneartags allcommits
4126 catch {unset pending_select}
4127 $canv delete hover
4128 normalline
4129 cancel_next_highlight
4130 if {$l < 0 || $l >= $numcommits} return
4131 set y [expr {$canvy0 + $l * $linespc}]
4132 set ymax [lindex [$canv cget -scrollregion] 3]
4133 set ytop [expr {$y - $linespc - 1}]
4134 set ybot [expr {$y + $linespc + 1}]
4135 set wnow [$canv yview]
4136 set wtop [expr {[lindex $wnow 0] * $ymax}]
4137 set wbot [expr {[lindex $wnow 1] * $ymax}]
4138 set wh [expr {$wbot - $wtop}]
4139 set newtop $wtop
4140 if {$ytop < $wtop} {
4141 if {$ybot < $wtop} {
4142 set newtop [expr {$y - $wh / 2.0}]
4143 } else {
4144 set newtop $ytop
4145 if {$newtop > $wtop - $linespc} {
4146 set newtop [expr {$wtop - $linespc}]
4149 } elseif {$ybot > $wbot} {
4150 if {$ytop > $wbot} {
4151 set newtop [expr {$y - $wh / 2.0}]
4152 } else {
4153 set newtop [expr {$ybot - $wh}]
4154 if {$newtop < $wtop + $linespc} {
4155 set newtop [expr {$wtop + $linespc}]
4159 if {$newtop != $wtop} {
4160 if {$newtop < 0} {
4161 set newtop 0
4163 allcanvs yview moveto [expr {$newtop * 1.0 / $ymax}]
4164 drawvisible
4167 if {![info exists linehtag($l)]} return
4168 $canv delete secsel
4169 set t [eval $canv create rect [$canv bbox $linehtag($l)] -outline {{}} \
4170 -tags secsel -fill [$canv cget -selectbackground]]
4171 $canv lower $t
4172 $canv2 delete secsel
4173 set t [eval $canv2 create rect [$canv2 bbox $linentag($l)] -outline {{}} \
4174 -tags secsel -fill [$canv2 cget -selectbackground]]
4175 $canv2 lower $t
4176 $canv3 delete secsel
4177 set t [eval $canv3 create rect [$canv3 bbox $linedtag($l)] -outline {{}} \
4178 -tags secsel -fill [$canv3 cget -selectbackground]]
4179 $canv3 lower $t
4181 if {$isnew} {
4182 addtohistory [list selectline $l 0]
4185 set selectedline $l
4187 set id [lindex $displayorder $l]
4188 set currentid $id
4189 $sha1entry delete 0 end
4190 $sha1entry insert 0 $id
4191 $sha1entry selection from 0
4192 $sha1entry selection to end
4193 rhighlight_sel $id
4195 $ctext conf -state normal
4196 clear_ctext
4197 set linknum 0
4198 set info $commitinfo($id)
4199 set date [formatdate [lindex $info 2]]
4200 $ctext insert end "Author: [lindex $info 1] $date\n"
4201 set date [formatdate [lindex $info 4]]
4202 $ctext insert end "Committer: [lindex $info 3] $date\n"
4203 if {[info exists idtags($id)]} {
4204 $ctext insert end "Tags:"
4205 foreach tag $idtags($id) {
4206 $ctext insert end " $tag"
4208 $ctext insert end "\n"
4211 set headers {}
4212 set olds [lindex $parentlist $l]
4213 if {[llength $olds] > 1} {
4214 set np 0
4215 foreach p $olds {
4216 if {$np >= $mergemax} {
4217 set tag mmax
4218 } else {
4219 set tag m$np
4221 $ctext insert end "Parent: " $tag
4222 appendwithlinks [commit_descriptor $p] {}
4223 incr np
4225 } else {
4226 foreach p $olds {
4227 append headers "Parent: [commit_descriptor $p]"
4231 foreach c [lindex $childlist $l] {
4232 append headers "Child: [commit_descriptor $c]"
4235 # make anything that looks like a SHA1 ID be a clickable link
4236 appendwithlinks $headers {}
4237 if {$showneartags} {
4238 if {![info exists allcommits]} {
4239 getallcommits
4241 $ctext insert end "Branch: "
4242 $ctext mark set branch "end -1c"
4243 $ctext mark gravity branch left
4244 $ctext insert end "\nFollows: "
4245 $ctext mark set follows "end -1c"
4246 $ctext mark gravity follows left
4247 $ctext insert end "\nPrecedes: "
4248 $ctext mark set precedes "end -1c"
4249 $ctext mark gravity precedes left
4250 $ctext insert end "\n"
4251 dispneartags 1
4253 $ctext insert end "\n"
4254 set comment [lindex $info 5]
4255 if {[string first "\r" $comment] >= 0} {
4256 set comment [string map {"\r" "\n "} $comment]
4258 appendwithlinks $comment {comment}
4260 $ctext tag delete Comments
4261 $ctext tag remove found 1.0 end
4262 $ctext conf -state disabled
4263 set commentend [$ctext index "end - 1c"]
4265 init_flist "Comments"
4266 if {$cmitmode eq "tree"} {
4267 gettree $id
4268 } elseif {[llength $olds] <= 1} {
4269 startdiff $id
4270 } else {
4271 mergediff $id $l
4275 proc selfirstline {} {
4276 unmarkmatches
4277 selectline 0 1
4280 proc sellastline {} {
4281 global numcommits
4282 unmarkmatches
4283 set l [expr {$numcommits - 1}]
4284 selectline $l 1
4287 proc selnextline {dir} {
4288 global selectedline
4289 if {![info exists selectedline]} return
4290 set l [expr {$selectedline + $dir}]
4291 unmarkmatches
4292 selectline $l 1
4295 proc selnextpage {dir} {
4296 global canv linespc selectedline numcommits
4298 set lpp [expr {([winfo height $canv] - 2) / $linespc}]
4299 if {$lpp < 1} {
4300 set lpp 1
4302 allcanvs yview scroll [expr {$dir * $lpp}] units
4303 drawvisible
4304 if {![info exists selectedline]} return
4305 set l [expr {$selectedline + $dir * $lpp}]
4306 if {$l < 0} {
4307 set l 0
4308 } elseif {$l >= $numcommits} {
4309 set l [expr $numcommits - 1]
4311 unmarkmatches
4312 selectline $l 1
4315 proc unselectline {} {
4316 global selectedline currentid
4318 catch {unset selectedline}
4319 catch {unset currentid}
4320 allcanvs delete secsel
4321 rhighlight_none
4322 cancel_next_highlight
4325 proc reselectline {} {
4326 global selectedline
4328 if {[info exists selectedline]} {
4329 selectline $selectedline 0
4333 proc addtohistory {cmd} {
4334 global history historyindex curview
4336 set elt [list $curview $cmd]
4337 if {$historyindex > 0
4338 && [lindex $history [expr {$historyindex - 1}]] == $elt} {
4339 return
4342 if {$historyindex < [llength $history]} {
4343 set history [lreplace $history $historyindex end $elt]
4344 } else {
4345 lappend history $elt
4347 incr historyindex
4348 if {$historyindex > 1} {
4349 .tf.bar.leftbut conf -state normal
4350 } else {
4351 .tf.bar.leftbut conf -state disabled
4353 .tf.bar.rightbut conf -state disabled
4356 proc godo {elt} {
4357 global curview
4359 set view [lindex $elt 0]
4360 set cmd [lindex $elt 1]
4361 if {$curview != $view} {
4362 showview $view
4364 eval $cmd
4367 proc goback {} {
4368 global history historyindex
4370 if {$historyindex > 1} {
4371 incr historyindex -1
4372 godo [lindex $history [expr {$historyindex - 1}]]
4373 .tf.bar.rightbut conf -state normal
4375 if {$historyindex <= 1} {
4376 .tf.bar.leftbut conf -state disabled
4380 proc goforw {} {
4381 global history historyindex
4383 if {$historyindex < [llength $history]} {
4384 set cmd [lindex $history $historyindex]
4385 incr historyindex
4386 godo $cmd
4387 .tf.bar.leftbut conf -state normal
4389 if {$historyindex >= [llength $history]} {
4390 .tf.bar.rightbut conf -state disabled
4394 proc gettree {id} {
4395 global treefilelist treeidlist diffids diffmergeid treepending
4397 set diffids $id
4398 catch {unset diffmergeid}
4399 if {![info exists treefilelist($id)]} {
4400 if {![info exists treepending]} {
4401 if {[catch {set gtf [open [concat | git ls-tree -r $id] r]}]} {
4402 return
4404 set treepending $id
4405 set treefilelist($id) {}
4406 set treeidlist($id) {}
4407 fconfigure $gtf -blocking 0
4408 filerun $gtf [list gettreeline $gtf $id]
4410 } else {
4411 setfilelist $id
4415 proc gettreeline {gtf id} {
4416 global treefilelist treeidlist treepending cmitmode diffids
4418 set nl 0
4419 while {[incr nl] <= 1000 && [gets $gtf line] >= 0} {
4420 set tl [split $line "\t"]
4421 if {[lindex $tl 0 1] ne "blob"} continue
4422 set sha1 [lindex $tl 0 2]
4423 set fname [lindex $tl 1]
4424 if {[string index $fname 0] eq "\""} {
4425 set fname [lindex $fname 0]
4427 lappend treeidlist($id) $sha1
4428 lappend treefilelist($id) $fname
4430 if {![eof $gtf]} {
4431 return [expr {$nl >= 1000? 2: 1}]
4433 close $gtf
4434 unset treepending
4435 if {$cmitmode ne "tree"} {
4436 if {![info exists diffmergeid]} {
4437 gettreediffs $diffids
4439 } elseif {$id ne $diffids} {
4440 gettree $diffids
4441 } else {
4442 setfilelist $id
4444 return 0
4447 proc showfile {f} {
4448 global treefilelist treeidlist diffids
4449 global ctext commentend
4451 set i [lsearch -exact $treefilelist($diffids) $f]
4452 if {$i < 0} {
4453 puts "oops, $f not in list for id $diffids"
4454 return
4456 set blob [lindex $treeidlist($diffids) $i]
4457 if {[catch {set bf [open [concat | git cat-file blob $blob] r]} err]} {
4458 puts "oops, error reading blob $blob: $err"
4459 return
4461 fconfigure $bf -blocking 0
4462 filerun $bf [list getblobline $bf $diffids]
4463 $ctext config -state normal
4464 clear_ctext $commentend
4465 $ctext insert end "\n"
4466 $ctext insert end "$f\n" filesep
4467 $ctext config -state disabled
4468 $ctext yview $commentend
4471 proc getblobline {bf id} {
4472 global diffids cmitmode ctext
4474 if {$id ne $diffids || $cmitmode ne "tree"} {
4475 catch {close $bf}
4476 return 0
4478 $ctext config -state normal
4479 set nl 0
4480 while {[incr nl] <= 1000 && [gets $bf line] >= 0} {
4481 $ctext insert end "$line\n"
4483 if {[eof $bf]} {
4484 # delete last newline
4485 $ctext delete "end - 2c" "end - 1c"
4486 close $bf
4487 return 0
4489 $ctext config -state disabled
4490 return [expr {$nl >= 1000? 2: 1}]
4493 proc mergediff {id l} {
4494 global diffmergeid diffopts mdifffd
4495 global diffids
4496 global parentlist
4498 set diffmergeid $id
4499 set diffids $id
4500 # this doesn't seem to actually affect anything...
4501 set env(GIT_DIFF_OPTS) $diffopts
4502 set cmd [concat | git diff-tree --no-commit-id --cc $id]
4503 if {[catch {set mdf [open $cmd r]} err]} {
4504 error_popup "Error getting merge diffs: $err"
4505 return
4507 fconfigure $mdf -blocking 0
4508 set mdifffd($id) $mdf
4509 set np [llength [lindex $parentlist $l]]
4510 filerun $mdf [list getmergediffline $mdf $id $np]
4513 proc getmergediffline {mdf id np} {
4514 global diffmergeid ctext cflist mergemax
4515 global difffilestart mdifffd
4517 $ctext conf -state normal
4518 set nr 0
4519 while {[incr nr] <= 1000 && [gets $mdf line] >= 0} {
4520 if {![info exists diffmergeid] || $id != $diffmergeid
4521 || $mdf != $mdifffd($id)} {
4522 close $mdf
4523 return 0
4525 if {[regexp {^diff --cc (.*)} $line match fname]} {
4526 # start of a new file
4527 $ctext insert end "\n"
4528 set here [$ctext index "end - 1c"]
4529 lappend difffilestart $here
4530 add_flist [list $fname]
4531 set l [expr {(78 - [string length $fname]) / 2}]
4532 set pad [string range "----------------------------------------" 1 $l]
4533 $ctext insert end "$pad $fname $pad\n" filesep
4534 } elseif {[regexp {^@@} $line]} {
4535 $ctext insert end "$line\n" hunksep
4536 } elseif {[regexp {^[0-9a-f]{40}$} $line] || [regexp {^index} $line]} {
4537 # do nothing
4538 } else {
4539 # parse the prefix - one ' ', '-' or '+' for each parent
4540 set spaces {}
4541 set minuses {}
4542 set pluses {}
4543 set isbad 0
4544 for {set j 0} {$j < $np} {incr j} {
4545 set c [string range $line $j $j]
4546 if {$c == " "} {
4547 lappend spaces $j
4548 } elseif {$c == "-"} {
4549 lappend minuses $j
4550 } elseif {$c == "+"} {
4551 lappend pluses $j
4552 } else {
4553 set isbad 1
4554 break
4557 set tags {}
4558 set num {}
4559 if {!$isbad && $minuses ne {} && $pluses eq {}} {
4560 # line doesn't appear in result, parents in $minuses have the line
4561 set num [lindex $minuses 0]
4562 } elseif {!$isbad && $pluses ne {} && $minuses eq {}} {
4563 # line appears in result, parents in $pluses don't have the line
4564 lappend tags mresult
4565 set num [lindex $spaces 0]
4567 if {$num ne {}} {
4568 if {$num >= $mergemax} {
4569 set num "max"
4571 lappend tags m$num
4573 $ctext insert end "$line\n" $tags
4576 $ctext conf -state disabled
4577 if {[eof $mdf]} {
4578 close $mdf
4579 return 0
4581 return [expr {$nr >= 1000? 2: 1}]
4584 proc startdiff {ids} {
4585 global treediffs diffids treepending diffmergeid
4587 set diffids $ids
4588 catch {unset diffmergeid}
4589 if {![info exists treediffs($ids)]} {
4590 if {![info exists treepending]} {
4591 gettreediffs $ids
4593 } else {
4594 addtocflist $ids
4598 proc addtocflist {ids} {
4599 global treediffs cflist
4600 add_flist $treediffs($ids)
4601 getblobdiffs $ids
4604 proc gettreediffs {ids} {
4605 global treediff treepending
4606 set treepending $ids
4607 set treediff {}
4608 if {[catch \
4609 {set gdtf [open [concat | git diff-tree --no-commit-id -r $ids] r]} \
4610 ]} return
4611 fconfigure $gdtf -blocking 0
4612 filerun $gdtf [list gettreediffline $gdtf $ids]
4615 proc gettreediffline {gdtf ids} {
4616 global treediff treediffs treepending diffids diffmergeid
4617 global cmitmode
4619 set nr 0
4620 while {[incr nr] <= 1000 && [gets $gdtf line] >= 0} {
4621 set file [lindex $line 5]
4622 lappend treediff $file
4624 if {![eof $gdtf]} {
4625 return [expr {$nr >= 1000? 2: 1}]
4627 close $gdtf
4628 set treediffs($ids) $treediff
4629 unset treepending
4630 if {$cmitmode eq "tree"} {
4631 gettree $diffids
4632 } elseif {$ids != $diffids} {
4633 if {![info exists diffmergeid]} {
4634 gettreediffs $diffids
4636 } else {
4637 addtocflist $ids
4639 return 0
4642 proc getblobdiffs {ids} {
4643 global diffopts blobdifffd diffids env curdifftag curtagstart
4644 global diffinhdr treediffs
4646 set env(GIT_DIFF_OPTS) $diffopts
4647 set cmd [concat | git diff-tree --no-commit-id -r -p -C $ids]
4648 if {[catch {set bdf [open $cmd r]} err]} {
4649 puts "error getting diffs: $err"
4650 return
4652 set diffinhdr 0
4653 fconfigure $bdf -blocking 0
4654 set blobdifffd($ids) $bdf
4655 set curdifftag Comments
4656 set curtagstart 0.0
4657 filerun $bdf [list getblobdiffline $bdf $diffids]
4660 proc setinlist {var i val} {
4661 global $var
4663 while {[llength [set $var]] < $i} {
4664 lappend $var {}
4666 if {[llength [set $var]] == $i} {
4667 lappend $var $val
4668 } else {
4669 lset $var $i $val
4673 proc getblobdiffline {bdf ids} {
4674 global diffids blobdifffd ctext curdifftag curtagstart
4675 global diffnexthead diffnextnote difffilestart
4676 global diffinhdr treediffs
4678 set nr 0
4679 $ctext conf -state normal
4680 while {[incr nr] <= 1000 && [gets $bdf line] >= 0} {
4681 if {$ids != $diffids || $bdf != $blobdifffd($ids)} {
4682 close $bdf
4683 return 0
4685 if {[regexp {^diff --git a/(.*) b/(.*)} $line match fname newname]} {
4686 # start of a new file
4687 $ctext insert end "\n"
4688 $ctext tag add $curdifftag $curtagstart end
4689 set here [$ctext index "end - 1c"]
4690 set curtagstart $here
4691 set header $newname
4692 set i [lsearch -exact $treediffs($ids) $fname]
4693 if {$i >= 0} {
4694 setinlist difffilestart $i $here
4696 if {$newname ne $fname} {
4697 set i [lsearch -exact $treediffs($ids) $newname]
4698 if {$i >= 0} {
4699 setinlist difffilestart $i $here
4702 set curdifftag "f:$fname"
4703 $ctext tag delete $curdifftag
4704 set l [expr {(78 - [string length $header]) / 2}]
4705 set pad [string range "----------------------------------------" \
4706 1 $l]
4707 $ctext insert end "$pad $header $pad\n" filesep
4708 set diffinhdr 1
4709 } elseif {$diffinhdr && [string compare -length 3 $line "---"] == 0} {
4710 # do nothing
4711 } elseif {$diffinhdr && [string compare -length 3 $line "+++"] == 0} {
4712 set diffinhdr 0
4713 } elseif {[regexp {^@@ -([0-9]+),([0-9]+) \+([0-9]+),([0-9]+) @@(.*)} \
4714 $line match f1l f1c f2l f2c rest]} {
4715 $ctext insert end "$line\n" hunksep
4716 set diffinhdr 0
4717 } else {
4718 set x [string range $line 0 0]
4719 if {$x == "-" || $x == "+"} {
4720 set tag [expr {$x == "+"}]
4721 $ctext insert end "$line\n" d$tag
4722 } elseif {$x == " "} {
4723 $ctext insert end "$line\n"
4724 } elseif {$diffinhdr || $x == "\\"} {
4725 # e.g. "\ No newline at end of file"
4726 $ctext insert end "$line\n" filesep
4727 } else {
4728 # Something else we don't recognize
4729 if {$curdifftag != "Comments"} {
4730 $ctext insert end "\n"
4731 $ctext tag add $curdifftag $curtagstart end
4732 set curtagstart [$ctext index "end - 1c"]
4733 set curdifftag Comments
4735 $ctext insert end "$line\n" filesep
4739 $ctext conf -state disabled
4740 if {[eof $bdf]} {
4741 close $bdf
4742 if {$ids == $diffids && $bdf == $blobdifffd($ids)} {
4743 $ctext tag add $curdifftag $curtagstart end
4745 return 0
4747 return [expr {$nr >= 1000? 2: 1}]
4750 proc changediffdisp {} {
4751 global ctext diffelide
4753 $ctext tag conf d0 -elide [lindex $diffelide 0]
4754 $ctext tag conf d1 -elide [lindex $diffelide 1]
4757 proc prevfile {} {
4758 global difffilestart ctext
4759 set prev [lindex $difffilestart 0]
4760 set here [$ctext index @0,0]
4761 foreach loc $difffilestart {
4762 if {[$ctext compare $loc >= $here]} {
4763 $ctext yview $prev
4764 return
4766 set prev $loc
4768 $ctext yview $prev
4771 proc nextfile {} {
4772 global difffilestart ctext
4773 set here [$ctext index @0,0]
4774 foreach loc $difffilestart {
4775 if {[$ctext compare $loc > $here]} {
4776 $ctext yview $loc
4777 return
4782 proc clear_ctext {{first 1.0}} {
4783 global ctext smarktop smarkbot
4785 set l [lindex [split $first .] 0]
4786 if {![info exists smarktop] || [$ctext compare $first < $smarktop.0]} {
4787 set smarktop $l
4789 if {![info exists smarkbot] || [$ctext compare $first < $smarkbot.0]} {
4790 set smarkbot $l
4792 $ctext delete $first end
4795 proc incrsearch {name ix op} {
4796 global ctext searchstring searchdirn
4798 $ctext tag remove found 1.0 end
4799 if {[catch {$ctext index anchor}]} {
4800 # no anchor set, use start of selection, or of visible area
4801 set sel [$ctext tag ranges sel]
4802 if {$sel ne {}} {
4803 $ctext mark set anchor [lindex $sel 0]
4804 } elseif {$searchdirn eq "-forwards"} {
4805 $ctext mark set anchor @0,0
4806 } else {
4807 $ctext mark set anchor @0,[winfo height $ctext]
4810 if {$searchstring ne {}} {
4811 set here [$ctext search $searchdirn -- $searchstring anchor]
4812 if {$here ne {}} {
4813 $ctext see $here
4815 searchmarkvisible 1
4819 proc dosearch {} {
4820 global sstring ctext searchstring searchdirn
4822 focus $sstring
4823 $sstring icursor end
4824 set searchdirn -forwards
4825 if {$searchstring ne {}} {
4826 set sel [$ctext tag ranges sel]
4827 if {$sel ne {}} {
4828 set start "[lindex $sel 0] + 1c"
4829 } elseif {[catch {set start [$ctext index anchor]}]} {
4830 set start "@0,0"
4832 set match [$ctext search -count mlen -- $searchstring $start]
4833 $ctext tag remove sel 1.0 end
4834 if {$match eq {}} {
4835 bell
4836 return
4838 $ctext see $match
4839 set mend "$match + $mlen c"
4840 $ctext tag add sel $match $mend
4841 $ctext mark unset anchor
4845 proc dosearchback {} {
4846 global sstring ctext searchstring searchdirn
4848 focus $sstring
4849 $sstring icursor end
4850 set searchdirn -backwards
4851 if {$searchstring ne {}} {
4852 set sel [$ctext tag ranges sel]
4853 if {$sel ne {}} {
4854 set start [lindex $sel 0]
4855 } elseif {[catch {set start [$ctext index anchor]}]} {
4856 set start @0,[winfo height $ctext]
4858 set match [$ctext search -backwards -count ml -- $searchstring $start]
4859 $ctext tag remove sel 1.0 end
4860 if {$match eq {}} {
4861 bell
4862 return
4864 $ctext see $match
4865 set mend "$match + $ml c"
4866 $ctext tag add sel $match $mend
4867 $ctext mark unset anchor
4871 proc searchmark {first last} {
4872 global ctext searchstring
4874 set mend $first.0
4875 while {1} {
4876 set match [$ctext search -count mlen -- $searchstring $mend $last.end]
4877 if {$match eq {}} break
4878 set mend "$match + $mlen c"
4879 $ctext tag add found $match $mend
4883 proc searchmarkvisible {doall} {
4884 global ctext smarktop smarkbot
4886 set topline [lindex [split [$ctext index @0,0] .] 0]
4887 set botline [lindex [split [$ctext index @0,[winfo height $ctext]] .] 0]
4888 if {$doall || $botline < $smarktop || $topline > $smarkbot} {
4889 # no overlap with previous
4890 searchmark $topline $botline
4891 set smarktop $topline
4892 set smarkbot $botline
4893 } else {
4894 if {$topline < $smarktop} {
4895 searchmark $topline [expr {$smarktop-1}]
4896 set smarktop $topline
4898 if {$botline > $smarkbot} {
4899 searchmark [expr {$smarkbot+1}] $botline
4900 set smarkbot $botline
4905 proc scrolltext {f0 f1} {
4906 global searchstring
4908 .bleft.sb set $f0 $f1
4909 if {$searchstring ne {}} {
4910 searchmarkvisible 0
4914 proc setcoords {} {
4915 global linespc charspc canvx0 canvy0 mainfont
4916 global xspc1 xspc2 lthickness
4918 set linespc [font metrics $mainfont -linespace]
4919 set charspc [font measure $mainfont "m"]
4920 set canvy0 [expr {int(3 + 0.5 * $linespc)}]
4921 set canvx0 [expr {int(3 + 0.5 * $linespc)}]
4922 set lthickness [expr {int($linespc / 9) + 1}]
4923 set xspc1(0) $linespc
4924 set xspc2 $linespc
4927 proc redisplay {} {
4928 global canv
4929 global selectedline
4931 set ymax [lindex [$canv cget -scrollregion] 3]
4932 if {$ymax eq {} || $ymax == 0} return
4933 set span [$canv yview]
4934 clear_display
4935 setcanvscroll
4936 allcanvs yview moveto [lindex $span 0]
4937 drawvisible
4938 if {[info exists selectedline]} {
4939 selectline $selectedline 0
4940 allcanvs yview moveto [lindex $span 0]
4944 proc incrfont {inc} {
4945 global mainfont textfont ctext canv phase cflist
4946 global charspc tabstop
4947 global stopped entries
4948 unmarkmatches
4949 set mainfont [lreplace $mainfont 1 1 [expr {[lindex $mainfont 1] + $inc}]]
4950 set textfont [lreplace $textfont 1 1 [expr {[lindex $textfont 1] + $inc}]]
4951 setcoords
4952 $ctext conf -font $textfont -tabs "[expr {$tabstop * $charspc}]"
4953 $cflist conf -font $textfont
4954 $ctext tag conf filesep -font [concat $textfont bold]
4955 foreach e $entries {
4956 $e conf -font $mainfont
4958 if {$phase eq "getcommits"} {
4959 $canv itemconf textitems -font $mainfont
4961 redisplay
4964 proc clearsha1 {} {
4965 global sha1entry sha1string
4966 if {[string length $sha1string] == 40} {
4967 $sha1entry delete 0 end
4971 proc sha1change {n1 n2 op} {
4972 global sha1string currentid sha1but
4973 if {$sha1string == {}
4974 || ([info exists currentid] && $sha1string == $currentid)} {
4975 set state disabled
4976 } else {
4977 set state normal
4979 if {[$sha1but cget -state] == $state} return
4980 if {$state == "normal"} {
4981 $sha1but conf -state normal -relief raised -text "Goto: "
4982 } else {
4983 $sha1but conf -state disabled -relief flat -text "SHA1 ID: "
4987 proc gotocommit {} {
4988 global sha1string currentid commitrow tagids headids
4989 global displayorder numcommits curview
4991 if {$sha1string == {}
4992 || ([info exists currentid] && $sha1string == $currentid)} return
4993 if {[info exists tagids($sha1string)]} {
4994 set id $tagids($sha1string)
4995 } elseif {[info exists headids($sha1string)]} {
4996 set id $headids($sha1string)
4997 } else {
4998 set id [string tolower $sha1string]
4999 if {[regexp {^[0-9a-f]{4,39}$} $id]} {
5000 set matches {}
5001 foreach i $displayorder {
5002 if {[string match $id* $i]} {
5003 lappend matches $i
5006 if {$matches ne {}} {
5007 if {[llength $matches] > 1} {
5008 error_popup "Short SHA1 id $id is ambiguous"
5009 return
5011 set id [lindex $matches 0]
5015 if {[info exists commitrow($curview,$id)]} {
5016 selectline $commitrow($curview,$id) 1
5017 return
5019 if {[regexp {^[0-9a-fA-F]{4,}$} $sha1string]} {
5020 set type "SHA1 id"
5021 } else {
5022 set type "Tag/Head"
5024 error_popup "$type $sha1string is not known"
5027 proc lineenter {x y id} {
5028 global hoverx hovery hoverid hovertimer
5029 global commitinfo canv
5031 if {![info exists commitinfo($id)] && ![getcommit $id]} return
5032 set hoverx $x
5033 set hovery $y
5034 set hoverid $id
5035 if {[info exists hovertimer]} {
5036 after cancel $hovertimer
5038 set hovertimer [after 500 linehover]
5039 $canv delete hover
5042 proc linemotion {x y id} {
5043 global hoverx hovery hoverid hovertimer
5045 if {[info exists hoverid] && $id == $hoverid} {
5046 set hoverx $x
5047 set hovery $y
5048 if {[info exists hovertimer]} {
5049 after cancel $hovertimer
5051 set hovertimer [after 500 linehover]
5055 proc lineleave {id} {
5056 global hoverid hovertimer canv
5058 if {[info exists hoverid] && $id == $hoverid} {
5059 $canv delete hover
5060 if {[info exists hovertimer]} {
5061 after cancel $hovertimer
5062 unset hovertimer
5064 unset hoverid
5068 proc linehover {} {
5069 global hoverx hovery hoverid hovertimer
5070 global canv linespc lthickness
5071 global commitinfo mainfont
5073 set text [lindex $commitinfo($hoverid) 0]
5074 set ymax [lindex [$canv cget -scrollregion] 3]
5075 if {$ymax == {}} return
5076 set yfrac [lindex [$canv yview] 0]
5077 set x [expr {$hoverx + 2 * $linespc}]
5078 set y [expr {$hovery + $yfrac * $ymax - $linespc / 2}]
5079 set x0 [expr {$x - 2 * $lthickness}]
5080 set y0 [expr {$y - 2 * $lthickness}]
5081 set x1 [expr {$x + [font measure $mainfont $text] + 2 * $lthickness}]
5082 set y1 [expr {$y + $linespc + 2 * $lthickness}]
5083 set t [$canv create rectangle $x0 $y0 $x1 $y1 \
5084 -fill \#ffff80 -outline black -width 1 -tags hover]
5085 $canv raise $t
5086 set t [$canv create text $x $y -anchor nw -text $text -tags hover \
5087 -font $mainfont]
5088 $canv raise $t
5091 proc clickisonarrow {id y} {
5092 global lthickness
5094 set ranges [rowranges $id]
5095 set thresh [expr {2 * $lthickness + 6}]
5096 set n [expr {[llength $ranges] - 1}]
5097 for {set i 1} {$i < $n} {incr i} {
5098 set row [lindex $ranges $i]
5099 if {abs([yc $row] - $y) < $thresh} {
5100 return $i
5103 return {}
5106 proc arrowjump {id n y} {
5107 global canv
5109 # 1 <-> 2, 3 <-> 4, etc...
5110 set n [expr {(($n - 1) ^ 1) + 1}]
5111 set row [lindex [rowranges $id] $n]
5112 set yt [yc $row]
5113 set ymax [lindex [$canv cget -scrollregion] 3]
5114 if {$ymax eq {} || $ymax <= 0} return
5115 set view [$canv yview]
5116 set yspan [expr {[lindex $view 1] - [lindex $view 0]}]
5117 set yfrac [expr {$yt / $ymax - $yspan / 2}]
5118 if {$yfrac < 0} {
5119 set yfrac 0
5121 allcanvs yview moveto $yfrac
5124 proc lineclick {x y id isnew} {
5125 global ctext commitinfo children canv thickerline curview
5127 if {![info exists commitinfo($id)] && ![getcommit $id]} return
5128 unmarkmatches
5129 unselectline
5130 normalline
5131 $canv delete hover
5132 # draw this line thicker than normal
5133 set thickerline $id
5134 drawlines $id
5135 if {$isnew} {
5136 set ymax [lindex [$canv cget -scrollregion] 3]
5137 if {$ymax eq {}} return
5138 set yfrac [lindex [$canv yview] 0]
5139 set y [expr {$y + $yfrac * $ymax}]
5141 set dirn [clickisonarrow $id $y]
5142 if {$dirn ne {}} {
5143 arrowjump $id $dirn $y
5144 return
5147 if {$isnew} {
5148 addtohistory [list lineclick $x $y $id 0]
5150 # fill the details pane with info about this line
5151 $ctext conf -state normal
5152 clear_ctext
5153 $ctext tag conf link -foreground blue -underline 1
5154 $ctext tag bind link <Enter> { %W configure -cursor hand2 }
5155 $ctext tag bind link <Leave> { %W configure -cursor $curtextcursor }
5156 $ctext insert end "Parent:\t"
5157 $ctext insert end $id [list link link0]
5158 $ctext tag bind link0 <1> [list selbyid $id]
5159 set info $commitinfo($id)
5160 $ctext insert end "\n\t[lindex $info 0]\n"
5161 $ctext insert end "\tAuthor:\t[lindex $info 1]\n"
5162 set date [formatdate [lindex $info 2]]
5163 $ctext insert end "\tDate:\t$date\n"
5164 set kids $children($curview,$id)
5165 if {$kids ne {}} {
5166 $ctext insert end "\nChildren:"
5167 set i 0
5168 foreach child $kids {
5169 incr i
5170 if {![info exists commitinfo($child)] && ![getcommit $child]} continue
5171 set info $commitinfo($child)
5172 $ctext insert end "\n\t"
5173 $ctext insert end $child [list link link$i]
5174 $ctext tag bind link$i <1> [list selbyid $child]
5175 $ctext insert end "\n\t[lindex $info 0]"
5176 $ctext insert end "\n\tAuthor:\t[lindex $info 1]"
5177 set date [formatdate [lindex $info 2]]
5178 $ctext insert end "\n\tDate:\t$date\n"
5181 $ctext conf -state disabled
5182 init_flist {}
5185 proc normalline {} {
5186 global thickerline
5187 if {[info exists thickerline]} {
5188 set id $thickerline
5189 unset thickerline
5190 drawlines $id
5194 proc selbyid {id} {
5195 global commitrow curview
5196 if {[info exists commitrow($curview,$id)]} {
5197 selectline $commitrow($curview,$id) 1
5201 proc mstime {} {
5202 global startmstime
5203 if {![info exists startmstime]} {
5204 set startmstime [clock clicks -milliseconds]
5206 return [format "%.3f" [expr {([clock click -milliseconds] - $startmstime) / 1000.0}]]
5209 proc rowmenu {x y id} {
5210 global rowctxmenu commitrow selectedline rowmenuid curview
5212 if {![info exists selectedline]
5213 || $commitrow($curview,$id) eq $selectedline} {
5214 set state disabled
5215 } else {
5216 set state normal
5218 $rowctxmenu entryconfigure "Diff this*" -state $state
5219 $rowctxmenu entryconfigure "Diff selected*" -state $state
5220 $rowctxmenu entryconfigure "Make patch" -state $state
5221 set rowmenuid $id
5222 tk_popup $rowctxmenu $x $y
5225 proc diffvssel {dirn} {
5226 global rowmenuid selectedline displayorder
5228 if {![info exists selectedline]} return
5229 if {$dirn} {
5230 set oldid [lindex $displayorder $selectedline]
5231 set newid $rowmenuid
5232 } else {
5233 set oldid $rowmenuid
5234 set newid [lindex $displayorder $selectedline]
5236 addtohistory [list doseldiff $oldid $newid]
5237 doseldiff $oldid $newid
5240 proc doseldiff {oldid newid} {
5241 global ctext
5242 global commitinfo
5244 $ctext conf -state normal
5245 clear_ctext
5246 init_flist "Top"
5247 $ctext insert end "From "
5248 $ctext tag conf link -foreground blue -underline 1
5249 $ctext tag bind link <Enter> { %W configure -cursor hand2 }
5250 $ctext tag bind link <Leave> { %W configure -cursor $curtextcursor }
5251 $ctext tag bind link0 <1> [list selbyid $oldid]
5252 $ctext insert end $oldid [list link link0]
5253 $ctext insert end "\n "
5254 $ctext insert end [lindex $commitinfo($oldid) 0]
5255 $ctext insert end "\n\nTo "
5256 $ctext tag bind link1 <1> [list selbyid $newid]
5257 $ctext insert end $newid [list link link1]
5258 $ctext insert end "\n "
5259 $ctext insert end [lindex $commitinfo($newid) 0]
5260 $ctext insert end "\n"
5261 $ctext conf -state disabled
5262 $ctext tag delete Comments
5263 $ctext tag remove found 1.0 end
5264 startdiff [list $oldid $newid]
5267 proc mkpatch {} {
5268 global rowmenuid currentid commitinfo patchtop patchnum
5270 if {![info exists currentid]} return
5271 set oldid $currentid
5272 set oldhead [lindex $commitinfo($oldid) 0]
5273 set newid $rowmenuid
5274 set newhead [lindex $commitinfo($newid) 0]
5275 set top .patch
5276 set patchtop $top
5277 catch {destroy $top}
5278 toplevel $top
5279 label $top.title -text "Generate patch"
5280 grid $top.title - -pady 10
5281 label $top.from -text "From:"
5282 entry $top.fromsha1 -width 40 -relief flat
5283 $top.fromsha1 insert 0 $oldid
5284 $top.fromsha1 conf -state readonly
5285 grid $top.from $top.fromsha1 -sticky w
5286 entry $top.fromhead -width 60 -relief flat
5287 $top.fromhead insert 0 $oldhead
5288 $top.fromhead conf -state readonly
5289 grid x $top.fromhead -sticky w
5290 label $top.to -text "To:"
5291 entry $top.tosha1 -width 40 -relief flat
5292 $top.tosha1 insert 0 $newid
5293 $top.tosha1 conf -state readonly
5294 grid $top.to $top.tosha1 -sticky w
5295 entry $top.tohead -width 60 -relief flat
5296 $top.tohead insert 0 $newhead
5297 $top.tohead conf -state readonly
5298 grid x $top.tohead -sticky w
5299 button $top.rev -text "Reverse" -command mkpatchrev -padx 5
5300 grid $top.rev x -pady 10
5301 label $top.flab -text "Output file:"
5302 entry $top.fname -width 60
5303 $top.fname insert 0 [file normalize "patch$patchnum.patch"]
5304 incr patchnum
5305 grid $top.flab $top.fname -sticky w
5306 frame $top.buts
5307 button $top.buts.gen -text "Generate" -command mkpatchgo
5308 button $top.buts.can -text "Cancel" -command mkpatchcan
5309 grid $top.buts.gen $top.buts.can
5310 grid columnconfigure $top.buts 0 -weight 1 -uniform a
5311 grid columnconfigure $top.buts 1 -weight 1 -uniform a
5312 grid $top.buts - -pady 10 -sticky ew
5313 focus $top.fname
5316 proc mkpatchrev {} {
5317 global patchtop
5319 set oldid [$patchtop.fromsha1 get]
5320 set oldhead [$patchtop.fromhead get]
5321 set newid [$patchtop.tosha1 get]
5322 set newhead [$patchtop.tohead get]
5323 foreach e [list fromsha1 fromhead tosha1 tohead] \
5324 v [list $newid $newhead $oldid $oldhead] {
5325 $patchtop.$e conf -state normal
5326 $patchtop.$e delete 0 end
5327 $patchtop.$e insert 0 $v
5328 $patchtop.$e conf -state readonly
5332 proc mkpatchgo {} {
5333 global patchtop
5335 set oldid [$patchtop.fromsha1 get]
5336 set newid [$patchtop.tosha1 get]
5337 set fname [$patchtop.fname get]
5338 if {[catch {exec git diff-tree -p $oldid $newid >$fname &} err]} {
5339 error_popup "Error creating patch: $err"
5341 catch {destroy $patchtop}
5342 unset patchtop
5345 proc mkpatchcan {} {
5346 global patchtop
5348 catch {destroy $patchtop}
5349 unset patchtop
5352 proc mktag {} {
5353 global rowmenuid mktagtop commitinfo
5355 set top .maketag
5356 set mktagtop $top
5357 catch {destroy $top}
5358 toplevel $top
5359 label $top.title -text "Create tag"
5360 grid $top.title - -pady 10
5361 label $top.id -text "ID:"
5362 entry $top.sha1 -width 40 -relief flat
5363 $top.sha1 insert 0 $rowmenuid
5364 $top.sha1 conf -state readonly
5365 grid $top.id $top.sha1 -sticky w
5366 entry $top.head -width 60 -relief flat
5367 $top.head insert 0 [lindex $commitinfo($rowmenuid) 0]
5368 $top.head conf -state readonly
5369 grid x $top.head -sticky w
5370 label $top.tlab -text "Tag name:"
5371 entry $top.tag -width 60
5372 grid $top.tlab $top.tag -sticky w
5373 frame $top.buts
5374 button $top.buts.gen -text "Create" -command mktaggo
5375 button $top.buts.can -text "Cancel" -command mktagcan
5376 grid $top.buts.gen $top.buts.can
5377 grid columnconfigure $top.buts 0 -weight 1 -uniform a
5378 grid columnconfigure $top.buts 1 -weight 1 -uniform a
5379 grid $top.buts - -pady 10 -sticky ew
5380 focus $top.tag
5383 proc domktag {} {
5384 global mktagtop env tagids idtags
5386 set id [$mktagtop.sha1 get]
5387 set tag [$mktagtop.tag get]
5388 if {$tag == {}} {
5389 error_popup "No tag name specified"
5390 return
5392 if {[info exists tagids($tag)]} {
5393 error_popup "Tag \"$tag\" already exists"
5394 return
5396 if {[catch {
5397 set dir [gitdir]
5398 set fname [file join $dir "refs/tags" $tag]
5399 set f [open $fname w]
5400 puts $f $id
5401 close $f
5402 } err]} {
5403 error_popup "Error creating tag: $err"
5404 return
5407 set tagids($tag) $id
5408 lappend idtags($id) $tag
5409 redrawtags $id
5410 addedtag $id
5413 proc redrawtags {id} {
5414 global canv linehtag commitrow idpos selectedline curview
5415 global mainfont canvxmax iddrawn
5417 if {![info exists commitrow($curview,$id)]} return
5418 if {![info exists iddrawn($id)]} return
5419 drawcommits $commitrow($curview,$id)
5420 $canv delete tag.$id
5421 set xt [eval drawtags $id $idpos($id)]
5422 $canv coords $linehtag($commitrow($curview,$id)) $xt [lindex $idpos($id) 2]
5423 set text [$canv itemcget $linehtag($commitrow($curview,$id)) -text]
5424 set xr [expr {$xt + [font measure $mainfont $text]}]
5425 if {$xr > $canvxmax} {
5426 set canvxmax $xr
5427 setcanvscroll
5429 if {[info exists selectedline]
5430 && $selectedline == $commitrow($curview,$id)} {
5431 selectline $selectedline 0
5435 proc mktagcan {} {
5436 global mktagtop
5438 catch {destroy $mktagtop}
5439 unset mktagtop
5442 proc mktaggo {} {
5443 domktag
5444 mktagcan
5447 proc writecommit {} {
5448 global rowmenuid wrcomtop commitinfo wrcomcmd
5450 set top .writecommit
5451 set wrcomtop $top
5452 catch {destroy $top}
5453 toplevel $top
5454 label $top.title -text "Write commit to file"
5455 grid $top.title - -pady 10
5456 label $top.id -text "ID:"
5457 entry $top.sha1 -width 40 -relief flat
5458 $top.sha1 insert 0 $rowmenuid
5459 $top.sha1 conf -state readonly
5460 grid $top.id $top.sha1 -sticky w
5461 entry $top.head -width 60 -relief flat
5462 $top.head insert 0 [lindex $commitinfo($rowmenuid) 0]
5463 $top.head conf -state readonly
5464 grid x $top.head -sticky w
5465 label $top.clab -text "Command:"
5466 entry $top.cmd -width 60 -textvariable wrcomcmd
5467 grid $top.clab $top.cmd -sticky w -pady 10
5468 label $top.flab -text "Output file:"
5469 entry $top.fname -width 60
5470 $top.fname insert 0 [file normalize "commit-[string range $rowmenuid 0 6]"]
5471 grid $top.flab $top.fname -sticky w
5472 frame $top.buts
5473 button $top.buts.gen -text "Write" -command wrcomgo
5474 button $top.buts.can -text "Cancel" -command wrcomcan
5475 grid $top.buts.gen $top.buts.can
5476 grid columnconfigure $top.buts 0 -weight 1 -uniform a
5477 grid columnconfigure $top.buts 1 -weight 1 -uniform a
5478 grid $top.buts - -pady 10 -sticky ew
5479 focus $top.fname
5482 proc wrcomgo {} {
5483 global wrcomtop
5485 set id [$wrcomtop.sha1 get]
5486 set cmd "echo $id | [$wrcomtop.cmd get]"
5487 set fname [$wrcomtop.fname get]
5488 if {[catch {exec sh -c $cmd >$fname &} err]} {
5489 error_popup "Error writing commit: $err"
5491 catch {destroy $wrcomtop}
5492 unset wrcomtop
5495 proc wrcomcan {} {
5496 global wrcomtop
5498 catch {destroy $wrcomtop}
5499 unset wrcomtop
5502 proc mkbranch {} {
5503 global rowmenuid mkbrtop
5505 set top .makebranch
5506 catch {destroy $top}
5507 toplevel $top
5508 label $top.title -text "Create new branch"
5509 grid $top.title - -pady 10
5510 label $top.id -text "ID:"
5511 entry $top.sha1 -width 40 -relief flat
5512 $top.sha1 insert 0 $rowmenuid
5513 $top.sha1 conf -state readonly
5514 grid $top.id $top.sha1 -sticky w
5515 label $top.nlab -text "Name:"
5516 entry $top.name -width 40
5517 grid $top.nlab $top.name -sticky w
5518 frame $top.buts
5519 button $top.buts.go -text "Create" -command [list mkbrgo $top]
5520 button $top.buts.can -text "Cancel" -command "catch {destroy $top}"
5521 grid $top.buts.go $top.buts.can
5522 grid columnconfigure $top.buts 0 -weight 1 -uniform a
5523 grid columnconfigure $top.buts 1 -weight 1 -uniform a
5524 grid $top.buts - -pady 10 -sticky ew
5525 focus $top.name
5528 proc mkbrgo {top} {
5529 global headids idheads
5531 set name [$top.name get]
5532 set id [$top.sha1 get]
5533 if {$name eq {}} {
5534 error_popup "Please specify a name for the new branch"
5535 return
5537 catch {destroy $top}
5538 nowbusy newbranch
5539 update
5540 if {[catch {
5541 exec git branch $name $id
5542 } err]} {
5543 notbusy newbranch
5544 error_popup $err
5545 } else {
5546 set headids($name) $id
5547 lappend idheads($id) $name
5548 addedhead $id $name
5549 notbusy newbranch
5550 redrawtags $id
5551 dispneartags 0
5555 proc cherrypick {} {
5556 global rowmenuid curview commitrow
5557 global mainhead
5559 set oldhead [exec git rev-parse HEAD]
5560 set dheads [descheads $rowmenuid]
5561 if {$dheads ne {} && [lsearch -exact $dheads $oldhead] >= 0} {
5562 set ok [confirm_popup "Commit [string range $rowmenuid 0 7] is already\
5563 included in branch $mainhead -- really re-apply it?"]
5564 if {!$ok} return
5566 nowbusy cherrypick
5567 update
5568 # Unfortunately git-cherry-pick writes stuff to stderr even when
5569 # no error occurs, and exec takes that as an indication of error...
5570 if {[catch {exec sh -c "git cherry-pick -r $rowmenuid 2>&1"} err]} {
5571 notbusy cherrypick
5572 error_popup $err
5573 return
5575 set newhead [exec git rev-parse HEAD]
5576 if {$newhead eq $oldhead} {
5577 notbusy cherrypick
5578 error_popup "No changes committed"
5579 return
5581 addnewchild $newhead $oldhead
5582 if {[info exists commitrow($curview,$oldhead)]} {
5583 insertrow $commitrow($curview,$oldhead) $newhead
5584 if {$mainhead ne {}} {
5585 movehead $newhead $mainhead
5586 movedhead $newhead $mainhead
5588 redrawtags $oldhead
5589 redrawtags $newhead
5591 notbusy cherrypick
5594 # context menu for a head
5595 proc headmenu {x y id head} {
5596 global headmenuid headmenuhead headctxmenu mainhead
5598 set headmenuid $id
5599 set headmenuhead $head
5600 set state normal
5601 if {$head eq $mainhead} {
5602 set state disabled
5604 $headctxmenu entryconfigure 0 -state $state
5605 $headctxmenu entryconfigure 1 -state $state
5606 tk_popup $headctxmenu $x $y
5609 proc cobranch {} {
5610 global headmenuid headmenuhead mainhead headids
5612 # check the tree is clean first??
5613 set oldmainhead $mainhead
5614 nowbusy checkout
5615 update
5616 if {[catch {
5617 exec git checkout -q $headmenuhead
5618 } err]} {
5619 notbusy checkout
5620 error_popup $err
5621 } else {
5622 notbusy checkout
5623 set mainhead $headmenuhead
5624 if {[info exists headids($oldmainhead)]} {
5625 redrawtags $headids($oldmainhead)
5627 redrawtags $headmenuid
5631 proc rmbranch {} {
5632 global headmenuid headmenuhead mainhead
5633 global headids idheads
5635 set head $headmenuhead
5636 set id $headmenuid
5637 # this check shouldn't be needed any more...
5638 if {$head eq $mainhead} {
5639 error_popup "Cannot delete the currently checked-out branch"
5640 return
5642 set dheads [descheads $id]
5643 if {$dheads eq $headids($head)} {
5644 # the stuff on this branch isn't on any other branch
5645 if {![confirm_popup "The commits on branch $head aren't on any other\
5646 branch.\nReally delete branch $head?"]} return
5648 nowbusy rmbranch
5649 update
5650 if {[catch {exec git branch -D $head} err]} {
5651 notbusy rmbranch
5652 error_popup $err
5653 return
5655 removehead $id $head
5656 removedhead $id $head
5657 redrawtags $id
5658 notbusy rmbranch
5659 dispneartags 0
5662 # Stuff for finding nearby tags
5663 proc getallcommits {} {
5664 global allcommits allids nbmp nextarc seeds
5666 set allids {}
5667 set nbmp 0
5668 set nextarc 0
5669 set allcommits 0
5670 set seeds {}
5671 regetallcommits
5674 # Called when the graph might have changed
5675 proc regetallcommits {} {
5676 global allcommits seeds
5678 set cmd [concat | git rev-list --all --parents]
5679 foreach id $seeds {
5680 lappend cmd "^$id"
5682 set fd [open $cmd r]
5683 fconfigure $fd -blocking 0
5684 incr allcommits
5685 nowbusy allcommits
5686 filerun $fd [list getallclines $fd]
5689 # Since most commits have 1 parent and 1 child, we group strings of
5690 # such commits into "arcs" joining branch/merge points (BMPs), which
5691 # are commits that either don't have 1 parent or don't have 1 child.
5693 # arcnos(id) - incoming arcs for BMP, arc we're on for other nodes
5694 # arcout(id) - outgoing arcs for BMP
5695 # arcids(a) - list of IDs on arc including end but not start
5696 # arcstart(a) - BMP ID at start of arc
5697 # arcend(a) - BMP ID at end of arc
5698 # growing(a) - arc a is still growing
5699 # arctags(a) - IDs out of arcids (excluding end) that have tags
5700 # archeads(a) - IDs out of arcids (excluding end) that have heads
5701 # The start of an arc is at the descendent end, so "incoming" means
5702 # coming from descendents, and "outgoing" means going towards ancestors.
5704 proc getallclines {fd} {
5705 global allids allparents allchildren idtags nextarc nbmp
5706 global arcnos arcids arctags arcout arcend arcstart archeads growing
5707 global seeds allcommits
5709 set nid 0
5710 while {[incr nid] <= 1000 && [gets $fd line] >= 0} {
5711 set id [lindex $line 0]
5712 if {[info exists allparents($id)]} {
5713 # seen it already
5714 continue
5716 lappend allids $id
5717 set olds [lrange $line 1 end]
5718 set allparents($id) $olds
5719 if {![info exists allchildren($id)]} {
5720 set allchildren($id) {}
5721 set arcnos($id) {}
5722 lappend seeds $id
5723 } else {
5724 set a $arcnos($id)
5725 if {[llength $olds] == 1 && [llength $a] == 1} {
5726 lappend arcids($a) $id
5727 if {[info exists idtags($id)]} {
5728 lappend arctags($a) $id
5730 if {[info exists idheads($id)]} {
5731 lappend archeads($a) $id
5733 if {[info exists allparents($olds)]} {
5734 # seen parent already
5735 if {![info exists arcout($olds)]} {
5736 splitarc $olds
5738 lappend arcids($a) $olds
5739 set arcend($a) $olds
5740 unset growing($a)
5742 lappend allchildren($olds) $id
5743 lappend arcnos($olds) $a
5744 continue
5747 incr nbmp
5748 foreach a $arcnos($id) {
5749 lappend arcids($a) $id
5750 set arcend($a) $id
5751 unset growing($a)
5754 set ao {}
5755 foreach p $olds {
5756 lappend allchildren($p) $id
5757 set a [incr nextarc]
5758 set arcstart($a) $id
5759 set archeads($a) {}
5760 set arctags($a) {}
5761 set archeads($a) {}
5762 set arcids($a) {}
5763 lappend ao $a
5764 set growing($a) 1
5765 if {[info exists allparents($p)]} {
5766 # seen it already, may need to make a new branch
5767 if {![info exists arcout($p)]} {
5768 splitarc $p
5770 lappend arcids($a) $p
5771 set arcend($a) $p
5772 unset growing($a)
5774 lappend arcnos($p) $a
5776 set arcout($id) $ao
5778 if {![eof $fd]} {
5779 return [expr {$nid >= 1000? 2: 1}]
5781 close $fd
5782 if {[incr allcommits -1] == 0} {
5783 notbusy allcommits
5785 dispneartags 0
5786 return 0
5789 proc recalcarc {a} {
5790 global arctags archeads arcids idtags idheads
5792 set at {}
5793 set ah {}
5794 foreach id [lrange $arcids($a) 0 end-1] {
5795 if {[info exists idtags($id)]} {
5796 lappend at $id
5798 if {[info exists idheads($id)]} {
5799 lappend ah $id
5802 set arctags($a) $at
5803 set archeads($a) $ah
5806 proc splitarc {p} {
5807 global arcnos arcids nextarc nbmp arctags archeads idtags idheads
5808 global arcstart arcend arcout allparents growing
5810 set a $arcnos($p)
5811 if {[llength $a] != 1} {
5812 puts "oops splitarc called but [llength $a] arcs already"
5813 return
5815 set a [lindex $a 0]
5816 set i [lsearch -exact $arcids($a) $p]
5817 if {$i < 0} {
5818 puts "oops splitarc $p not in arc $a"
5819 return
5821 set na [incr nextarc]
5822 if {[info exists arcend($a)]} {
5823 set arcend($na) $arcend($a)
5824 } else {
5825 set l [lindex $allparents([lindex $arcids($a) end]) 0]
5826 set j [lsearch -exact $arcnos($l) $a]
5827 set arcnos($l) [lreplace $arcnos($l) $j $j $na]
5829 set tail [lrange $arcids($a) [expr {$i+1}] end]
5830 set arcids($a) [lrange $arcids($a) 0 $i]
5831 set arcend($a) $p
5832 set arcstart($na) $p
5833 set arcout($p) $na
5834 set arcids($na) $tail
5835 if {[info exists growing($a)]} {
5836 set growing($na) 1
5837 unset growing($a)
5839 incr nbmp
5841 foreach id $tail {
5842 if {[llength $arcnos($id)] == 1} {
5843 set arcnos($id) $na
5844 } else {
5845 set j [lsearch -exact $arcnos($id) $a]
5846 set arcnos($id) [lreplace $arcnos($id) $j $j $na]
5850 # reconstruct tags and heads lists
5851 if {$arctags($a) ne {} || $archeads($a) ne {}} {
5852 recalcarc $a
5853 recalcarc $na
5854 } else {
5855 set arctags($na) {}
5856 set archeads($na) {}
5860 # Update things for a new commit added that is a child of one
5861 # existing commit. Used when cherry-picking.
5862 proc addnewchild {id p} {
5863 global allids allparents allchildren idtags nextarc nbmp
5864 global arcnos arcids arctags arcout arcend arcstart archeads growing
5865 global seeds
5867 lappend allids $id
5868 set allparents($id) [list $p]
5869 set allchildren($id) {}
5870 set arcnos($id) {}
5871 lappend seeds $id
5872 incr nbmp
5873 lappend allchildren($p) $id
5874 set a [incr nextarc]
5875 set arcstart($a) $id
5876 set archeads($a) {}
5877 set arctags($a) {}
5878 set arcids($a) [list $p]
5879 set arcend($a) $p
5880 if {![info exists arcout($p)]} {
5881 splitarc $p
5883 lappend arcnos($p) $a
5884 set arcout($id) [list $a]
5887 # Returns 1 if a is an ancestor of b, -1 if b is an ancestor of a,
5888 # or 0 if neither is true.
5889 proc anc_or_desc {a b} {
5890 global arcout arcstart arcend arcnos cached_isanc
5892 if {$arcnos($a) eq $arcnos($b)} {
5893 # Both are on the same arc(s); either both are the same BMP,
5894 # or if one is not a BMP, the other is also not a BMP or is
5895 # the BMP at end of the arc (and it only has 1 incoming arc).
5896 if {$a eq $b} {
5897 return 0
5899 # assert {[llength $arcnos($a)] == 1}
5900 set arc [lindex $arcnos($a) 0]
5901 set i [lsearch -exact $arcids($arc) $a]
5902 set j [lsearch -exact $arcids($arc) $b]
5903 if {$i < 0 || $i > $j} {
5904 return 1
5905 } else {
5906 return -1
5910 if {![info exists arcout($a)]} {
5911 set arc [lindex $arcnos($a) 0]
5912 if {[info exists arcend($arc)]} {
5913 set aend $arcend($arc)
5914 } else {
5915 set aend {}
5917 set a $arcstart($arc)
5918 } else {
5919 set aend $a
5921 if {![info exists arcout($b)]} {
5922 set arc [lindex $arcnos($b) 0]
5923 if {[info exists arcend($arc)]} {
5924 set bend $arcend($arc)
5925 } else {
5926 set bend {}
5928 set b $arcstart($arc)
5929 } else {
5930 set bend $b
5932 if {$a eq $bend} {
5933 return 1
5935 if {$b eq $aend} {
5936 return -1
5938 if {[info exists cached_isanc($a,$bend)]} {
5939 if {$cached_isanc($a,$bend)} {
5940 return 1
5943 if {[info exists cached_isanc($b,$aend)]} {
5944 if {$cached_isanc($b,$aend)} {
5945 return -1
5947 if {[info exists cached_isanc($a,$bend)]} {
5948 return 0
5952 set todo [list $a $b]
5953 set anc($a) a
5954 set anc($b) b
5955 for {set i 0} {$i < [llength $todo]} {incr i} {
5956 set x [lindex $todo $i]
5957 if {$anc($x) eq {}} {
5958 continue
5960 foreach arc $arcnos($x) {
5961 set xd $arcstart($arc)
5962 if {$xd eq $bend} {
5963 set cached_isanc($a,$bend) 1
5964 set cached_isanc($b,$aend) 0
5965 return 1
5966 } elseif {$xd eq $aend} {
5967 set cached_isanc($b,$aend) 1
5968 set cached_isanc($a,$bend) 0
5969 return -1
5971 if {![info exists anc($xd)]} {
5972 set anc($xd) $anc($x)
5973 lappend todo $xd
5974 } elseif {$anc($xd) ne $anc($x)} {
5975 set anc($xd) {}
5979 set cached_isanc($a,$bend) 0
5980 set cached_isanc($b,$aend) 0
5981 return 0
5984 # This identifies whether $desc has an ancestor that is
5985 # a growing tip of the graph and which is not an ancestor of $anc
5986 # and returns 0 if so and 1 if not.
5987 # If we subsequently discover a tag on such a growing tip, and that
5988 # turns out to be a descendent of $anc (which it could, since we
5989 # don't necessarily see children before parents), then $desc
5990 # isn't a good choice to display as a descendent tag of
5991 # $anc (since it is the descendent of another tag which is
5992 # a descendent of $anc). Similarly, $anc isn't a good choice to
5993 # display as a ancestor tag of $desc.
5995 proc is_certain {desc anc} {
5996 global arcnos arcout arcstart arcend growing problems
5998 set certain {}
5999 if {[llength $arcnos($anc)] == 1} {
6000 # tags on the same arc are certain
6001 if {$arcnos($desc) eq $arcnos($anc)} {
6002 return 1
6004 if {![info exists arcout($anc)]} {
6005 # if $anc is partway along an arc, use the start of the arc instead
6006 set a [lindex $arcnos($anc) 0]
6007 set anc $arcstart($a)
6010 if {[llength $arcnos($desc)] > 1 || [info exists arcout($desc)]} {
6011 set x $desc
6012 } else {
6013 set a [lindex $arcnos($desc) 0]
6014 set x $arcend($a)
6016 if {$x == $anc} {
6017 return 1
6019 set anclist [list $x]
6020 set dl($x) 1
6021 set nnh 1
6022 set ngrowanc 0
6023 for {set i 0} {$i < [llength $anclist] && ($nnh > 0 || $ngrowanc > 0)} {incr i} {
6024 set x [lindex $anclist $i]
6025 if {$dl($x)} {
6026 incr nnh -1
6028 set done($x) 1
6029 foreach a $arcout($x) {
6030 if {[info exists growing($a)]} {
6031 if {![info exists growanc($x)] && $dl($x)} {
6032 set growanc($x) 1
6033 incr ngrowanc
6035 } else {
6036 set y $arcend($a)
6037 if {[info exists dl($y)]} {
6038 if {$dl($y)} {
6039 if {!$dl($x)} {
6040 set dl($y) 0
6041 if {![info exists done($y)]} {
6042 incr nnh -1
6044 if {[info exists growanc($x)]} {
6045 incr ngrowanc -1
6047 set xl [list $y]
6048 for {set k 0} {$k < [llength $xl]} {incr k} {
6049 set z [lindex $xl $k]
6050 foreach c $arcout($z) {
6051 if {[info exists arcend($c)]} {
6052 set v $arcend($c)
6053 if {[info exists dl($v)] && $dl($v)} {
6054 set dl($v) 0
6055 if {![info exists done($v)]} {
6056 incr nnh -1
6058 if {[info exists growanc($v)]} {
6059 incr ngrowanc -1
6061 lappend xl $v
6068 } elseif {$y eq $anc || !$dl($x)} {
6069 set dl($y) 0
6070 lappend anclist $y
6071 } else {
6072 set dl($y) 1
6073 lappend anclist $y
6074 incr nnh
6079 foreach x [array names growanc] {
6080 if {$dl($x)} {
6081 return 0
6083 return 0
6085 return 1
6088 proc validate_arctags {a} {
6089 global arctags idtags
6091 set i -1
6092 set na $arctags($a)
6093 foreach id $arctags($a) {
6094 incr i
6095 if {![info exists idtags($id)]} {
6096 set na [lreplace $na $i $i]
6097 incr i -1
6100 set arctags($a) $na
6103 proc validate_archeads {a} {
6104 global archeads idheads
6106 set i -1
6107 set na $archeads($a)
6108 foreach id $archeads($a) {
6109 incr i
6110 if {![info exists idheads($id)]} {
6111 set na [lreplace $na $i $i]
6112 incr i -1
6115 set archeads($a) $na
6118 # Return the list of IDs that have tags that are descendents of id,
6119 # ignoring IDs that are descendents of IDs already reported.
6120 proc desctags {id} {
6121 global arcnos arcstart arcids arctags idtags allparents
6122 global growing cached_dtags
6124 if {![info exists allparents($id)]} {
6125 return {}
6127 set t1 [clock clicks -milliseconds]
6128 set argid $id
6129 if {[llength $arcnos($id)] == 1 && [llength $allparents($id)] == 1} {
6130 # part-way along an arc; check that arc first
6131 set a [lindex $arcnos($id) 0]
6132 if {$arctags($a) ne {}} {
6133 validate_arctags $a
6134 set i [lsearch -exact $arcids($a) $id]
6135 set tid {}
6136 foreach t $arctags($a) {
6137 set j [lsearch -exact $arcids($a) $t]
6138 if {$j >= $i} break
6139 set tid $t
6141 if {$tid ne {}} {
6142 return $tid
6145 set id $arcstart($a)
6146 if {[info exists idtags($id)]} {
6147 return $id
6150 if {[info exists cached_dtags($id)]} {
6151 return $cached_dtags($id)
6154 set origid $id
6155 set todo [list $id]
6156 set queued($id) 1
6157 set nc 1
6158 for {set i 0} {$i < [llength $todo] && $nc > 0} {incr i} {
6159 set id [lindex $todo $i]
6160 set done($id) 1
6161 set ta [info exists hastaggedancestor($id)]
6162 if {!$ta} {
6163 incr nc -1
6165 # ignore tags on starting node
6166 if {!$ta && $i > 0} {
6167 if {[info exists idtags($id)]} {
6168 set tagloc($id) $id
6169 set ta 1
6170 } elseif {[info exists cached_dtags($id)]} {
6171 set tagloc($id) $cached_dtags($id)
6172 set ta 1
6175 foreach a $arcnos($id) {
6176 set d $arcstart($a)
6177 if {!$ta && $arctags($a) ne {}} {
6178 validate_arctags $a
6179 if {$arctags($a) ne {}} {
6180 lappend tagloc($id) [lindex $arctags($a) end]
6183 if {$ta || $arctags($a) ne {}} {
6184 set tomark [list $d]
6185 for {set j 0} {$j < [llength $tomark]} {incr j} {
6186 set dd [lindex $tomark $j]
6187 if {![info exists hastaggedancestor($dd)]} {
6188 if {[info exists done($dd)]} {
6189 foreach b $arcnos($dd) {
6190 lappend tomark $arcstart($b)
6192 if {[info exists tagloc($dd)]} {
6193 unset tagloc($dd)
6195 } elseif {[info exists queued($dd)]} {
6196 incr nc -1
6198 set hastaggedancestor($dd) 1
6202 if {![info exists queued($d)]} {
6203 lappend todo $d
6204 set queued($d) 1
6205 if {![info exists hastaggedancestor($d)]} {
6206 incr nc
6211 set tags {}
6212 foreach id [array names tagloc] {
6213 if {![info exists hastaggedancestor($id)]} {
6214 foreach t $tagloc($id) {
6215 if {[lsearch -exact $tags $t] < 0} {
6216 lappend tags $t
6221 set t2 [clock clicks -milliseconds]
6222 set loopix $i
6224 # remove tags that are descendents of other tags
6225 for {set i 0} {$i < [llength $tags]} {incr i} {
6226 set a [lindex $tags $i]
6227 for {set j 0} {$j < $i} {incr j} {
6228 set b [lindex $tags $j]
6229 set r [anc_or_desc $a $b]
6230 if {$r == 1} {
6231 set tags [lreplace $tags $j $j]
6232 incr j -1
6233 incr i -1
6234 } elseif {$r == -1} {
6235 set tags [lreplace $tags $i $i]
6236 incr i -1
6237 break
6242 if {[array names growing] ne {}} {
6243 # graph isn't finished, need to check if any tag could get
6244 # eclipsed by another tag coming later. Simply ignore any
6245 # tags that could later get eclipsed.
6246 set ctags {}
6247 foreach t $tags {
6248 if {[is_certain $t $origid]} {
6249 lappend ctags $t
6252 if {$tags eq $ctags} {
6253 set cached_dtags($origid) $tags
6254 } else {
6255 set tags $ctags
6257 } else {
6258 set cached_dtags($origid) $tags
6260 set t3 [clock clicks -milliseconds]
6261 if {0 && $t3 - $t1 >= 100} {
6262 puts "iterating descendents ($loopix/[llength $todo] nodes) took\
6263 [expr {$t2-$t1}]+[expr {$t3-$t2}]ms, $nc candidates left"
6265 return $tags
6268 proc anctags {id} {
6269 global arcnos arcids arcout arcend arctags idtags allparents
6270 global growing cached_atags
6272 if {![info exists allparents($id)]} {
6273 return {}
6275 set t1 [clock clicks -milliseconds]
6276 set argid $id
6277 if {[llength $arcnos($id)] == 1 && [llength $allparents($id)] == 1} {
6278 # part-way along an arc; check that arc first
6279 set a [lindex $arcnos($id) 0]
6280 if {$arctags($a) ne {}} {
6281 validate_arctags $a
6282 set i [lsearch -exact $arcids($a) $id]
6283 foreach t $arctags($a) {
6284 set j [lsearch -exact $arcids($a) $t]
6285 if {$j > $i} {
6286 return $t
6290 if {![info exists arcend($a)]} {
6291 return {}
6293 set id $arcend($a)
6294 if {[info exists idtags($id)]} {
6295 return $id
6298 if {[info exists cached_atags($id)]} {
6299 return $cached_atags($id)
6302 set origid $id
6303 set todo [list $id]
6304 set queued($id) 1
6305 set taglist {}
6306 set nc 1
6307 for {set i 0} {$i < [llength $todo] && $nc > 0} {incr i} {
6308 set id [lindex $todo $i]
6309 set done($id) 1
6310 set td [info exists hastaggeddescendent($id)]
6311 if {!$td} {
6312 incr nc -1
6314 # ignore tags on starting node
6315 if {!$td && $i > 0} {
6316 if {[info exists idtags($id)]} {
6317 set tagloc($id) $id
6318 set td 1
6319 } elseif {[info exists cached_atags($id)]} {
6320 set tagloc($id) $cached_atags($id)
6321 set td 1
6324 foreach a $arcout($id) {
6325 if {!$td && $arctags($a) ne {}} {
6326 validate_arctags $a
6327 if {$arctags($a) ne {}} {
6328 lappend tagloc($id) [lindex $arctags($a) 0]
6331 if {![info exists arcend($a)]} continue
6332 set d $arcend($a)
6333 if {$td || $arctags($a) ne {}} {
6334 set tomark [list $d]
6335 for {set j 0} {$j < [llength $tomark]} {incr j} {
6336 set dd [lindex $tomark $j]
6337 if {![info exists hastaggeddescendent($dd)]} {
6338 if {[info exists done($dd)]} {
6339 foreach b $arcout($dd) {
6340 if {[info exists arcend($b)]} {
6341 lappend tomark $arcend($b)
6344 if {[info exists tagloc($dd)]} {
6345 unset tagloc($dd)
6347 } elseif {[info exists queued($dd)]} {
6348 incr nc -1
6350 set hastaggeddescendent($dd) 1
6354 if {![info exists queued($d)]} {
6355 lappend todo $d
6356 set queued($d) 1
6357 if {![info exists hastaggeddescendent($d)]} {
6358 incr nc
6363 set t2 [clock clicks -milliseconds]
6364 set loopix $i
6365 set tags {}
6366 foreach id [array names tagloc] {
6367 if {![info exists hastaggeddescendent($id)]} {
6368 foreach t $tagloc($id) {
6369 if {[lsearch -exact $tags $t] < 0} {
6370 lappend tags $t
6376 # remove tags that are ancestors of other tags
6377 for {set i 0} {$i < [llength $tags]} {incr i} {
6378 set a [lindex $tags $i]
6379 for {set j 0} {$j < $i} {incr j} {
6380 set b [lindex $tags $j]
6381 set r [anc_or_desc $a $b]
6382 if {$r == -1} {
6383 set tags [lreplace $tags $j $j]
6384 incr j -1
6385 incr i -1
6386 } elseif {$r == 1} {
6387 set tags [lreplace $tags $i $i]
6388 incr i -1
6389 break
6394 if {[array names growing] ne {}} {
6395 # graph isn't finished, need to check if any tag could get
6396 # eclipsed by another tag coming later. Simply ignore any
6397 # tags that could later get eclipsed.
6398 set ctags {}
6399 foreach t $tags {
6400 if {[is_certain $origid $t]} {
6401 lappend ctags $t
6404 if {$tags eq $ctags} {
6405 set cached_atags($origid) $tags
6406 } else {
6407 set tags $ctags
6409 } else {
6410 set cached_atags($origid) $tags
6412 set t3 [clock clicks -milliseconds]
6413 if {0 && $t3 - $t1 >= 100} {
6414 puts "iterating ancestors ($loopix/[llength $todo] nodes) took\
6415 [expr {$t2-$t1}]+[expr {$t3-$t2}]ms, $nc candidates left"
6417 return $tags
6420 # Return the list of IDs that have heads that are descendents of id,
6421 # including id itself if it has a head.
6422 proc descheads {id} {
6423 global arcnos arcstart arcids archeads idheads cached_dheads
6424 global allparents
6426 if {![info exists allparents($id)]} {
6427 return {}
6429 set ret {}
6430 if {[llength $arcnos($id)] == 1 && [llength $allparents($id)] == 1} {
6431 # part-way along an arc; check it first
6432 set a [lindex $arcnos($id) 0]
6433 if {$archeads($a) ne {}} {
6434 validate_archeads $a
6435 set i [lsearch -exact $arcids($a) $id]
6436 foreach t $archeads($a) {
6437 set j [lsearch -exact $arcids($a) $t]
6438 if {$j > $i} break
6439 lappend $ret $t
6442 set id $arcstart($a)
6444 set origid $id
6445 set todo [list $id]
6446 set seen($id) 1
6447 for {set i 0} {$i < [llength $todo]} {incr i} {
6448 set id [lindex $todo $i]
6449 if {[info exists cached_dheads($id)]} {
6450 set ret [concat $ret $cached_dheads($id)]
6451 } else {
6452 if {[info exists idheads($id)]} {
6453 lappend ret $id
6455 foreach a $arcnos($id) {
6456 if {$archeads($a) ne {}} {
6457 set ret [concat $ret $archeads($a)]
6459 set d $arcstart($a)
6460 if {![info exists seen($d)]} {
6461 lappend todo $d
6462 set seen($d) 1
6467 set ret [lsort -unique $ret]
6468 set cached_dheads($origid) $ret
6471 proc addedtag {id} {
6472 global arcnos arcout cached_dtags cached_atags
6474 if {![info exists arcnos($id)]} return
6475 if {![info exists arcout($id)]} {
6476 recalcarc [lindex $arcnos($id) 0]
6478 catch {unset cached_dtags}
6479 catch {unset cached_atags}
6482 proc addedhead {hid head} {
6483 global arcnos arcout cached_dheads
6485 if {![info exists arcnos($hid)]} return
6486 if {![info exists arcout($hid)]} {
6487 recalcarc [lindex $arcnos($hid) 0]
6489 catch {unset cached_dheads}
6492 proc removedhead {hid head} {
6493 global cached_dheads
6495 catch {unset cached_dheads}
6498 proc movedhead {hid head} {
6499 global arcnos arcout cached_dheads
6501 if {![info exists arcnos($hid)]} return
6502 if {![info exists arcout($hid)]} {
6503 recalcarc [lindex $arcnos($hid) 0]
6505 catch {unset cached_dheads}
6508 proc changedrefs {} {
6509 global cached_dheads cached_dtags cached_atags
6510 global arctags archeads arcnos arcout idheads idtags
6512 foreach id [concat [array names idheads] [array names idtags]] {
6513 if {[info exists arcnos($id)] && ![info exists arcout($id)]} {
6514 set a [lindex $arcnos($id) 0]
6515 if {![info exists donearc($a)]} {
6516 recalcarc $a
6517 set donearc($a) 1
6521 catch {unset cached_dtags}
6522 catch {unset cached_atags}
6523 catch {unset cached_dheads}
6526 proc rereadrefs {} {
6527 global idtags idheads idotherrefs mainhead
6529 set refids [concat [array names idtags] \
6530 [array names idheads] [array names idotherrefs]]
6531 foreach id $refids {
6532 if {![info exists ref($id)]} {
6533 set ref($id) [listrefs $id]
6536 set oldmainhead $mainhead
6537 readrefs
6538 changedrefs
6539 set refids [lsort -unique [concat $refids [array names idtags] \
6540 [array names idheads] [array names idotherrefs]]]
6541 foreach id $refids {
6542 set v [listrefs $id]
6543 if {![info exists ref($id)] || $ref($id) != $v ||
6544 ($id eq $oldmainhead && $id ne $mainhead) ||
6545 ($id eq $mainhead && $id ne $oldmainhead)} {
6546 redrawtags $id
6551 proc listrefs {id} {
6552 global idtags idheads idotherrefs
6554 set x {}
6555 if {[info exists idtags($id)]} {
6556 set x $idtags($id)
6558 set y {}
6559 if {[info exists idheads($id)]} {
6560 set y $idheads($id)
6562 set z {}
6563 if {[info exists idotherrefs($id)]} {
6564 set z $idotherrefs($id)
6566 return [list $x $y $z]
6569 proc showtag {tag isnew} {
6570 global ctext tagcontents tagids linknum
6572 if {$isnew} {
6573 addtohistory [list showtag $tag 0]
6575 $ctext conf -state normal
6576 clear_ctext
6577 set linknum 0
6578 if {[info exists tagcontents($tag)]} {
6579 set text $tagcontents($tag)
6580 } else {
6581 set text "Tag: $tag\nId: $tagids($tag)"
6583 appendwithlinks $text {}
6584 $ctext conf -state disabled
6585 init_flist {}
6588 proc doquit {} {
6589 global stopped
6590 set stopped 100
6591 savestuff .
6592 destroy .
6595 proc doprefs {} {
6596 global maxwidth maxgraphpct diffopts
6597 global oldprefs prefstop showneartags
6598 global bgcolor fgcolor ctext diffcolors selectbgcolor
6599 global uifont tabstop
6601 set top .gitkprefs
6602 set prefstop $top
6603 if {[winfo exists $top]} {
6604 raise $top
6605 return
6607 foreach v {maxwidth maxgraphpct diffopts showneartags} {
6608 set oldprefs($v) [set $v]
6610 toplevel $top
6611 wm title $top "Gitk preferences"
6612 label $top.ldisp -text "Commit list display options"
6613 $top.ldisp configure -font $uifont
6614 grid $top.ldisp - -sticky w -pady 10
6615 label $top.spacer -text " "
6616 label $top.maxwidthl -text "Maximum graph width (lines)" \
6617 -font optionfont
6618 spinbox $top.maxwidth -from 0 -to 100 -width 4 -textvariable maxwidth
6619 grid $top.spacer $top.maxwidthl $top.maxwidth -sticky w
6620 label $top.maxpctl -text "Maximum graph width (% of pane)" \
6621 -font optionfont
6622 spinbox $top.maxpct -from 1 -to 100 -width 4 -textvariable maxgraphpct
6623 grid x $top.maxpctl $top.maxpct -sticky w
6625 label $top.ddisp -text "Diff display options"
6626 $top.ddisp configure -font $uifont
6627 grid $top.ddisp - -sticky w -pady 10
6628 label $top.diffoptl -text "Options for diff program" \
6629 -font optionfont
6630 entry $top.diffopt -width 20 -textvariable diffopts
6631 grid x $top.diffoptl $top.diffopt -sticky w
6632 frame $top.ntag
6633 label $top.ntag.l -text "Display nearby tags" -font optionfont
6634 checkbutton $top.ntag.b -variable showneartags
6635 pack $top.ntag.b $top.ntag.l -side left
6636 grid x $top.ntag -sticky w
6637 label $top.tabstopl -text "tabstop" -font optionfont
6638 entry $top.tabstop -width 10 -textvariable tabstop
6639 grid x $top.tabstopl $top.tabstop -sticky w
6641 label $top.cdisp -text "Colors: press to choose"
6642 $top.cdisp configure -font $uifont
6643 grid $top.cdisp - -sticky w -pady 10
6644 label $top.bg -padx 40 -relief sunk -background $bgcolor
6645 button $top.bgbut -text "Background" -font optionfont \
6646 -command [list choosecolor bgcolor 0 $top.bg background setbg]
6647 grid x $top.bgbut $top.bg -sticky w
6648 label $top.fg -padx 40 -relief sunk -background $fgcolor
6649 button $top.fgbut -text "Foreground" -font optionfont \
6650 -command [list choosecolor fgcolor 0 $top.fg foreground setfg]
6651 grid x $top.fgbut $top.fg -sticky w
6652 label $top.diffold -padx 40 -relief sunk -background [lindex $diffcolors 0]
6653 button $top.diffoldbut -text "Diff: old lines" -font optionfont \
6654 -command [list choosecolor diffcolors 0 $top.diffold "diff old lines" \
6655 [list $ctext tag conf d0 -foreground]]
6656 grid x $top.diffoldbut $top.diffold -sticky w
6657 label $top.diffnew -padx 40 -relief sunk -background [lindex $diffcolors 1]
6658 button $top.diffnewbut -text "Diff: new lines" -font optionfont \
6659 -command [list choosecolor diffcolors 1 $top.diffnew "diff new lines" \
6660 [list $ctext tag conf d1 -foreground]]
6661 grid x $top.diffnewbut $top.diffnew -sticky w
6662 label $top.hunksep -padx 40 -relief sunk -background [lindex $diffcolors 2]
6663 button $top.hunksepbut -text "Diff: hunk header" -font optionfont \
6664 -command [list choosecolor diffcolors 2 $top.hunksep \
6665 "diff hunk header" \
6666 [list $ctext tag conf hunksep -foreground]]
6667 grid x $top.hunksepbut $top.hunksep -sticky w
6668 label $top.selbgsep -padx 40 -relief sunk -background $selectbgcolor
6669 button $top.selbgbut -text "Select bg" -font optionfont \
6670 -command [list choosecolor selectbgcolor 0 $top.bg background setselbg]
6671 grid x $top.selbgbut $top.selbgsep -sticky w
6673 frame $top.buts
6674 button $top.buts.ok -text "OK" -command prefsok -default active
6675 $top.buts.ok configure -font $uifont
6676 button $top.buts.can -text "Cancel" -command prefscan -default normal
6677 $top.buts.can configure -font $uifont
6678 grid $top.buts.ok $top.buts.can
6679 grid columnconfigure $top.buts 0 -weight 1 -uniform a
6680 grid columnconfigure $top.buts 1 -weight 1 -uniform a
6681 grid $top.buts - - -pady 10 -sticky ew
6682 bind $top <Visibility> "focus $top.buts.ok"
6685 proc choosecolor {v vi w x cmd} {
6686 global $v
6688 set c [tk_chooseColor -initialcolor [lindex [set $v] $vi] \
6689 -title "Gitk: choose color for $x"]
6690 if {$c eq {}} return
6691 $w conf -background $c
6692 lset $v $vi $c
6693 eval $cmd $c
6696 proc setselbg {c} {
6697 global bglist cflist
6698 foreach w $bglist {
6699 $w configure -selectbackground $c
6701 $cflist tag configure highlight \
6702 -background [$cflist cget -selectbackground]
6703 allcanvs itemconf secsel -fill $c
6706 proc setbg {c} {
6707 global bglist
6709 foreach w $bglist {
6710 $w conf -background $c
6714 proc setfg {c} {
6715 global fglist canv
6717 foreach w $fglist {
6718 $w conf -foreground $c
6720 allcanvs itemconf text -fill $c
6721 $canv itemconf circle -outline $c
6724 proc prefscan {} {
6725 global maxwidth maxgraphpct diffopts
6726 global oldprefs prefstop showneartags
6728 foreach v {maxwidth maxgraphpct diffopts showneartags} {
6729 set $v $oldprefs($v)
6731 catch {destroy $prefstop}
6732 unset prefstop
6735 proc prefsok {} {
6736 global maxwidth maxgraphpct
6737 global oldprefs prefstop showneartags
6738 global charspc ctext tabstop
6740 catch {destroy $prefstop}
6741 unset prefstop
6742 $ctext configure -tabs "[expr {$tabstop * $charspc}]"
6743 if {$maxwidth != $oldprefs(maxwidth)
6744 || $maxgraphpct != $oldprefs(maxgraphpct)} {
6745 redisplay
6746 } elseif {$showneartags != $oldprefs(showneartags)} {
6747 reselectline
6751 proc formatdate {d} {
6752 return [clock format $d -format "%Y-%m-%d %H:%M:%S"]
6755 # This list of encoding names and aliases is distilled from
6756 # http://www.iana.org/assignments/character-sets.
6757 # Not all of them are supported by Tcl.
6758 set encoding_aliases {
6759 { ANSI_X3.4-1968 iso-ir-6 ANSI_X3.4-1986 ISO_646.irv:1991 ASCII
6760 ISO646-US US-ASCII us IBM367 cp367 csASCII }
6761 { ISO-10646-UTF-1 csISO10646UTF1 }
6762 { ISO_646.basic:1983 ref csISO646basic1983 }
6763 { INVARIANT csINVARIANT }
6764 { ISO_646.irv:1983 iso-ir-2 irv csISO2IntlRefVersion }
6765 { BS_4730 iso-ir-4 ISO646-GB gb uk csISO4UnitedKingdom }
6766 { NATS-SEFI iso-ir-8-1 csNATSSEFI }
6767 { NATS-SEFI-ADD iso-ir-8-2 csNATSSEFIADD }
6768 { NATS-DANO iso-ir-9-1 csNATSDANO }
6769 { NATS-DANO-ADD iso-ir-9-2 csNATSDANOADD }
6770 { SEN_850200_B iso-ir-10 FI ISO646-FI ISO646-SE se csISO10Swedish }
6771 { SEN_850200_C iso-ir-11 ISO646-SE2 se2 csISO11SwedishForNames }
6772 { KS_C_5601-1987 iso-ir-149 KS_C_5601-1989 KSC_5601 korean csKSC56011987 }
6773 { ISO-2022-KR csISO2022KR }
6774 { EUC-KR csEUCKR }
6775 { ISO-2022-JP csISO2022JP }
6776 { ISO-2022-JP-2 csISO2022JP2 }
6777 { JIS_C6220-1969-jp JIS_C6220-1969 iso-ir-13 katakana x0201-7
6778 csISO13JISC6220jp }
6779 { JIS_C6220-1969-ro iso-ir-14 jp ISO646-JP csISO14JISC6220ro }
6780 { IT iso-ir-15 ISO646-IT csISO15Italian }
6781 { PT iso-ir-16 ISO646-PT csISO16Portuguese }
6782 { ES iso-ir-17 ISO646-ES csISO17Spanish }
6783 { greek7-old iso-ir-18 csISO18Greek7Old }
6784 { latin-greek iso-ir-19 csISO19LatinGreek }
6785 { DIN_66003 iso-ir-21 de ISO646-DE csISO21German }
6786 { NF_Z_62-010_(1973) iso-ir-25 ISO646-FR1 csISO25French }
6787 { Latin-greek-1 iso-ir-27 csISO27LatinGreek1 }
6788 { ISO_5427 iso-ir-37 csISO5427Cyrillic }
6789 { JIS_C6226-1978 iso-ir-42 csISO42JISC62261978 }
6790 { BS_viewdata iso-ir-47 csISO47BSViewdata }
6791 { INIS iso-ir-49 csISO49INIS }
6792 { INIS-8 iso-ir-50 csISO50INIS8 }
6793 { INIS-cyrillic iso-ir-51 csISO51INISCyrillic }
6794 { ISO_5427:1981 iso-ir-54 ISO5427Cyrillic1981 }
6795 { ISO_5428:1980 iso-ir-55 csISO5428Greek }
6796 { GB_1988-80 iso-ir-57 cn ISO646-CN csISO57GB1988 }
6797 { GB_2312-80 iso-ir-58 chinese csISO58GB231280 }
6798 { NS_4551-1 iso-ir-60 ISO646-NO no csISO60DanishNorwegian
6799 csISO60Norwegian1 }
6800 { NS_4551-2 ISO646-NO2 iso-ir-61 no2 csISO61Norwegian2 }
6801 { NF_Z_62-010 iso-ir-69 ISO646-FR fr csISO69French }
6802 { videotex-suppl iso-ir-70 csISO70VideotexSupp1 }
6803 { PT2 iso-ir-84 ISO646-PT2 csISO84Portuguese2 }
6804 { ES2 iso-ir-85 ISO646-ES2 csISO85Spanish2 }
6805 { MSZ_7795.3 iso-ir-86 ISO646-HU hu csISO86Hungarian }
6806 { JIS_C6226-1983 iso-ir-87 x0208 JIS_X0208-1983 csISO87JISX0208 }
6807 { greek7 iso-ir-88 csISO88Greek7 }
6808 { ASMO_449 ISO_9036 arabic7 iso-ir-89 csISO89ASMO449 }
6809 { iso-ir-90 csISO90 }
6810 { JIS_C6229-1984-a iso-ir-91 jp-ocr-a csISO91JISC62291984a }
6811 { JIS_C6229-1984-b iso-ir-92 ISO646-JP-OCR-B jp-ocr-b
6812 csISO92JISC62991984b }
6813 { JIS_C6229-1984-b-add iso-ir-93 jp-ocr-b-add csISO93JIS62291984badd }
6814 { JIS_C6229-1984-hand iso-ir-94 jp-ocr-hand csISO94JIS62291984hand }
6815 { JIS_C6229-1984-hand-add iso-ir-95 jp-ocr-hand-add
6816 csISO95JIS62291984handadd }
6817 { JIS_C6229-1984-kana iso-ir-96 csISO96JISC62291984kana }
6818 { ISO_2033-1983 iso-ir-98 e13b csISO2033 }
6819 { ANSI_X3.110-1983 iso-ir-99 CSA_T500-1983 NAPLPS csISO99NAPLPS }
6820 { ISO_8859-1:1987 iso-ir-100 ISO_8859-1 ISO-8859-1 latin1 l1 IBM819
6821 CP819 csISOLatin1 }
6822 { ISO_8859-2:1987 iso-ir-101 ISO_8859-2 ISO-8859-2 latin2 l2 csISOLatin2 }
6823 { T.61-7bit iso-ir-102 csISO102T617bit }
6824 { T.61-8bit T.61 iso-ir-103 csISO103T618bit }
6825 { ISO_8859-3:1988 iso-ir-109 ISO_8859-3 ISO-8859-3 latin3 l3 csISOLatin3 }
6826 { ISO_8859-4:1988 iso-ir-110 ISO_8859-4 ISO-8859-4 latin4 l4 csISOLatin4 }
6827 { ECMA-cyrillic iso-ir-111 KOI8-E csISO111ECMACyrillic }
6828 { CSA_Z243.4-1985-1 iso-ir-121 ISO646-CA csa7-1 ca csISO121Canadian1 }
6829 { CSA_Z243.4-1985-2 iso-ir-122 ISO646-CA2 csa7-2 csISO122Canadian2 }
6830 { CSA_Z243.4-1985-gr iso-ir-123 csISO123CSAZ24341985gr }
6831 { ISO_8859-6:1987 iso-ir-127 ISO_8859-6 ISO-8859-6 ECMA-114 ASMO-708
6832 arabic csISOLatinArabic }
6833 { ISO_8859-6-E csISO88596E ISO-8859-6-E }
6834 { ISO_8859-6-I csISO88596I ISO-8859-6-I }
6835 { ISO_8859-7:1987 iso-ir-126 ISO_8859-7 ISO-8859-7 ELOT_928 ECMA-118
6836 greek greek8 csISOLatinGreek }
6837 { T.101-G2 iso-ir-128 csISO128T101G2 }
6838 { ISO_8859-8:1988 iso-ir-138 ISO_8859-8 ISO-8859-8 hebrew
6839 csISOLatinHebrew }
6840 { ISO_8859-8-E csISO88598E ISO-8859-8-E }
6841 { ISO_8859-8-I csISO88598I ISO-8859-8-I }
6842 { CSN_369103 iso-ir-139 csISO139CSN369103 }
6843 { JUS_I.B1.002 iso-ir-141 ISO646-YU js yu csISO141JUSIB1002 }
6844 { ISO_6937-2-add iso-ir-142 csISOTextComm }
6845 { IEC_P27-1 iso-ir-143 csISO143IECP271 }
6846 { ISO_8859-5:1988 iso-ir-144 ISO_8859-5 ISO-8859-5 cyrillic
6847 csISOLatinCyrillic }
6848 { JUS_I.B1.003-serb iso-ir-146 serbian csISO146Serbian }
6849 { JUS_I.B1.003-mac macedonian iso-ir-147 csISO147Macedonian }
6850 { ISO_8859-9:1989 iso-ir-148 ISO_8859-9 ISO-8859-9 latin5 l5 csISOLatin5 }
6851 { greek-ccitt iso-ir-150 csISO150 csISO150GreekCCITT }
6852 { NC_NC00-10:81 cuba iso-ir-151 ISO646-CU csISO151Cuba }
6853 { ISO_6937-2-25 iso-ir-152 csISO6937Add }
6854 { GOST_19768-74 ST_SEV_358-88 iso-ir-153 csISO153GOST1976874 }
6855 { ISO_8859-supp iso-ir-154 latin1-2-5 csISO8859Supp }
6856 { ISO_10367-box iso-ir-155 csISO10367Box }
6857 { ISO-8859-10 iso-ir-157 l6 ISO_8859-10:1992 csISOLatin6 latin6 }
6858 { latin-lap lap iso-ir-158 csISO158Lap }
6859 { JIS_X0212-1990 x0212 iso-ir-159 csISO159JISX02121990 }
6860 { DS_2089 DS2089 ISO646-DK dk csISO646Danish }
6861 { us-dk csUSDK }
6862 { dk-us csDKUS }
6863 { JIS_X0201 X0201 csHalfWidthKatakana }
6864 { KSC5636 ISO646-KR csKSC5636 }
6865 { ISO-10646-UCS-2 csUnicode }
6866 { ISO-10646-UCS-4 csUCS4 }
6867 { DEC-MCS dec csDECMCS }
6868 { hp-roman8 roman8 r8 csHPRoman8 }
6869 { macintosh mac csMacintosh }
6870 { IBM037 cp037 ebcdic-cp-us ebcdic-cp-ca ebcdic-cp-wt ebcdic-cp-nl
6871 csIBM037 }
6872 { IBM038 EBCDIC-INT cp038 csIBM038 }
6873 { IBM273 CP273 csIBM273 }
6874 { IBM274 EBCDIC-BE CP274 csIBM274 }
6875 { IBM275 EBCDIC-BR cp275 csIBM275 }
6876 { IBM277 EBCDIC-CP-DK EBCDIC-CP-NO csIBM277 }
6877 { IBM278 CP278 ebcdic-cp-fi ebcdic-cp-se csIBM278 }
6878 { IBM280 CP280 ebcdic-cp-it csIBM280 }
6879 { IBM281 EBCDIC-JP-E cp281 csIBM281 }
6880 { IBM284 CP284 ebcdic-cp-es csIBM284 }
6881 { IBM285 CP285 ebcdic-cp-gb csIBM285 }
6882 { IBM290 cp290 EBCDIC-JP-kana csIBM290 }
6883 { IBM297 cp297 ebcdic-cp-fr csIBM297 }
6884 { IBM420 cp420 ebcdic-cp-ar1 csIBM420 }
6885 { IBM423 cp423 ebcdic-cp-gr csIBM423 }
6886 { IBM424 cp424 ebcdic-cp-he csIBM424 }
6887 { IBM437 cp437 437 csPC8CodePage437 }
6888 { IBM500 CP500 ebcdic-cp-be ebcdic-cp-ch csIBM500 }
6889 { IBM775 cp775 csPC775Baltic }
6890 { IBM850 cp850 850 csPC850Multilingual }
6891 { IBM851 cp851 851 csIBM851 }
6892 { IBM852 cp852 852 csPCp852 }
6893 { IBM855 cp855 855 csIBM855 }
6894 { IBM857 cp857 857 csIBM857 }
6895 { IBM860 cp860 860 csIBM860 }
6896 { IBM861 cp861 861 cp-is csIBM861 }
6897 { IBM862 cp862 862 csPC862LatinHebrew }
6898 { IBM863 cp863 863 csIBM863 }
6899 { IBM864 cp864 csIBM864 }
6900 { IBM865 cp865 865 csIBM865 }
6901 { IBM866 cp866 866 csIBM866 }
6902 { IBM868 CP868 cp-ar csIBM868 }
6903 { IBM869 cp869 869 cp-gr csIBM869 }
6904 { IBM870 CP870 ebcdic-cp-roece ebcdic-cp-yu csIBM870 }
6905 { IBM871 CP871 ebcdic-cp-is csIBM871 }
6906 { IBM880 cp880 EBCDIC-Cyrillic csIBM880 }
6907 { IBM891 cp891 csIBM891 }
6908 { IBM903 cp903 csIBM903 }
6909 { IBM904 cp904 904 csIBBM904 }
6910 { IBM905 CP905 ebcdic-cp-tr csIBM905 }
6911 { IBM918 CP918 ebcdic-cp-ar2 csIBM918 }
6912 { IBM1026 CP1026 csIBM1026 }
6913 { EBCDIC-AT-DE csIBMEBCDICATDE }
6914 { EBCDIC-AT-DE-A csEBCDICATDEA }
6915 { EBCDIC-CA-FR csEBCDICCAFR }
6916 { EBCDIC-DK-NO csEBCDICDKNO }
6917 { EBCDIC-DK-NO-A csEBCDICDKNOA }
6918 { EBCDIC-FI-SE csEBCDICFISE }
6919 { EBCDIC-FI-SE-A csEBCDICFISEA }
6920 { EBCDIC-FR csEBCDICFR }
6921 { EBCDIC-IT csEBCDICIT }
6922 { EBCDIC-PT csEBCDICPT }
6923 { EBCDIC-ES csEBCDICES }
6924 { EBCDIC-ES-A csEBCDICESA }
6925 { EBCDIC-ES-S csEBCDICESS }
6926 { EBCDIC-UK csEBCDICUK }
6927 { EBCDIC-US csEBCDICUS }
6928 { UNKNOWN-8BIT csUnknown8BiT }
6929 { MNEMONIC csMnemonic }
6930 { MNEM csMnem }
6931 { VISCII csVISCII }
6932 { VIQR csVIQR }
6933 { KOI8-R csKOI8R }
6934 { IBM00858 CCSID00858 CP00858 PC-Multilingual-850+euro }
6935 { IBM00924 CCSID00924 CP00924 ebcdic-Latin9--euro }
6936 { IBM01140 CCSID01140 CP01140 ebcdic-us-37+euro }
6937 { IBM01141 CCSID01141 CP01141 ebcdic-de-273+euro }
6938 { IBM01142 CCSID01142 CP01142 ebcdic-dk-277+euro ebcdic-no-277+euro }
6939 { IBM01143 CCSID01143 CP01143 ebcdic-fi-278+euro ebcdic-se-278+euro }
6940 { IBM01144 CCSID01144 CP01144 ebcdic-it-280+euro }
6941 { IBM01145 CCSID01145 CP01145 ebcdic-es-284+euro }
6942 { IBM01146 CCSID01146 CP01146 ebcdic-gb-285+euro }
6943 { IBM01147 CCSID01147 CP01147 ebcdic-fr-297+euro }
6944 { IBM01148 CCSID01148 CP01148 ebcdic-international-500+euro }
6945 { IBM01149 CCSID01149 CP01149 ebcdic-is-871+euro }
6946 { IBM1047 IBM-1047 }
6947 { PTCP154 csPTCP154 PT154 CP154 Cyrillic-Asian }
6948 { Amiga-1251 Ami1251 Amiga1251 Ami-1251 }
6949 { UNICODE-1-1 csUnicode11 }
6950 { CESU-8 csCESU-8 }
6951 { BOCU-1 csBOCU-1 }
6952 { UNICODE-1-1-UTF-7 csUnicode11UTF7 }
6953 { ISO-8859-14 iso-ir-199 ISO_8859-14:1998 ISO_8859-14 latin8 iso-celtic
6954 l8 }
6955 { ISO-8859-15 ISO_8859-15 Latin-9 }
6956 { ISO-8859-16 iso-ir-226 ISO_8859-16:2001 ISO_8859-16 latin10 l10 }
6957 { GBK CP936 MS936 windows-936 }
6958 { JIS_Encoding csJISEncoding }
6959 { Shift_JIS MS_Kanji csShiftJIS }
6960 { Extended_UNIX_Code_Packed_Format_for_Japanese csEUCPkdFmtJapanese
6961 EUC-JP }
6962 { Extended_UNIX_Code_Fixed_Width_for_Japanese csEUCFixWidJapanese }
6963 { ISO-10646-UCS-Basic csUnicodeASCII }
6964 { ISO-10646-Unicode-Latin1 csUnicodeLatin1 ISO-10646 }
6965 { ISO-Unicode-IBM-1261 csUnicodeIBM1261 }
6966 { ISO-Unicode-IBM-1268 csUnicodeIBM1268 }
6967 { ISO-Unicode-IBM-1276 csUnicodeIBM1276 }
6968 { ISO-Unicode-IBM-1264 csUnicodeIBM1264 }
6969 { ISO-Unicode-IBM-1265 csUnicodeIBM1265 }
6970 { ISO-8859-1-Windows-3.0-Latin-1 csWindows30Latin1 }
6971 { ISO-8859-1-Windows-3.1-Latin-1 csWindows31Latin1 }
6972 { ISO-8859-2-Windows-Latin-2 csWindows31Latin2 }
6973 { ISO-8859-9-Windows-Latin-5 csWindows31Latin5 }
6974 { Adobe-Standard-Encoding csAdobeStandardEncoding }
6975 { Ventura-US csVenturaUS }
6976 { Ventura-International csVenturaInternational }
6977 { PC8-Danish-Norwegian csPC8DanishNorwegian }
6978 { PC8-Turkish csPC8Turkish }
6979 { IBM-Symbols csIBMSymbols }
6980 { IBM-Thai csIBMThai }
6981 { HP-Legal csHPLegal }
6982 { HP-Pi-font csHPPiFont }
6983 { HP-Math8 csHPMath8 }
6984 { Adobe-Symbol-Encoding csHPPSMath }
6985 { HP-DeskTop csHPDesktop }
6986 { Ventura-Math csVenturaMath }
6987 { Microsoft-Publishing csMicrosoftPublishing }
6988 { Windows-31J csWindows31J }
6989 { GB2312 csGB2312 }
6990 { Big5 csBig5 }
6993 proc tcl_encoding {enc} {
6994 global encoding_aliases
6995 set names [encoding names]
6996 set lcnames [string tolower $names]
6997 set enc [string tolower $enc]
6998 set i [lsearch -exact $lcnames $enc]
6999 if {$i < 0} {
7000 # look for "isonnn" instead of "iso-nnn" or "iso_nnn"
7001 if {[regsub {^iso[-_]} $enc iso encx]} {
7002 set i [lsearch -exact $lcnames $encx]
7005 if {$i < 0} {
7006 foreach l $encoding_aliases {
7007 set ll [string tolower $l]
7008 if {[lsearch -exact $ll $enc] < 0} continue
7009 # look through the aliases for one that tcl knows about
7010 foreach e $ll {
7011 set i [lsearch -exact $lcnames $e]
7012 if {$i < 0} {
7013 if {[regsub {^iso[-_]} $e iso ex]} {
7014 set i [lsearch -exact $lcnames $ex]
7017 if {$i >= 0} break
7019 break
7022 if {$i >= 0} {
7023 return [lindex $names $i]
7025 return {}
7028 # defaults...
7029 set datemode 0
7030 set diffopts "-U 5 -p"
7031 set wrcomcmd "git diff-tree --stdin -p --pretty"
7033 set gitencoding {}
7034 catch {
7035 set gitencoding [exec git config --get i18n.commitencoding]
7037 if {$gitencoding == ""} {
7038 set gitencoding "utf-8"
7040 set tclencoding [tcl_encoding $gitencoding]
7041 if {$tclencoding == {}} {
7042 puts stderr "Warning: encoding $gitencoding is not supported by Tcl/Tk"
7045 set mainfont {Helvetica 9}
7046 set textfont {Courier 9}
7047 set uifont {Helvetica 9 bold}
7048 set tabstop 8
7049 set findmergefiles 0
7050 set maxgraphpct 50
7051 set maxwidth 16
7052 set revlistorder 0
7053 set fastdate 0
7054 set uparrowlen 7
7055 set downarrowlen 7
7056 set mingaplen 30
7057 set cmitmode "patch"
7058 set wrapcomment "none"
7059 set showneartags 1
7060 set maxrefs 20
7061 set maxlinelen 200
7063 set colors {green red blue magenta darkgrey brown orange}
7064 set bgcolor white
7065 set fgcolor black
7066 set diffcolors {red "#00a000" blue}
7067 set selectbgcolor gray85
7069 catch {source ~/.gitk}
7071 font create optionfont -family sans-serif -size -12
7073 set revtreeargs {}
7074 foreach arg $argv {
7075 switch -regexp -- $arg {
7076 "^$" { }
7077 "^-d" { set datemode 1 }
7078 default {
7079 lappend revtreeargs $arg
7084 # check that we can find a .git directory somewhere...
7085 set gitdir [gitdir]
7086 if {![file isdirectory $gitdir]} {
7087 show_error {} . "Cannot find the git directory \"$gitdir\"."
7088 exit 1
7091 set cmdline_files {}
7092 set i [lsearch -exact $revtreeargs "--"]
7093 if {$i >= 0} {
7094 set cmdline_files [lrange $revtreeargs [expr {$i + 1}] end]
7095 set revtreeargs [lrange $revtreeargs 0 [expr {$i - 1}]]
7096 } elseif {$revtreeargs ne {}} {
7097 if {[catch {
7098 set f [eval exec git rev-parse --no-revs --no-flags $revtreeargs]
7099 set cmdline_files [split $f "\n"]
7100 set n [llength $cmdline_files]
7101 set revtreeargs [lrange $revtreeargs 0 end-$n]
7102 } err]} {
7103 # unfortunately we get both stdout and stderr in $err,
7104 # so look for "fatal:".
7105 set i [string first "fatal:" $err]
7106 if {$i > 0} {
7107 set err [string range $err [expr {$i + 6}] end]
7109 show_error {} . "Bad arguments to gitk:\n$err"
7110 exit 1
7114 set runq {}
7115 set history {}
7116 set historyindex 0
7117 set fh_serial 0
7118 set nhl_names {}
7119 set highlight_paths {}
7120 set searchdirn -forwards
7121 set boldrows {}
7122 set boldnamerows {}
7123 set diffelide {0 0}
7125 set optim_delay 16
7127 set nextviewnum 1
7128 set curview 0
7129 set selectedview 0
7130 set selectedhlview None
7131 set viewfiles(0) {}
7132 set viewperm(0) 0
7133 set viewargs(0) {}
7135 set cmdlineok 0
7136 set stopped 0
7137 set stuffsaved 0
7138 set patchnum 0
7139 setcoords
7140 makewindow
7141 wm title . "[file tail $argv0]: [file tail [pwd]]"
7142 readrefs
7144 if {$cmdline_files ne {} || $revtreeargs ne {}} {
7145 # create a view for the files/dirs specified on the command line
7146 set curview 1
7147 set selectedview 1
7148 set nextviewnum 2
7149 set viewname(1) "Command line"
7150 set viewfiles(1) $cmdline_files
7151 set viewargs(1) $revtreeargs
7152 set viewperm(1) 0
7153 addviewmenu 1
7154 .bar.view entryconf Edit* -state normal
7155 .bar.view entryconf Delete* -state normal
7158 if {[info exists permviews]} {
7159 foreach v $permviews {
7160 set n $nextviewnum
7161 incr nextviewnum
7162 set viewname($n) [lindex $v 0]
7163 set viewfiles($n) [lindex $v 1]
7164 set viewargs($n) [lindex $v 2]
7165 set viewperm($n) 1
7166 addviewmenu $n
7169 getcommits