gitk: Remove the unused stopfindproc function
[git/libgit-gsoc.git] / gitk
blob45e16e4fd5ebb6670b289a2cd4006a878c80df6c
1 #!/bin/sh
2 # Tcl ignores the next line -*- tcl -*- \
3 exec wish "$0" -- "$@"
5 # Copyright (C) 2005-2006 Paul Mackerras. All rights reserved.
6 # This program is free software; it may be used, copied, modified
7 # and distributed under the terms of the GNU General Public Licence,
8 # either version 2, or (at your option) any later version.
10 proc gitdir {} {
11 global env
12 if {[info exists env(GIT_DIR)]} {
13 return $env(GIT_DIR)
14 } else {
15 return [exec git rev-parse --git-dir]
19 # A simple scheduler for compute-intensive stuff.
20 # The aim is to make sure that event handlers for GUI actions can
21 # run at least every 50-100 ms. Unfortunately fileevent handlers are
22 # run before X event handlers, so reading from a fast source can
23 # make the GUI completely unresponsive.
24 proc run args {
25 global isonrunq runq
27 set script $args
28 if {[info exists isonrunq($script)]} return
29 if {$runq eq {}} {
30 after idle dorunq
32 lappend runq [list {} $script]
33 set isonrunq($script) 1
36 proc filerun {fd script} {
37 fileevent $fd readable [list filereadable $fd $script]
40 proc filereadable {fd script} {
41 global runq
43 fileevent $fd readable {}
44 if {$runq eq {}} {
45 after idle dorunq
47 lappend runq [list $fd $script]
50 proc dorunq {} {
51 global isonrunq runq
53 set tstart [clock clicks -milliseconds]
54 set t0 $tstart
55 while {$runq ne {}} {
56 set fd [lindex $runq 0 0]
57 set script [lindex $runq 0 1]
58 set repeat [eval $script]
59 set t1 [clock clicks -milliseconds]
60 set t [expr {$t1 - $t0}]
61 set runq [lrange $runq 1 end]
62 if {$repeat ne {} && $repeat} {
63 if {$fd eq {} || $repeat == 2} {
64 # script returns 1 if it wants to be readded
65 # file readers return 2 if they could do more straight away
66 lappend runq [list $fd $script]
67 } else {
68 fileevent $fd readable [list filereadable $fd $script]
70 } elseif {$fd eq {}} {
71 unset isonrunq($script)
73 set t0 $t1
74 if {$t1 - $tstart >= 80} break
76 if {$runq ne {}} {
77 after idle dorunq
81 # Start off a git rev-list process and arrange to read its output
82 proc start_rev_list {view} {
83 global startmsecs
84 global commfd leftover tclencoding datemode
85 global viewargs viewfiles commitidx
86 global lookingforhead showlocalchanges
88 set startmsecs [clock clicks -milliseconds]
89 set commitidx($view) 0
90 set args $viewargs($view)
91 if {$viewfiles($view) ne {}} {
92 set args [concat $args "--" $viewfiles($view)]
94 set order "--topo-order"
95 if {$datemode} {
96 set order "--date-order"
98 if {[catch {
99 set fd [open [concat | git rev-list --header $order \
100 --parents --boundary --default HEAD $args] r]
101 } err]} {
102 puts stderr "Error executing git rev-list: $err"
103 exit 1
105 set commfd($view) $fd
106 set leftover($view) {}
107 set lookingforhead $showlocalchanges
108 fconfigure $fd -blocking 0 -translation lf
109 if {$tclencoding != {}} {
110 fconfigure $fd -encoding $tclencoding
112 filerun $fd [list getcommitlines $fd $view]
113 nowbusy $view
116 proc stop_rev_list {} {
117 global commfd curview
119 if {![info exists commfd($curview)]} return
120 set fd $commfd($curview)
121 catch {
122 set pid [pid $fd]
123 exec kill $pid
125 catch {close $fd}
126 unset commfd($curview)
129 proc getcommits {} {
130 global phase canv mainfont curview
132 set phase getcommits
133 initlayout
134 start_rev_list $curview
135 show_status "Reading commits..."
138 proc getcommitlines {fd view} {
139 global commitlisted
140 global leftover commfd
141 global displayorder commitidx commitrow commitdata
142 global parentlist children curview hlview
143 global vparentlist vdisporder vcmitlisted
145 set stuff [read $fd 500000]
146 if {$stuff == {}} {
147 if {![eof $fd]} {
148 return 1
150 global viewname
151 unset commfd($view)
152 notbusy $view
153 # set it blocking so we wait for the process to terminate
154 fconfigure $fd -blocking 1
155 if {[catch {close $fd} err]} {
156 set fv {}
157 if {$view != $curview} {
158 set fv " for the \"$viewname($view)\" view"
160 if {[string range $err 0 4] == "usage"} {
161 set err "Gitk: error reading commits$fv:\
162 bad arguments to git rev-list."
163 if {$viewname($view) eq "Command line"} {
164 append err \
165 " (Note: arguments to gitk are passed to git rev-list\
166 to allow selection of commits to be displayed.)"
168 } else {
169 set err "Error reading commits$fv: $err"
171 error_popup $err
173 if {$view == $curview} {
174 run chewcommits $view
176 return 0
178 set start 0
179 set gotsome 0
180 while 1 {
181 set i [string first "\0" $stuff $start]
182 if {$i < 0} {
183 append leftover($view) [string range $stuff $start end]
184 break
186 if {$start == 0} {
187 set cmit $leftover($view)
188 append cmit [string range $stuff 0 [expr {$i - 1}]]
189 set leftover($view) {}
190 } else {
191 set cmit [string range $stuff $start [expr {$i - 1}]]
193 set start [expr {$i + 1}]
194 set j [string first "\n" $cmit]
195 set ok 0
196 set listed 1
197 if {$j >= 0} {
198 set ids [string range $cmit 0 [expr {$j - 1}]]
199 if {[string range $ids 0 0] == "-"} {
200 set listed 0
201 set ids [string range $ids 1 end]
203 set ok 1
204 foreach id $ids {
205 if {[string length $id] != 40} {
206 set ok 0
207 break
211 if {!$ok} {
212 set shortcmit $cmit
213 if {[string length $shortcmit] > 80} {
214 set shortcmit "[string range $shortcmit 0 80]..."
216 error_popup "Can't parse git rev-list output: {$shortcmit}"
217 exit 1
219 set id [lindex $ids 0]
220 if {$listed} {
221 set olds [lrange $ids 1 end]
222 set i 0
223 foreach p $olds {
224 if {$i == 0 || [lsearch -exact $olds $p] >= $i} {
225 lappend children($view,$p) $id
227 incr i
229 } else {
230 set olds {}
232 if {![info exists children($view,$id)]} {
233 set children($view,$id) {}
235 set commitdata($id) [string range $cmit [expr {$j + 1}] end]
236 set commitrow($view,$id) $commitidx($view)
237 incr commitidx($view)
238 if {$view == $curview} {
239 lappend parentlist $olds
240 lappend displayorder $id
241 lappend commitlisted $listed
242 } else {
243 lappend vparentlist($view) $olds
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 nullid 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 tagobjid
389 global otherrefids idotherrefs mainhead mainheadid
391 foreach v {tagids idtags headids idheads otherrefids idotherrefs} {
392 catch {unset $v}
394 set refd [open [list | git show-ref -d] r]
395 while {[gets $refd line] >= 0} {
396 if {[string index $line 40] ne " "} continue
397 set id [string range $line 0 39]
398 set ref [string range $line 41 end]
399 if {![string match "refs/*" $ref]} continue
400 set name [string range $ref 5 end]
401 if {[string match "remotes/*" $name]} {
402 if {![string match "*/HEAD" $name]} {
403 set headids($name) $id
404 lappend idheads($id) $name
406 } elseif {[string match "heads/*" $name]} {
407 set name [string range $name 6 end]
408 set headids($name) $id
409 lappend idheads($id) $name
410 } elseif {[string match "tags/*" $name]} {
411 # this lets refs/tags/foo^{} overwrite refs/tags/foo,
412 # which is what we want since the former is the commit ID
413 set name [string range $name 5 end]
414 if {[string match "*^{}" $name]} {
415 set name [string range $name 0 end-3]
416 } else {
417 set tagobjid($name) $id
419 set tagids($name) $id
420 lappend idtags($id) $name
421 } else {
422 set otherrefids($name) $id
423 lappend idotherrefs($id) $name
426 close $refd
427 set mainhead {}
428 set mainheadid {}
429 catch {
430 set thehead [exec git symbolic-ref HEAD]
431 if {[string match "refs/heads/*" $thehead]} {
432 set mainhead [string range $thehead 11 end]
433 if {[info exists headids($mainhead)]} {
434 set mainheadid $headids($mainhead)
440 # update things for a head moved to a child of its previous location
441 proc movehead {id name} {
442 global headids idheads
444 removehead $headids($name) $name
445 set headids($name) $id
446 lappend idheads($id) $name
449 # update things when a head has been removed
450 proc removehead {id name} {
451 global headids idheads
453 if {$idheads($id) eq $name} {
454 unset idheads($id)
455 } else {
456 set i [lsearch -exact $idheads($id) $name]
457 if {$i >= 0} {
458 set idheads($id) [lreplace $idheads($id) $i $i]
461 unset headids($name)
464 proc show_error {w top msg} {
465 message $w.m -text $msg -justify center -aspect 400
466 pack $w.m -side top -fill x -padx 20 -pady 20
467 button $w.ok -text OK -command "destroy $top"
468 pack $w.ok -side bottom -fill x
469 bind $top <Visibility> "grab $top; focus $top"
470 bind $top <Key-Return> "destroy $top"
471 tkwait window $top
474 proc error_popup msg {
475 set w .error
476 toplevel $w
477 wm transient $w .
478 show_error $w $w $msg
481 proc confirm_popup msg {
482 global confirm_ok
483 set confirm_ok 0
484 set w .confirm
485 toplevel $w
486 wm transient $w .
487 message $w.m -text $msg -justify center -aspect 400
488 pack $w.m -side top -fill x -padx 20 -pady 20
489 button $w.ok -text OK -command "set confirm_ok 1; destroy $w"
490 pack $w.ok -side left -fill x
491 button $w.cancel -text Cancel -command "destroy $w"
492 pack $w.cancel -side right -fill x
493 bind $w <Visibility> "grab $w; focus $w"
494 tkwait window $w
495 return $confirm_ok
498 proc makewindow {} {
499 global canv canv2 canv3 linespc charspc ctext cflist
500 global textfont mainfont uifont tabstop
501 global findtype findtypemenu findloc findstring fstring geometry
502 global entries sha1entry sha1string sha1but
503 global maincursor textcursor curtextcursor
504 global rowctxmenu fakerowmenu mergemax wrapcomment
505 global highlight_files gdttype
506 global searchstring sstring
507 global bgcolor fgcolor bglist fglist diffcolors selectbgcolor
508 global headctxmenu
510 menu .bar
511 .bar add cascade -label "File" -menu .bar.file
512 .bar configure -font $uifont
513 menu .bar.file
514 .bar.file add command -label "Update" -command updatecommits
515 .bar.file add command -label "Reread references" -command rereadrefs
516 .bar.file add command -label "Quit" -command doquit
517 .bar.file configure -font $uifont
518 menu .bar.edit
519 .bar add cascade -label "Edit" -menu .bar.edit
520 .bar.edit add command -label "Preferences" -command doprefs
521 .bar.edit configure -font $uifont
523 menu .bar.view -font $uifont
524 .bar add cascade -label "View" -menu .bar.view
525 .bar.view add command -label "New view..." -command {newview 0}
526 .bar.view add command -label "Edit view..." -command editview \
527 -state disabled
528 .bar.view add command -label "Delete view" -command delview -state disabled
529 .bar.view add separator
530 .bar.view add radiobutton -label "All files" -command {showview 0} \
531 -variable selectedview -value 0
533 menu .bar.help
534 .bar add cascade -label "Help" -menu .bar.help
535 .bar.help add command -label "About gitk" -command about
536 .bar.help add command -label "Key bindings" -command keys
537 .bar.help configure -font $uifont
538 . configure -menu .bar
540 # the gui has upper and lower half, parts of a paned window.
541 panedwindow .ctop -orient vertical
543 # possibly use assumed geometry
544 if {![info exists geometry(pwsash0)]} {
545 set geometry(topheight) [expr {15 * $linespc}]
546 set geometry(topwidth) [expr {80 * $charspc}]
547 set geometry(botheight) [expr {15 * $linespc}]
548 set geometry(botwidth) [expr {50 * $charspc}]
549 set geometry(pwsash0) "[expr {40 * $charspc}] 2"
550 set geometry(pwsash1) "[expr {60 * $charspc}] 2"
553 # the upper half will have a paned window, a scroll bar to the right, and some stuff below
554 frame .tf -height $geometry(topheight) -width $geometry(topwidth)
555 frame .tf.histframe
556 panedwindow .tf.histframe.pwclist -orient horizontal -sashpad 0 -handlesize 4
558 # create three canvases
559 set cscroll .tf.histframe.csb
560 set canv .tf.histframe.pwclist.canv
561 canvas $canv \
562 -selectbackground $selectbgcolor \
563 -background $bgcolor -bd 0 \
564 -yscrollincr $linespc -yscrollcommand "scrollcanv $cscroll"
565 .tf.histframe.pwclist add $canv
566 set canv2 .tf.histframe.pwclist.canv2
567 canvas $canv2 \
568 -selectbackground $selectbgcolor \
569 -background $bgcolor -bd 0 -yscrollincr $linespc
570 .tf.histframe.pwclist add $canv2
571 set canv3 .tf.histframe.pwclist.canv3
572 canvas $canv3 \
573 -selectbackground $selectbgcolor \
574 -background $bgcolor -bd 0 -yscrollincr $linespc
575 .tf.histframe.pwclist add $canv3
576 eval .tf.histframe.pwclist sash place 0 $geometry(pwsash0)
577 eval .tf.histframe.pwclist sash place 1 $geometry(pwsash1)
579 # a scroll bar to rule them
580 scrollbar $cscroll -command {allcanvs yview} -highlightthickness 0
581 pack $cscroll -side right -fill y
582 bind .tf.histframe.pwclist <Configure> {resizeclistpanes %W %w}
583 lappend bglist $canv $canv2 $canv3
584 pack .tf.histframe.pwclist -fill both -expand 1 -side left
586 # we have two button bars at bottom of top frame. Bar 1
587 frame .tf.bar
588 frame .tf.lbar -height 15
590 set sha1entry .tf.bar.sha1
591 set entries $sha1entry
592 set sha1but .tf.bar.sha1label
593 button $sha1but -text "SHA1 ID: " -state disabled -relief flat \
594 -command gotocommit -width 8 -font $uifont
595 $sha1but conf -disabledforeground [$sha1but cget -foreground]
596 pack .tf.bar.sha1label -side left
597 entry $sha1entry -width 40 -font $textfont -textvariable sha1string
598 trace add variable sha1string write sha1change
599 pack $sha1entry -side left -pady 2
601 image create bitmap bm-left -data {
602 #define left_width 16
603 #define left_height 16
604 static unsigned char left_bits[] = {
605 0x00, 0x00, 0xc0, 0x01, 0xe0, 0x00, 0x70, 0x00, 0x38, 0x00, 0x1c, 0x00,
606 0x0e, 0x00, 0xff, 0x7f, 0xff, 0x7f, 0xff, 0x7f, 0x0e, 0x00, 0x1c, 0x00,
607 0x38, 0x00, 0x70, 0x00, 0xe0, 0x00, 0xc0, 0x01};
609 image create bitmap bm-right -data {
610 #define right_width 16
611 #define right_height 16
612 static unsigned char right_bits[] = {
613 0x00, 0x00, 0xc0, 0x01, 0x80, 0x03, 0x00, 0x07, 0x00, 0x0e, 0x00, 0x1c,
614 0x00, 0x38, 0xff, 0x7f, 0xff, 0x7f, 0xff, 0x7f, 0x00, 0x38, 0x00, 0x1c,
615 0x00, 0x0e, 0x00, 0x07, 0x80, 0x03, 0xc0, 0x01};
617 button .tf.bar.leftbut -image bm-left -command goback \
618 -state disabled -width 26
619 pack .tf.bar.leftbut -side left -fill y
620 button .tf.bar.rightbut -image bm-right -command goforw \
621 -state disabled -width 26
622 pack .tf.bar.rightbut -side left -fill y
624 button .tf.bar.findbut -text "Find" -command dofind -font $uifont
625 pack .tf.bar.findbut -side left
626 set findstring {}
627 set fstring .tf.bar.findstring
628 lappend entries $fstring
629 entry $fstring -width 30 -font $textfont -textvariable findstring
630 trace add variable findstring write find_change
631 pack $fstring -side left -expand 1 -fill x -in .tf.bar
632 set findtype Exact
633 set findtypemenu [tk_optionMenu .tf.bar.findtype \
634 findtype Exact IgnCase Regexp]
635 trace add variable findtype write find_change
636 .tf.bar.findtype configure -font $uifont
637 .tf.bar.findtype.menu configure -font $uifont
638 set findloc "All fields"
639 tk_optionMenu .tf.bar.findloc findloc "All fields" Headline \
640 Comments Author Committer
641 trace add variable findloc write find_change
642 .tf.bar.findloc configure -font $uifont
643 .tf.bar.findloc.menu configure -font $uifont
644 pack .tf.bar.findloc -side right
645 pack .tf.bar.findtype -side right
647 # build up the bottom bar of upper window
648 label .tf.lbar.flabel -text "Highlight: Commits " \
649 -font $uifont
650 pack .tf.lbar.flabel -side left -fill y
651 set gdttype "touching paths:"
652 set gm [tk_optionMenu .tf.lbar.gdttype gdttype "touching paths:" \
653 "adding/removing string:"]
654 trace add variable gdttype write hfiles_change
655 $gm conf -font $uifont
656 .tf.lbar.gdttype conf -font $uifont
657 pack .tf.lbar.gdttype -side left -fill y
658 entry .tf.lbar.fent -width 25 -font $textfont \
659 -textvariable highlight_files
660 trace add variable highlight_files write hfiles_change
661 lappend entries .tf.lbar.fent
662 pack .tf.lbar.fent -side left -fill x -expand 1
663 label .tf.lbar.vlabel -text " OR in view" -font $uifont
664 pack .tf.lbar.vlabel -side left -fill y
665 global viewhlmenu selectedhlview
666 set viewhlmenu [tk_optionMenu .tf.lbar.vhl selectedhlview None]
667 $viewhlmenu entryconf None -command delvhighlight
668 $viewhlmenu conf -font $uifont
669 .tf.lbar.vhl conf -font $uifont
670 pack .tf.lbar.vhl -side left -fill y
671 label .tf.lbar.rlabel -text " OR " -font $uifont
672 pack .tf.lbar.rlabel -side left -fill y
673 global highlight_related
674 set m [tk_optionMenu .tf.lbar.relm highlight_related None \
675 "Descendent" "Not descendent" "Ancestor" "Not ancestor"]
676 $m conf -font $uifont
677 .tf.lbar.relm conf -font $uifont
678 trace add variable highlight_related write vrel_change
679 pack .tf.lbar.relm -side left -fill y
681 # Finish putting the upper half of the viewer together
682 pack .tf.lbar -in .tf -side bottom -fill x
683 pack .tf.bar -in .tf -side bottom -fill x
684 pack .tf.histframe -fill both -side top -expand 1
685 .ctop add .tf
686 .ctop paneconfigure .tf -height $geometry(topheight)
687 .ctop paneconfigure .tf -width $geometry(topwidth)
689 # now build up the bottom
690 panedwindow .pwbottom -orient horizontal
692 # lower left, a text box over search bar, scroll bar to the right
693 # if we know window height, then that will set the lower text height, otherwise
694 # we set lower text height which will drive window height
695 if {[info exists geometry(main)]} {
696 frame .bleft -width $geometry(botwidth)
697 } else {
698 frame .bleft -width $geometry(botwidth) -height $geometry(botheight)
700 frame .bleft.top
701 frame .bleft.mid
703 button .bleft.top.search -text "Search" -command dosearch \
704 -font $uifont
705 pack .bleft.top.search -side left -padx 5
706 set sstring .bleft.top.sstring
707 entry $sstring -width 20 -font $textfont -textvariable searchstring
708 lappend entries $sstring
709 trace add variable searchstring write incrsearch
710 pack $sstring -side left -expand 1 -fill x
711 radiobutton .bleft.mid.diff -text "Diff" \
712 -command changediffdisp -variable diffelide -value {0 0}
713 radiobutton .bleft.mid.old -text "Old version" \
714 -command changediffdisp -variable diffelide -value {0 1}
715 radiobutton .bleft.mid.new -text "New version" \
716 -command changediffdisp -variable diffelide -value {1 0}
717 pack .bleft.mid.diff .bleft.mid.old .bleft.mid.new -side left
718 set ctext .bleft.ctext
719 text $ctext -background $bgcolor -foreground $fgcolor \
720 -tabs "[expr {$tabstop * $charspc}]" \
721 -state disabled -font $textfont \
722 -yscrollcommand scrolltext -wrap none
723 scrollbar .bleft.sb -command "$ctext yview"
724 pack .bleft.top -side top -fill x
725 pack .bleft.mid -side top -fill x
726 pack .bleft.sb -side right -fill y
727 pack $ctext -side left -fill both -expand 1
728 lappend bglist $ctext
729 lappend fglist $ctext
731 $ctext tag conf comment -wrap $wrapcomment
732 $ctext tag conf filesep -font [concat $textfont bold] -back "#aaaaaa"
733 $ctext tag conf hunksep -fore [lindex $diffcolors 2]
734 $ctext tag conf d0 -fore [lindex $diffcolors 0]
735 $ctext tag conf d1 -fore [lindex $diffcolors 1]
736 $ctext tag conf m0 -fore red
737 $ctext tag conf m1 -fore blue
738 $ctext tag conf m2 -fore green
739 $ctext tag conf m3 -fore purple
740 $ctext tag conf m4 -fore brown
741 $ctext tag conf m5 -fore "#009090"
742 $ctext tag conf m6 -fore magenta
743 $ctext tag conf m7 -fore "#808000"
744 $ctext tag conf m8 -fore "#009000"
745 $ctext tag conf m9 -fore "#ff0080"
746 $ctext tag conf m10 -fore cyan
747 $ctext tag conf m11 -fore "#b07070"
748 $ctext tag conf m12 -fore "#70b0f0"
749 $ctext tag conf m13 -fore "#70f0b0"
750 $ctext tag conf m14 -fore "#f0b070"
751 $ctext tag conf m15 -fore "#ff70b0"
752 $ctext tag conf mmax -fore darkgrey
753 set mergemax 16
754 $ctext tag conf mresult -font [concat $textfont bold]
755 $ctext tag conf msep -font [concat $textfont bold]
756 $ctext tag conf found -back yellow
758 .pwbottom add .bleft
759 .pwbottom paneconfigure .bleft -width $geometry(botwidth)
761 # lower right
762 frame .bright
763 frame .bright.mode
764 radiobutton .bright.mode.patch -text "Patch" \
765 -command reselectline -variable cmitmode -value "patch"
766 .bright.mode.patch configure -font $uifont
767 radiobutton .bright.mode.tree -text "Tree" \
768 -command reselectline -variable cmitmode -value "tree"
769 .bright.mode.tree configure -font $uifont
770 grid .bright.mode.patch .bright.mode.tree -sticky ew
771 pack .bright.mode -side top -fill x
772 set cflist .bright.cfiles
773 set indent [font measure $mainfont "nn"]
774 text $cflist \
775 -selectbackground $selectbgcolor \
776 -background $bgcolor -foreground $fgcolor \
777 -font $mainfont \
778 -tabs [list $indent [expr {2 * $indent}]] \
779 -yscrollcommand ".bright.sb set" \
780 -cursor [. cget -cursor] \
781 -spacing1 1 -spacing3 1
782 lappend bglist $cflist
783 lappend fglist $cflist
784 scrollbar .bright.sb -command "$cflist yview"
785 pack .bright.sb -side right -fill y
786 pack $cflist -side left -fill both -expand 1
787 $cflist tag configure highlight \
788 -background [$cflist cget -selectbackground]
789 $cflist tag configure bold -font [concat $mainfont bold]
791 .pwbottom add .bright
792 .ctop add .pwbottom
794 # restore window position if known
795 if {[info exists geometry(main)]} {
796 wm geometry . "$geometry(main)"
799 bind .pwbottom <Configure> {resizecdetpanes %W %w}
800 pack .ctop -fill both -expand 1
801 bindall <1> {selcanvline %W %x %y}
802 #bindall <B1-Motion> {selcanvline %W %x %y}
803 bindall <ButtonRelease-4> "allcanvs yview scroll -5 units"
804 bindall <ButtonRelease-5> "allcanvs yview scroll 5 units"
805 bindall <2> "canvscan mark %W %x %y"
806 bindall <B2-Motion> "canvscan dragto %W %x %y"
807 bindkey <Home> selfirstline
808 bindkey <End> sellastline
809 bind . <Key-Up> "selnextline -1"
810 bind . <Key-Down> "selnextline 1"
811 bind . <Shift-Key-Up> "next_highlight -1"
812 bind . <Shift-Key-Down> "next_highlight 1"
813 bindkey <Key-Right> "goforw"
814 bindkey <Key-Left> "goback"
815 bind . <Key-Prior> "selnextpage -1"
816 bind . <Key-Next> "selnextpage 1"
817 bind . <Control-Home> "allcanvs yview moveto 0.0"
818 bind . <Control-End> "allcanvs yview moveto 1.0"
819 bind . <Control-Key-Up> "allcanvs yview scroll -1 units"
820 bind . <Control-Key-Down> "allcanvs yview scroll 1 units"
821 bind . <Control-Key-Prior> "allcanvs yview scroll -1 pages"
822 bind . <Control-Key-Next> "allcanvs yview scroll 1 pages"
823 bindkey <Key-Delete> "$ctext yview scroll -1 pages"
824 bindkey <Key-BackSpace> "$ctext yview scroll -1 pages"
825 bindkey <Key-space> "$ctext yview scroll 1 pages"
826 bindkey p "selnextline -1"
827 bindkey n "selnextline 1"
828 bindkey z "goback"
829 bindkey x "goforw"
830 bindkey i "selnextline -1"
831 bindkey k "selnextline 1"
832 bindkey j "goback"
833 bindkey l "goforw"
834 bindkey b "$ctext yview scroll -1 pages"
835 bindkey d "$ctext yview scroll 18 units"
836 bindkey u "$ctext yview scroll -18 units"
837 bindkey / {findnext 1}
838 bindkey <Key-Return> {findnext 0}
839 bindkey ? findprev
840 bindkey f nextfile
841 bindkey <F5> updatecommits
842 bind . <Control-q> doquit
843 bind . <Control-f> dofind
844 bind . <Control-g> {findnext 0}
845 bind . <Control-r> dosearchback
846 bind . <Control-s> dosearch
847 bind . <Control-equal> {incrfont 1}
848 bind . <Control-KP_Add> {incrfont 1}
849 bind . <Control-minus> {incrfont -1}
850 bind . <Control-KP_Subtract> {incrfont -1}
851 wm protocol . WM_DELETE_WINDOW doquit
852 bind . <Button-1> "click %W"
853 bind $fstring <Key-Return> dofind
854 bind $sha1entry <Key-Return> gotocommit
855 bind $sha1entry <<PasteSelection>> clearsha1
856 bind $cflist <1> {sel_flist %W %x %y; break}
857 bind $cflist <B1-Motion> {sel_flist %W %x %y; break}
858 bind $cflist <ButtonRelease-1> {treeclick %W %x %y}
860 set maincursor [. cget -cursor]
861 set textcursor [$ctext cget -cursor]
862 set curtextcursor $textcursor
864 set rowctxmenu .rowctxmenu
865 menu $rowctxmenu -tearoff 0
866 $rowctxmenu add command -label "Diff this -> selected" \
867 -command {diffvssel 0}
868 $rowctxmenu add command -label "Diff selected -> this" \
869 -command {diffvssel 1}
870 $rowctxmenu add command -label "Make patch" -command mkpatch
871 $rowctxmenu add command -label "Create tag" -command mktag
872 $rowctxmenu add command -label "Write commit to file" -command writecommit
873 $rowctxmenu add command -label "Create new branch" -command mkbranch
874 $rowctxmenu add command -label "Cherry-pick this commit" \
875 -command cherrypick
876 $rowctxmenu add command -label "Reset HEAD branch to here" \
877 -command resethead
879 set fakerowmenu .fakerowmenu
880 menu $fakerowmenu -tearoff 0
881 $fakerowmenu add command -label "Diff this -> selected" \
882 -command {diffvssel 0}
883 $fakerowmenu add command -label "Diff selected -> this" \
884 -command {diffvssel 1}
885 $fakerowmenu add command -label "Make patch" -command mkpatch
886 # $fakerowmenu add command -label "Commit" -command {mkcommit 0}
887 # $fakerowmenu add command -label "Commit all" -command {mkcommit 1}
888 # $fakerowmenu add command -label "Revert local changes" -command revertlocal
890 set headctxmenu .headctxmenu
891 menu $headctxmenu -tearoff 0
892 $headctxmenu add command -label "Check out this branch" \
893 -command cobranch
894 $headctxmenu add command -label "Remove this branch" \
895 -command rmbranch
898 # mouse-2 makes all windows scan vertically, but only the one
899 # the cursor is in scans horizontally
900 proc canvscan {op w x y} {
901 global canv canv2 canv3
902 foreach c [list $canv $canv2 $canv3] {
903 if {$c == $w} {
904 $c scan $op $x $y
905 } else {
906 $c scan $op 0 $y
911 proc scrollcanv {cscroll f0 f1} {
912 $cscroll set $f0 $f1
913 drawfrac $f0 $f1
914 flushhighlights
917 # when we make a key binding for the toplevel, make sure
918 # it doesn't get triggered when that key is pressed in the
919 # find string entry widget.
920 proc bindkey {ev script} {
921 global entries
922 bind . $ev $script
923 set escript [bind Entry $ev]
924 if {$escript == {}} {
925 set escript [bind Entry <Key>]
927 foreach e $entries {
928 bind $e $ev "$escript; break"
932 # set the focus back to the toplevel for any click outside
933 # the entry widgets
934 proc click {w} {
935 global entries
936 foreach e $entries {
937 if {$w == $e} return
939 focus .
942 proc savestuff {w} {
943 global canv canv2 canv3 ctext cflist mainfont textfont uifont tabstop
944 global stuffsaved findmergefiles maxgraphpct
945 global maxwidth showneartags showlocalchanges
946 global viewname viewfiles viewargs viewperm nextviewnum
947 global cmitmode wrapcomment
948 global colors bgcolor fgcolor diffcolors selectbgcolor
950 if {$stuffsaved} return
951 if {![winfo viewable .]} return
952 catch {
953 set f [open "~/.gitk-new" w]
954 puts $f [list set mainfont $mainfont]
955 puts $f [list set textfont $textfont]
956 puts $f [list set uifont $uifont]
957 puts $f [list set tabstop $tabstop]
958 puts $f [list set findmergefiles $findmergefiles]
959 puts $f [list set maxgraphpct $maxgraphpct]
960 puts $f [list set maxwidth $maxwidth]
961 puts $f [list set cmitmode $cmitmode]
962 puts $f [list set wrapcomment $wrapcomment]
963 puts $f [list set showneartags $showneartags]
964 puts $f [list set showlocalchanges $showlocalchanges]
965 puts $f [list set bgcolor $bgcolor]
966 puts $f [list set fgcolor $fgcolor]
967 puts $f [list set colors $colors]
968 puts $f [list set diffcolors $diffcolors]
969 puts $f [list set selectbgcolor $selectbgcolor]
971 puts $f "set geometry(main) [wm geometry .]"
972 puts $f "set geometry(topwidth) [winfo width .tf]"
973 puts $f "set geometry(topheight) [winfo height .tf]"
974 puts $f "set geometry(pwsash0) \"[.tf.histframe.pwclist sash coord 0]\""
975 puts $f "set geometry(pwsash1) \"[.tf.histframe.pwclist sash coord 1]\""
976 puts $f "set geometry(botwidth) [winfo width .bleft]"
977 puts $f "set geometry(botheight) [winfo height .bleft]"
979 puts -nonewline $f "set permviews {"
980 for {set v 0} {$v < $nextviewnum} {incr v} {
981 if {$viewperm($v)} {
982 puts $f "{[list $viewname($v) $viewfiles($v) $viewargs($v)]}"
985 puts $f "}"
986 close $f
987 file rename -force "~/.gitk-new" "~/.gitk"
989 set stuffsaved 1
992 proc resizeclistpanes {win w} {
993 global oldwidth
994 if {[info exists oldwidth($win)]} {
995 set s0 [$win sash coord 0]
996 set s1 [$win sash coord 1]
997 if {$w < 60} {
998 set sash0 [expr {int($w/2 - 2)}]
999 set sash1 [expr {int($w*5/6 - 2)}]
1000 } else {
1001 set factor [expr {1.0 * $w / $oldwidth($win)}]
1002 set sash0 [expr {int($factor * [lindex $s0 0])}]
1003 set sash1 [expr {int($factor * [lindex $s1 0])}]
1004 if {$sash0 < 30} {
1005 set sash0 30
1007 if {$sash1 < $sash0 + 20} {
1008 set sash1 [expr {$sash0 + 20}]
1010 if {$sash1 > $w - 10} {
1011 set sash1 [expr {$w - 10}]
1012 if {$sash0 > $sash1 - 20} {
1013 set sash0 [expr {$sash1 - 20}]
1017 $win sash place 0 $sash0 [lindex $s0 1]
1018 $win sash place 1 $sash1 [lindex $s1 1]
1020 set oldwidth($win) $w
1023 proc resizecdetpanes {win w} {
1024 global oldwidth
1025 if {[info exists oldwidth($win)]} {
1026 set s0 [$win sash coord 0]
1027 if {$w < 60} {
1028 set sash0 [expr {int($w*3/4 - 2)}]
1029 } else {
1030 set factor [expr {1.0 * $w / $oldwidth($win)}]
1031 set sash0 [expr {int($factor * [lindex $s0 0])}]
1032 if {$sash0 < 45} {
1033 set sash0 45
1035 if {$sash0 > $w - 15} {
1036 set sash0 [expr {$w - 15}]
1039 $win sash place 0 $sash0 [lindex $s0 1]
1041 set oldwidth($win) $w
1044 proc allcanvs args {
1045 global canv canv2 canv3
1046 eval $canv $args
1047 eval $canv2 $args
1048 eval $canv3 $args
1051 proc bindall {event action} {
1052 global canv canv2 canv3
1053 bind $canv $event $action
1054 bind $canv2 $event $action
1055 bind $canv3 $event $action
1058 proc about {} {
1059 global uifont
1060 set w .about
1061 if {[winfo exists $w]} {
1062 raise $w
1063 return
1065 toplevel $w
1066 wm title $w "About gitk"
1067 message $w.m -text {
1068 Gitk - a commit viewer for git
1070 Copyright © 2005-2006 Paul Mackerras
1072 Use and redistribute under the terms of the GNU General Public License} \
1073 -justify center -aspect 400 -border 2 -bg white -relief groove
1074 pack $w.m -side top -fill x -padx 2 -pady 2
1075 $w.m configure -font $uifont
1076 button $w.ok -text Close -command "destroy $w" -default active
1077 pack $w.ok -side bottom
1078 $w.ok configure -font $uifont
1079 bind $w <Visibility> "focus $w.ok"
1080 bind $w <Key-Escape> "destroy $w"
1081 bind $w <Key-Return> "destroy $w"
1084 proc keys {} {
1085 global uifont
1086 set w .keys
1087 if {[winfo exists $w]} {
1088 raise $w
1089 return
1091 toplevel $w
1092 wm title $w "Gitk key bindings"
1093 message $w.m -text {
1094 Gitk key bindings:
1096 <Ctrl-Q> Quit
1097 <Home> Move to first commit
1098 <End> Move to last commit
1099 <Up>, p, i Move up one commit
1100 <Down>, n, k Move down one commit
1101 <Left>, z, j Go back in history list
1102 <Right>, x, l Go forward in history list
1103 <PageUp> Move up one page in commit list
1104 <PageDown> Move down one page in commit list
1105 <Ctrl-Home> Scroll to top of commit list
1106 <Ctrl-End> Scroll to bottom of commit list
1107 <Ctrl-Up> Scroll commit list up one line
1108 <Ctrl-Down> Scroll commit list down one line
1109 <Ctrl-PageUp> Scroll commit list up one page
1110 <Ctrl-PageDown> Scroll commit list down one page
1111 <Shift-Up> Move to previous highlighted line
1112 <Shift-Down> Move to next highlighted line
1113 <Delete>, b Scroll diff view up one page
1114 <Backspace> Scroll diff view up one page
1115 <Space> Scroll diff view down one page
1116 u Scroll diff view up 18 lines
1117 d Scroll diff view down 18 lines
1118 <Ctrl-F> Find
1119 <Ctrl-G> Move to next find hit
1120 <Return> Move to next find hit
1121 / Move to next find hit, or redo find
1122 ? Move to previous find hit
1123 f Scroll diff view to next file
1124 <Ctrl-S> Search for next hit in diff view
1125 <Ctrl-R> Search for previous hit in diff view
1126 <Ctrl-KP+> Increase font size
1127 <Ctrl-plus> Increase font size
1128 <Ctrl-KP-> Decrease font size
1129 <Ctrl-minus> Decrease font size
1130 <F5> Update
1132 -justify left -bg white -border 2 -relief groove
1133 pack $w.m -side top -fill both -padx 2 -pady 2
1134 $w.m configure -font $uifont
1135 button $w.ok -text Close -command "destroy $w" -default active
1136 pack $w.ok -side bottom
1137 $w.ok configure -font $uifont
1138 bind $w <Visibility> "focus $w.ok"
1139 bind $w <Key-Escape> "destroy $w"
1140 bind $w <Key-Return> "destroy $w"
1143 # Procedures for manipulating the file list window at the
1144 # bottom right of the overall window.
1146 proc treeview {w l openlevs} {
1147 global treecontents treediropen treeheight treeparent treeindex
1149 set ix 0
1150 set treeindex() 0
1151 set lev 0
1152 set prefix {}
1153 set prefixend -1
1154 set prefendstack {}
1155 set htstack {}
1156 set ht 0
1157 set treecontents() {}
1158 $w conf -state normal
1159 foreach f $l {
1160 while {[string range $f 0 $prefixend] ne $prefix} {
1161 if {$lev <= $openlevs} {
1162 $w mark set e:$treeindex($prefix) "end -1c"
1163 $w mark gravity e:$treeindex($prefix) left
1165 set treeheight($prefix) $ht
1166 incr ht [lindex $htstack end]
1167 set htstack [lreplace $htstack end end]
1168 set prefixend [lindex $prefendstack end]
1169 set prefendstack [lreplace $prefendstack end end]
1170 set prefix [string range $prefix 0 $prefixend]
1171 incr lev -1
1173 set tail [string range $f [expr {$prefixend+1}] end]
1174 while {[set slash [string first "/" $tail]] >= 0} {
1175 lappend htstack $ht
1176 set ht 0
1177 lappend prefendstack $prefixend
1178 incr prefixend [expr {$slash + 1}]
1179 set d [string range $tail 0 $slash]
1180 lappend treecontents($prefix) $d
1181 set oldprefix $prefix
1182 append prefix $d
1183 set treecontents($prefix) {}
1184 set treeindex($prefix) [incr ix]
1185 set treeparent($prefix) $oldprefix
1186 set tail [string range $tail [expr {$slash+1}] end]
1187 if {$lev <= $openlevs} {
1188 set ht 1
1189 set treediropen($prefix) [expr {$lev < $openlevs}]
1190 set bm [expr {$lev == $openlevs? "tri-rt": "tri-dn"}]
1191 $w mark set d:$ix "end -1c"
1192 $w mark gravity d:$ix left
1193 set str "\n"
1194 for {set i 0} {$i < $lev} {incr i} {append str "\t"}
1195 $w insert end $str
1196 $w image create end -align center -image $bm -padx 1 \
1197 -name a:$ix
1198 $w insert end $d [highlight_tag $prefix]
1199 $w mark set s:$ix "end -1c"
1200 $w mark gravity s:$ix left
1202 incr lev
1204 if {$tail ne {}} {
1205 if {$lev <= $openlevs} {
1206 incr ht
1207 set str "\n"
1208 for {set i 0} {$i < $lev} {incr i} {append str "\t"}
1209 $w insert end $str
1210 $w insert end $tail [highlight_tag $f]
1212 lappend treecontents($prefix) $tail
1215 while {$htstack ne {}} {
1216 set treeheight($prefix) $ht
1217 incr ht [lindex $htstack end]
1218 set htstack [lreplace $htstack end end]
1220 $w conf -state disabled
1223 proc linetoelt {l} {
1224 global treeheight treecontents
1226 set y 2
1227 set prefix {}
1228 while {1} {
1229 foreach e $treecontents($prefix) {
1230 if {$y == $l} {
1231 return "$prefix$e"
1233 set n 1
1234 if {[string index $e end] eq "/"} {
1235 set n $treeheight($prefix$e)
1236 if {$y + $n > $l} {
1237 append prefix $e
1238 incr y
1239 break
1242 incr y $n
1247 proc highlight_tree {y prefix} {
1248 global treeheight treecontents cflist
1250 foreach e $treecontents($prefix) {
1251 set path $prefix$e
1252 if {[highlight_tag $path] ne {}} {
1253 $cflist tag add bold $y.0 "$y.0 lineend"
1255 incr y
1256 if {[string index $e end] eq "/" && $treeheight($path) > 1} {
1257 set y [highlight_tree $y $path]
1260 return $y
1263 proc treeclosedir {w dir} {
1264 global treediropen treeheight treeparent treeindex
1266 set ix $treeindex($dir)
1267 $w conf -state normal
1268 $w delete s:$ix e:$ix
1269 set treediropen($dir) 0
1270 $w image configure a:$ix -image tri-rt
1271 $w conf -state disabled
1272 set n [expr {1 - $treeheight($dir)}]
1273 while {$dir ne {}} {
1274 incr treeheight($dir) $n
1275 set dir $treeparent($dir)
1279 proc treeopendir {w dir} {
1280 global treediropen treeheight treeparent treecontents treeindex
1282 set ix $treeindex($dir)
1283 $w conf -state normal
1284 $w image configure a:$ix -image tri-dn
1285 $w mark set e:$ix s:$ix
1286 $w mark gravity e:$ix right
1287 set lev 0
1288 set str "\n"
1289 set n [llength $treecontents($dir)]
1290 for {set x $dir} {$x ne {}} {set x $treeparent($x)} {
1291 incr lev
1292 append str "\t"
1293 incr treeheight($x) $n
1295 foreach e $treecontents($dir) {
1296 set de $dir$e
1297 if {[string index $e end] eq "/"} {
1298 set iy $treeindex($de)
1299 $w mark set d:$iy e:$ix
1300 $w mark gravity d:$iy left
1301 $w insert e:$ix $str
1302 set treediropen($de) 0
1303 $w image create e:$ix -align center -image tri-rt -padx 1 \
1304 -name a:$iy
1305 $w insert e:$ix $e [highlight_tag $de]
1306 $w mark set s:$iy e:$ix
1307 $w mark gravity s:$iy left
1308 set treeheight($de) 1
1309 } else {
1310 $w insert e:$ix $str
1311 $w insert e:$ix $e [highlight_tag $de]
1314 $w mark gravity e:$ix left
1315 $w conf -state disabled
1316 set treediropen($dir) 1
1317 set top [lindex [split [$w index @0,0] .] 0]
1318 set ht [$w cget -height]
1319 set l [lindex [split [$w index s:$ix] .] 0]
1320 if {$l < $top} {
1321 $w yview $l.0
1322 } elseif {$l + $n + 1 > $top + $ht} {
1323 set top [expr {$l + $n + 2 - $ht}]
1324 if {$l < $top} {
1325 set top $l
1327 $w yview $top.0
1331 proc treeclick {w x y} {
1332 global treediropen cmitmode ctext cflist cflist_top
1334 if {$cmitmode ne "tree"} return
1335 if {![info exists cflist_top]} return
1336 set l [lindex [split [$w index "@$x,$y"] "."] 0]
1337 $cflist tag remove highlight $cflist_top.0 "$cflist_top.0 lineend"
1338 $cflist tag add highlight $l.0 "$l.0 lineend"
1339 set cflist_top $l
1340 if {$l == 1} {
1341 $ctext yview 1.0
1342 return
1344 set e [linetoelt $l]
1345 if {[string index $e end] ne "/"} {
1346 showfile $e
1347 } elseif {$treediropen($e)} {
1348 treeclosedir $w $e
1349 } else {
1350 treeopendir $w $e
1354 proc setfilelist {id} {
1355 global treefilelist cflist
1357 treeview $cflist $treefilelist($id) 0
1360 image create bitmap tri-rt -background black -foreground blue -data {
1361 #define tri-rt_width 13
1362 #define tri-rt_height 13
1363 static unsigned char tri-rt_bits[] = {
1364 0x00, 0x00, 0x00, 0x00, 0x10, 0x00, 0x30, 0x00, 0x70, 0x00, 0xf0, 0x00,
1365 0xf0, 0x01, 0xf0, 0x00, 0x70, 0x00, 0x30, 0x00, 0x10, 0x00, 0x00, 0x00,
1366 0x00, 0x00};
1367 } -maskdata {
1368 #define tri-rt-mask_width 13
1369 #define tri-rt-mask_height 13
1370 static unsigned char tri-rt-mask_bits[] = {
1371 0x08, 0x00, 0x18, 0x00, 0x38, 0x00, 0x78, 0x00, 0xf8, 0x00, 0xf8, 0x01,
1372 0xf8, 0x03, 0xf8, 0x01, 0xf8, 0x00, 0x78, 0x00, 0x38, 0x00, 0x18, 0x00,
1373 0x08, 0x00};
1375 image create bitmap tri-dn -background black -foreground blue -data {
1376 #define tri-dn_width 13
1377 #define tri-dn_height 13
1378 static unsigned char tri-dn_bits[] = {
1379 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0xfc, 0x07, 0xf8, 0x03,
1380 0xf0, 0x01, 0xe0, 0x00, 0x40, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
1381 0x00, 0x00};
1382 } -maskdata {
1383 #define tri-dn-mask_width 13
1384 #define tri-dn-mask_height 13
1385 static unsigned char tri-dn-mask_bits[] = {
1386 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0xff, 0x1f, 0xfe, 0x0f, 0xfc, 0x07,
1387 0xf8, 0x03, 0xf0, 0x01, 0xe0, 0x00, 0x40, 0x00, 0x00, 0x00, 0x00, 0x00,
1388 0x00, 0x00};
1391 proc init_flist {first} {
1392 global cflist cflist_top selectedline difffilestart
1394 $cflist conf -state normal
1395 $cflist delete 0.0 end
1396 if {$first ne {}} {
1397 $cflist insert end $first
1398 set cflist_top 1
1399 $cflist tag add highlight 1.0 "1.0 lineend"
1400 } else {
1401 catch {unset cflist_top}
1403 $cflist conf -state disabled
1404 set difffilestart {}
1407 proc highlight_tag {f} {
1408 global highlight_paths
1410 foreach p $highlight_paths {
1411 if {[string match $p $f]} {
1412 return "bold"
1415 return {}
1418 proc highlight_filelist {} {
1419 global cmitmode cflist
1421 $cflist conf -state normal
1422 if {$cmitmode ne "tree"} {
1423 set end [lindex [split [$cflist index end] .] 0]
1424 for {set l 2} {$l < $end} {incr l} {
1425 set line [$cflist get $l.0 "$l.0 lineend"]
1426 if {[highlight_tag $line] ne {}} {
1427 $cflist tag add bold $l.0 "$l.0 lineend"
1430 } else {
1431 highlight_tree 2 {}
1433 $cflist conf -state disabled
1436 proc unhighlight_filelist {} {
1437 global cflist
1439 $cflist conf -state normal
1440 $cflist tag remove bold 1.0 end
1441 $cflist conf -state disabled
1444 proc add_flist {fl} {
1445 global cflist
1447 $cflist conf -state normal
1448 foreach f $fl {
1449 $cflist insert end "\n"
1450 $cflist insert end $f [highlight_tag $f]
1452 $cflist conf -state disabled
1455 proc sel_flist {w x y} {
1456 global ctext difffilestart cflist cflist_top cmitmode
1458 if {$cmitmode eq "tree"} return
1459 if {![info exists cflist_top]} return
1460 set l [lindex [split [$w index "@$x,$y"] "."] 0]
1461 $cflist tag remove highlight $cflist_top.0 "$cflist_top.0 lineend"
1462 $cflist tag add highlight $l.0 "$l.0 lineend"
1463 set cflist_top $l
1464 if {$l == 1} {
1465 $ctext yview 1.0
1466 } else {
1467 catch {$ctext yview [lindex $difffilestart [expr {$l - 2}]]}
1471 # Functions for adding and removing shell-type quoting
1473 proc shellquote {str} {
1474 if {![string match "*\['\"\\ \t]*" $str]} {
1475 return $str
1477 if {![string match "*\['\"\\]*" $str]} {
1478 return "\"$str\""
1480 if {![string match "*'*" $str]} {
1481 return "'$str'"
1483 return "\"[string map {\" \\\" \\ \\\\} $str]\""
1486 proc shellarglist {l} {
1487 set str {}
1488 foreach a $l {
1489 if {$str ne {}} {
1490 append str " "
1492 append str [shellquote $a]
1494 return $str
1497 proc shelldequote {str} {
1498 set ret {}
1499 set used -1
1500 while {1} {
1501 incr used
1502 if {![regexp -start $used -indices "\['\"\\\\ \t]" $str first]} {
1503 append ret [string range $str $used end]
1504 set used [string length $str]
1505 break
1507 set first [lindex $first 0]
1508 set ch [string index $str $first]
1509 if {$first > $used} {
1510 append ret [string range $str $used [expr {$first - 1}]]
1511 set used $first
1513 if {$ch eq " " || $ch eq "\t"} break
1514 incr used
1515 if {$ch eq "'"} {
1516 set first [string first "'" $str $used]
1517 if {$first < 0} {
1518 error "unmatched single-quote"
1520 append ret [string range $str $used [expr {$first - 1}]]
1521 set used $first
1522 continue
1524 if {$ch eq "\\"} {
1525 if {$used >= [string length $str]} {
1526 error "trailing backslash"
1528 append ret [string index $str $used]
1529 continue
1531 # here ch == "\""
1532 while {1} {
1533 if {![regexp -start $used -indices "\[\"\\\\]" $str first]} {
1534 error "unmatched double-quote"
1536 set first [lindex $first 0]
1537 set ch [string index $str $first]
1538 if {$first > $used} {
1539 append ret [string range $str $used [expr {$first - 1}]]
1540 set used $first
1542 if {$ch eq "\""} break
1543 incr used
1544 append ret [string index $str $used]
1545 incr used
1548 return [list $used $ret]
1551 proc shellsplit {str} {
1552 set l {}
1553 while {1} {
1554 set str [string trimleft $str]
1555 if {$str eq {}} break
1556 set dq [shelldequote $str]
1557 set n [lindex $dq 0]
1558 set word [lindex $dq 1]
1559 set str [string range $str $n end]
1560 lappend l $word
1562 return $l
1565 # Code to implement multiple views
1567 proc newview {ishighlight} {
1568 global nextviewnum newviewname newviewperm uifont newishighlight
1569 global newviewargs revtreeargs
1571 set newishighlight $ishighlight
1572 set top .gitkview
1573 if {[winfo exists $top]} {
1574 raise $top
1575 return
1577 set newviewname($nextviewnum) "View $nextviewnum"
1578 set newviewperm($nextviewnum) 0
1579 set newviewargs($nextviewnum) [shellarglist $revtreeargs]
1580 vieweditor $top $nextviewnum "Gitk view definition"
1583 proc editview {} {
1584 global curview
1585 global viewname viewperm newviewname newviewperm
1586 global viewargs newviewargs
1588 set top .gitkvedit-$curview
1589 if {[winfo exists $top]} {
1590 raise $top
1591 return
1593 set newviewname($curview) $viewname($curview)
1594 set newviewperm($curview) $viewperm($curview)
1595 set newviewargs($curview) [shellarglist $viewargs($curview)]
1596 vieweditor $top $curview "Gitk: edit view $viewname($curview)"
1599 proc vieweditor {top n title} {
1600 global newviewname newviewperm viewfiles
1601 global uifont
1603 toplevel $top
1604 wm title $top $title
1605 label $top.nl -text "Name" -font $uifont
1606 entry $top.name -width 20 -textvariable newviewname($n) -font $uifont
1607 grid $top.nl $top.name -sticky w -pady 5
1608 checkbutton $top.perm -text "Remember this view" -variable newviewperm($n) \
1609 -font $uifont
1610 grid $top.perm - -pady 5 -sticky w
1611 message $top.al -aspect 1000 -font $uifont \
1612 -text "Commits to include (arguments to git rev-list):"
1613 grid $top.al - -sticky w -pady 5
1614 entry $top.args -width 50 -textvariable newviewargs($n) \
1615 -background white -font $uifont
1616 grid $top.args - -sticky ew -padx 5
1617 message $top.l -aspect 1000 -font $uifont \
1618 -text "Enter files and directories to include, one per line:"
1619 grid $top.l - -sticky w
1620 text $top.t -width 40 -height 10 -background white -font $uifont
1621 if {[info exists viewfiles($n)]} {
1622 foreach f $viewfiles($n) {
1623 $top.t insert end $f
1624 $top.t insert end "\n"
1626 $top.t delete {end - 1c} end
1627 $top.t mark set insert 0.0
1629 grid $top.t - -sticky ew -padx 5
1630 frame $top.buts
1631 button $top.buts.ok -text "OK" -command [list newviewok $top $n] \
1632 -font $uifont
1633 button $top.buts.can -text "Cancel" -command [list destroy $top] \
1634 -font $uifont
1635 grid $top.buts.ok $top.buts.can
1636 grid columnconfigure $top.buts 0 -weight 1 -uniform a
1637 grid columnconfigure $top.buts 1 -weight 1 -uniform a
1638 grid $top.buts - -pady 10 -sticky ew
1639 focus $top.t
1642 proc doviewmenu {m first cmd op argv} {
1643 set nmenu [$m index end]
1644 for {set i $first} {$i <= $nmenu} {incr i} {
1645 if {[$m entrycget $i -command] eq $cmd} {
1646 eval $m $op $i $argv
1647 break
1652 proc allviewmenus {n op args} {
1653 global viewhlmenu
1655 doviewmenu .bar.view 5 [list showview $n] $op $args
1656 doviewmenu $viewhlmenu 1 [list addvhighlight $n] $op $args
1659 proc newviewok {top n} {
1660 global nextviewnum newviewperm newviewname newishighlight
1661 global viewname viewfiles viewperm selectedview curview
1662 global viewargs newviewargs viewhlmenu
1664 if {[catch {
1665 set newargs [shellsplit $newviewargs($n)]
1666 } err]} {
1667 error_popup "Error in commit selection arguments: $err"
1668 wm raise $top
1669 focus $top
1670 return
1672 set files {}
1673 foreach f [split [$top.t get 0.0 end] "\n"] {
1674 set ft [string trim $f]
1675 if {$ft ne {}} {
1676 lappend files $ft
1679 if {![info exists viewfiles($n)]} {
1680 # creating a new view
1681 incr nextviewnum
1682 set viewname($n) $newviewname($n)
1683 set viewperm($n) $newviewperm($n)
1684 set viewfiles($n) $files
1685 set viewargs($n) $newargs
1686 addviewmenu $n
1687 if {!$newishighlight} {
1688 run showview $n
1689 } else {
1690 run addvhighlight $n
1692 } else {
1693 # editing an existing view
1694 set viewperm($n) $newviewperm($n)
1695 if {$newviewname($n) ne $viewname($n)} {
1696 set viewname($n) $newviewname($n)
1697 doviewmenu .bar.view 5 [list showview $n] \
1698 entryconf [list -label $viewname($n)]
1699 doviewmenu $viewhlmenu 1 [list addvhighlight $n] \
1700 entryconf [list -label $viewname($n) -value $viewname($n)]
1702 if {$files ne $viewfiles($n) || $newargs ne $viewargs($n)} {
1703 set viewfiles($n) $files
1704 set viewargs($n) $newargs
1705 if {$curview == $n} {
1706 run updatecommits
1710 catch {destroy $top}
1713 proc delview {} {
1714 global curview viewdata viewperm hlview selectedhlview
1716 if {$curview == 0} return
1717 if {[info exists hlview] && $hlview == $curview} {
1718 set selectedhlview None
1719 unset hlview
1721 allviewmenus $curview delete
1722 set viewdata($curview) {}
1723 set viewperm($curview) 0
1724 showview 0
1727 proc addviewmenu {n} {
1728 global viewname viewhlmenu
1730 .bar.view add radiobutton -label $viewname($n) \
1731 -command [list showview $n] -variable selectedview -value $n
1732 $viewhlmenu add radiobutton -label $viewname($n) \
1733 -command [list addvhighlight $n] -variable selectedhlview
1736 proc flatten {var} {
1737 global $var
1739 set ret {}
1740 foreach i [array names $var] {
1741 lappend ret $i [set $var\($i\)]
1743 return $ret
1746 proc unflatten {var l} {
1747 global $var
1749 catch {unset $var}
1750 foreach {i v} $l {
1751 set $var\($i\) $v
1755 proc showview {n} {
1756 global curview viewdata viewfiles
1757 global displayorder parentlist rowidlist rowoffsets
1758 global colormap rowtextx commitrow nextcolor canvxmax
1759 global numcommits rowrangelist commitlisted idrowranges rowchk
1760 global selectedline currentid canv canvy0
1761 global treediffs
1762 global pending_select phase
1763 global commitidx rowlaidout rowoptim
1764 global commfd
1765 global selectedview selectfirst
1766 global vparentlist vdisporder vcmitlisted
1767 global hlview selectedhlview
1769 if {$n == $curview} return
1770 set selid {}
1771 if {[info exists selectedline]} {
1772 set selid $currentid
1773 set y [yc $selectedline]
1774 set ymax [lindex [$canv cget -scrollregion] 3]
1775 set span [$canv yview]
1776 set ytop [expr {[lindex $span 0] * $ymax}]
1777 set ybot [expr {[lindex $span 1] * $ymax}]
1778 if {$ytop < $y && $y < $ybot} {
1779 set yscreen [expr {$y - $ytop}]
1780 } else {
1781 set yscreen [expr {($ybot - $ytop) / 2}]
1783 } elseif {[info exists pending_select]} {
1784 set selid $pending_select
1785 unset pending_select
1787 unselectline
1788 normalline
1789 if {$curview >= 0} {
1790 set vparentlist($curview) $parentlist
1791 set vdisporder($curview) $displayorder
1792 set vcmitlisted($curview) $commitlisted
1793 if {$phase ne {}} {
1794 set viewdata($curview) \
1795 [list $phase $rowidlist $rowoffsets $rowrangelist \
1796 [flatten idrowranges] [flatten idinlist] \
1797 $rowlaidout $rowoptim $numcommits]
1798 } elseif {![info exists viewdata($curview)]
1799 || [lindex $viewdata($curview) 0] ne {}} {
1800 set viewdata($curview) \
1801 [list {} $rowidlist $rowoffsets $rowrangelist]
1804 catch {unset treediffs}
1805 clear_display
1806 if {[info exists hlview] && $hlview == $n} {
1807 unset hlview
1808 set selectedhlview None
1811 set curview $n
1812 set selectedview $n
1813 .bar.view entryconf Edit* -state [expr {$n == 0? "disabled": "normal"}]
1814 .bar.view entryconf Delete* -state [expr {$n == 0? "disabled": "normal"}]
1816 if {![info exists viewdata($n)]} {
1817 if {$selid ne {}} {
1818 set pending_select $selid
1820 getcommits
1821 return
1824 set v $viewdata($n)
1825 set phase [lindex $v 0]
1826 set displayorder $vdisporder($n)
1827 set parentlist $vparentlist($n)
1828 set commitlisted $vcmitlisted($n)
1829 set rowidlist [lindex $v 1]
1830 set rowoffsets [lindex $v 2]
1831 set rowrangelist [lindex $v 3]
1832 if {$phase eq {}} {
1833 set numcommits [llength $displayorder]
1834 catch {unset idrowranges}
1835 } else {
1836 unflatten idrowranges [lindex $v 4]
1837 unflatten idinlist [lindex $v 5]
1838 set rowlaidout [lindex $v 6]
1839 set rowoptim [lindex $v 7]
1840 set numcommits [lindex $v 8]
1841 catch {unset rowchk}
1844 catch {unset colormap}
1845 catch {unset rowtextx}
1846 set nextcolor 0
1847 set canvxmax [$canv cget -width]
1848 set curview $n
1849 set row 0
1850 setcanvscroll
1851 set yf 0
1852 set row {}
1853 set selectfirst 0
1854 if {$selid ne {} && [info exists commitrow($n,$selid)]} {
1855 set row $commitrow($n,$selid)
1856 # try to get the selected row in the same position on the screen
1857 set ymax [lindex [$canv cget -scrollregion] 3]
1858 set ytop [expr {[yc $row] - $yscreen}]
1859 if {$ytop < 0} {
1860 set ytop 0
1862 set yf [expr {$ytop * 1.0 / $ymax}]
1864 allcanvs yview moveto $yf
1865 drawvisible
1866 if {$row ne {}} {
1867 selectline $row 0
1868 } elseif {$selid ne {}} {
1869 set pending_select $selid
1870 } else {
1871 set row [expr {[lindex $displayorder 0] eq $nullid}]
1872 if {$row < $numcommits} {
1873 selectline $row 0
1874 } else {
1875 set selectfirst 1
1878 if {$phase ne {}} {
1879 if {$phase eq "getcommits"} {
1880 show_status "Reading commits..."
1882 run chewcommits $n
1883 } elseif {$numcommits == 0} {
1884 show_status "No commits selected"
1888 # Stuff relating to the highlighting facility
1890 proc ishighlighted {row} {
1891 global vhighlights fhighlights nhighlights rhighlights
1893 if {[info exists nhighlights($row)] && $nhighlights($row) > 0} {
1894 return $nhighlights($row)
1896 if {[info exists vhighlights($row)] && $vhighlights($row) > 0} {
1897 return $vhighlights($row)
1899 if {[info exists fhighlights($row)] && $fhighlights($row) > 0} {
1900 return $fhighlights($row)
1902 if {[info exists rhighlights($row)] && $rhighlights($row) > 0} {
1903 return $rhighlights($row)
1905 return 0
1908 proc bolden {row font} {
1909 global canv linehtag selectedline boldrows
1911 lappend boldrows $row
1912 $canv itemconf $linehtag($row) -font $font
1913 if {[info exists selectedline] && $row == $selectedline} {
1914 $canv delete secsel
1915 set t [eval $canv create rect [$canv bbox $linehtag($row)] \
1916 -outline {{}} -tags secsel \
1917 -fill [$canv cget -selectbackground]]
1918 $canv lower $t
1922 proc bolden_name {row font} {
1923 global canv2 linentag selectedline boldnamerows
1925 lappend boldnamerows $row
1926 $canv2 itemconf $linentag($row) -font $font
1927 if {[info exists selectedline] && $row == $selectedline} {
1928 $canv2 delete secsel
1929 set t [eval $canv2 create rect [$canv2 bbox $linentag($row)] \
1930 -outline {{}} -tags secsel \
1931 -fill [$canv2 cget -selectbackground]]
1932 $canv2 lower $t
1936 proc unbolden {} {
1937 global mainfont boldrows
1939 set stillbold {}
1940 foreach row $boldrows {
1941 if {![ishighlighted $row]} {
1942 bolden $row $mainfont
1943 } else {
1944 lappend stillbold $row
1947 set boldrows $stillbold
1950 proc addvhighlight {n} {
1951 global hlview curview viewdata vhl_done vhighlights commitidx
1953 if {[info exists hlview]} {
1954 delvhighlight
1956 set hlview $n
1957 if {$n != $curview && ![info exists viewdata($n)]} {
1958 set viewdata($n) [list getcommits {{}} {{}} {} {} {} 0 0 0 {}]
1959 set vparentlist($n) {}
1960 set vdisporder($n) {}
1961 set vcmitlisted($n) {}
1962 start_rev_list $n
1964 set vhl_done $commitidx($hlview)
1965 if {$vhl_done > 0} {
1966 drawvisible
1970 proc delvhighlight {} {
1971 global hlview vhighlights
1973 if {![info exists hlview]} return
1974 unset hlview
1975 catch {unset vhighlights}
1976 unbolden
1979 proc vhighlightmore {} {
1980 global hlview vhl_done commitidx vhighlights
1981 global displayorder vdisporder curview mainfont
1983 set font [concat $mainfont bold]
1984 set max $commitidx($hlview)
1985 if {$hlview == $curview} {
1986 set disp $displayorder
1987 } else {
1988 set disp $vdisporder($hlview)
1990 set vr [visiblerows]
1991 set r0 [lindex $vr 0]
1992 set r1 [lindex $vr 1]
1993 for {set i $vhl_done} {$i < $max} {incr i} {
1994 set id [lindex $disp $i]
1995 if {[info exists commitrow($curview,$id)]} {
1996 set row $commitrow($curview,$id)
1997 if {$r0 <= $row && $row <= $r1} {
1998 if {![highlighted $row]} {
1999 bolden $row $font
2001 set vhighlights($row) 1
2005 set vhl_done $max
2008 proc askvhighlight {row id} {
2009 global hlview vhighlights commitrow iddrawn mainfont
2011 if {[info exists commitrow($hlview,$id)]} {
2012 if {[info exists iddrawn($id)] && ![ishighlighted $row]} {
2013 bolden $row [concat $mainfont bold]
2015 set vhighlights($row) 1
2016 } else {
2017 set vhighlights($row) 0
2021 proc hfiles_change {name ix op} {
2022 global highlight_files filehighlight fhighlights fh_serial
2023 global mainfont highlight_paths
2025 if {[info exists filehighlight]} {
2026 # delete previous highlights
2027 catch {close $filehighlight}
2028 unset filehighlight
2029 catch {unset fhighlights}
2030 unbolden
2031 unhighlight_filelist
2033 set highlight_paths {}
2034 after cancel do_file_hl $fh_serial
2035 incr fh_serial
2036 if {$highlight_files ne {}} {
2037 after 300 do_file_hl $fh_serial
2041 proc makepatterns {l} {
2042 set ret {}
2043 foreach e $l {
2044 set ee [string map {"*" "\\*" "?" "\\?" "\[" "\\\[" "\\" "\\\\"} $e]
2045 if {[string index $ee end] eq "/"} {
2046 lappend ret "$ee*"
2047 } else {
2048 lappend ret $ee
2049 lappend ret "$ee/*"
2052 return $ret
2055 proc do_file_hl {serial} {
2056 global highlight_files filehighlight highlight_paths gdttype fhl_list
2058 if {$gdttype eq "touching paths:"} {
2059 if {[catch {set paths [shellsplit $highlight_files]}]} return
2060 set highlight_paths [makepatterns $paths]
2061 highlight_filelist
2062 set gdtargs [concat -- $paths]
2063 } else {
2064 set gdtargs [list "-S$highlight_files"]
2066 set cmd [concat | git diff-tree -r -s --stdin $gdtargs]
2067 set filehighlight [open $cmd r+]
2068 fconfigure $filehighlight -blocking 0
2069 filerun $filehighlight readfhighlight
2070 set fhl_list {}
2071 drawvisible
2072 flushhighlights
2075 proc flushhighlights {} {
2076 global filehighlight fhl_list
2078 if {[info exists filehighlight]} {
2079 lappend fhl_list {}
2080 puts $filehighlight ""
2081 flush $filehighlight
2085 proc askfilehighlight {row id} {
2086 global filehighlight fhighlights fhl_list
2088 lappend fhl_list $id
2089 set fhighlights($row) -1
2090 puts $filehighlight $id
2093 proc readfhighlight {} {
2094 global filehighlight fhighlights commitrow curview mainfont iddrawn
2095 global fhl_list
2097 if {![info exists filehighlight]} {
2098 return 0
2100 set nr 0
2101 while {[incr nr] <= 100 && [gets $filehighlight line] >= 0} {
2102 set line [string trim $line]
2103 set i [lsearch -exact $fhl_list $line]
2104 if {$i < 0} continue
2105 for {set j 0} {$j < $i} {incr j} {
2106 set id [lindex $fhl_list $j]
2107 if {[info exists commitrow($curview,$id)]} {
2108 set fhighlights($commitrow($curview,$id)) 0
2111 set fhl_list [lrange $fhl_list [expr {$i+1}] end]
2112 if {$line eq {}} continue
2113 if {![info exists commitrow($curview,$line)]} continue
2114 set row $commitrow($curview,$line)
2115 if {[info exists iddrawn($line)] && ![ishighlighted $row]} {
2116 bolden $row [concat $mainfont bold]
2118 set fhighlights($row) 1
2120 if {[eof $filehighlight]} {
2121 # strange...
2122 puts "oops, git diff-tree died"
2123 catch {close $filehighlight}
2124 unset filehighlight
2125 return 0
2127 next_hlcont
2128 return 1
2131 proc find_change {name ix op} {
2132 global nhighlights mainfont boldnamerows
2133 global findstring findpattern findtype markingmatches
2135 # delete previous highlights, if any
2136 foreach row $boldnamerows {
2137 bolden_name $row $mainfont
2139 set boldnamerows {}
2140 catch {unset nhighlights}
2141 unbolden
2142 unmarkmatches
2143 if {$findtype ne "Regexp"} {
2144 set e [string map {"*" "\\*" "?" "\\?" "\[" "\\\[" "\\" "\\\\"} \
2145 $findstring]
2146 set findpattern "*$e*"
2148 set markingmatches [expr {$findstring ne {}}]
2149 drawvisible
2152 proc doesmatch {f} {
2153 global findtype findstring findpattern
2155 if {$findtype eq "Regexp"} {
2156 return [regexp $findstring $f]
2157 } elseif {$findtype eq "IgnCase"} {
2158 return [string match -nocase $findpattern $f]
2159 } else {
2160 return [string match $findpattern $f]
2164 proc askfindhighlight {row id} {
2165 global nhighlights commitinfo iddrawn mainfont
2166 global findloc
2167 global markingmatches
2169 if {![info exists commitinfo($id)]} {
2170 getcommit $id
2172 set info $commitinfo($id)
2173 set isbold 0
2174 set fldtypes {Headline Author Date Committer CDate Comments}
2175 foreach f $info ty $fldtypes {
2176 if {($findloc eq "All fields" || $findloc eq $ty) &&
2177 [doesmatch $f]} {
2178 if {$ty eq "Author"} {
2179 set isbold 2
2180 break
2182 set isbold 1
2185 if {$isbold && [info exists iddrawn($id)]} {
2186 set f [concat $mainfont bold]
2187 if {![ishighlighted $row]} {
2188 bolden $row $f
2189 if {$isbold > 1} {
2190 bolden_name $row $f
2193 if {$markingmatches} {
2194 markrowmatches $row [lindex $info 0] [lindex $info 1]
2197 set nhighlights($row) $isbold
2200 proc markrowmatches {row headline author} {
2201 global canv canv2 linehtag linentag
2203 $canv delete match$row
2204 $canv2 delete match$row
2205 set m [findmatches $headline]
2206 if {$m ne {}} {
2207 markmatches $canv $row $headline $linehtag($row) $m \
2208 [$canv itemcget $linehtag($row) -font]
2210 set m [findmatches $author]
2211 if {$m ne {}} {
2212 markmatches $canv2 $row $author $linentag($row) $m \
2213 [$canv2 itemcget $linentag($row) -font]
2217 proc vrel_change {name ix op} {
2218 global highlight_related
2220 rhighlight_none
2221 if {$highlight_related ne "None"} {
2222 run drawvisible
2226 # prepare for testing whether commits are descendents or ancestors of a
2227 proc rhighlight_sel {a} {
2228 global descendent desc_todo ancestor anc_todo
2229 global highlight_related rhighlights
2231 catch {unset descendent}
2232 set desc_todo [list $a]
2233 catch {unset ancestor}
2234 set anc_todo [list $a]
2235 if {$highlight_related ne "None"} {
2236 rhighlight_none
2237 run drawvisible
2241 proc rhighlight_none {} {
2242 global rhighlights
2244 catch {unset rhighlights}
2245 unbolden
2248 proc is_descendent {a} {
2249 global curview children commitrow descendent desc_todo
2251 set v $curview
2252 set la $commitrow($v,$a)
2253 set todo $desc_todo
2254 set leftover {}
2255 set done 0
2256 for {set i 0} {$i < [llength $todo]} {incr i} {
2257 set do [lindex $todo $i]
2258 if {$commitrow($v,$do) < $la} {
2259 lappend leftover $do
2260 continue
2262 foreach nk $children($v,$do) {
2263 if {![info exists descendent($nk)]} {
2264 set descendent($nk) 1
2265 lappend todo $nk
2266 if {$nk eq $a} {
2267 set done 1
2271 if {$done} {
2272 set desc_todo [concat $leftover [lrange $todo [expr {$i+1}] end]]
2273 return
2276 set descendent($a) 0
2277 set desc_todo $leftover
2280 proc is_ancestor {a} {
2281 global curview parentlist commitrow ancestor anc_todo
2283 set v $curview
2284 set la $commitrow($v,$a)
2285 set todo $anc_todo
2286 set leftover {}
2287 set done 0
2288 for {set i 0} {$i < [llength $todo]} {incr i} {
2289 set do [lindex $todo $i]
2290 if {![info exists commitrow($v,$do)] || $commitrow($v,$do) > $la} {
2291 lappend leftover $do
2292 continue
2294 foreach np [lindex $parentlist $commitrow($v,$do)] {
2295 if {![info exists ancestor($np)]} {
2296 set ancestor($np) 1
2297 lappend todo $np
2298 if {$np eq $a} {
2299 set done 1
2303 if {$done} {
2304 set anc_todo [concat $leftover [lrange $todo [expr {$i+1}] end]]
2305 return
2308 set ancestor($a) 0
2309 set anc_todo $leftover
2312 proc askrelhighlight {row id} {
2313 global descendent highlight_related iddrawn mainfont rhighlights
2314 global selectedline ancestor
2316 if {![info exists selectedline]} return
2317 set isbold 0
2318 if {$highlight_related eq "Descendent" ||
2319 $highlight_related eq "Not descendent"} {
2320 if {![info exists descendent($id)]} {
2321 is_descendent $id
2323 if {$descendent($id) == ($highlight_related eq "Descendent")} {
2324 set isbold 1
2326 } elseif {$highlight_related eq "Ancestor" ||
2327 $highlight_related eq "Not ancestor"} {
2328 if {![info exists ancestor($id)]} {
2329 is_ancestor $id
2331 if {$ancestor($id) == ($highlight_related eq "Ancestor")} {
2332 set isbold 1
2335 if {[info exists iddrawn($id)]} {
2336 if {$isbold && ![ishighlighted $row]} {
2337 bolden $row [concat $mainfont bold]
2340 set rhighlights($row) $isbold
2343 proc next_hlcont {} {
2344 global fhl_row fhl_dirn displayorder numcommits
2345 global vhighlights fhighlights nhighlights rhighlights
2346 global hlview filehighlight findstring highlight_related
2348 if {![info exists fhl_dirn] || $fhl_dirn == 0} return
2349 set row $fhl_row
2350 while {1} {
2351 if {$row < 0 || $row >= $numcommits} {
2352 bell
2353 set fhl_dirn 0
2354 return
2356 set id [lindex $displayorder $row]
2357 if {[info exists hlview]} {
2358 if {![info exists vhighlights($row)]} {
2359 askvhighlight $row $id
2361 if {$vhighlights($row) > 0} break
2363 if {$findstring ne {}} {
2364 if {![info exists nhighlights($row)]} {
2365 askfindhighlight $row $id
2367 if {$nhighlights($row) > 0} break
2369 if {$highlight_related ne "None"} {
2370 if {![info exists rhighlights($row)]} {
2371 askrelhighlight $row $id
2373 if {$rhighlights($row) > 0} break
2375 if {[info exists filehighlight]} {
2376 if {![info exists fhighlights($row)]} {
2377 # ask for a few more while we're at it...
2378 set r $row
2379 for {set n 0} {$n < 100} {incr n} {
2380 if {![info exists fhighlights($r)]} {
2381 askfilehighlight $r [lindex $displayorder $r]
2383 incr r $fhl_dirn
2384 if {$r < 0 || $r >= $numcommits} break
2386 flushhighlights
2388 if {$fhighlights($row) < 0} {
2389 set fhl_row $row
2390 return
2392 if {$fhighlights($row) > 0} break
2394 incr row $fhl_dirn
2396 set fhl_dirn 0
2397 selectline $row 1
2400 proc next_highlight {dirn} {
2401 global selectedline fhl_row fhl_dirn
2402 global hlview filehighlight findstring highlight_related
2404 if {![info exists selectedline]} return
2405 if {!([info exists hlview] || $findstring ne {} ||
2406 $highlight_related ne "None" || [info exists filehighlight])} return
2407 set fhl_row [expr {$selectedline + $dirn}]
2408 set fhl_dirn $dirn
2409 next_hlcont
2412 proc cancel_next_highlight {} {
2413 global fhl_dirn
2415 set fhl_dirn 0
2418 # Graph layout functions
2420 proc shortids {ids} {
2421 set res {}
2422 foreach id $ids {
2423 if {[llength $id] > 1} {
2424 lappend res [shortids $id]
2425 } elseif {[regexp {^[0-9a-f]{40}$} $id]} {
2426 lappend res [string range $id 0 7]
2427 } else {
2428 lappend res $id
2431 return $res
2434 proc incrange {l x o} {
2435 set n [llength $l]
2436 while {$x < $n} {
2437 set e [lindex $l $x]
2438 if {$e ne {}} {
2439 lset l $x [expr {$e + $o}]
2441 incr x
2443 return $l
2446 proc ntimes {n o} {
2447 set ret {}
2448 for {} {$n > 0} {incr n -1} {
2449 lappend ret $o
2451 return $ret
2454 proc usedinrange {id l1 l2} {
2455 global children commitrow curview
2457 if {[info exists commitrow($curview,$id)]} {
2458 set r $commitrow($curview,$id)
2459 if {$l1 <= $r && $r <= $l2} {
2460 return [expr {$r - $l1 + 1}]
2463 set kids $children($curview,$id)
2464 foreach c $kids {
2465 set r $commitrow($curview,$c)
2466 if {$l1 <= $r && $r <= $l2} {
2467 return [expr {$r - $l1 + 1}]
2470 return 0
2473 proc sanity {row {full 0}} {
2474 global rowidlist rowoffsets
2476 set col -1
2477 set ids [lindex $rowidlist $row]
2478 foreach id $ids {
2479 incr col
2480 if {$id eq {}} continue
2481 if {$col < [llength $ids] - 1 &&
2482 [lsearch -exact -start [expr {$col+1}] $ids $id] >= 0} {
2483 puts "oops: [shortids $id] repeated in row $row col $col: {[shortids [lindex $rowidlist $row]]}"
2485 set o [lindex $rowoffsets $row $col]
2486 set y $row
2487 set x $col
2488 while {$o ne {}} {
2489 incr y -1
2490 incr x $o
2491 if {[lindex $rowidlist $y $x] != $id} {
2492 puts "oops: rowoffsets wrong at row [expr {$y+1}] col [expr {$x-$o}]"
2493 puts " id=[shortids $id] check started at row $row"
2494 for {set i $row} {$i >= $y} {incr i -1} {
2495 puts " row $i ids={[shortids [lindex $rowidlist $i]]} offs={[lindex $rowoffsets $i]}"
2497 break
2499 if {!$full} break
2500 set o [lindex $rowoffsets $y $x]
2505 proc makeuparrow {oid x y z} {
2506 global rowidlist rowoffsets uparrowlen idrowranges displayorder
2508 for {set i 1} {$i < $uparrowlen && $y > 1} {incr i} {
2509 incr y -1
2510 incr x $z
2511 set off0 [lindex $rowoffsets $y]
2512 for {set x0 $x} {1} {incr x0} {
2513 if {$x0 >= [llength $off0]} {
2514 set x0 [llength [lindex $rowoffsets [expr {$y-1}]]]
2515 break
2517 set z [lindex $off0 $x0]
2518 if {$z ne {}} {
2519 incr x0 $z
2520 break
2523 set z [expr {$x0 - $x}]
2524 lset rowidlist $y [linsert [lindex $rowidlist $y] $x $oid]
2525 lset rowoffsets $y [linsert [lindex $rowoffsets $y] $x $z]
2527 set tmp [lreplace [lindex $rowoffsets $y] $x $x {}]
2528 lset rowoffsets $y [incrange $tmp [expr {$x+1}] -1]
2529 lappend idrowranges($oid) [lindex $displayorder $y]
2532 proc initlayout {} {
2533 global rowidlist rowoffsets displayorder commitlisted
2534 global rowlaidout rowoptim
2535 global idinlist rowchk rowrangelist idrowranges
2536 global numcommits canvxmax canv
2537 global nextcolor
2538 global parentlist
2539 global colormap rowtextx
2540 global selectfirst
2542 set numcommits 0
2543 set displayorder {}
2544 set commitlisted {}
2545 set parentlist {}
2546 set rowrangelist {}
2547 set nextcolor 0
2548 set rowidlist {{}}
2549 set rowoffsets {{}}
2550 catch {unset idinlist}
2551 catch {unset rowchk}
2552 set rowlaidout 0
2553 set rowoptim 0
2554 set canvxmax [$canv cget -width]
2555 catch {unset colormap}
2556 catch {unset rowtextx}
2557 catch {unset idrowranges}
2558 set selectfirst 1
2561 proc setcanvscroll {} {
2562 global canv canv2 canv3 numcommits linespc canvxmax canvy0
2564 set ymax [expr {$canvy0 + ($numcommits - 0.5) * $linespc + 2}]
2565 $canv conf -scrollregion [list 0 0 $canvxmax $ymax]
2566 $canv2 conf -scrollregion [list 0 0 0 $ymax]
2567 $canv3 conf -scrollregion [list 0 0 0 $ymax]
2570 proc visiblerows {} {
2571 global canv numcommits linespc
2573 set ymax [lindex [$canv cget -scrollregion] 3]
2574 if {$ymax eq {} || $ymax == 0} return
2575 set f [$canv yview]
2576 set y0 [expr {int([lindex $f 0] * $ymax)}]
2577 set r0 [expr {int(($y0 - 3) / $linespc) - 1}]
2578 if {$r0 < 0} {
2579 set r0 0
2581 set y1 [expr {int([lindex $f 1] * $ymax)}]
2582 set r1 [expr {int(($y1 - 3) / $linespc) + 1}]
2583 if {$r1 >= $numcommits} {
2584 set r1 [expr {$numcommits - 1}]
2586 return [list $r0 $r1]
2589 proc layoutmore {tmax allread} {
2590 global rowlaidout rowoptim commitidx numcommits optim_delay
2591 global uparrowlen curview rowidlist idinlist
2593 set showlast 0
2594 set showdelay $optim_delay
2595 set optdelay [expr {$uparrowlen + 1}]
2596 while {1} {
2597 if {$rowoptim - $showdelay > $numcommits} {
2598 showstuff [expr {$rowoptim - $showdelay}] $showlast
2599 } elseif {$rowlaidout - $optdelay > $rowoptim} {
2600 set nr [expr {$rowlaidout - $optdelay - $rowoptim}]
2601 if {$nr > 100} {
2602 set nr 100
2604 optimize_rows $rowoptim 0 [expr {$rowoptim + $nr}]
2605 incr rowoptim $nr
2606 } elseif {$commitidx($curview) > $rowlaidout} {
2607 set nr [expr {$commitidx($curview) - $rowlaidout}]
2608 # may need to increase this threshold if uparrowlen or
2609 # mingaplen are increased...
2610 if {$nr > 150} {
2611 set nr 150
2613 set row $rowlaidout
2614 set rowlaidout [layoutrows $row [expr {$row + $nr}] $allread]
2615 if {$rowlaidout == $row} {
2616 return 0
2618 } elseif {$allread} {
2619 set optdelay 0
2620 set nrows $commitidx($curview)
2621 if {[lindex $rowidlist $nrows] ne {} ||
2622 [array names idinlist] ne {}} {
2623 layouttail
2624 set rowlaidout $commitidx($curview)
2625 } elseif {$rowoptim == $nrows} {
2626 set showdelay 0
2627 set showlast 1
2628 if {$numcommits == $nrows} {
2629 return 0
2632 } else {
2633 return 0
2635 if {$tmax ne {} && [clock clicks -milliseconds] >= $tmax} {
2636 return 1
2641 proc showstuff {canshow last} {
2642 global numcommits commitrow pending_select selectedline curview
2643 global lookingforhead mainheadid displayorder nullid selectfirst
2644 global lastscrollset
2646 if {$numcommits == 0} {
2647 global phase
2648 set phase "incrdraw"
2649 allcanvs delete all
2651 set r0 $numcommits
2652 set prev $numcommits
2653 set numcommits $canshow
2654 set t [clock clicks -milliseconds]
2655 if {$prev < 100 || $last || $t - $lastscrollset > 500} {
2656 set lastscrollset $t
2657 setcanvscroll
2659 set rows [visiblerows]
2660 set r1 [lindex $rows 1]
2661 if {$r1 >= $canshow} {
2662 set r1 [expr {$canshow - 1}]
2664 if {$r0 <= $r1} {
2665 drawcommits $r0 $r1
2667 if {[info exists pending_select] &&
2668 [info exists commitrow($curview,$pending_select)] &&
2669 $commitrow($curview,$pending_select) < $numcommits} {
2670 selectline $commitrow($curview,$pending_select) 1
2672 if {$selectfirst} {
2673 if {[info exists selectedline] || [info exists pending_select]} {
2674 set selectfirst 0
2675 } else {
2676 set l [expr {[lindex $displayorder 0] eq $nullid}]
2677 selectline $l 1
2678 set selectfirst 0
2681 if {$lookingforhead && [info exists commitrow($curview,$mainheadid)]
2682 && ($last || $commitrow($curview,$mainheadid) < $numcommits - 1)} {
2683 set lookingforhead 0
2684 dodiffindex
2688 proc doshowlocalchanges {} {
2689 global lookingforhead curview mainheadid phase commitrow
2691 if {[info exists commitrow($curview,$mainheadid)] &&
2692 ($phase eq {} || $commitrow($curview,$mainheadid) < $numcommits - 1)} {
2693 dodiffindex
2694 } elseif {$phase ne {}} {
2695 set lookingforhead 1
2699 proc dohidelocalchanges {} {
2700 global lookingforhead localrow lserial
2702 set lookingforhead 0
2703 if {$localrow >= 0} {
2704 removerow $localrow
2705 set localrow -1
2707 incr lserial
2710 # spawn off a process to do git diff-index HEAD
2711 proc dodiffindex {} {
2712 global localrow lserial
2714 incr lserial
2715 set localrow -1
2716 set fd [open "|git diff-index HEAD" r]
2717 fconfigure $fd -blocking 0
2718 filerun $fd [list readdiffindex $fd $lserial]
2721 proc readdiffindex {fd serial} {
2722 global localrow commitrow mainheadid nullid curview
2723 global commitinfo commitdata lserial
2725 if {[gets $fd line] < 0} {
2726 if {[eof $fd]} {
2727 close $fd
2728 return 0
2730 return 1
2732 # we only need to see one line and we don't really care what it says...
2733 close $fd
2735 if {$serial == $lserial && $localrow == -1} {
2736 # add the line for the local diff to the graph
2737 set localrow $commitrow($curview,$mainheadid)
2738 set hl "Local uncommitted changes"
2739 set commitinfo($nullid) [list $hl {} {} {} {} " $hl\n"]
2740 set commitdata($nullid) "\n $hl\n"
2741 insertrow $localrow $nullid
2743 return 0
2746 proc layoutrows {row endrow last} {
2747 global rowidlist rowoffsets displayorder
2748 global uparrowlen downarrowlen maxwidth mingaplen
2749 global children parentlist
2750 global idrowranges
2751 global commitidx curview
2752 global idinlist rowchk rowrangelist
2754 set idlist [lindex $rowidlist $row]
2755 set offs [lindex $rowoffsets $row]
2756 while {$row < $endrow} {
2757 set id [lindex $displayorder $row]
2758 set oldolds {}
2759 set newolds {}
2760 foreach p [lindex $parentlist $row] {
2761 if {![info exists idinlist($p)]} {
2762 lappend newolds $p
2763 } elseif {!$idinlist($p)} {
2764 lappend oldolds $p
2767 set nev [expr {[llength $idlist] + [llength $newolds]
2768 + [llength $oldolds] - $maxwidth + 1}]
2769 if {$nev > 0} {
2770 if {!$last &&
2771 $row + $uparrowlen + $mingaplen >= $commitidx($curview)} break
2772 for {set x [llength $idlist]} {[incr x -1] >= 0} {} {
2773 set i [lindex $idlist $x]
2774 if {![info exists rowchk($i)] || $row >= $rowchk($i)} {
2775 set r [usedinrange $i [expr {$row - $downarrowlen}] \
2776 [expr {$row + $uparrowlen + $mingaplen}]]
2777 if {$r == 0} {
2778 set idlist [lreplace $idlist $x $x]
2779 set offs [lreplace $offs $x $x]
2780 set offs [incrange $offs $x 1]
2781 set idinlist($i) 0
2782 set rm1 [expr {$row - 1}]
2783 lappend idrowranges($i) [lindex $displayorder $rm1]
2784 if {[incr nev -1] <= 0} break
2785 continue
2787 set rowchk($id) [expr {$row + $r}]
2790 lset rowidlist $row $idlist
2791 lset rowoffsets $row $offs
2793 set col [lsearch -exact $idlist $id]
2794 if {$col < 0} {
2795 set col [llength $idlist]
2796 lappend idlist $id
2797 lset rowidlist $row $idlist
2798 set z {}
2799 if {$children($curview,$id) ne {}} {
2800 set z [expr {[llength [lindex $rowidlist [expr {$row-1}]]] - $col}]
2801 unset idinlist($id)
2803 lappend offs $z
2804 lset rowoffsets $row $offs
2805 if {$z ne {}} {
2806 makeuparrow $id $col $row $z
2808 } else {
2809 unset idinlist($id)
2811 set ranges {}
2812 if {[info exists idrowranges($id)]} {
2813 set ranges $idrowranges($id)
2814 lappend ranges $id
2815 unset idrowranges($id)
2817 lappend rowrangelist $ranges
2818 incr row
2819 set offs [ntimes [llength $idlist] 0]
2820 set l [llength $newolds]
2821 set idlist [eval lreplace \$idlist $col $col $newolds]
2822 set o 0
2823 if {$l != 1} {
2824 set offs [lrange $offs 0 [expr {$col - 1}]]
2825 foreach x $newolds {
2826 lappend offs {}
2827 incr o -1
2829 incr o
2830 set tmp [expr {[llength $idlist] - [llength $offs]}]
2831 if {$tmp > 0} {
2832 set offs [concat $offs [ntimes $tmp $o]]
2834 } else {
2835 lset offs $col {}
2837 foreach i $newolds {
2838 set idinlist($i) 1
2839 set idrowranges($i) $id
2841 incr col $l
2842 foreach oid $oldolds {
2843 set idinlist($oid) 1
2844 set idlist [linsert $idlist $col $oid]
2845 set offs [linsert $offs $col $o]
2846 makeuparrow $oid $col $row $o
2847 incr col
2849 lappend rowidlist $idlist
2850 lappend rowoffsets $offs
2852 return $row
2855 proc addextraid {id row} {
2856 global displayorder commitrow commitinfo
2857 global commitidx commitlisted
2858 global parentlist children curview
2860 incr commitidx($curview)
2861 lappend displayorder $id
2862 lappend commitlisted 0
2863 lappend parentlist {}
2864 set commitrow($curview,$id) $row
2865 readcommit $id
2866 if {![info exists commitinfo($id)]} {
2867 set commitinfo($id) {"No commit information available"}
2869 if {![info exists children($curview,$id)]} {
2870 set children($curview,$id) {}
2874 proc layouttail {} {
2875 global rowidlist rowoffsets idinlist commitidx curview
2876 global idrowranges rowrangelist
2878 set row $commitidx($curview)
2879 set idlist [lindex $rowidlist $row]
2880 while {$idlist ne {}} {
2881 set col [expr {[llength $idlist] - 1}]
2882 set id [lindex $idlist $col]
2883 addextraid $id $row
2884 unset idinlist($id)
2885 lappend idrowranges($id) $row
2886 lappend rowrangelist $idrowranges($id)
2887 unset idrowranges($id)
2888 incr row
2889 set offs [ntimes $col 0]
2890 set idlist [lreplace $idlist $col $col]
2891 lappend rowidlist $idlist
2892 lappend rowoffsets $offs
2895 foreach id [array names idinlist] {
2896 unset idinlist($id)
2897 addextraid $id $row
2898 lset rowidlist $row [list $id]
2899 lset rowoffsets $row 0
2900 makeuparrow $id 0 $row 0
2901 lappend idrowranges($id) $row
2902 lappend rowrangelist $idrowranges($id)
2903 unset idrowranges($id)
2904 incr row
2905 lappend rowidlist {}
2906 lappend rowoffsets {}
2910 proc insert_pad {row col npad} {
2911 global rowidlist rowoffsets
2913 set pad [ntimes $npad {}]
2914 lset rowidlist $row [eval linsert [list [lindex $rowidlist $row]] $col $pad]
2915 set tmp [eval linsert [list [lindex $rowoffsets $row]] $col $pad]
2916 lset rowoffsets $row [incrange $tmp [expr {$col + $npad}] [expr {-$npad}]]
2919 proc optimize_rows {row col endrow} {
2920 global rowidlist rowoffsets displayorder
2922 for {} {$row < $endrow} {incr row} {
2923 set idlist [lindex $rowidlist $row]
2924 set offs [lindex $rowoffsets $row]
2925 set haspad 0
2926 for {} {$col < [llength $offs]} {incr col} {
2927 if {[lindex $idlist $col] eq {}} {
2928 set haspad 1
2929 continue
2931 set z [lindex $offs $col]
2932 if {$z eq {}} continue
2933 set isarrow 0
2934 set x0 [expr {$col + $z}]
2935 set y0 [expr {$row - 1}]
2936 set z0 [lindex $rowoffsets $y0 $x0]
2937 if {$z0 eq {}} {
2938 set id [lindex $idlist $col]
2939 set ranges [rowranges $id]
2940 if {$ranges ne {} && $y0 > [lindex $ranges 0]} {
2941 set isarrow 1
2944 # Looking at lines from this row to the previous row,
2945 # make them go straight up if they end in an arrow on
2946 # the previous row; otherwise make them go straight up
2947 # or at 45 degrees.
2948 if {$z < -1 || ($z < 0 && $isarrow)} {
2949 # Line currently goes left too much;
2950 # insert pads in the previous row, then optimize it
2951 set npad [expr {-1 - $z + $isarrow}]
2952 set offs [incrange $offs $col $npad]
2953 insert_pad $y0 $x0 $npad
2954 if {$y0 > 0} {
2955 optimize_rows $y0 $x0 $row
2957 set z [lindex $offs $col]
2958 set x0 [expr {$col + $z}]
2959 set z0 [lindex $rowoffsets $y0 $x0]
2960 } elseif {$z > 1 || ($z > 0 && $isarrow)} {
2961 # Line currently goes right too much;
2962 # insert pads in this line and adjust the next's rowoffsets
2963 set npad [expr {$z - 1 + $isarrow}]
2964 set y1 [expr {$row + 1}]
2965 set offs2 [lindex $rowoffsets $y1]
2966 set x1 -1
2967 foreach z $offs2 {
2968 incr x1
2969 if {$z eq {} || $x1 + $z < $col} continue
2970 if {$x1 + $z > $col} {
2971 incr npad
2973 lset rowoffsets $y1 [incrange $offs2 $x1 $npad]
2974 break
2976 set pad [ntimes $npad {}]
2977 set idlist [eval linsert \$idlist $col $pad]
2978 set tmp [eval linsert \$offs $col $pad]
2979 incr col $npad
2980 set offs [incrange $tmp $col [expr {-$npad}]]
2981 set z [lindex $offs $col]
2982 set haspad 1
2984 if {$z0 eq {} && !$isarrow} {
2985 # this line links to its first child on row $row-2
2986 set rm2 [expr {$row - 2}]
2987 set id [lindex $displayorder $rm2]
2988 set xc [lsearch -exact [lindex $rowidlist $rm2] $id]
2989 if {$xc >= 0} {
2990 set z0 [expr {$xc - $x0}]
2993 # avoid lines jigging left then immediately right
2994 if {$z0 ne {} && $z < 0 && $z0 > 0} {
2995 insert_pad $y0 $x0 1
2996 set offs [incrange $offs $col 1]
2997 optimize_rows $y0 [expr {$x0 + 1}] $row
3000 if {!$haspad} {
3001 set o {}
3002 # Find the first column that doesn't have a line going right
3003 for {set col [llength $idlist]} {[incr col -1] >= 0} {} {
3004 set o [lindex $offs $col]
3005 if {$o eq {}} {
3006 # check if this is the link to the first child
3007 set id [lindex $idlist $col]
3008 set ranges [rowranges $id]
3009 if {$ranges ne {} && $row == [lindex $ranges 0]} {
3010 # it is, work out offset to child
3011 set y0 [expr {$row - 1}]
3012 set id [lindex $displayorder $y0]
3013 set x0 [lsearch -exact [lindex $rowidlist $y0] $id]
3014 if {$x0 >= 0} {
3015 set o [expr {$x0 - $col}]
3019 if {$o eq {} || $o <= 0} break
3021 # Insert a pad at that column as long as it has a line and
3022 # isn't the last column, and adjust the next row' offsets
3023 if {$o ne {} && [incr col] < [llength $idlist]} {
3024 set y1 [expr {$row + 1}]
3025 set offs2 [lindex $rowoffsets $y1]
3026 set x1 -1
3027 foreach z $offs2 {
3028 incr x1
3029 if {$z eq {} || $x1 + $z < $col} continue
3030 lset rowoffsets $y1 [incrange $offs2 $x1 1]
3031 break
3033 set idlist [linsert $idlist $col {}]
3034 set tmp [linsert $offs $col {}]
3035 incr col
3036 set offs [incrange $tmp $col -1]
3039 lset rowidlist $row $idlist
3040 lset rowoffsets $row $offs
3041 set col 0
3045 proc xc {row col} {
3046 global canvx0 linespc
3047 return [expr {$canvx0 + $col * $linespc}]
3050 proc yc {row} {
3051 global canvy0 linespc
3052 return [expr {$canvy0 + $row * $linespc}]
3055 proc linewidth {id} {
3056 global thickerline lthickness
3058 set wid $lthickness
3059 if {[info exists thickerline] && $id eq $thickerline} {
3060 set wid [expr {2 * $lthickness}]
3062 return $wid
3065 proc rowranges {id} {
3066 global phase idrowranges commitrow rowlaidout rowrangelist curview
3068 set ranges {}
3069 if {$phase eq {} ||
3070 ([info exists commitrow($curview,$id)]
3071 && $commitrow($curview,$id) < $rowlaidout)} {
3072 set ranges [lindex $rowrangelist $commitrow($curview,$id)]
3073 } elseif {[info exists idrowranges($id)]} {
3074 set ranges $idrowranges($id)
3076 set linenos {}
3077 foreach rid $ranges {
3078 lappend linenos $commitrow($curview,$rid)
3080 if {$linenos ne {}} {
3081 lset linenos 0 [expr {[lindex $linenos 0] + 1}]
3083 return $linenos
3086 # work around tk8.4 refusal to draw arrows on diagonal segments
3087 proc adjarrowhigh {coords} {
3088 global linespc
3090 set x0 [lindex $coords 0]
3091 set x1 [lindex $coords 2]
3092 if {$x0 != $x1} {
3093 set y0 [lindex $coords 1]
3094 set y1 [lindex $coords 3]
3095 if {$y0 - $y1 <= 2 * $linespc && $x1 == [lindex $coords 4]} {
3096 # we have a nearby vertical segment, just trim off the diag bit
3097 set coords [lrange $coords 2 end]
3098 } else {
3099 set slope [expr {($x0 - $x1) / ($y0 - $y1)}]
3100 set xi [expr {$x0 - $slope * $linespc / 2}]
3101 set yi [expr {$y0 - $linespc / 2}]
3102 set coords [lreplace $coords 0 1 $xi $y0 $xi $yi]
3105 return $coords
3108 proc drawlineseg {id row endrow arrowlow} {
3109 global rowidlist displayorder iddrawn linesegs
3110 global canv colormap linespc curview maxlinelen
3112 set cols [list [lsearch -exact [lindex $rowidlist $row] $id]]
3113 set le [expr {$row + 1}]
3114 set arrowhigh 1
3115 while {1} {
3116 set c [lsearch -exact [lindex $rowidlist $le] $id]
3117 if {$c < 0} {
3118 incr le -1
3119 break
3121 lappend cols $c
3122 set x [lindex $displayorder $le]
3123 if {$x eq $id} {
3124 set arrowhigh 0
3125 break
3127 if {[info exists iddrawn($x)] || $le == $endrow} {
3128 set c [lsearch -exact [lindex $rowidlist [expr {$le+1}]] $id]
3129 if {$c >= 0} {
3130 lappend cols $c
3131 set arrowhigh 0
3133 break
3135 incr le
3137 if {$le <= $row} {
3138 return $row
3141 set lines {}
3142 set i 0
3143 set joinhigh 0
3144 if {[info exists linesegs($id)]} {
3145 set lines $linesegs($id)
3146 foreach li $lines {
3147 set r0 [lindex $li 0]
3148 if {$r0 > $row} {
3149 if {$r0 == $le && [lindex $li 1] - $row <= $maxlinelen} {
3150 set joinhigh 1
3152 break
3154 incr i
3157 set joinlow 0
3158 if {$i > 0} {
3159 set li [lindex $lines [expr {$i-1}]]
3160 set r1 [lindex $li 1]
3161 if {$r1 == $row && $le - [lindex $li 0] <= $maxlinelen} {
3162 set joinlow 1
3166 set x [lindex $cols [expr {$le - $row}]]
3167 set xp [lindex $cols [expr {$le - 1 - $row}]]
3168 set dir [expr {$xp - $x}]
3169 if {$joinhigh} {
3170 set ith [lindex $lines $i 2]
3171 set coords [$canv coords $ith]
3172 set ah [$canv itemcget $ith -arrow]
3173 set arrowhigh [expr {$ah eq "first" || $ah eq "both"}]
3174 set x2 [lindex $cols [expr {$le + 1 - $row}]]
3175 if {$x2 ne {} && $x - $x2 == $dir} {
3176 set coords [lrange $coords 0 end-2]
3178 } else {
3179 set coords [list [xc $le $x] [yc $le]]
3181 if {$joinlow} {
3182 set itl [lindex $lines [expr {$i-1}] 2]
3183 set al [$canv itemcget $itl -arrow]
3184 set arrowlow [expr {$al eq "last" || $al eq "both"}]
3185 } elseif {$arrowlow &&
3186 [lsearch -exact [lindex $rowidlist [expr {$row-1}]] $id] >= 0} {
3187 set arrowlow 0
3189 set arrow [lindex {none first last both} [expr {$arrowhigh + 2*$arrowlow}]]
3190 for {set y $le} {[incr y -1] > $row} {} {
3191 set x $xp
3192 set xp [lindex $cols [expr {$y - 1 - $row}]]
3193 set ndir [expr {$xp - $x}]
3194 if {$dir != $ndir || $xp < 0} {
3195 lappend coords [xc $y $x] [yc $y]
3197 set dir $ndir
3199 if {!$joinlow} {
3200 if {$xp < 0} {
3201 # join parent line to first child
3202 set ch [lindex $displayorder $row]
3203 set xc [lsearch -exact [lindex $rowidlist $row] $ch]
3204 if {$xc < 0} {
3205 puts "oops: drawlineseg: child $ch not on row $row"
3206 } else {
3207 if {$xc < $x - 1} {
3208 lappend coords [xc $row [expr {$x-1}]] [yc $row]
3209 } elseif {$xc > $x + 1} {
3210 lappend coords [xc $row [expr {$x+1}]] [yc $row]
3212 set x $xc
3214 lappend coords [xc $row $x] [yc $row]
3215 } else {
3216 set xn [xc $row $xp]
3217 set yn [yc $row]
3218 # work around tk8.4 refusal to draw arrows on diagonal segments
3219 if {$arrowlow && $xn != [lindex $coords end-1]} {
3220 if {[llength $coords] < 4 ||
3221 [lindex $coords end-3] != [lindex $coords end-1] ||
3222 [lindex $coords end] - $yn > 2 * $linespc} {
3223 set xn [xc $row [expr {$xp - 0.5 * $dir}]]
3224 set yo [yc [expr {$row + 0.5}]]
3225 lappend coords $xn $yo $xn $yn
3227 } else {
3228 lappend coords $xn $yn
3231 if {!$joinhigh} {
3232 if {$arrowhigh} {
3233 set coords [adjarrowhigh $coords]
3235 assigncolor $id
3236 set t [$canv create line $coords -width [linewidth $id] \
3237 -fill $colormap($id) -tags lines.$id -arrow $arrow]
3238 $canv lower $t
3239 bindline $t $id
3240 set lines [linsert $lines $i [list $row $le $t]]
3241 } else {
3242 $canv coords $ith $coords
3243 if {$arrow ne $ah} {
3244 $canv itemconf $ith -arrow $arrow
3246 lset lines $i 0 $row
3248 } else {
3249 set xo [lsearch -exact [lindex $rowidlist [expr {$row - 1}]] $id]
3250 set ndir [expr {$xo - $xp}]
3251 set clow [$canv coords $itl]
3252 if {$dir == $ndir} {
3253 set clow [lrange $clow 2 end]
3255 set coords [concat $coords $clow]
3256 if {!$joinhigh} {
3257 lset lines [expr {$i-1}] 1 $le
3258 if {$arrowhigh} {
3259 set coords [adjarrowhigh $coords]
3261 } else {
3262 # coalesce two pieces
3263 $canv delete $ith
3264 set b [lindex $lines [expr {$i-1}] 0]
3265 set e [lindex $lines $i 1]
3266 set lines [lreplace $lines [expr {$i-1}] $i [list $b $e $itl]]
3268 $canv coords $itl $coords
3269 if {$arrow ne $al} {
3270 $canv itemconf $itl -arrow $arrow
3274 set linesegs($id) $lines
3275 return $le
3278 proc drawparentlinks {id row} {
3279 global rowidlist canv colormap curview parentlist
3280 global idpos
3282 set rowids [lindex $rowidlist $row]
3283 set col [lsearch -exact $rowids $id]
3284 if {$col < 0} return
3285 set olds [lindex $parentlist $row]
3286 set row2 [expr {$row + 1}]
3287 set x [xc $row $col]
3288 set y [yc $row]
3289 set y2 [yc $row2]
3290 set ids [lindex $rowidlist $row2]
3291 # rmx = right-most X coord used
3292 set rmx 0
3293 foreach p $olds {
3294 set i [lsearch -exact $ids $p]
3295 if {$i < 0} {
3296 puts "oops, parent $p of $id not in list"
3297 continue
3299 set x2 [xc $row2 $i]
3300 if {$x2 > $rmx} {
3301 set rmx $x2
3303 if {[lsearch -exact $rowids $p] < 0} {
3304 # drawlineseg will do this one for us
3305 continue
3307 assigncolor $p
3308 # should handle duplicated parents here...
3309 set coords [list $x $y]
3310 if {$i < $col - 1} {
3311 lappend coords [xc $row [expr {$i + 1}]] $y
3312 } elseif {$i > $col + 1} {
3313 lappend coords [xc $row [expr {$i - 1}]] $y
3315 lappend coords $x2 $y2
3316 set t [$canv create line $coords -width [linewidth $p] \
3317 -fill $colormap($p) -tags lines.$p]
3318 $canv lower $t
3319 bindline $t $p
3321 if {$rmx > [lindex $idpos($id) 1]} {
3322 lset idpos($id) 1 $rmx
3323 redrawtags $id
3327 proc drawlines {id} {
3328 global canv
3330 $canv itemconf lines.$id -width [linewidth $id]
3333 proc drawcmittext {id row col} {
3334 global linespc canv canv2 canv3 canvy0 fgcolor
3335 global commitlisted commitinfo rowidlist parentlist
3336 global rowtextx idpos idtags idheads idotherrefs
3337 global linehtag linentag linedtag markingmatches
3338 global mainfont canvxmax boldrows boldnamerows fgcolor nullid
3340 if {$id eq $nullid} {
3341 set ofill red
3342 } else {
3343 set ofill [expr {[lindex $commitlisted $row]? "blue": "white"}]
3345 set x [xc $row $col]
3346 set y [yc $row]
3347 set orad [expr {$linespc / 3}]
3348 set t [$canv create oval [expr {$x - $orad}] [expr {$y - $orad}] \
3349 [expr {$x + $orad - 1}] [expr {$y + $orad - 1}] \
3350 -fill $ofill -outline $fgcolor -width 1 -tags circle]
3351 $canv raise $t
3352 $canv bind $t <1> {selcanvline {} %x %y}
3353 set rmx [llength [lindex $rowidlist $row]]
3354 set olds [lindex $parentlist $row]
3355 if {$olds ne {}} {
3356 set nextids [lindex $rowidlist [expr {$row + 1}]]
3357 foreach p $olds {
3358 set i [lsearch -exact $nextids $p]
3359 if {$i > $rmx} {
3360 set rmx $i
3364 set xt [xc $row $rmx]
3365 set rowtextx($row) $xt
3366 set idpos($id) [list $x $xt $y]
3367 if {[info exists idtags($id)] || [info exists idheads($id)]
3368 || [info exists idotherrefs($id)]} {
3369 set xt [drawtags $id $x $xt $y]
3371 set headline [lindex $commitinfo($id) 0]
3372 set name [lindex $commitinfo($id) 1]
3373 set date [lindex $commitinfo($id) 2]
3374 set date [formatdate $date]
3375 set font $mainfont
3376 set nfont $mainfont
3377 set isbold [ishighlighted $row]
3378 if {$isbold > 0} {
3379 lappend boldrows $row
3380 lappend font bold
3381 if {$isbold > 1} {
3382 lappend boldnamerows $row
3383 lappend nfont bold
3386 set linehtag($row) [$canv create text $xt $y -anchor w -fill $fgcolor \
3387 -text $headline -font $font -tags text]
3388 $canv bind $linehtag($row) <Button-3> "rowmenu %X %Y $id"
3389 set linentag($row) [$canv2 create text 3 $y -anchor w -fill $fgcolor \
3390 -text $name -font $nfont -tags text]
3391 set linedtag($row) [$canv3 create text 3 $y -anchor w -fill $fgcolor \
3392 -text $date -font $mainfont -tags text]
3393 set xr [expr {$xt + [font measure $mainfont $headline]}]
3394 if {$markingmatches} {
3395 markrowmatches $row $headline $name
3397 if {$xr > $canvxmax} {
3398 set canvxmax $xr
3399 setcanvscroll
3403 proc drawcmitrow {row} {
3404 global displayorder rowidlist
3405 global iddrawn
3406 global commitinfo parentlist numcommits
3407 global filehighlight fhighlights findstring nhighlights
3408 global hlview vhighlights
3409 global highlight_related rhighlights
3411 if {$row >= $numcommits} return
3413 set id [lindex $displayorder $row]
3414 if {[info exists hlview] && ![info exists vhighlights($row)]} {
3415 askvhighlight $row $id
3417 if {[info exists filehighlight] && ![info exists fhighlights($row)]} {
3418 askfilehighlight $row $id
3420 if {$findstring ne {} && ![info exists nhighlights($row)]} {
3421 askfindhighlight $row $id
3423 if {$highlight_related ne "None" && ![info exists rhighlights($row)]} {
3424 askrelhighlight $row $id
3426 if {[info exists iddrawn($id)]} return
3427 set col [lsearch -exact [lindex $rowidlist $row] $id]
3428 if {$col < 0} {
3429 puts "oops, row $row id $id not in list"
3430 return
3432 if {![info exists commitinfo($id)]} {
3433 getcommit $id
3435 assigncolor $id
3436 drawcmittext $id $row $col
3437 set iddrawn($id) 1
3440 proc drawcommits {row {endrow {}}} {
3441 global numcommits iddrawn displayorder curview
3442 global parentlist rowidlist
3444 if {$row < 0} {
3445 set row 0
3447 if {$endrow eq {}} {
3448 set endrow $row
3450 if {$endrow >= $numcommits} {
3451 set endrow [expr {$numcommits - 1}]
3454 # make the lines join to already-drawn rows either side
3455 set r [expr {$row - 1}]
3456 if {$r < 0 || ![info exists iddrawn([lindex $displayorder $r])]} {
3457 set r $row
3459 set er [expr {$endrow + 1}]
3460 if {$er >= $numcommits ||
3461 ![info exists iddrawn([lindex $displayorder $er])]} {
3462 set er $endrow
3464 for {} {$r <= $er} {incr r} {
3465 set id [lindex $displayorder $r]
3466 set wasdrawn [info exists iddrawn($id)]
3467 drawcmitrow $r
3468 if {$r == $er} break
3469 set nextid [lindex $displayorder [expr {$r + 1}]]
3470 if {$wasdrawn && [info exists iddrawn($nextid)]} {
3471 catch {unset prevlines}
3472 continue
3474 drawparentlinks $id $r
3476 if {[info exists lineends($r)]} {
3477 foreach lid $lineends($r) {
3478 unset prevlines($lid)
3481 set rowids [lindex $rowidlist $r]
3482 foreach lid $rowids {
3483 if {$lid eq {}} continue
3484 if {$lid eq $id} {
3485 # see if this is the first child of any of its parents
3486 foreach p [lindex $parentlist $r] {
3487 if {[lsearch -exact $rowids $p] < 0} {
3488 # make this line extend up to the child
3489 set le [drawlineseg $p $r $er 0]
3490 lappend lineends($le) $p
3491 set prevlines($p) 1
3494 } elseif {![info exists prevlines($lid)]} {
3495 set le [drawlineseg $lid $r $er 1]
3496 lappend lineends($le) $lid
3497 set prevlines($lid) 1
3503 proc drawfrac {f0 f1} {
3504 global canv linespc
3506 set ymax [lindex [$canv cget -scrollregion] 3]
3507 if {$ymax eq {} || $ymax == 0} return
3508 set y0 [expr {int($f0 * $ymax)}]
3509 set row [expr {int(($y0 - 3) / $linespc) - 1}]
3510 set y1 [expr {int($f1 * $ymax)}]
3511 set endrow [expr {int(($y1 - 3) / $linespc) + 1}]
3512 drawcommits $row $endrow
3515 proc drawvisible {} {
3516 global canv
3517 eval drawfrac [$canv yview]
3520 proc clear_display {} {
3521 global iddrawn linesegs
3522 global vhighlights fhighlights nhighlights rhighlights
3524 allcanvs delete all
3525 catch {unset iddrawn}
3526 catch {unset linesegs}
3527 catch {unset vhighlights}
3528 catch {unset fhighlights}
3529 catch {unset nhighlights}
3530 catch {unset rhighlights}
3533 proc findcrossings {id} {
3534 global rowidlist parentlist numcommits rowoffsets displayorder
3536 set cross {}
3537 set ccross {}
3538 foreach {s e} [rowranges $id] {
3539 if {$e >= $numcommits} {
3540 set e [expr {$numcommits - 1}]
3542 if {$e <= $s} continue
3543 set x [lsearch -exact [lindex $rowidlist $e] $id]
3544 if {$x < 0} {
3545 puts "findcrossings: oops, no [shortids $id] in row $e"
3546 continue
3548 for {set row $e} {[incr row -1] >= $s} {} {
3549 set olds [lindex $parentlist $row]
3550 set kid [lindex $displayorder $row]
3551 set kidx [lsearch -exact [lindex $rowidlist $row] $kid]
3552 if {$kidx < 0} continue
3553 set nextrow [lindex $rowidlist [expr {$row + 1}]]
3554 foreach p $olds {
3555 set px [lsearch -exact $nextrow $p]
3556 if {$px < 0} continue
3557 if {($kidx < $x && $x < $px) || ($px < $x && $x < $kidx)} {
3558 if {[lsearch -exact $ccross $p] >= 0} continue
3559 if {$x == $px + ($kidx < $px? -1: 1)} {
3560 lappend ccross $p
3561 } elseif {[lsearch -exact $cross $p] < 0} {
3562 lappend cross $p
3566 set inc [lindex $rowoffsets $row $x]
3567 if {$inc eq {}} break
3568 incr x $inc
3571 return [concat $ccross {{}} $cross]
3574 proc assigncolor {id} {
3575 global colormap colors nextcolor
3576 global commitrow parentlist children children curview
3578 if {[info exists colormap($id)]} return
3579 set ncolors [llength $colors]
3580 if {[info exists children($curview,$id)]} {
3581 set kids $children($curview,$id)
3582 } else {
3583 set kids {}
3585 if {[llength $kids] == 1} {
3586 set child [lindex $kids 0]
3587 if {[info exists colormap($child)]
3588 && [llength [lindex $parentlist $commitrow($curview,$child)]] == 1} {
3589 set colormap($id) $colormap($child)
3590 return
3593 set badcolors {}
3594 set origbad {}
3595 foreach x [findcrossings $id] {
3596 if {$x eq {}} {
3597 # delimiter between corner crossings and other crossings
3598 if {[llength $badcolors] >= $ncolors - 1} break
3599 set origbad $badcolors
3601 if {[info exists colormap($x)]
3602 && [lsearch -exact $badcolors $colormap($x)] < 0} {
3603 lappend badcolors $colormap($x)
3606 if {[llength $badcolors] >= $ncolors} {
3607 set badcolors $origbad
3609 set origbad $badcolors
3610 if {[llength $badcolors] < $ncolors - 1} {
3611 foreach child $kids {
3612 if {[info exists colormap($child)]
3613 && [lsearch -exact $badcolors $colormap($child)] < 0} {
3614 lappend badcolors $colormap($child)
3616 foreach p [lindex $parentlist $commitrow($curview,$child)] {
3617 if {[info exists colormap($p)]
3618 && [lsearch -exact $badcolors $colormap($p)] < 0} {
3619 lappend badcolors $colormap($p)
3623 if {[llength $badcolors] >= $ncolors} {
3624 set badcolors $origbad
3627 for {set i 0} {$i <= $ncolors} {incr i} {
3628 set c [lindex $colors $nextcolor]
3629 if {[incr nextcolor] >= $ncolors} {
3630 set nextcolor 0
3632 if {[lsearch -exact $badcolors $c]} break
3634 set colormap($id) $c
3637 proc bindline {t id} {
3638 global canv
3640 $canv bind $t <Enter> "lineenter %x %y $id"
3641 $canv bind $t <Motion> "linemotion %x %y $id"
3642 $canv bind $t <Leave> "lineleave $id"
3643 $canv bind $t <Button-1> "lineclick %x %y $id 1"
3646 proc drawtags {id x xt y1} {
3647 global idtags idheads idotherrefs mainhead
3648 global linespc lthickness
3649 global canv mainfont commitrow rowtextx curview fgcolor bgcolor
3651 set marks {}
3652 set ntags 0
3653 set nheads 0
3654 if {[info exists idtags($id)]} {
3655 set marks $idtags($id)
3656 set ntags [llength $marks]
3658 if {[info exists idheads($id)]} {
3659 set marks [concat $marks $idheads($id)]
3660 set nheads [llength $idheads($id)]
3662 if {[info exists idotherrefs($id)]} {
3663 set marks [concat $marks $idotherrefs($id)]
3665 if {$marks eq {}} {
3666 return $xt
3669 set delta [expr {int(0.5 * ($linespc - $lthickness))}]
3670 set yt [expr {$y1 - 0.5 * $linespc}]
3671 set yb [expr {$yt + $linespc - 1}]
3672 set xvals {}
3673 set wvals {}
3674 set i -1
3675 foreach tag $marks {
3676 incr i
3677 if {$i >= $ntags && $i < $ntags + $nheads && $tag eq $mainhead} {
3678 set wid [font measure [concat $mainfont bold] $tag]
3679 } else {
3680 set wid [font measure $mainfont $tag]
3682 lappend xvals $xt
3683 lappend wvals $wid
3684 set xt [expr {$xt + $delta + $wid + $lthickness + $linespc}]
3686 set t [$canv create line $x $y1 [lindex $xvals end] $y1 \
3687 -width $lthickness -fill black -tags tag.$id]
3688 $canv lower $t
3689 foreach tag $marks x $xvals wid $wvals {
3690 set xl [expr {$x + $delta}]
3691 set xr [expr {$x + $delta + $wid + $lthickness}]
3692 set font $mainfont
3693 if {[incr ntags -1] >= 0} {
3694 # draw a tag
3695 set t [$canv create polygon $x [expr {$yt + $delta}] $xl $yt \
3696 $xr $yt $xr $yb $xl $yb $x [expr {$yb - $delta}] \
3697 -width 1 -outline black -fill yellow -tags tag.$id]
3698 $canv bind $t <1> [list showtag $tag 1]
3699 set rowtextx($commitrow($curview,$id)) [expr {$xr + $linespc}]
3700 } else {
3701 # draw a head or other ref
3702 if {[incr nheads -1] >= 0} {
3703 set col green
3704 if {$tag eq $mainhead} {
3705 lappend font bold
3707 } else {
3708 set col "#ddddff"
3710 set xl [expr {$xl - $delta/2}]
3711 $canv create polygon $x $yt $xr $yt $xr $yb $x $yb \
3712 -width 1 -outline black -fill $col -tags tag.$id
3713 if {[regexp {^(remotes/.*/|remotes/)} $tag match remoteprefix]} {
3714 set rwid [font measure $mainfont $remoteprefix]
3715 set xi [expr {$x + 1}]
3716 set yti [expr {$yt + 1}]
3717 set xri [expr {$x + $rwid}]
3718 $canv create polygon $xi $yti $xri $yti $xri $yb $xi $yb \
3719 -width 0 -fill "#ffddaa" -tags tag.$id
3722 set t [$canv create text $xl $y1 -anchor w -text $tag -fill $fgcolor \
3723 -font $font -tags [list tag.$id text]]
3724 if {$ntags >= 0} {
3725 $canv bind $t <1> [list showtag $tag 1]
3726 } elseif {$nheads >= 0} {
3727 $canv bind $t <Button-3> [list headmenu %X %Y $id $tag]
3730 return $xt
3733 proc xcoord {i level ln} {
3734 global canvx0 xspc1 xspc2
3736 set x [expr {$canvx0 + $i * $xspc1($ln)}]
3737 if {$i > 0 && $i == $level} {
3738 set x [expr {$x + 0.5 * ($xspc2 - $xspc1($ln))}]
3739 } elseif {$i > $level} {
3740 set x [expr {$x + $xspc2 - $xspc1($ln)}]
3742 return $x
3745 proc show_status {msg} {
3746 global canv mainfont fgcolor
3748 clear_display
3749 $canv create text 3 3 -anchor nw -text $msg -font $mainfont \
3750 -tags text -fill $fgcolor
3753 # Insert a new commit as the child of the commit on row $row.
3754 # The new commit will be displayed on row $row and the commits
3755 # on that row and below will move down one row.
3756 proc insertrow {row newcmit} {
3757 global displayorder parentlist commitlisted children
3758 global commitrow curview rowidlist rowoffsets numcommits
3759 global rowrangelist rowlaidout rowoptim numcommits
3760 global selectedline rowchk commitidx
3762 if {$row >= $numcommits} {
3763 puts "oops, inserting new row $row but only have $numcommits rows"
3764 return
3766 set p [lindex $displayorder $row]
3767 set displayorder [linsert $displayorder $row $newcmit]
3768 set parentlist [linsert $parentlist $row $p]
3769 set kids $children($curview,$p)
3770 lappend kids $newcmit
3771 set children($curview,$p) $kids
3772 set children($curview,$newcmit) {}
3773 set commitlisted [linsert $commitlisted $row 1]
3774 set l [llength $displayorder]
3775 for {set r $row} {$r < $l} {incr r} {
3776 set id [lindex $displayorder $r]
3777 set commitrow($curview,$id) $r
3779 incr commitidx($curview)
3781 set idlist [lindex $rowidlist $row]
3782 set offs [lindex $rowoffsets $row]
3783 set newoffs {}
3784 foreach x $idlist {
3785 if {$x eq {} || ($x eq $p && [llength $kids] == 1)} {
3786 lappend newoffs {}
3787 } else {
3788 lappend newoffs 0
3791 if {[llength $kids] == 1} {
3792 set col [lsearch -exact $idlist $p]
3793 lset idlist $col $newcmit
3794 } else {
3795 set col [llength $idlist]
3796 lappend idlist $newcmit
3797 lappend offs {}
3798 lset rowoffsets $row $offs
3800 set rowidlist [linsert $rowidlist $row $idlist]
3801 set rowoffsets [linsert $rowoffsets [expr {$row+1}] $newoffs]
3803 set rowrangelist [linsert $rowrangelist $row {}]
3804 if {[llength $kids] > 1} {
3805 set rp1 [expr {$row + 1}]
3806 set ranges [lindex $rowrangelist $rp1]
3807 if {$ranges eq {}} {
3808 set ranges [list $newcmit $p]
3809 } elseif {[lindex $ranges end-1] eq $p} {
3810 lset ranges end-1 $newcmit
3812 lset rowrangelist $rp1 $ranges
3815 catch {unset rowchk}
3817 incr rowlaidout
3818 incr rowoptim
3819 incr numcommits
3821 if {[info exists selectedline] && $selectedline >= $row} {
3822 incr selectedline
3824 redisplay
3827 # Remove a commit that was inserted with insertrow on row $row.
3828 proc removerow {row} {
3829 global displayorder parentlist commitlisted children
3830 global commitrow curview rowidlist rowoffsets numcommits
3831 global rowrangelist idrowranges rowlaidout rowoptim numcommits
3832 global linesegends selectedline rowchk commitidx
3834 if {$row >= $numcommits} {
3835 puts "oops, removing row $row but only have $numcommits rows"
3836 return
3838 set rp1 [expr {$row + 1}]
3839 set id [lindex $displayorder $row]
3840 set p [lindex $parentlist $row]
3841 set displayorder [lreplace $displayorder $row $row]
3842 set parentlist [lreplace $parentlist $row $row]
3843 set commitlisted [lreplace $commitlisted $row $row]
3844 set kids $children($curview,$p)
3845 set i [lsearch -exact $kids $id]
3846 if {$i >= 0} {
3847 set kids [lreplace $kids $i $i]
3848 set children($curview,$p) $kids
3850 set l [llength $displayorder]
3851 for {set r $row} {$r < $l} {incr r} {
3852 set id [lindex $displayorder $r]
3853 set commitrow($curview,$id) $r
3855 incr commitidx($curview) -1
3857 set rowidlist [lreplace $rowidlist $row $row]
3858 set rowoffsets [lreplace $rowoffsets $rp1 $rp1]
3859 if {$kids ne {}} {
3860 set offs [lindex $rowoffsets $row]
3861 set offs [lreplace $offs end end]
3862 lset rowoffsets $row $offs
3865 set rowrangelist [lreplace $rowrangelist $row $row]
3866 if {[llength $kids] > 0} {
3867 set ranges [lindex $rowrangelist $row]
3868 if {[lindex $ranges end-1] eq $id} {
3869 set ranges [lreplace $ranges end-1 end]
3870 lset rowrangelist $row $ranges
3874 catch {unset rowchk}
3876 incr rowlaidout -1
3877 incr rowoptim -1
3878 incr numcommits -1
3880 if {[info exists selectedline] && $selectedline > $row} {
3881 incr selectedline -1
3883 redisplay
3886 # Don't change the text pane cursor if it is currently the hand cursor,
3887 # showing that we are over a sha1 ID link.
3888 proc settextcursor {c} {
3889 global ctext curtextcursor
3891 if {[$ctext cget -cursor] == $curtextcursor} {
3892 $ctext config -cursor $c
3894 set curtextcursor $c
3897 proc nowbusy {what} {
3898 global isbusy
3900 if {[array names isbusy] eq {}} {
3901 . config -cursor watch
3902 settextcursor watch
3904 set isbusy($what) 1
3907 proc notbusy {what} {
3908 global isbusy maincursor textcursor
3910 catch {unset isbusy($what)}
3911 if {[array names isbusy] eq {}} {
3912 . config -cursor $maincursor
3913 settextcursor $textcursor
3917 proc findmatches {f} {
3918 global findtype findstring
3919 if {$findtype == "Regexp"} {
3920 set matches [regexp -indices -all -inline $findstring $f]
3921 } else {
3922 set fs $findstring
3923 if {$findtype == "IgnCase"} {
3924 set f [string tolower $f]
3925 set fs [string tolower $fs]
3927 set matches {}
3928 set i 0
3929 set l [string length $fs]
3930 while {[set j [string first $fs $f $i]] >= 0} {
3931 lappend matches [list $j [expr {$j+$l-1}]]
3932 set i [expr {$j + $l}]
3935 return $matches
3938 proc dofind {{rev 0}} {
3939 global findstring findstartline findcurline selectedline numcommits
3941 unmarkmatches
3942 cancel_next_highlight
3943 focus .
3944 if {$findstring eq {} || $numcommits == 0} return
3945 if {![info exists selectedline]} {
3946 set findstartline [lindex [visiblerows] $rev]
3947 } else {
3948 set findstartline $selectedline
3950 set findcurline $findstartline
3951 nowbusy finding
3952 if {!$rev} {
3953 run findmore
3954 } else {
3955 set findcurline $findstartline
3956 if {$findcurline == 0} {
3957 set findcurline $numcommits
3959 incr findcurline -1
3960 run findmorerev
3964 proc findnext {restart} {
3965 global findcurline
3966 if {![info exists findcurline]} {
3967 if {$restart} {
3968 dofind
3969 } else {
3970 bell
3972 } else {
3973 run findmore
3974 nowbusy finding
3978 proc findprev {} {
3979 global findcurline
3980 if {![info exists findcurline]} {
3981 dofind 1
3982 } else {
3983 run findmorerev
3984 nowbusy finding
3988 proc findmore {} {
3989 global commitdata commitinfo numcommits findstring findpattern findloc
3990 global findstartline findcurline markingmatches displayorder
3992 set fldtypes {Headline Author Date Committer CDate Comments}
3993 set l [expr {$findcurline + 1}]
3994 if {$l >= $numcommits} {
3995 set l 0
3997 if {$l <= $findstartline} {
3998 set lim [expr {$findstartline + 1}]
3999 } else {
4000 set lim $numcommits
4002 if {$lim - $l > 500} {
4003 set lim [expr {$l + 500}]
4005 set last 0
4006 for {} {$l < $lim} {incr l} {
4007 set id [lindex $displayorder $l]
4008 if {![doesmatch $commitdata($id)]} continue
4009 if {![info exists commitinfo($id)]} {
4010 getcommit $id
4012 set info $commitinfo($id)
4013 foreach f $info ty $fldtypes {
4014 if {($findloc eq "All fields" || $findloc eq $ty) &&
4015 [doesmatch $f]} {
4016 set markingmatches 1
4017 findselectline $l
4018 notbusy finding
4019 return 0
4023 if {$l == $findstartline + 1} {
4024 bell
4025 unset findcurline
4026 notbusy finding
4027 return 0
4029 set findcurline [expr {$l - 1}]
4030 return 1
4033 proc findmorerev {} {
4034 global commitdata commitinfo numcommits findstring findpattern findloc
4035 global findstartline findcurline markingmatches displayorder
4037 set fldtypes {Headline Author Date Committer CDate Comments}
4038 set l $findcurline
4039 if {$l == 0} {
4040 set l $numcommits
4042 incr l -1
4043 if {$l >= $findstartline} {
4044 set lim [expr {$findstartline - 1}]
4045 } else {
4046 set lim -1
4048 if {$l - $lim > 500} {
4049 set lim [expr {$l - 500}]
4051 set last 0
4052 for {} {$l > $lim} {incr l -1} {
4053 set id [lindex $displayorder $l]
4054 if {![doesmatch $commitdata($id)]} continue
4055 if {![info exists commitinfo($id)]} {
4056 getcommit $id
4058 set info $commitinfo($id)
4059 foreach f $info ty $fldtypes {
4060 if {($findloc eq "All fields" || $findloc eq $ty) &&
4061 [doesmatch $f]} {
4062 set markingmatches 1
4063 findselectline $l
4064 notbusy finding
4065 return 0
4069 if {$l == -1} {
4070 bell
4071 unset findcurline
4072 notbusy finding
4073 return 0
4075 set findcurline [expr {$l + 1}]
4076 return 1
4079 proc findselectline {l} {
4080 global findloc commentend ctext
4081 selectline $l 1
4082 if {$findloc == "All fields" || $findloc == "Comments"} {
4083 # highlight the matches in the comments
4084 set f [$ctext get 1.0 $commentend]
4085 set matches [findmatches $f]
4086 foreach match $matches {
4087 set start [lindex $match 0]
4088 set end [expr {[lindex $match 1] + 1}]
4089 $ctext tag add found "1.0 + $start c" "1.0 + $end c"
4094 # mark the bits of a headline or author that match a find string
4095 proc markmatches {canv l str tag matches font} {
4096 set bbox [$canv bbox $tag]
4097 set x0 [lindex $bbox 0]
4098 set y0 [lindex $bbox 1]
4099 set y1 [lindex $bbox 3]
4100 foreach match $matches {
4101 set start [lindex $match 0]
4102 set end [lindex $match 1]
4103 if {$start > $end} continue
4104 set xoff [font measure $font [string range $str 0 [expr {$start-1}]]]
4105 set xlen [font measure $font [string range $str 0 [expr {$end}]]]
4106 set t [$canv create rect [expr {$x0+$xoff}] $y0 \
4107 [expr {$x0+$xlen+2}] $y1 \
4108 -outline {} -tags [list match$l matches] -fill yellow]
4109 $canv lower $t
4113 proc unmarkmatches {} {
4114 global findids markingmatches findcurline
4116 allcanvs delete matches
4117 catch {unset findids}
4118 set markingmatches 0
4119 catch {unset findcurline}
4122 proc selcanvline {w x y} {
4123 global canv canvy0 ctext linespc
4124 global rowtextx
4125 set ymax [lindex [$canv cget -scrollregion] 3]
4126 if {$ymax == {}} return
4127 set yfrac [lindex [$canv yview] 0]
4128 set y [expr {$y + $yfrac * $ymax}]
4129 set l [expr {int(($y - $canvy0) / $linespc + 0.5)}]
4130 if {$l < 0} {
4131 set l 0
4133 if {$w eq $canv} {
4134 if {![info exists rowtextx($l)] || $x < $rowtextx($l)} return
4136 unmarkmatches
4137 selectline $l 1
4140 proc commit_descriptor {p} {
4141 global commitinfo
4142 if {![info exists commitinfo($p)]} {
4143 getcommit $p
4145 set l "..."
4146 if {[llength $commitinfo($p)] > 1} {
4147 set l [lindex $commitinfo($p) 0]
4149 return "$p ($l)\n"
4152 # append some text to the ctext widget, and make any SHA1 ID
4153 # that we know about be a clickable link.
4154 proc appendwithlinks {text tags} {
4155 global ctext commitrow linknum curview
4157 set start [$ctext index "end - 1c"]
4158 $ctext insert end $text $tags
4159 set links [regexp -indices -all -inline {[0-9a-f]{40}} $text]
4160 foreach l $links {
4161 set s [lindex $l 0]
4162 set e [lindex $l 1]
4163 set linkid [string range $text $s $e]
4164 if {![info exists commitrow($curview,$linkid)]} continue
4165 incr e
4166 $ctext tag add link "$start + $s c" "$start + $e c"
4167 $ctext tag add link$linknum "$start + $s c" "$start + $e c"
4168 $ctext tag bind link$linknum <1> \
4169 [list selectline $commitrow($curview,$linkid) 1]
4170 incr linknum
4172 $ctext tag conf link -foreground blue -underline 1
4173 $ctext tag bind link <Enter> { %W configure -cursor hand2 }
4174 $ctext tag bind link <Leave> { %W configure -cursor $curtextcursor }
4177 proc viewnextline {dir} {
4178 global canv linespc
4180 $canv delete hover
4181 set ymax [lindex [$canv cget -scrollregion] 3]
4182 set wnow [$canv yview]
4183 set wtop [expr {[lindex $wnow 0] * $ymax}]
4184 set newtop [expr {$wtop + $dir * $linespc}]
4185 if {$newtop < 0} {
4186 set newtop 0
4187 } elseif {$newtop > $ymax} {
4188 set newtop $ymax
4190 allcanvs yview moveto [expr {$newtop * 1.0 / $ymax}]
4193 # add a list of tag or branch names at position pos
4194 # returns the number of names inserted
4195 proc appendrefs {pos ids var} {
4196 global ctext commitrow linknum curview $var maxrefs
4198 if {[catch {$ctext index $pos}]} {
4199 return 0
4201 $ctext conf -state normal
4202 $ctext delete $pos "$pos lineend"
4203 set tags {}
4204 foreach id $ids {
4205 foreach tag [set $var\($id\)] {
4206 lappend tags [list $tag $id]
4209 if {[llength $tags] > $maxrefs} {
4210 $ctext insert $pos "many ([llength $tags])"
4211 } else {
4212 set tags [lsort -index 0 -decreasing $tags]
4213 set sep {}
4214 foreach ti $tags {
4215 set id [lindex $ti 1]
4216 set lk link$linknum
4217 incr linknum
4218 $ctext tag delete $lk
4219 $ctext insert $pos $sep
4220 $ctext insert $pos [lindex $ti 0] $lk
4221 if {[info exists commitrow($curview,$id)]} {
4222 $ctext tag conf $lk -foreground blue
4223 $ctext tag bind $lk <1> \
4224 [list selectline $commitrow($curview,$id) 1]
4225 $ctext tag conf $lk -underline 1
4226 $ctext tag bind $lk <Enter> { %W configure -cursor hand2 }
4227 $ctext tag bind $lk <Leave> \
4228 { %W configure -cursor $curtextcursor }
4230 set sep ", "
4233 $ctext conf -state disabled
4234 return [llength $tags]
4237 # called when we have finished computing the nearby tags
4238 proc dispneartags {delay} {
4239 global selectedline currentid showneartags tagphase
4241 if {![info exists selectedline] || !$showneartags} return
4242 after cancel dispnexttag
4243 if {$delay} {
4244 after 200 dispnexttag
4245 set tagphase -1
4246 } else {
4247 after idle dispnexttag
4248 set tagphase 0
4252 proc dispnexttag {} {
4253 global selectedline currentid showneartags tagphase ctext
4255 if {![info exists selectedline] || !$showneartags} return
4256 switch -- $tagphase {
4258 set dtags [desctags $currentid]
4259 if {$dtags ne {}} {
4260 appendrefs precedes $dtags idtags
4264 set atags [anctags $currentid]
4265 if {$atags ne {}} {
4266 appendrefs follows $atags idtags
4270 set dheads [descheads $currentid]
4271 if {$dheads ne {}} {
4272 if {[appendrefs branch $dheads idheads] > 1
4273 && [$ctext get "branch -3c"] eq "h"} {
4274 # turn "Branch" into "Branches"
4275 $ctext conf -state normal
4276 $ctext insert "branch -2c" "es"
4277 $ctext conf -state disabled
4282 if {[incr tagphase] <= 2} {
4283 after idle dispnexttag
4287 proc selectline {l isnew} {
4288 global canv canv2 canv3 ctext commitinfo selectedline
4289 global displayorder linehtag linentag linedtag
4290 global canvy0 linespc parentlist children curview
4291 global currentid sha1entry
4292 global commentend idtags linknum
4293 global mergemax numcommits pending_select
4294 global cmitmode showneartags allcommits
4296 catch {unset pending_select}
4297 $canv delete hover
4298 normalline
4299 cancel_next_highlight
4300 if {$l < 0 || $l >= $numcommits} return
4301 set y [expr {$canvy0 + $l * $linespc}]
4302 set ymax [lindex [$canv cget -scrollregion] 3]
4303 set ytop [expr {$y - $linespc - 1}]
4304 set ybot [expr {$y + $linespc + 1}]
4305 set wnow [$canv yview]
4306 set wtop [expr {[lindex $wnow 0] * $ymax}]
4307 set wbot [expr {[lindex $wnow 1] * $ymax}]
4308 set wh [expr {$wbot - $wtop}]
4309 set newtop $wtop
4310 if {$ytop < $wtop} {
4311 if {$ybot < $wtop} {
4312 set newtop [expr {$y - $wh / 2.0}]
4313 } else {
4314 set newtop $ytop
4315 if {$newtop > $wtop - $linespc} {
4316 set newtop [expr {$wtop - $linespc}]
4319 } elseif {$ybot > $wbot} {
4320 if {$ytop > $wbot} {
4321 set newtop [expr {$y - $wh / 2.0}]
4322 } else {
4323 set newtop [expr {$ybot - $wh}]
4324 if {$newtop < $wtop + $linespc} {
4325 set newtop [expr {$wtop + $linespc}]
4329 if {$newtop != $wtop} {
4330 if {$newtop < 0} {
4331 set newtop 0
4333 allcanvs yview moveto [expr {$newtop * 1.0 / $ymax}]
4334 drawvisible
4337 if {![info exists linehtag($l)]} return
4338 $canv delete secsel
4339 set t [eval $canv create rect [$canv bbox $linehtag($l)] -outline {{}} \
4340 -tags secsel -fill [$canv cget -selectbackground]]
4341 $canv lower $t
4342 $canv2 delete secsel
4343 set t [eval $canv2 create rect [$canv2 bbox $linentag($l)] -outline {{}} \
4344 -tags secsel -fill [$canv2 cget -selectbackground]]
4345 $canv2 lower $t
4346 $canv3 delete secsel
4347 set t [eval $canv3 create rect [$canv3 bbox $linedtag($l)] -outline {{}} \
4348 -tags secsel -fill [$canv3 cget -selectbackground]]
4349 $canv3 lower $t
4351 if {$isnew} {
4352 addtohistory [list selectline $l 0]
4355 set selectedline $l
4357 set id [lindex $displayorder $l]
4358 set currentid $id
4359 $sha1entry delete 0 end
4360 $sha1entry insert 0 $id
4361 $sha1entry selection from 0
4362 $sha1entry selection to end
4363 rhighlight_sel $id
4365 $ctext conf -state normal
4366 clear_ctext
4367 set linknum 0
4368 set info $commitinfo($id)
4369 set date [formatdate [lindex $info 2]]
4370 $ctext insert end "Author: [lindex $info 1] $date\n"
4371 set date [formatdate [lindex $info 4]]
4372 $ctext insert end "Committer: [lindex $info 3] $date\n"
4373 if {[info exists idtags($id)]} {
4374 $ctext insert end "Tags:"
4375 foreach tag $idtags($id) {
4376 $ctext insert end " $tag"
4378 $ctext insert end "\n"
4381 set headers {}
4382 set olds [lindex $parentlist $l]
4383 if {[llength $olds] > 1} {
4384 set np 0
4385 foreach p $olds {
4386 if {$np >= $mergemax} {
4387 set tag mmax
4388 } else {
4389 set tag m$np
4391 $ctext insert end "Parent: " $tag
4392 appendwithlinks [commit_descriptor $p] {}
4393 incr np
4395 } else {
4396 foreach p $olds {
4397 append headers "Parent: [commit_descriptor $p]"
4401 foreach c $children($curview,$id) {
4402 append headers "Child: [commit_descriptor $c]"
4405 # make anything that looks like a SHA1 ID be a clickable link
4406 appendwithlinks $headers {}
4407 if {$showneartags} {
4408 if {![info exists allcommits]} {
4409 getallcommits
4411 $ctext insert end "Branch: "
4412 $ctext mark set branch "end -1c"
4413 $ctext mark gravity branch left
4414 $ctext insert end "\nFollows: "
4415 $ctext mark set follows "end -1c"
4416 $ctext mark gravity follows left
4417 $ctext insert end "\nPrecedes: "
4418 $ctext mark set precedes "end -1c"
4419 $ctext mark gravity precedes left
4420 $ctext insert end "\n"
4421 dispneartags 1
4423 $ctext insert end "\n"
4424 set comment [lindex $info 5]
4425 if {[string first "\r" $comment] >= 0} {
4426 set comment [string map {"\r" "\n "} $comment]
4428 appendwithlinks $comment {comment}
4430 $ctext tag remove found 1.0 end
4431 $ctext conf -state disabled
4432 set commentend [$ctext index "end - 1c"]
4434 init_flist "Comments"
4435 if {$cmitmode eq "tree"} {
4436 gettree $id
4437 } elseif {[llength $olds] <= 1} {
4438 startdiff $id
4439 } else {
4440 mergediff $id $l
4444 proc selfirstline {} {
4445 unmarkmatches
4446 selectline 0 1
4449 proc sellastline {} {
4450 global numcommits
4451 unmarkmatches
4452 set l [expr {$numcommits - 1}]
4453 selectline $l 1
4456 proc selnextline {dir} {
4457 global selectedline
4458 if {![info exists selectedline]} return
4459 set l [expr {$selectedline + $dir}]
4460 unmarkmatches
4461 selectline $l 1
4464 proc selnextpage {dir} {
4465 global canv linespc selectedline numcommits
4467 set lpp [expr {([winfo height $canv] - 2) / $linespc}]
4468 if {$lpp < 1} {
4469 set lpp 1
4471 allcanvs yview scroll [expr {$dir * $lpp}] units
4472 drawvisible
4473 if {![info exists selectedline]} return
4474 set l [expr {$selectedline + $dir * $lpp}]
4475 if {$l < 0} {
4476 set l 0
4477 } elseif {$l >= $numcommits} {
4478 set l [expr $numcommits - 1]
4480 unmarkmatches
4481 selectline $l 1
4484 proc unselectline {} {
4485 global selectedline currentid
4487 catch {unset selectedline}
4488 catch {unset currentid}
4489 allcanvs delete secsel
4490 rhighlight_none
4491 cancel_next_highlight
4494 proc reselectline {} {
4495 global selectedline
4497 if {[info exists selectedline]} {
4498 selectline $selectedline 0
4502 proc addtohistory {cmd} {
4503 global history historyindex curview
4505 set elt [list $curview $cmd]
4506 if {$historyindex > 0
4507 && [lindex $history [expr {$historyindex - 1}]] == $elt} {
4508 return
4511 if {$historyindex < [llength $history]} {
4512 set history [lreplace $history $historyindex end $elt]
4513 } else {
4514 lappend history $elt
4516 incr historyindex
4517 if {$historyindex > 1} {
4518 .tf.bar.leftbut conf -state normal
4519 } else {
4520 .tf.bar.leftbut conf -state disabled
4522 .tf.bar.rightbut conf -state disabled
4525 proc godo {elt} {
4526 global curview
4528 set view [lindex $elt 0]
4529 set cmd [lindex $elt 1]
4530 if {$curview != $view} {
4531 showview $view
4533 eval $cmd
4536 proc goback {} {
4537 global history historyindex
4539 if {$historyindex > 1} {
4540 incr historyindex -1
4541 godo [lindex $history [expr {$historyindex - 1}]]
4542 .tf.bar.rightbut conf -state normal
4544 if {$historyindex <= 1} {
4545 .tf.bar.leftbut conf -state disabled
4549 proc goforw {} {
4550 global history historyindex
4552 if {$historyindex < [llength $history]} {
4553 set cmd [lindex $history $historyindex]
4554 incr historyindex
4555 godo $cmd
4556 .tf.bar.leftbut conf -state normal
4558 if {$historyindex >= [llength $history]} {
4559 .tf.bar.rightbut conf -state disabled
4563 proc gettree {id} {
4564 global treefilelist treeidlist diffids diffmergeid treepending nullid
4566 set diffids $id
4567 catch {unset diffmergeid}
4568 if {![info exists treefilelist($id)]} {
4569 if {![info exists treepending]} {
4570 if {$id ne $nullid} {
4571 set cmd [concat | git ls-tree -r $id]
4572 } else {
4573 set cmd [concat | git ls-files]
4575 if {[catch {set gtf [open $cmd r]}]} {
4576 return
4578 set treepending $id
4579 set treefilelist($id) {}
4580 set treeidlist($id) {}
4581 fconfigure $gtf -blocking 0
4582 filerun $gtf [list gettreeline $gtf $id]
4584 } else {
4585 setfilelist $id
4589 proc gettreeline {gtf id} {
4590 global treefilelist treeidlist treepending cmitmode diffids nullid
4592 set nl 0
4593 while {[incr nl] <= 1000 && [gets $gtf line] >= 0} {
4594 if {$diffids ne $nullid} {
4595 if {[lindex $line 1] ne "blob"} continue
4596 set i [string first "\t" $line]
4597 if {$i < 0} continue
4598 set sha1 [lindex $line 2]
4599 set fname [string range $line [expr {$i+1}] end]
4600 if {[string index $fname 0] eq "\""} {
4601 set fname [lindex $fname 0]
4603 lappend treeidlist($id) $sha1
4604 } else {
4605 set fname $line
4607 lappend treefilelist($id) $fname
4609 if {![eof $gtf]} {
4610 return [expr {$nl >= 1000? 2: 1}]
4612 close $gtf
4613 unset treepending
4614 if {$cmitmode ne "tree"} {
4615 if {![info exists diffmergeid]} {
4616 gettreediffs $diffids
4618 } elseif {$id ne $diffids} {
4619 gettree $diffids
4620 } else {
4621 setfilelist $id
4623 return 0
4626 proc showfile {f} {
4627 global treefilelist treeidlist diffids nullid
4628 global ctext commentend
4630 set i [lsearch -exact $treefilelist($diffids) $f]
4631 if {$i < 0} {
4632 puts "oops, $f not in list for id $diffids"
4633 return
4635 if {$diffids ne $nullid} {
4636 set blob [lindex $treeidlist($diffids) $i]
4637 if {[catch {set bf [open [concat | git cat-file blob $blob] r]} err]} {
4638 puts "oops, error reading blob $blob: $err"
4639 return
4641 } else {
4642 if {[catch {set bf [open $f r]} err]} {
4643 puts "oops, can't read $f: $err"
4644 return
4647 fconfigure $bf -blocking 0
4648 filerun $bf [list getblobline $bf $diffids]
4649 $ctext config -state normal
4650 clear_ctext $commentend
4651 $ctext insert end "\n"
4652 $ctext insert end "$f\n" filesep
4653 $ctext config -state disabled
4654 $ctext yview $commentend
4657 proc getblobline {bf id} {
4658 global diffids cmitmode ctext
4660 if {$id ne $diffids || $cmitmode ne "tree"} {
4661 catch {close $bf}
4662 return 0
4664 $ctext config -state normal
4665 set nl 0
4666 while {[incr nl] <= 1000 && [gets $bf line] >= 0} {
4667 $ctext insert end "$line\n"
4669 if {[eof $bf]} {
4670 # delete last newline
4671 $ctext delete "end - 2c" "end - 1c"
4672 close $bf
4673 return 0
4675 $ctext config -state disabled
4676 return [expr {$nl >= 1000? 2: 1}]
4679 proc mergediff {id l} {
4680 global diffmergeid diffopts mdifffd
4681 global diffids
4682 global parentlist
4684 set diffmergeid $id
4685 set diffids $id
4686 # this doesn't seem to actually affect anything...
4687 set env(GIT_DIFF_OPTS) $diffopts
4688 set cmd [concat | git diff-tree --no-commit-id --cc $id]
4689 if {[catch {set mdf [open $cmd r]} err]} {
4690 error_popup "Error getting merge diffs: $err"
4691 return
4693 fconfigure $mdf -blocking 0
4694 set mdifffd($id) $mdf
4695 set np [llength [lindex $parentlist $l]]
4696 filerun $mdf [list getmergediffline $mdf $id $np]
4699 proc getmergediffline {mdf id np} {
4700 global diffmergeid ctext cflist mergemax
4701 global difffilestart mdifffd
4703 $ctext conf -state normal
4704 set nr 0
4705 while {[incr nr] <= 1000 && [gets $mdf line] >= 0} {
4706 if {![info exists diffmergeid] || $id != $diffmergeid
4707 || $mdf != $mdifffd($id)} {
4708 close $mdf
4709 return 0
4711 if {[regexp {^diff --cc (.*)} $line match fname]} {
4712 # start of a new file
4713 $ctext insert end "\n"
4714 set here [$ctext index "end - 1c"]
4715 lappend difffilestart $here
4716 add_flist [list $fname]
4717 set l [expr {(78 - [string length $fname]) / 2}]
4718 set pad [string range "----------------------------------------" 1 $l]
4719 $ctext insert end "$pad $fname $pad\n" filesep
4720 } elseif {[regexp {^@@} $line]} {
4721 $ctext insert end "$line\n" hunksep
4722 } elseif {[regexp {^[0-9a-f]{40}$} $line] || [regexp {^index} $line]} {
4723 # do nothing
4724 } else {
4725 # parse the prefix - one ' ', '-' or '+' for each parent
4726 set spaces {}
4727 set minuses {}
4728 set pluses {}
4729 set isbad 0
4730 for {set j 0} {$j < $np} {incr j} {
4731 set c [string range $line $j $j]
4732 if {$c == " "} {
4733 lappend spaces $j
4734 } elseif {$c == "-"} {
4735 lappend minuses $j
4736 } elseif {$c == "+"} {
4737 lappend pluses $j
4738 } else {
4739 set isbad 1
4740 break
4743 set tags {}
4744 set num {}
4745 if {!$isbad && $minuses ne {} && $pluses eq {}} {
4746 # line doesn't appear in result, parents in $minuses have the line
4747 set num [lindex $minuses 0]
4748 } elseif {!$isbad && $pluses ne {} && $minuses eq {}} {
4749 # line appears in result, parents in $pluses don't have the line
4750 lappend tags mresult
4751 set num [lindex $spaces 0]
4753 if {$num ne {}} {
4754 if {$num >= $mergemax} {
4755 set num "max"
4757 lappend tags m$num
4759 $ctext insert end "$line\n" $tags
4762 $ctext conf -state disabled
4763 if {[eof $mdf]} {
4764 close $mdf
4765 return 0
4767 return [expr {$nr >= 1000? 2: 1}]
4770 proc startdiff {ids} {
4771 global treediffs diffids treepending diffmergeid nullid
4773 set diffids $ids
4774 catch {unset diffmergeid}
4775 if {![info exists treediffs($ids)] || [lsearch -exact $ids $nullid] >= 0} {
4776 if {![info exists treepending]} {
4777 gettreediffs $ids
4779 } else {
4780 addtocflist $ids
4784 proc addtocflist {ids} {
4785 global treediffs cflist
4786 add_flist $treediffs($ids)
4787 getblobdiffs $ids
4790 proc diffcmd {ids flags} {
4791 global nullid
4793 set i [lsearch -exact $ids $nullid]
4794 if {$i >= 0} {
4795 set cmd [concat | git diff-index $flags]
4796 if {[llength $ids] > 1} {
4797 if {$i == 0} {
4798 lappend cmd -R [lindex $ids 1]
4799 } else {
4800 lappend cmd [lindex $ids 0]
4802 } else {
4803 lappend cmd HEAD
4805 } else {
4806 set cmd [concat | git diff-tree --no-commit-id -r $flags $ids]
4808 return $cmd
4811 proc gettreediffs {ids} {
4812 global treediff treepending
4814 set treepending $ids
4815 set treediff {}
4816 if {[catch {set gdtf [open [diffcmd $ids {}] r]}]} return
4817 fconfigure $gdtf -blocking 0
4818 filerun $gdtf [list gettreediffline $gdtf $ids]
4821 proc gettreediffline {gdtf ids} {
4822 global treediff treediffs treepending diffids diffmergeid
4823 global cmitmode
4825 set nr 0
4826 while {[incr nr] <= 1000 && [gets $gdtf line] >= 0} {
4827 set i [string first "\t" $line]
4828 if {$i >= 0} {
4829 set file [string range $line [expr {$i+1}] end]
4830 if {[string index $file 0] eq "\""} {
4831 set file [lindex $file 0]
4833 lappend treediff $file
4836 if {![eof $gdtf]} {
4837 return [expr {$nr >= 1000? 2: 1}]
4839 close $gdtf
4840 set treediffs($ids) $treediff
4841 unset treepending
4842 if {$cmitmode eq "tree"} {
4843 gettree $diffids
4844 } elseif {$ids != $diffids} {
4845 if {![info exists diffmergeid]} {
4846 gettreediffs $diffids
4848 } else {
4849 addtocflist $ids
4851 return 0
4854 proc getblobdiffs {ids} {
4855 global diffopts blobdifffd diffids env
4856 global diffinhdr treediffs
4858 set env(GIT_DIFF_OPTS) $diffopts
4859 if {[catch {set bdf [open [diffcmd $ids {-p -C}] r]} err]} {
4860 puts "error getting diffs: $err"
4861 return
4863 set diffinhdr 0
4864 fconfigure $bdf -blocking 0
4865 set blobdifffd($ids) $bdf
4866 filerun $bdf [list getblobdiffline $bdf $diffids]
4869 proc setinlist {var i val} {
4870 global $var
4872 while {[llength [set $var]] < $i} {
4873 lappend $var {}
4875 if {[llength [set $var]] == $i} {
4876 lappend $var $val
4877 } else {
4878 lset $var $i $val
4882 proc makediffhdr {fname ids} {
4883 global ctext curdiffstart treediffs
4885 set i [lsearch -exact $treediffs($ids) $fname]
4886 if {$i >= 0} {
4887 setinlist difffilestart $i $curdiffstart
4889 set l [expr {(78 - [string length $fname]) / 2}]
4890 set pad [string range "----------------------------------------" 1 $l]
4891 $ctext insert $curdiffstart "$pad $fname $pad" filesep
4894 proc getblobdiffline {bdf ids} {
4895 global diffids blobdifffd ctext curdiffstart
4896 global diffnexthead diffnextnote difffilestart
4897 global diffinhdr treediffs
4899 set nr 0
4900 $ctext conf -state normal
4901 while {[incr nr] <= 1000 && [gets $bdf line] >= 0} {
4902 if {$ids != $diffids || $bdf != $blobdifffd($ids)} {
4903 close $bdf
4904 return 0
4906 if {![string compare -length 11 "diff --git " $line]} {
4907 # trim off "diff --git "
4908 set line [string range $line 11 end]
4909 set diffinhdr 1
4910 # start of a new file
4911 $ctext insert end "\n"
4912 set curdiffstart [$ctext index "end - 1c"]
4913 $ctext insert end "\n" filesep
4914 # If the name hasn't changed the length will be odd,
4915 # the middle char will be a space, and the two bits either
4916 # side will be a/name and b/name, or "a/name" and "b/name".
4917 # If the name has changed we'll get "rename from" and
4918 # "rename to" lines following this, and we'll use them
4919 # to get the filenames.
4920 # This complexity is necessary because spaces in the filename(s)
4921 # don't get escaped.
4922 set l [string length $line]
4923 set i [expr {$l / 2}]
4924 if {!(($l & 1) && [string index $line $i] eq " " &&
4925 [string range $line 2 [expr {$i - 1}]] eq \
4926 [string range $line [expr {$i + 3}] end])} {
4927 continue
4929 # unescape if quoted and chop off the a/ from the front
4930 if {[string index $line 0] eq "\""} {
4931 set fname [string range [lindex $line 0] 2 end]
4932 } else {
4933 set fname [string range $line 2 [expr {$i - 1}]]
4935 makediffhdr $fname $ids
4937 } elseif {[regexp {^@@ -([0-9]+)(,[0-9]+)? \+([0-9]+)(,[0-9]+)? @@(.*)} \
4938 $line match f1l f1c f2l f2c rest]} {
4939 $ctext insert end "$line\n" hunksep
4940 set diffinhdr 0
4942 } elseif {$diffinhdr} {
4943 if {![string compare -length 12 "rename from " $line]} {
4944 set fname [string range $line 12 end]
4945 if {[string index $fname 0] eq "\""} {
4946 set fname [lindex $fname 0]
4948 set i [lsearch -exact $treediffs($ids) $fname]
4949 if {$i >= 0} {
4950 setinlist difffilestart $i $curdiffstart
4952 } elseif {![string compare -length 10 $line "rename to "]} {
4953 set fname [string range $line 10 end]
4954 if {[string index $fname 0] eq "\""} {
4955 set fname [lindex $fname 0]
4957 makediffhdr $fname $ids
4958 } elseif {[string compare -length 3 $line "---"] == 0} {
4959 # do nothing
4960 continue
4961 } elseif {[string compare -length 3 $line "+++"] == 0} {
4962 set diffinhdr 0
4963 continue
4965 $ctext insert end "$line\n" filesep
4967 } else {
4968 set x [string range $line 0 0]
4969 if {$x == "-" || $x == "+"} {
4970 set tag [expr {$x == "+"}]
4971 $ctext insert end "$line\n" d$tag
4972 } elseif {$x == " "} {
4973 $ctext insert end "$line\n"
4974 } else {
4975 # "\ No newline at end of file",
4976 # or something else we don't recognize
4977 $ctext insert end "$line\n" hunksep
4981 $ctext conf -state disabled
4982 if {[eof $bdf]} {
4983 close $bdf
4984 return 0
4986 return [expr {$nr >= 1000? 2: 1}]
4989 proc changediffdisp {} {
4990 global ctext diffelide
4992 $ctext tag conf d0 -elide [lindex $diffelide 0]
4993 $ctext tag conf d1 -elide [lindex $diffelide 1]
4996 proc prevfile {} {
4997 global difffilestart ctext
4998 set prev [lindex $difffilestart 0]
4999 set here [$ctext index @0,0]
5000 foreach loc $difffilestart {
5001 if {[$ctext compare $loc >= $here]} {
5002 $ctext yview $prev
5003 return
5005 set prev $loc
5007 $ctext yview $prev
5010 proc nextfile {} {
5011 global difffilestart ctext
5012 set here [$ctext index @0,0]
5013 foreach loc $difffilestart {
5014 if {[$ctext compare $loc > $here]} {
5015 $ctext yview $loc
5016 return
5021 proc clear_ctext {{first 1.0}} {
5022 global ctext smarktop smarkbot
5024 set l [lindex [split $first .] 0]
5025 if {![info exists smarktop] || [$ctext compare $first < $smarktop.0]} {
5026 set smarktop $l
5028 if {![info exists smarkbot] || [$ctext compare $first < $smarkbot.0]} {
5029 set smarkbot $l
5031 $ctext delete $first end
5034 proc incrsearch {name ix op} {
5035 global ctext searchstring searchdirn
5037 $ctext tag remove found 1.0 end
5038 if {[catch {$ctext index anchor}]} {
5039 # no anchor set, use start of selection, or of visible area
5040 set sel [$ctext tag ranges sel]
5041 if {$sel ne {}} {
5042 $ctext mark set anchor [lindex $sel 0]
5043 } elseif {$searchdirn eq "-forwards"} {
5044 $ctext mark set anchor @0,0
5045 } else {
5046 $ctext mark set anchor @0,[winfo height $ctext]
5049 if {$searchstring ne {}} {
5050 set here [$ctext search $searchdirn -- $searchstring anchor]
5051 if {$here ne {}} {
5052 $ctext see $here
5054 searchmarkvisible 1
5058 proc dosearch {} {
5059 global sstring ctext searchstring searchdirn
5061 focus $sstring
5062 $sstring icursor end
5063 set searchdirn -forwards
5064 if {$searchstring ne {}} {
5065 set sel [$ctext tag ranges sel]
5066 if {$sel ne {}} {
5067 set start "[lindex $sel 0] + 1c"
5068 } elseif {[catch {set start [$ctext index anchor]}]} {
5069 set start "@0,0"
5071 set match [$ctext search -count mlen -- $searchstring $start]
5072 $ctext tag remove sel 1.0 end
5073 if {$match eq {}} {
5074 bell
5075 return
5077 $ctext see $match
5078 set mend "$match + $mlen c"
5079 $ctext tag add sel $match $mend
5080 $ctext mark unset anchor
5084 proc dosearchback {} {
5085 global sstring ctext searchstring searchdirn
5087 focus $sstring
5088 $sstring icursor end
5089 set searchdirn -backwards
5090 if {$searchstring ne {}} {
5091 set sel [$ctext tag ranges sel]
5092 if {$sel ne {}} {
5093 set start [lindex $sel 0]
5094 } elseif {[catch {set start [$ctext index anchor]}]} {
5095 set start @0,[winfo height $ctext]
5097 set match [$ctext search -backwards -count ml -- $searchstring $start]
5098 $ctext tag remove sel 1.0 end
5099 if {$match eq {}} {
5100 bell
5101 return
5103 $ctext see $match
5104 set mend "$match + $ml c"
5105 $ctext tag add sel $match $mend
5106 $ctext mark unset anchor
5110 proc searchmark {first last} {
5111 global ctext searchstring
5113 set mend $first.0
5114 while {1} {
5115 set match [$ctext search -count mlen -- $searchstring $mend $last.end]
5116 if {$match eq {}} break
5117 set mend "$match + $mlen c"
5118 $ctext tag add found $match $mend
5122 proc searchmarkvisible {doall} {
5123 global ctext smarktop smarkbot
5125 set topline [lindex [split [$ctext index @0,0] .] 0]
5126 set botline [lindex [split [$ctext index @0,[winfo height $ctext]] .] 0]
5127 if {$doall || $botline < $smarktop || $topline > $smarkbot} {
5128 # no overlap with previous
5129 searchmark $topline $botline
5130 set smarktop $topline
5131 set smarkbot $botline
5132 } else {
5133 if {$topline < $smarktop} {
5134 searchmark $topline [expr {$smarktop-1}]
5135 set smarktop $topline
5137 if {$botline > $smarkbot} {
5138 searchmark [expr {$smarkbot+1}] $botline
5139 set smarkbot $botline
5144 proc scrolltext {f0 f1} {
5145 global searchstring
5147 .bleft.sb set $f0 $f1
5148 if {$searchstring ne {}} {
5149 searchmarkvisible 0
5153 proc setcoords {} {
5154 global linespc charspc canvx0 canvy0 mainfont
5155 global xspc1 xspc2 lthickness
5157 set linespc [font metrics $mainfont -linespace]
5158 set charspc [font measure $mainfont "m"]
5159 set canvy0 [expr {int(3 + 0.5 * $linespc)}]
5160 set canvx0 [expr {int(3 + 0.5 * $linespc)}]
5161 set lthickness [expr {int($linespc / 9) + 1}]
5162 set xspc1(0) $linespc
5163 set xspc2 $linespc
5166 proc redisplay {} {
5167 global canv
5168 global selectedline
5170 set ymax [lindex [$canv cget -scrollregion] 3]
5171 if {$ymax eq {} || $ymax == 0} return
5172 set span [$canv yview]
5173 clear_display
5174 setcanvscroll
5175 allcanvs yview moveto [lindex $span 0]
5176 drawvisible
5177 if {[info exists selectedline]} {
5178 selectline $selectedline 0
5179 allcanvs yview moveto [lindex $span 0]
5183 proc incrfont {inc} {
5184 global mainfont textfont ctext canv phase cflist
5185 global charspc tabstop
5186 global stopped entries
5187 unmarkmatches
5188 set mainfont [lreplace $mainfont 1 1 [expr {[lindex $mainfont 1] + $inc}]]
5189 set textfont [lreplace $textfont 1 1 [expr {[lindex $textfont 1] + $inc}]]
5190 setcoords
5191 $ctext conf -font $textfont -tabs "[expr {$tabstop * $charspc}]"
5192 $cflist conf -font $textfont
5193 $ctext tag conf filesep -font [concat $textfont bold]
5194 foreach e $entries {
5195 $e conf -font $mainfont
5197 if {$phase eq "getcommits"} {
5198 $canv itemconf textitems -font $mainfont
5200 redisplay
5203 proc clearsha1 {} {
5204 global sha1entry sha1string
5205 if {[string length $sha1string] == 40} {
5206 $sha1entry delete 0 end
5210 proc sha1change {n1 n2 op} {
5211 global sha1string currentid sha1but
5212 if {$sha1string == {}
5213 || ([info exists currentid] && $sha1string == $currentid)} {
5214 set state disabled
5215 } else {
5216 set state normal
5218 if {[$sha1but cget -state] == $state} return
5219 if {$state == "normal"} {
5220 $sha1but conf -state normal -relief raised -text "Goto: "
5221 } else {
5222 $sha1but conf -state disabled -relief flat -text "SHA1 ID: "
5226 proc gotocommit {} {
5227 global sha1string currentid commitrow tagids headids
5228 global displayorder numcommits curview
5230 if {$sha1string == {}
5231 || ([info exists currentid] && $sha1string == $currentid)} return
5232 if {[info exists tagids($sha1string)]} {
5233 set id $tagids($sha1string)
5234 } elseif {[info exists headids($sha1string)]} {
5235 set id $headids($sha1string)
5236 } else {
5237 set id [string tolower $sha1string]
5238 if {[regexp {^[0-9a-f]{4,39}$} $id]} {
5239 set matches {}
5240 foreach i $displayorder {
5241 if {[string match $id* $i]} {
5242 lappend matches $i
5245 if {$matches ne {}} {
5246 if {[llength $matches] > 1} {
5247 error_popup "Short SHA1 id $id is ambiguous"
5248 return
5250 set id [lindex $matches 0]
5254 if {[info exists commitrow($curview,$id)]} {
5255 selectline $commitrow($curview,$id) 1
5256 return
5258 if {[regexp {^[0-9a-fA-F]{4,}$} $sha1string]} {
5259 set type "SHA1 id"
5260 } else {
5261 set type "Tag/Head"
5263 error_popup "$type $sha1string is not known"
5266 proc lineenter {x y id} {
5267 global hoverx hovery hoverid hovertimer
5268 global commitinfo canv
5270 if {![info exists commitinfo($id)] && ![getcommit $id]} return
5271 set hoverx $x
5272 set hovery $y
5273 set hoverid $id
5274 if {[info exists hovertimer]} {
5275 after cancel $hovertimer
5277 set hovertimer [after 500 linehover]
5278 $canv delete hover
5281 proc linemotion {x y id} {
5282 global hoverx hovery hoverid hovertimer
5284 if {[info exists hoverid] && $id == $hoverid} {
5285 set hoverx $x
5286 set hovery $y
5287 if {[info exists hovertimer]} {
5288 after cancel $hovertimer
5290 set hovertimer [after 500 linehover]
5294 proc lineleave {id} {
5295 global hoverid hovertimer canv
5297 if {[info exists hoverid] && $id == $hoverid} {
5298 $canv delete hover
5299 if {[info exists hovertimer]} {
5300 after cancel $hovertimer
5301 unset hovertimer
5303 unset hoverid
5307 proc linehover {} {
5308 global hoverx hovery hoverid hovertimer
5309 global canv linespc lthickness
5310 global commitinfo mainfont
5312 set text [lindex $commitinfo($hoverid) 0]
5313 set ymax [lindex [$canv cget -scrollregion] 3]
5314 if {$ymax == {}} return
5315 set yfrac [lindex [$canv yview] 0]
5316 set x [expr {$hoverx + 2 * $linespc}]
5317 set y [expr {$hovery + $yfrac * $ymax - $linespc / 2}]
5318 set x0 [expr {$x - 2 * $lthickness}]
5319 set y0 [expr {$y - 2 * $lthickness}]
5320 set x1 [expr {$x + [font measure $mainfont $text] + 2 * $lthickness}]
5321 set y1 [expr {$y + $linespc + 2 * $lthickness}]
5322 set t [$canv create rectangle $x0 $y0 $x1 $y1 \
5323 -fill \#ffff80 -outline black -width 1 -tags hover]
5324 $canv raise $t
5325 set t [$canv create text $x $y -anchor nw -text $text -tags hover \
5326 -font $mainfont]
5327 $canv raise $t
5330 proc clickisonarrow {id y} {
5331 global lthickness
5333 set ranges [rowranges $id]
5334 set thresh [expr {2 * $lthickness + 6}]
5335 set n [expr {[llength $ranges] - 1}]
5336 for {set i 1} {$i < $n} {incr i} {
5337 set row [lindex $ranges $i]
5338 if {abs([yc $row] - $y) < $thresh} {
5339 return $i
5342 return {}
5345 proc arrowjump {id n y} {
5346 global canv
5348 # 1 <-> 2, 3 <-> 4, etc...
5349 set n [expr {(($n - 1) ^ 1) + 1}]
5350 set row [lindex [rowranges $id] $n]
5351 set yt [yc $row]
5352 set ymax [lindex [$canv cget -scrollregion] 3]
5353 if {$ymax eq {} || $ymax <= 0} return
5354 set view [$canv yview]
5355 set yspan [expr {[lindex $view 1] - [lindex $view 0]}]
5356 set yfrac [expr {$yt / $ymax - $yspan / 2}]
5357 if {$yfrac < 0} {
5358 set yfrac 0
5360 allcanvs yview moveto $yfrac
5363 proc lineclick {x y id isnew} {
5364 global ctext commitinfo children canv thickerline curview
5366 if {![info exists commitinfo($id)] && ![getcommit $id]} return
5367 unmarkmatches
5368 unselectline
5369 normalline
5370 $canv delete hover
5371 # draw this line thicker than normal
5372 set thickerline $id
5373 drawlines $id
5374 if {$isnew} {
5375 set ymax [lindex [$canv cget -scrollregion] 3]
5376 if {$ymax eq {}} return
5377 set yfrac [lindex [$canv yview] 0]
5378 set y [expr {$y + $yfrac * $ymax}]
5380 set dirn [clickisonarrow $id $y]
5381 if {$dirn ne {}} {
5382 arrowjump $id $dirn $y
5383 return
5386 if {$isnew} {
5387 addtohistory [list lineclick $x $y $id 0]
5389 # fill the details pane with info about this line
5390 $ctext conf -state normal
5391 clear_ctext
5392 $ctext tag conf link -foreground blue -underline 1
5393 $ctext tag bind link <Enter> { %W configure -cursor hand2 }
5394 $ctext tag bind link <Leave> { %W configure -cursor $curtextcursor }
5395 $ctext insert end "Parent:\t"
5396 $ctext insert end $id [list link link0]
5397 $ctext tag bind link0 <1> [list selbyid $id]
5398 set info $commitinfo($id)
5399 $ctext insert end "\n\t[lindex $info 0]\n"
5400 $ctext insert end "\tAuthor:\t[lindex $info 1]\n"
5401 set date [formatdate [lindex $info 2]]
5402 $ctext insert end "\tDate:\t$date\n"
5403 set kids $children($curview,$id)
5404 if {$kids ne {}} {
5405 $ctext insert end "\nChildren:"
5406 set i 0
5407 foreach child $kids {
5408 incr i
5409 if {![info exists commitinfo($child)] && ![getcommit $child]} continue
5410 set info $commitinfo($child)
5411 $ctext insert end "\n\t"
5412 $ctext insert end $child [list link link$i]
5413 $ctext tag bind link$i <1> [list selbyid $child]
5414 $ctext insert end "\n\t[lindex $info 0]"
5415 $ctext insert end "\n\tAuthor:\t[lindex $info 1]"
5416 set date [formatdate [lindex $info 2]]
5417 $ctext insert end "\n\tDate:\t$date\n"
5420 $ctext conf -state disabled
5421 init_flist {}
5424 proc normalline {} {
5425 global thickerline
5426 if {[info exists thickerline]} {
5427 set id $thickerline
5428 unset thickerline
5429 drawlines $id
5433 proc selbyid {id} {
5434 global commitrow curview
5435 if {[info exists commitrow($curview,$id)]} {
5436 selectline $commitrow($curview,$id) 1
5440 proc mstime {} {
5441 global startmstime
5442 if {![info exists startmstime]} {
5443 set startmstime [clock clicks -milliseconds]
5445 return [format "%.3f" [expr {([clock click -milliseconds] - $startmstime) / 1000.0}]]
5448 proc rowmenu {x y id} {
5449 global rowctxmenu commitrow selectedline rowmenuid curview
5450 global nullid fakerowmenu mainhead
5452 set rowmenuid $id
5453 if {![info exists selectedline]
5454 || $commitrow($curview,$id) eq $selectedline} {
5455 set state disabled
5456 } else {
5457 set state normal
5459 if {$id ne $nullid} {
5460 set menu $rowctxmenu
5461 $menu entryconfigure 7 -label "Reset $mainhead branch to here"
5462 } else {
5463 set menu $fakerowmenu
5465 $menu entryconfigure "Diff this*" -state $state
5466 $menu entryconfigure "Diff selected*" -state $state
5467 $menu entryconfigure "Make patch" -state $state
5468 tk_popup $menu $x $y
5471 proc diffvssel {dirn} {
5472 global rowmenuid selectedline displayorder
5474 if {![info exists selectedline]} return
5475 if {$dirn} {
5476 set oldid [lindex $displayorder $selectedline]
5477 set newid $rowmenuid
5478 } else {
5479 set oldid $rowmenuid
5480 set newid [lindex $displayorder $selectedline]
5482 addtohistory [list doseldiff $oldid $newid]
5483 doseldiff $oldid $newid
5486 proc doseldiff {oldid newid} {
5487 global ctext
5488 global commitinfo
5490 $ctext conf -state normal
5491 clear_ctext
5492 init_flist "Top"
5493 $ctext insert end "From "
5494 $ctext tag conf link -foreground blue -underline 1
5495 $ctext tag bind link <Enter> { %W configure -cursor hand2 }
5496 $ctext tag bind link <Leave> { %W configure -cursor $curtextcursor }
5497 $ctext tag bind link0 <1> [list selbyid $oldid]
5498 $ctext insert end $oldid [list link link0]
5499 $ctext insert end "\n "
5500 $ctext insert end [lindex $commitinfo($oldid) 0]
5501 $ctext insert end "\n\nTo "
5502 $ctext tag bind link1 <1> [list selbyid $newid]
5503 $ctext insert end $newid [list link link1]
5504 $ctext insert end "\n "
5505 $ctext insert end [lindex $commitinfo($newid) 0]
5506 $ctext insert end "\n"
5507 $ctext conf -state disabled
5508 $ctext tag remove found 1.0 end
5509 startdiff [list $oldid $newid]
5512 proc mkpatch {} {
5513 global rowmenuid currentid commitinfo patchtop patchnum
5515 if {![info exists currentid]} return
5516 set oldid $currentid
5517 set oldhead [lindex $commitinfo($oldid) 0]
5518 set newid $rowmenuid
5519 set newhead [lindex $commitinfo($newid) 0]
5520 set top .patch
5521 set patchtop $top
5522 catch {destroy $top}
5523 toplevel $top
5524 label $top.title -text "Generate patch"
5525 grid $top.title - -pady 10
5526 label $top.from -text "From:"
5527 entry $top.fromsha1 -width 40 -relief flat
5528 $top.fromsha1 insert 0 $oldid
5529 $top.fromsha1 conf -state readonly
5530 grid $top.from $top.fromsha1 -sticky w
5531 entry $top.fromhead -width 60 -relief flat
5532 $top.fromhead insert 0 $oldhead
5533 $top.fromhead conf -state readonly
5534 grid x $top.fromhead -sticky w
5535 label $top.to -text "To:"
5536 entry $top.tosha1 -width 40 -relief flat
5537 $top.tosha1 insert 0 $newid
5538 $top.tosha1 conf -state readonly
5539 grid $top.to $top.tosha1 -sticky w
5540 entry $top.tohead -width 60 -relief flat
5541 $top.tohead insert 0 $newhead
5542 $top.tohead conf -state readonly
5543 grid x $top.tohead -sticky w
5544 button $top.rev -text "Reverse" -command mkpatchrev -padx 5
5545 grid $top.rev x -pady 10
5546 label $top.flab -text "Output file:"
5547 entry $top.fname -width 60
5548 $top.fname insert 0 [file normalize "patch$patchnum.patch"]
5549 incr patchnum
5550 grid $top.flab $top.fname -sticky w
5551 frame $top.buts
5552 button $top.buts.gen -text "Generate" -command mkpatchgo
5553 button $top.buts.can -text "Cancel" -command mkpatchcan
5554 grid $top.buts.gen $top.buts.can
5555 grid columnconfigure $top.buts 0 -weight 1 -uniform a
5556 grid columnconfigure $top.buts 1 -weight 1 -uniform a
5557 grid $top.buts - -pady 10 -sticky ew
5558 focus $top.fname
5561 proc mkpatchrev {} {
5562 global patchtop
5564 set oldid [$patchtop.fromsha1 get]
5565 set oldhead [$patchtop.fromhead get]
5566 set newid [$patchtop.tosha1 get]
5567 set newhead [$patchtop.tohead get]
5568 foreach e [list fromsha1 fromhead tosha1 tohead] \
5569 v [list $newid $newhead $oldid $oldhead] {
5570 $patchtop.$e conf -state normal
5571 $patchtop.$e delete 0 end
5572 $patchtop.$e insert 0 $v
5573 $patchtop.$e conf -state readonly
5577 proc mkpatchgo {} {
5578 global patchtop nullid
5580 set oldid [$patchtop.fromsha1 get]
5581 set newid [$patchtop.tosha1 get]
5582 set fname [$patchtop.fname get]
5583 if {$newid eq $nullid} {
5584 set cmd [list git diff-index -p $oldid]
5585 } elseif {$oldid eq $nullid} {
5586 set cmd [list git diff-index -p -R $newid]
5587 } else {
5588 set cmd [list git diff-tree -p $oldid $newid]
5590 lappend cmd >$fname &
5591 if {[catch {eval exec $cmd} err]} {
5592 error_popup "Error creating patch: $err"
5594 catch {destroy $patchtop}
5595 unset patchtop
5598 proc mkpatchcan {} {
5599 global patchtop
5601 catch {destroy $patchtop}
5602 unset patchtop
5605 proc mktag {} {
5606 global rowmenuid mktagtop commitinfo
5608 set top .maketag
5609 set mktagtop $top
5610 catch {destroy $top}
5611 toplevel $top
5612 label $top.title -text "Create tag"
5613 grid $top.title - -pady 10
5614 label $top.id -text "ID:"
5615 entry $top.sha1 -width 40 -relief flat
5616 $top.sha1 insert 0 $rowmenuid
5617 $top.sha1 conf -state readonly
5618 grid $top.id $top.sha1 -sticky w
5619 entry $top.head -width 60 -relief flat
5620 $top.head insert 0 [lindex $commitinfo($rowmenuid) 0]
5621 $top.head conf -state readonly
5622 grid x $top.head -sticky w
5623 label $top.tlab -text "Tag name:"
5624 entry $top.tag -width 60
5625 grid $top.tlab $top.tag -sticky w
5626 frame $top.buts
5627 button $top.buts.gen -text "Create" -command mktaggo
5628 button $top.buts.can -text "Cancel" -command mktagcan
5629 grid $top.buts.gen $top.buts.can
5630 grid columnconfigure $top.buts 0 -weight 1 -uniform a
5631 grid columnconfigure $top.buts 1 -weight 1 -uniform a
5632 grid $top.buts - -pady 10 -sticky ew
5633 focus $top.tag
5636 proc domktag {} {
5637 global mktagtop env tagids idtags
5639 set id [$mktagtop.sha1 get]
5640 set tag [$mktagtop.tag get]
5641 if {$tag == {}} {
5642 error_popup "No tag name specified"
5643 return
5645 if {[info exists tagids($tag)]} {
5646 error_popup "Tag \"$tag\" already exists"
5647 return
5649 if {[catch {
5650 set dir [gitdir]
5651 set fname [file join $dir "refs/tags" $tag]
5652 set f [open $fname w]
5653 puts $f $id
5654 close $f
5655 } err]} {
5656 error_popup "Error creating tag: $err"
5657 return
5660 set tagids($tag) $id
5661 lappend idtags($id) $tag
5662 redrawtags $id
5663 addedtag $id
5666 proc redrawtags {id} {
5667 global canv linehtag commitrow idpos selectedline curview
5668 global mainfont canvxmax iddrawn
5670 if {![info exists commitrow($curview,$id)]} return
5671 if {![info exists iddrawn($id)]} return
5672 drawcommits $commitrow($curview,$id)
5673 $canv delete tag.$id
5674 set xt [eval drawtags $id $idpos($id)]
5675 $canv coords $linehtag($commitrow($curview,$id)) $xt [lindex $idpos($id) 2]
5676 set text [$canv itemcget $linehtag($commitrow($curview,$id)) -text]
5677 set xr [expr {$xt + [font measure $mainfont $text]}]
5678 if {$xr > $canvxmax} {
5679 set canvxmax $xr
5680 setcanvscroll
5682 if {[info exists selectedline]
5683 && $selectedline == $commitrow($curview,$id)} {
5684 selectline $selectedline 0
5688 proc mktagcan {} {
5689 global mktagtop
5691 catch {destroy $mktagtop}
5692 unset mktagtop
5695 proc mktaggo {} {
5696 domktag
5697 mktagcan
5700 proc writecommit {} {
5701 global rowmenuid wrcomtop commitinfo wrcomcmd
5703 set top .writecommit
5704 set wrcomtop $top
5705 catch {destroy $top}
5706 toplevel $top
5707 label $top.title -text "Write commit to file"
5708 grid $top.title - -pady 10
5709 label $top.id -text "ID:"
5710 entry $top.sha1 -width 40 -relief flat
5711 $top.sha1 insert 0 $rowmenuid
5712 $top.sha1 conf -state readonly
5713 grid $top.id $top.sha1 -sticky w
5714 entry $top.head -width 60 -relief flat
5715 $top.head insert 0 [lindex $commitinfo($rowmenuid) 0]
5716 $top.head conf -state readonly
5717 grid x $top.head -sticky w
5718 label $top.clab -text "Command:"
5719 entry $top.cmd -width 60 -textvariable wrcomcmd
5720 grid $top.clab $top.cmd -sticky w -pady 10
5721 label $top.flab -text "Output file:"
5722 entry $top.fname -width 60
5723 $top.fname insert 0 [file normalize "commit-[string range $rowmenuid 0 6]"]
5724 grid $top.flab $top.fname -sticky w
5725 frame $top.buts
5726 button $top.buts.gen -text "Write" -command wrcomgo
5727 button $top.buts.can -text "Cancel" -command wrcomcan
5728 grid $top.buts.gen $top.buts.can
5729 grid columnconfigure $top.buts 0 -weight 1 -uniform a
5730 grid columnconfigure $top.buts 1 -weight 1 -uniform a
5731 grid $top.buts - -pady 10 -sticky ew
5732 focus $top.fname
5735 proc wrcomgo {} {
5736 global wrcomtop
5738 set id [$wrcomtop.sha1 get]
5739 set cmd "echo $id | [$wrcomtop.cmd get]"
5740 set fname [$wrcomtop.fname get]
5741 if {[catch {exec sh -c $cmd >$fname &} err]} {
5742 error_popup "Error writing commit: $err"
5744 catch {destroy $wrcomtop}
5745 unset wrcomtop
5748 proc wrcomcan {} {
5749 global wrcomtop
5751 catch {destroy $wrcomtop}
5752 unset wrcomtop
5755 proc mkbranch {} {
5756 global rowmenuid mkbrtop
5758 set top .makebranch
5759 catch {destroy $top}
5760 toplevel $top
5761 label $top.title -text "Create new branch"
5762 grid $top.title - -pady 10
5763 label $top.id -text "ID:"
5764 entry $top.sha1 -width 40 -relief flat
5765 $top.sha1 insert 0 $rowmenuid
5766 $top.sha1 conf -state readonly
5767 grid $top.id $top.sha1 -sticky w
5768 label $top.nlab -text "Name:"
5769 entry $top.name -width 40
5770 grid $top.nlab $top.name -sticky w
5771 frame $top.buts
5772 button $top.buts.go -text "Create" -command [list mkbrgo $top]
5773 button $top.buts.can -text "Cancel" -command "catch {destroy $top}"
5774 grid $top.buts.go $top.buts.can
5775 grid columnconfigure $top.buts 0 -weight 1 -uniform a
5776 grid columnconfigure $top.buts 1 -weight 1 -uniform a
5777 grid $top.buts - -pady 10 -sticky ew
5778 focus $top.name
5781 proc mkbrgo {top} {
5782 global headids idheads
5784 set name [$top.name get]
5785 set id [$top.sha1 get]
5786 if {$name eq {}} {
5787 error_popup "Please specify a name for the new branch"
5788 return
5790 catch {destroy $top}
5791 nowbusy newbranch
5792 update
5793 if {[catch {
5794 exec git branch $name $id
5795 } err]} {
5796 notbusy newbranch
5797 error_popup $err
5798 } else {
5799 set headids($name) $id
5800 lappend idheads($id) $name
5801 addedhead $id $name
5802 notbusy newbranch
5803 redrawtags $id
5804 dispneartags 0
5808 proc cherrypick {} {
5809 global rowmenuid curview commitrow
5810 global mainhead
5812 set oldhead [exec git rev-parse HEAD]
5813 set dheads [descheads $rowmenuid]
5814 if {$dheads ne {} && [lsearch -exact $dheads $oldhead] >= 0} {
5815 set ok [confirm_popup "Commit [string range $rowmenuid 0 7] is already\
5816 included in branch $mainhead -- really re-apply it?"]
5817 if {!$ok} return
5819 nowbusy cherrypick
5820 update
5821 # Unfortunately git-cherry-pick writes stuff to stderr even when
5822 # no error occurs, and exec takes that as an indication of error...
5823 if {[catch {exec sh -c "git cherry-pick -r $rowmenuid 2>&1"} err]} {
5824 notbusy cherrypick
5825 error_popup $err
5826 return
5828 set newhead [exec git rev-parse HEAD]
5829 if {$newhead eq $oldhead} {
5830 notbusy cherrypick
5831 error_popup "No changes committed"
5832 return
5834 addnewchild $newhead $oldhead
5835 if {[info exists commitrow($curview,$oldhead)]} {
5836 insertrow $commitrow($curview,$oldhead) $newhead
5837 if {$mainhead ne {}} {
5838 movehead $newhead $mainhead
5839 movedhead $newhead $mainhead
5841 redrawtags $oldhead
5842 redrawtags $newhead
5844 notbusy cherrypick
5847 proc resethead {} {
5848 global mainheadid mainhead rowmenuid confirm_ok resettype
5849 global showlocalchanges
5851 set confirm_ok 0
5852 set w ".confirmreset"
5853 toplevel $w
5854 wm transient $w .
5855 wm title $w "Confirm reset"
5856 message $w.m -text \
5857 "Reset branch $mainhead to [string range $rowmenuid 0 7]?" \
5858 -justify center -aspect 1000
5859 pack $w.m -side top -fill x -padx 20 -pady 20
5860 frame $w.f -relief sunken -border 2
5861 message $w.f.rt -text "Reset type:" -aspect 1000
5862 grid $w.f.rt -sticky w
5863 set resettype mixed
5864 radiobutton $w.f.soft -value soft -variable resettype -justify left \
5865 -text "Soft: Leave working tree and index untouched"
5866 grid $w.f.soft -sticky w
5867 radiobutton $w.f.mixed -value mixed -variable resettype -justify left \
5868 -text "Mixed: Leave working tree untouched, reset index"
5869 grid $w.f.mixed -sticky w
5870 radiobutton $w.f.hard -value hard -variable resettype -justify left \
5871 -text "Hard: Reset working tree and index\n(discard ALL local changes)"
5872 grid $w.f.hard -sticky w
5873 pack $w.f -side top -fill x
5874 button $w.ok -text OK -command "set confirm_ok 1; destroy $w"
5875 pack $w.ok -side left -fill x -padx 20 -pady 20
5876 button $w.cancel -text Cancel -command "destroy $w"
5877 pack $w.cancel -side right -fill x -padx 20 -pady 20
5878 bind $w <Visibility> "grab $w; focus $w"
5879 tkwait window $w
5880 if {!$confirm_ok} return
5881 if {[catch {set fd [open \
5882 [list | sh -c "git reset --$resettype $rowmenuid 2>&1"] r]} err]} {
5883 error_popup $err
5884 } else {
5885 dohidelocalchanges
5886 set w ".resetprogress"
5887 filerun $fd [list readresetstat $fd $w]
5888 toplevel $w
5889 wm transient $w
5890 wm title $w "Reset progress"
5891 message $w.m -text "Reset in progress, please wait..." \
5892 -justify center -aspect 1000
5893 pack $w.m -side top -fill x -padx 20 -pady 5
5894 canvas $w.c -width 150 -height 20 -bg white
5895 $w.c create rect 0 0 0 20 -fill green -tags rect
5896 pack $w.c -side top -fill x -padx 20 -pady 5 -expand 1
5897 nowbusy reset
5901 proc readresetstat {fd w} {
5902 global mainhead mainheadid showlocalchanges
5904 if {[gets $fd line] >= 0} {
5905 if {[regexp {([0-9]+)% \(([0-9]+)/([0-9]+)\)} $line match p m n]} {
5906 set x [expr {($m * 150) / $n}]
5907 $w.c coords rect 0 0 $x 20
5909 return 1
5911 destroy $w
5912 notbusy reset
5913 if {[catch {close $fd} err]} {
5914 error_popup $err
5916 set oldhead $mainheadid
5917 set newhead [exec git rev-parse HEAD]
5918 if {$newhead ne $oldhead} {
5919 movehead $newhead $mainhead
5920 movedhead $newhead $mainhead
5921 set mainheadid $newhead
5922 redrawtags $oldhead
5923 redrawtags $newhead
5925 if {$showlocalchanges} {
5926 doshowlocalchanges
5928 return 0
5931 # context menu for a head
5932 proc headmenu {x y id head} {
5933 global headmenuid headmenuhead headctxmenu mainhead
5935 set headmenuid $id
5936 set headmenuhead $head
5937 set state normal
5938 if {$head eq $mainhead} {
5939 set state disabled
5941 $headctxmenu entryconfigure 0 -state $state
5942 $headctxmenu entryconfigure 1 -state $state
5943 tk_popup $headctxmenu $x $y
5946 proc cobranch {} {
5947 global headmenuid headmenuhead mainhead headids
5948 global showlocalchanges mainheadid
5950 # check the tree is clean first??
5951 set oldmainhead $mainhead
5952 nowbusy checkout
5953 update
5954 dohidelocalchanges
5955 if {[catch {
5956 exec git checkout -q $headmenuhead
5957 } err]} {
5958 notbusy checkout
5959 error_popup $err
5960 } else {
5961 notbusy checkout
5962 set mainhead $headmenuhead
5963 set mainheadid $headmenuid
5964 if {[info exists headids($oldmainhead)]} {
5965 redrawtags $headids($oldmainhead)
5967 redrawtags $headmenuid
5969 if {$showlocalchanges} {
5970 dodiffindex
5974 proc rmbranch {} {
5975 global headmenuid headmenuhead mainhead
5976 global headids idheads
5978 set head $headmenuhead
5979 set id $headmenuid
5980 # this check shouldn't be needed any more...
5981 if {$head eq $mainhead} {
5982 error_popup "Cannot delete the currently checked-out branch"
5983 return
5985 set dheads [descheads $id]
5986 if {$dheads eq $headids($head)} {
5987 # the stuff on this branch isn't on any other branch
5988 if {![confirm_popup "The commits on branch $head aren't on any other\
5989 branch.\nReally delete branch $head?"]} return
5991 nowbusy rmbranch
5992 update
5993 if {[catch {exec git branch -D $head} err]} {
5994 notbusy rmbranch
5995 error_popup $err
5996 return
5998 removehead $id $head
5999 removedhead $id $head
6000 redrawtags $id
6001 notbusy rmbranch
6002 dispneartags 0
6005 # Stuff for finding nearby tags
6006 proc getallcommits {} {
6007 global allcommits allids nbmp nextarc seeds
6009 set allids {}
6010 set nbmp 0
6011 set nextarc 0
6012 set allcommits 0
6013 set seeds {}
6014 regetallcommits
6017 # Called when the graph might have changed
6018 proc regetallcommits {} {
6019 global allcommits seeds
6021 set cmd [concat | git rev-list --all --parents]
6022 foreach id $seeds {
6023 lappend cmd "^$id"
6025 set fd [open $cmd r]
6026 fconfigure $fd -blocking 0
6027 incr allcommits
6028 nowbusy allcommits
6029 filerun $fd [list getallclines $fd]
6032 # Since most commits have 1 parent and 1 child, we group strings of
6033 # such commits into "arcs" joining branch/merge points (BMPs), which
6034 # are commits that either don't have 1 parent or don't have 1 child.
6036 # arcnos(id) - incoming arcs for BMP, arc we're on for other nodes
6037 # arcout(id) - outgoing arcs for BMP
6038 # arcids(a) - list of IDs on arc including end but not start
6039 # arcstart(a) - BMP ID at start of arc
6040 # arcend(a) - BMP ID at end of arc
6041 # growing(a) - arc a is still growing
6042 # arctags(a) - IDs out of arcids (excluding end) that have tags
6043 # archeads(a) - IDs out of arcids (excluding end) that have heads
6044 # The start of an arc is at the descendent end, so "incoming" means
6045 # coming from descendents, and "outgoing" means going towards ancestors.
6047 proc getallclines {fd} {
6048 global allids allparents allchildren idtags idheads nextarc nbmp
6049 global arcnos arcids arctags arcout arcend arcstart archeads growing
6050 global seeds allcommits
6052 set nid 0
6053 while {[incr nid] <= 1000 && [gets $fd line] >= 0} {
6054 set id [lindex $line 0]
6055 if {[info exists allparents($id)]} {
6056 # seen it already
6057 continue
6059 lappend allids $id
6060 set olds [lrange $line 1 end]
6061 set allparents($id) $olds
6062 if {![info exists allchildren($id)]} {
6063 set allchildren($id) {}
6064 set arcnos($id) {}
6065 lappend seeds $id
6066 } else {
6067 set a $arcnos($id)
6068 if {[llength $olds] == 1 && [llength $a] == 1} {
6069 lappend arcids($a) $id
6070 if {[info exists idtags($id)]} {
6071 lappend arctags($a) $id
6073 if {[info exists idheads($id)]} {
6074 lappend archeads($a) $id
6076 if {[info exists allparents($olds)]} {
6077 # seen parent already
6078 if {![info exists arcout($olds)]} {
6079 splitarc $olds
6081 lappend arcids($a) $olds
6082 set arcend($a) $olds
6083 unset growing($a)
6085 lappend allchildren($olds) $id
6086 lappend arcnos($olds) $a
6087 continue
6090 incr nbmp
6091 foreach a $arcnos($id) {
6092 lappend arcids($a) $id
6093 set arcend($a) $id
6094 unset growing($a)
6097 set ao {}
6098 foreach p $olds {
6099 lappend allchildren($p) $id
6100 set a [incr nextarc]
6101 set arcstart($a) $id
6102 set archeads($a) {}
6103 set arctags($a) {}
6104 set archeads($a) {}
6105 set arcids($a) {}
6106 lappend ao $a
6107 set growing($a) 1
6108 if {[info exists allparents($p)]} {
6109 # seen it already, may need to make a new branch
6110 if {![info exists arcout($p)]} {
6111 splitarc $p
6113 lappend arcids($a) $p
6114 set arcend($a) $p
6115 unset growing($a)
6117 lappend arcnos($p) $a
6119 set arcout($id) $ao
6121 if {$nid > 0} {
6122 global cached_dheads cached_dtags cached_atags
6123 catch {unset cached_dheads}
6124 catch {unset cached_dtags}
6125 catch {unset cached_atags}
6127 if {![eof $fd]} {
6128 return [expr {$nid >= 1000? 2: 1}]
6130 close $fd
6131 if {[incr allcommits -1] == 0} {
6132 notbusy allcommits
6134 dispneartags 0
6135 return 0
6138 proc recalcarc {a} {
6139 global arctags archeads arcids idtags idheads
6141 set at {}
6142 set ah {}
6143 foreach id [lrange $arcids($a) 0 end-1] {
6144 if {[info exists idtags($id)]} {
6145 lappend at $id
6147 if {[info exists idheads($id)]} {
6148 lappend ah $id
6151 set arctags($a) $at
6152 set archeads($a) $ah
6155 proc splitarc {p} {
6156 global arcnos arcids nextarc nbmp arctags archeads idtags idheads
6157 global arcstart arcend arcout allparents growing
6159 set a $arcnos($p)
6160 if {[llength $a] != 1} {
6161 puts "oops splitarc called but [llength $a] arcs already"
6162 return
6164 set a [lindex $a 0]
6165 set i [lsearch -exact $arcids($a) $p]
6166 if {$i < 0} {
6167 puts "oops splitarc $p not in arc $a"
6168 return
6170 set na [incr nextarc]
6171 if {[info exists arcend($a)]} {
6172 set arcend($na) $arcend($a)
6173 } else {
6174 set l [lindex $allparents([lindex $arcids($a) end]) 0]
6175 set j [lsearch -exact $arcnos($l) $a]
6176 set arcnos($l) [lreplace $arcnos($l) $j $j $na]
6178 set tail [lrange $arcids($a) [expr {$i+1}] end]
6179 set arcids($a) [lrange $arcids($a) 0 $i]
6180 set arcend($a) $p
6181 set arcstart($na) $p
6182 set arcout($p) $na
6183 set arcids($na) $tail
6184 if {[info exists growing($a)]} {
6185 set growing($na) 1
6186 unset growing($a)
6188 incr nbmp
6190 foreach id $tail {
6191 if {[llength $arcnos($id)] == 1} {
6192 set arcnos($id) $na
6193 } else {
6194 set j [lsearch -exact $arcnos($id) $a]
6195 set arcnos($id) [lreplace $arcnos($id) $j $j $na]
6199 # reconstruct tags and heads lists
6200 if {$arctags($a) ne {} || $archeads($a) ne {}} {
6201 recalcarc $a
6202 recalcarc $na
6203 } else {
6204 set arctags($na) {}
6205 set archeads($na) {}
6209 # Update things for a new commit added that is a child of one
6210 # existing commit. Used when cherry-picking.
6211 proc addnewchild {id p} {
6212 global allids allparents allchildren idtags nextarc nbmp
6213 global arcnos arcids arctags arcout arcend arcstart archeads growing
6214 global seeds
6216 lappend allids $id
6217 set allparents($id) [list $p]
6218 set allchildren($id) {}
6219 set arcnos($id) {}
6220 lappend seeds $id
6221 incr nbmp
6222 lappend allchildren($p) $id
6223 set a [incr nextarc]
6224 set arcstart($a) $id
6225 set archeads($a) {}
6226 set arctags($a) {}
6227 set arcids($a) [list $p]
6228 set arcend($a) $p
6229 if {![info exists arcout($p)]} {
6230 splitarc $p
6232 lappend arcnos($p) $a
6233 set arcout($id) [list $a]
6236 # Returns 1 if a is an ancestor of b, -1 if b is an ancestor of a,
6237 # or 0 if neither is true.
6238 proc anc_or_desc {a b} {
6239 global arcout arcstart arcend arcnos cached_isanc
6241 if {$arcnos($a) eq $arcnos($b)} {
6242 # Both are on the same arc(s); either both are the same BMP,
6243 # or if one is not a BMP, the other is also not a BMP or is
6244 # the BMP at end of the arc (and it only has 1 incoming arc).
6245 # Or both can be BMPs with no incoming arcs.
6246 if {$a eq $b || $arcnos($a) eq {}} {
6247 return 0
6249 # assert {[llength $arcnos($a)] == 1}
6250 set arc [lindex $arcnos($a) 0]
6251 set i [lsearch -exact $arcids($arc) $a]
6252 set j [lsearch -exact $arcids($arc) $b]
6253 if {$i < 0 || $i > $j} {
6254 return 1
6255 } else {
6256 return -1
6260 if {![info exists arcout($a)]} {
6261 set arc [lindex $arcnos($a) 0]
6262 if {[info exists arcend($arc)]} {
6263 set aend $arcend($arc)
6264 } else {
6265 set aend {}
6267 set a $arcstart($arc)
6268 } else {
6269 set aend $a
6271 if {![info exists arcout($b)]} {
6272 set arc [lindex $arcnos($b) 0]
6273 if {[info exists arcend($arc)]} {
6274 set bend $arcend($arc)
6275 } else {
6276 set bend {}
6278 set b $arcstart($arc)
6279 } else {
6280 set bend $b
6282 if {$a eq $bend} {
6283 return 1
6285 if {$b eq $aend} {
6286 return -1
6288 if {[info exists cached_isanc($a,$bend)]} {
6289 if {$cached_isanc($a,$bend)} {
6290 return 1
6293 if {[info exists cached_isanc($b,$aend)]} {
6294 if {$cached_isanc($b,$aend)} {
6295 return -1
6297 if {[info exists cached_isanc($a,$bend)]} {
6298 return 0
6302 set todo [list $a $b]
6303 set anc($a) a
6304 set anc($b) b
6305 for {set i 0} {$i < [llength $todo]} {incr i} {
6306 set x [lindex $todo $i]
6307 if {$anc($x) eq {}} {
6308 continue
6310 foreach arc $arcnos($x) {
6311 set xd $arcstart($arc)
6312 if {$xd eq $bend} {
6313 set cached_isanc($a,$bend) 1
6314 set cached_isanc($b,$aend) 0
6315 return 1
6316 } elseif {$xd eq $aend} {
6317 set cached_isanc($b,$aend) 1
6318 set cached_isanc($a,$bend) 0
6319 return -1
6321 if {![info exists anc($xd)]} {
6322 set anc($xd) $anc($x)
6323 lappend todo $xd
6324 } elseif {$anc($xd) ne $anc($x)} {
6325 set anc($xd) {}
6329 set cached_isanc($a,$bend) 0
6330 set cached_isanc($b,$aend) 0
6331 return 0
6334 # This identifies whether $desc has an ancestor that is
6335 # a growing tip of the graph and which is not an ancestor of $anc
6336 # and returns 0 if so and 1 if not.
6337 # If we subsequently discover a tag on such a growing tip, and that
6338 # turns out to be a descendent of $anc (which it could, since we
6339 # don't necessarily see children before parents), then $desc
6340 # isn't a good choice to display as a descendent tag of
6341 # $anc (since it is the descendent of another tag which is
6342 # a descendent of $anc). Similarly, $anc isn't a good choice to
6343 # display as a ancestor tag of $desc.
6345 proc is_certain {desc anc} {
6346 global arcnos arcout arcstart arcend growing problems
6348 set certain {}
6349 if {[llength $arcnos($anc)] == 1} {
6350 # tags on the same arc are certain
6351 if {$arcnos($desc) eq $arcnos($anc)} {
6352 return 1
6354 if {![info exists arcout($anc)]} {
6355 # if $anc is partway along an arc, use the start of the arc instead
6356 set a [lindex $arcnos($anc) 0]
6357 set anc $arcstart($a)
6360 if {[llength $arcnos($desc)] > 1 || [info exists arcout($desc)]} {
6361 set x $desc
6362 } else {
6363 set a [lindex $arcnos($desc) 0]
6364 set x $arcend($a)
6366 if {$x == $anc} {
6367 return 1
6369 set anclist [list $x]
6370 set dl($x) 1
6371 set nnh 1
6372 set ngrowanc 0
6373 for {set i 0} {$i < [llength $anclist] && ($nnh > 0 || $ngrowanc > 0)} {incr i} {
6374 set x [lindex $anclist $i]
6375 if {$dl($x)} {
6376 incr nnh -1
6378 set done($x) 1
6379 foreach a $arcout($x) {
6380 if {[info exists growing($a)]} {
6381 if {![info exists growanc($x)] && $dl($x)} {
6382 set growanc($x) 1
6383 incr ngrowanc
6385 } else {
6386 set y $arcend($a)
6387 if {[info exists dl($y)]} {
6388 if {$dl($y)} {
6389 if {!$dl($x)} {
6390 set dl($y) 0
6391 if {![info exists done($y)]} {
6392 incr nnh -1
6394 if {[info exists growanc($x)]} {
6395 incr ngrowanc -1
6397 set xl [list $y]
6398 for {set k 0} {$k < [llength $xl]} {incr k} {
6399 set z [lindex $xl $k]
6400 foreach c $arcout($z) {
6401 if {[info exists arcend($c)]} {
6402 set v $arcend($c)
6403 if {[info exists dl($v)] && $dl($v)} {
6404 set dl($v) 0
6405 if {![info exists done($v)]} {
6406 incr nnh -1
6408 if {[info exists growanc($v)]} {
6409 incr ngrowanc -1
6411 lappend xl $v
6418 } elseif {$y eq $anc || !$dl($x)} {
6419 set dl($y) 0
6420 lappend anclist $y
6421 } else {
6422 set dl($y) 1
6423 lappend anclist $y
6424 incr nnh
6429 foreach x [array names growanc] {
6430 if {$dl($x)} {
6431 return 0
6433 return 0
6435 return 1
6438 proc validate_arctags {a} {
6439 global arctags idtags
6441 set i -1
6442 set na $arctags($a)
6443 foreach id $arctags($a) {
6444 incr i
6445 if {![info exists idtags($id)]} {
6446 set na [lreplace $na $i $i]
6447 incr i -1
6450 set arctags($a) $na
6453 proc validate_archeads {a} {
6454 global archeads idheads
6456 set i -1
6457 set na $archeads($a)
6458 foreach id $archeads($a) {
6459 incr i
6460 if {![info exists idheads($id)]} {
6461 set na [lreplace $na $i $i]
6462 incr i -1
6465 set archeads($a) $na
6468 # Return the list of IDs that have tags that are descendents of id,
6469 # ignoring IDs that are descendents of IDs already reported.
6470 proc desctags {id} {
6471 global arcnos arcstart arcids arctags idtags allparents
6472 global growing cached_dtags
6474 if {![info exists allparents($id)]} {
6475 return {}
6477 set t1 [clock clicks -milliseconds]
6478 set argid $id
6479 if {[llength $arcnos($id)] == 1 && [llength $allparents($id)] == 1} {
6480 # part-way along an arc; check that arc first
6481 set a [lindex $arcnos($id) 0]
6482 if {$arctags($a) ne {}} {
6483 validate_arctags $a
6484 set i [lsearch -exact $arcids($a) $id]
6485 set tid {}
6486 foreach t $arctags($a) {
6487 set j [lsearch -exact $arcids($a) $t]
6488 if {$j >= $i} break
6489 set tid $t
6491 if {$tid ne {}} {
6492 return $tid
6495 set id $arcstart($a)
6496 if {[info exists idtags($id)]} {
6497 return $id
6500 if {[info exists cached_dtags($id)]} {
6501 return $cached_dtags($id)
6504 set origid $id
6505 set todo [list $id]
6506 set queued($id) 1
6507 set nc 1
6508 for {set i 0} {$i < [llength $todo] && $nc > 0} {incr i} {
6509 set id [lindex $todo $i]
6510 set done($id) 1
6511 set ta [info exists hastaggedancestor($id)]
6512 if {!$ta} {
6513 incr nc -1
6515 # ignore tags on starting node
6516 if {!$ta && $i > 0} {
6517 if {[info exists idtags($id)]} {
6518 set tagloc($id) $id
6519 set ta 1
6520 } elseif {[info exists cached_dtags($id)]} {
6521 set tagloc($id) $cached_dtags($id)
6522 set ta 1
6525 foreach a $arcnos($id) {
6526 set d $arcstart($a)
6527 if {!$ta && $arctags($a) ne {}} {
6528 validate_arctags $a
6529 if {$arctags($a) ne {}} {
6530 lappend tagloc($id) [lindex $arctags($a) end]
6533 if {$ta || $arctags($a) ne {}} {
6534 set tomark [list $d]
6535 for {set j 0} {$j < [llength $tomark]} {incr j} {
6536 set dd [lindex $tomark $j]
6537 if {![info exists hastaggedancestor($dd)]} {
6538 if {[info exists done($dd)]} {
6539 foreach b $arcnos($dd) {
6540 lappend tomark $arcstart($b)
6542 if {[info exists tagloc($dd)]} {
6543 unset tagloc($dd)
6545 } elseif {[info exists queued($dd)]} {
6546 incr nc -1
6548 set hastaggedancestor($dd) 1
6552 if {![info exists queued($d)]} {
6553 lappend todo $d
6554 set queued($d) 1
6555 if {![info exists hastaggedancestor($d)]} {
6556 incr nc
6561 set tags {}
6562 foreach id [array names tagloc] {
6563 if {![info exists hastaggedancestor($id)]} {
6564 foreach t $tagloc($id) {
6565 if {[lsearch -exact $tags $t] < 0} {
6566 lappend tags $t
6571 set t2 [clock clicks -milliseconds]
6572 set loopix $i
6574 # remove tags that are descendents of other tags
6575 for {set i 0} {$i < [llength $tags]} {incr i} {
6576 set a [lindex $tags $i]
6577 for {set j 0} {$j < $i} {incr j} {
6578 set b [lindex $tags $j]
6579 set r [anc_or_desc $a $b]
6580 if {$r == 1} {
6581 set tags [lreplace $tags $j $j]
6582 incr j -1
6583 incr i -1
6584 } elseif {$r == -1} {
6585 set tags [lreplace $tags $i $i]
6586 incr i -1
6587 break
6592 if {[array names growing] ne {}} {
6593 # graph isn't finished, need to check if any tag could get
6594 # eclipsed by another tag coming later. Simply ignore any
6595 # tags that could later get eclipsed.
6596 set ctags {}
6597 foreach t $tags {
6598 if {[is_certain $t $origid]} {
6599 lappend ctags $t
6602 if {$tags eq $ctags} {
6603 set cached_dtags($origid) $tags
6604 } else {
6605 set tags $ctags
6607 } else {
6608 set cached_dtags($origid) $tags
6610 set t3 [clock clicks -milliseconds]
6611 if {0 && $t3 - $t1 >= 100} {
6612 puts "iterating descendents ($loopix/[llength $todo] nodes) took\
6613 [expr {$t2-$t1}]+[expr {$t3-$t2}]ms, $nc candidates left"
6615 return $tags
6618 proc anctags {id} {
6619 global arcnos arcids arcout arcend arctags idtags allparents
6620 global growing cached_atags
6622 if {![info exists allparents($id)]} {
6623 return {}
6625 set t1 [clock clicks -milliseconds]
6626 set argid $id
6627 if {[llength $arcnos($id)] == 1 && [llength $allparents($id)] == 1} {
6628 # part-way along an arc; check that arc first
6629 set a [lindex $arcnos($id) 0]
6630 if {$arctags($a) ne {}} {
6631 validate_arctags $a
6632 set i [lsearch -exact $arcids($a) $id]
6633 foreach t $arctags($a) {
6634 set j [lsearch -exact $arcids($a) $t]
6635 if {$j > $i} {
6636 return $t
6640 if {![info exists arcend($a)]} {
6641 return {}
6643 set id $arcend($a)
6644 if {[info exists idtags($id)]} {
6645 return $id
6648 if {[info exists cached_atags($id)]} {
6649 return $cached_atags($id)
6652 set origid $id
6653 set todo [list $id]
6654 set queued($id) 1
6655 set taglist {}
6656 set nc 1
6657 for {set i 0} {$i < [llength $todo] && $nc > 0} {incr i} {
6658 set id [lindex $todo $i]
6659 set done($id) 1
6660 set td [info exists hastaggeddescendent($id)]
6661 if {!$td} {
6662 incr nc -1
6664 # ignore tags on starting node
6665 if {!$td && $i > 0} {
6666 if {[info exists idtags($id)]} {
6667 set tagloc($id) $id
6668 set td 1
6669 } elseif {[info exists cached_atags($id)]} {
6670 set tagloc($id) $cached_atags($id)
6671 set td 1
6674 foreach a $arcout($id) {
6675 if {!$td && $arctags($a) ne {}} {
6676 validate_arctags $a
6677 if {$arctags($a) ne {}} {
6678 lappend tagloc($id) [lindex $arctags($a) 0]
6681 if {![info exists arcend($a)]} continue
6682 set d $arcend($a)
6683 if {$td || $arctags($a) ne {}} {
6684 set tomark [list $d]
6685 for {set j 0} {$j < [llength $tomark]} {incr j} {
6686 set dd [lindex $tomark $j]
6687 if {![info exists hastaggeddescendent($dd)]} {
6688 if {[info exists done($dd)]} {
6689 foreach b $arcout($dd) {
6690 if {[info exists arcend($b)]} {
6691 lappend tomark $arcend($b)
6694 if {[info exists tagloc($dd)]} {
6695 unset tagloc($dd)
6697 } elseif {[info exists queued($dd)]} {
6698 incr nc -1
6700 set hastaggeddescendent($dd) 1
6704 if {![info exists queued($d)]} {
6705 lappend todo $d
6706 set queued($d) 1
6707 if {![info exists hastaggeddescendent($d)]} {
6708 incr nc
6713 set t2 [clock clicks -milliseconds]
6714 set loopix $i
6715 set tags {}
6716 foreach id [array names tagloc] {
6717 if {![info exists hastaggeddescendent($id)]} {
6718 foreach t $tagloc($id) {
6719 if {[lsearch -exact $tags $t] < 0} {
6720 lappend tags $t
6726 # remove tags that are ancestors of other tags
6727 for {set i 0} {$i < [llength $tags]} {incr i} {
6728 set a [lindex $tags $i]
6729 for {set j 0} {$j < $i} {incr j} {
6730 set b [lindex $tags $j]
6731 set r [anc_or_desc $a $b]
6732 if {$r == -1} {
6733 set tags [lreplace $tags $j $j]
6734 incr j -1
6735 incr i -1
6736 } elseif {$r == 1} {
6737 set tags [lreplace $tags $i $i]
6738 incr i -1
6739 break
6744 if {[array names growing] ne {}} {
6745 # graph isn't finished, need to check if any tag could get
6746 # eclipsed by another tag coming later. Simply ignore any
6747 # tags that could later get eclipsed.
6748 set ctags {}
6749 foreach t $tags {
6750 if {[is_certain $origid $t]} {
6751 lappend ctags $t
6754 if {$tags eq $ctags} {
6755 set cached_atags($origid) $tags
6756 } else {
6757 set tags $ctags
6759 } else {
6760 set cached_atags($origid) $tags
6762 set t3 [clock clicks -milliseconds]
6763 if {0 && $t3 - $t1 >= 100} {
6764 puts "iterating ancestors ($loopix/[llength $todo] nodes) took\
6765 [expr {$t2-$t1}]+[expr {$t3-$t2}]ms, $nc candidates left"
6767 return $tags
6770 # Return the list of IDs that have heads that are descendents of id,
6771 # including id itself if it has a head.
6772 proc descheads {id} {
6773 global arcnos arcstart arcids archeads idheads cached_dheads
6774 global allparents
6776 if {![info exists allparents($id)]} {
6777 return {}
6779 set aret {}
6780 if {[llength $arcnos($id)] == 1 && [llength $allparents($id)] == 1} {
6781 # part-way along an arc; check it first
6782 set a [lindex $arcnos($id) 0]
6783 if {$archeads($a) ne {}} {
6784 validate_archeads $a
6785 set i [lsearch -exact $arcids($a) $id]
6786 foreach t $archeads($a) {
6787 set j [lsearch -exact $arcids($a) $t]
6788 if {$j > $i} break
6789 lappend aret $t
6792 set id $arcstart($a)
6794 set origid $id
6795 set todo [list $id]
6796 set seen($id) 1
6797 set ret {}
6798 for {set i 0} {$i < [llength $todo]} {incr i} {
6799 set id [lindex $todo $i]
6800 if {[info exists cached_dheads($id)]} {
6801 set ret [concat $ret $cached_dheads($id)]
6802 } else {
6803 if {[info exists idheads($id)]} {
6804 lappend ret $id
6806 foreach a $arcnos($id) {
6807 if {$archeads($a) ne {}} {
6808 validate_archeads $a
6809 if {$archeads($a) ne {}} {
6810 set ret [concat $ret $archeads($a)]
6813 set d $arcstart($a)
6814 if {![info exists seen($d)]} {
6815 lappend todo $d
6816 set seen($d) 1
6821 set ret [lsort -unique $ret]
6822 set cached_dheads($origid) $ret
6823 return [concat $ret $aret]
6826 proc addedtag {id} {
6827 global arcnos arcout cached_dtags cached_atags
6829 if {![info exists arcnos($id)]} return
6830 if {![info exists arcout($id)]} {
6831 recalcarc [lindex $arcnos($id) 0]
6833 catch {unset cached_dtags}
6834 catch {unset cached_atags}
6837 proc addedhead {hid head} {
6838 global arcnos arcout cached_dheads
6840 if {![info exists arcnos($hid)]} return
6841 if {![info exists arcout($hid)]} {
6842 recalcarc [lindex $arcnos($hid) 0]
6844 catch {unset cached_dheads}
6847 proc removedhead {hid head} {
6848 global cached_dheads
6850 catch {unset cached_dheads}
6853 proc movedhead {hid head} {
6854 global arcnos arcout cached_dheads
6856 if {![info exists arcnos($hid)]} return
6857 if {![info exists arcout($hid)]} {
6858 recalcarc [lindex $arcnos($hid) 0]
6860 catch {unset cached_dheads}
6863 proc changedrefs {} {
6864 global cached_dheads cached_dtags cached_atags
6865 global arctags archeads arcnos arcout idheads idtags
6867 foreach id [concat [array names idheads] [array names idtags]] {
6868 if {[info exists arcnos($id)] && ![info exists arcout($id)]} {
6869 set a [lindex $arcnos($id) 0]
6870 if {![info exists donearc($a)]} {
6871 recalcarc $a
6872 set donearc($a) 1
6876 catch {unset cached_dtags}
6877 catch {unset cached_atags}
6878 catch {unset cached_dheads}
6881 proc rereadrefs {} {
6882 global idtags idheads idotherrefs mainhead
6884 set refids [concat [array names idtags] \
6885 [array names idheads] [array names idotherrefs]]
6886 foreach id $refids {
6887 if {![info exists ref($id)]} {
6888 set ref($id) [listrefs $id]
6891 set oldmainhead $mainhead
6892 readrefs
6893 changedrefs
6894 set refids [lsort -unique [concat $refids [array names idtags] \
6895 [array names idheads] [array names idotherrefs]]]
6896 foreach id $refids {
6897 set v [listrefs $id]
6898 if {![info exists ref($id)] || $ref($id) != $v ||
6899 ($id eq $oldmainhead && $id ne $mainhead) ||
6900 ($id eq $mainhead && $id ne $oldmainhead)} {
6901 redrawtags $id
6906 proc listrefs {id} {
6907 global idtags idheads idotherrefs
6909 set x {}
6910 if {[info exists idtags($id)]} {
6911 set x $idtags($id)
6913 set y {}
6914 if {[info exists idheads($id)]} {
6915 set y $idheads($id)
6917 set z {}
6918 if {[info exists idotherrefs($id)]} {
6919 set z $idotherrefs($id)
6921 return [list $x $y $z]
6924 proc showtag {tag isnew} {
6925 global ctext tagcontents tagids linknum tagobjid
6927 if {$isnew} {
6928 addtohistory [list showtag $tag 0]
6930 $ctext conf -state normal
6931 clear_ctext
6932 set linknum 0
6933 if {![info exists tagcontents($tag)]} {
6934 catch {
6935 set tagcontents($tag) [exec git cat-file tag $tagobjid($tag)]
6938 if {[info exists tagcontents($tag)]} {
6939 set text $tagcontents($tag)
6940 } else {
6941 set text "Tag: $tag\nId: $tagids($tag)"
6943 appendwithlinks $text {}
6944 $ctext conf -state disabled
6945 init_flist {}
6948 proc doquit {} {
6949 global stopped
6950 set stopped 100
6951 savestuff .
6952 destroy .
6955 proc doprefs {} {
6956 global maxwidth maxgraphpct diffopts
6957 global oldprefs prefstop showneartags showlocalchanges
6958 global bgcolor fgcolor ctext diffcolors selectbgcolor
6959 global uifont tabstop
6961 set top .gitkprefs
6962 set prefstop $top
6963 if {[winfo exists $top]} {
6964 raise $top
6965 return
6967 foreach v {maxwidth maxgraphpct diffopts showneartags showlocalchanges} {
6968 set oldprefs($v) [set $v]
6970 toplevel $top
6971 wm title $top "Gitk preferences"
6972 label $top.ldisp -text "Commit list display options"
6973 $top.ldisp configure -font $uifont
6974 grid $top.ldisp - -sticky w -pady 10
6975 label $top.spacer -text " "
6976 label $top.maxwidthl -text "Maximum graph width (lines)" \
6977 -font optionfont
6978 spinbox $top.maxwidth -from 0 -to 100 -width 4 -textvariable maxwidth
6979 grid $top.spacer $top.maxwidthl $top.maxwidth -sticky w
6980 label $top.maxpctl -text "Maximum graph width (% of pane)" \
6981 -font optionfont
6982 spinbox $top.maxpct -from 1 -to 100 -width 4 -textvariable maxgraphpct
6983 grid x $top.maxpctl $top.maxpct -sticky w
6984 frame $top.showlocal
6985 label $top.showlocal.l -text "Show local changes" -font optionfont
6986 checkbutton $top.showlocal.b -variable showlocalchanges
6987 pack $top.showlocal.b $top.showlocal.l -side left
6988 grid x $top.showlocal -sticky w
6990 label $top.ddisp -text "Diff display options"
6991 $top.ddisp configure -font $uifont
6992 grid $top.ddisp - -sticky w -pady 10
6993 label $top.diffoptl -text "Options for diff program" \
6994 -font optionfont
6995 entry $top.diffopt -width 20 -textvariable diffopts
6996 grid x $top.diffoptl $top.diffopt -sticky w
6997 frame $top.ntag
6998 label $top.ntag.l -text "Display nearby tags" -font optionfont
6999 checkbutton $top.ntag.b -variable showneartags
7000 pack $top.ntag.b $top.ntag.l -side left
7001 grid x $top.ntag -sticky w
7002 label $top.tabstopl -text "tabstop" -font optionfont
7003 spinbox $top.tabstop -from 1 -to 20 -width 4 -textvariable tabstop
7004 grid x $top.tabstopl $top.tabstop -sticky w
7006 label $top.cdisp -text "Colors: press to choose"
7007 $top.cdisp configure -font $uifont
7008 grid $top.cdisp - -sticky w -pady 10
7009 label $top.bg -padx 40 -relief sunk -background $bgcolor
7010 button $top.bgbut -text "Background" -font optionfont \
7011 -command [list choosecolor bgcolor 0 $top.bg background setbg]
7012 grid x $top.bgbut $top.bg -sticky w
7013 label $top.fg -padx 40 -relief sunk -background $fgcolor
7014 button $top.fgbut -text "Foreground" -font optionfont \
7015 -command [list choosecolor fgcolor 0 $top.fg foreground setfg]
7016 grid x $top.fgbut $top.fg -sticky w
7017 label $top.diffold -padx 40 -relief sunk -background [lindex $diffcolors 0]
7018 button $top.diffoldbut -text "Diff: old lines" -font optionfont \
7019 -command [list choosecolor diffcolors 0 $top.diffold "diff old lines" \
7020 [list $ctext tag conf d0 -foreground]]
7021 grid x $top.diffoldbut $top.diffold -sticky w
7022 label $top.diffnew -padx 40 -relief sunk -background [lindex $diffcolors 1]
7023 button $top.diffnewbut -text "Diff: new lines" -font optionfont \
7024 -command [list choosecolor diffcolors 1 $top.diffnew "diff new lines" \
7025 [list $ctext tag conf d1 -foreground]]
7026 grid x $top.diffnewbut $top.diffnew -sticky w
7027 label $top.hunksep -padx 40 -relief sunk -background [lindex $diffcolors 2]
7028 button $top.hunksepbut -text "Diff: hunk header" -font optionfont \
7029 -command [list choosecolor diffcolors 2 $top.hunksep \
7030 "diff hunk header" \
7031 [list $ctext tag conf hunksep -foreground]]
7032 grid x $top.hunksepbut $top.hunksep -sticky w
7033 label $top.selbgsep -padx 40 -relief sunk -background $selectbgcolor
7034 button $top.selbgbut -text "Select bg" -font optionfont \
7035 -command [list choosecolor selectbgcolor 0 $top.selbgsep background setselbg]
7036 grid x $top.selbgbut $top.selbgsep -sticky w
7038 frame $top.buts
7039 button $top.buts.ok -text "OK" -command prefsok -default active
7040 $top.buts.ok configure -font $uifont
7041 button $top.buts.can -text "Cancel" -command prefscan -default normal
7042 $top.buts.can configure -font $uifont
7043 grid $top.buts.ok $top.buts.can
7044 grid columnconfigure $top.buts 0 -weight 1 -uniform a
7045 grid columnconfigure $top.buts 1 -weight 1 -uniform a
7046 grid $top.buts - - -pady 10 -sticky ew
7047 bind $top <Visibility> "focus $top.buts.ok"
7050 proc choosecolor {v vi w x cmd} {
7051 global $v
7053 set c [tk_chooseColor -initialcolor [lindex [set $v] $vi] \
7054 -title "Gitk: choose color for $x"]
7055 if {$c eq {}} return
7056 $w conf -background $c
7057 lset $v $vi $c
7058 eval $cmd $c
7061 proc setselbg {c} {
7062 global bglist cflist
7063 foreach w $bglist {
7064 $w configure -selectbackground $c
7066 $cflist tag configure highlight \
7067 -background [$cflist cget -selectbackground]
7068 allcanvs itemconf secsel -fill $c
7071 proc setbg {c} {
7072 global bglist
7074 foreach w $bglist {
7075 $w conf -background $c
7079 proc setfg {c} {
7080 global fglist canv
7082 foreach w $fglist {
7083 $w conf -foreground $c
7085 allcanvs itemconf text -fill $c
7086 $canv itemconf circle -outline $c
7089 proc prefscan {} {
7090 global maxwidth maxgraphpct diffopts
7091 global oldprefs prefstop showneartags showlocalchanges
7093 foreach v {maxwidth maxgraphpct diffopts showneartags showlocalchanges} {
7094 set $v $oldprefs($v)
7096 catch {destroy $prefstop}
7097 unset prefstop
7100 proc prefsok {} {
7101 global maxwidth maxgraphpct
7102 global oldprefs prefstop showneartags showlocalchanges
7103 global charspc ctext tabstop
7105 catch {destroy $prefstop}
7106 unset prefstop
7107 $ctext configure -tabs "[expr {$tabstop * $charspc}]"
7108 if {$showlocalchanges != $oldprefs(showlocalchanges)} {
7109 if {$showlocalchanges} {
7110 doshowlocalchanges
7111 } else {
7112 dohidelocalchanges
7115 if {$maxwidth != $oldprefs(maxwidth)
7116 || $maxgraphpct != $oldprefs(maxgraphpct)} {
7117 redisplay
7118 } elseif {$showneartags != $oldprefs(showneartags)} {
7119 reselectline
7123 proc formatdate {d} {
7124 if {$d ne {}} {
7125 set d [clock format $d -format "%Y-%m-%d %H:%M:%S"]
7127 return $d
7130 # This list of encoding names and aliases is distilled from
7131 # http://www.iana.org/assignments/character-sets.
7132 # Not all of them are supported by Tcl.
7133 set encoding_aliases {
7134 { ANSI_X3.4-1968 iso-ir-6 ANSI_X3.4-1986 ISO_646.irv:1991 ASCII
7135 ISO646-US US-ASCII us IBM367 cp367 csASCII }
7136 { ISO-10646-UTF-1 csISO10646UTF1 }
7137 { ISO_646.basic:1983 ref csISO646basic1983 }
7138 { INVARIANT csINVARIANT }
7139 { ISO_646.irv:1983 iso-ir-2 irv csISO2IntlRefVersion }
7140 { BS_4730 iso-ir-4 ISO646-GB gb uk csISO4UnitedKingdom }
7141 { NATS-SEFI iso-ir-8-1 csNATSSEFI }
7142 { NATS-SEFI-ADD iso-ir-8-2 csNATSSEFIADD }
7143 { NATS-DANO iso-ir-9-1 csNATSDANO }
7144 { NATS-DANO-ADD iso-ir-9-2 csNATSDANOADD }
7145 { SEN_850200_B iso-ir-10 FI ISO646-FI ISO646-SE se csISO10Swedish }
7146 { SEN_850200_C iso-ir-11 ISO646-SE2 se2 csISO11SwedishForNames }
7147 { KS_C_5601-1987 iso-ir-149 KS_C_5601-1989 KSC_5601 korean csKSC56011987 }
7148 { ISO-2022-KR csISO2022KR }
7149 { EUC-KR csEUCKR }
7150 { ISO-2022-JP csISO2022JP }
7151 { ISO-2022-JP-2 csISO2022JP2 }
7152 { JIS_C6220-1969-jp JIS_C6220-1969 iso-ir-13 katakana x0201-7
7153 csISO13JISC6220jp }
7154 { JIS_C6220-1969-ro iso-ir-14 jp ISO646-JP csISO14JISC6220ro }
7155 { IT iso-ir-15 ISO646-IT csISO15Italian }
7156 { PT iso-ir-16 ISO646-PT csISO16Portuguese }
7157 { ES iso-ir-17 ISO646-ES csISO17Spanish }
7158 { greek7-old iso-ir-18 csISO18Greek7Old }
7159 { latin-greek iso-ir-19 csISO19LatinGreek }
7160 { DIN_66003 iso-ir-21 de ISO646-DE csISO21German }
7161 { NF_Z_62-010_(1973) iso-ir-25 ISO646-FR1 csISO25French }
7162 { Latin-greek-1 iso-ir-27 csISO27LatinGreek1 }
7163 { ISO_5427 iso-ir-37 csISO5427Cyrillic }
7164 { JIS_C6226-1978 iso-ir-42 csISO42JISC62261978 }
7165 { BS_viewdata iso-ir-47 csISO47BSViewdata }
7166 { INIS iso-ir-49 csISO49INIS }
7167 { INIS-8 iso-ir-50 csISO50INIS8 }
7168 { INIS-cyrillic iso-ir-51 csISO51INISCyrillic }
7169 { ISO_5427:1981 iso-ir-54 ISO5427Cyrillic1981 }
7170 { ISO_5428:1980 iso-ir-55 csISO5428Greek }
7171 { GB_1988-80 iso-ir-57 cn ISO646-CN csISO57GB1988 }
7172 { GB_2312-80 iso-ir-58 chinese csISO58GB231280 }
7173 { NS_4551-1 iso-ir-60 ISO646-NO no csISO60DanishNorwegian
7174 csISO60Norwegian1 }
7175 { NS_4551-2 ISO646-NO2 iso-ir-61 no2 csISO61Norwegian2 }
7176 { NF_Z_62-010 iso-ir-69 ISO646-FR fr csISO69French }
7177 { videotex-suppl iso-ir-70 csISO70VideotexSupp1 }
7178 { PT2 iso-ir-84 ISO646-PT2 csISO84Portuguese2 }
7179 { ES2 iso-ir-85 ISO646-ES2 csISO85Spanish2 }
7180 { MSZ_7795.3 iso-ir-86 ISO646-HU hu csISO86Hungarian }
7181 { JIS_C6226-1983 iso-ir-87 x0208 JIS_X0208-1983 csISO87JISX0208 }
7182 { greek7 iso-ir-88 csISO88Greek7 }
7183 { ASMO_449 ISO_9036 arabic7 iso-ir-89 csISO89ASMO449 }
7184 { iso-ir-90 csISO90 }
7185 { JIS_C6229-1984-a iso-ir-91 jp-ocr-a csISO91JISC62291984a }
7186 { JIS_C6229-1984-b iso-ir-92 ISO646-JP-OCR-B jp-ocr-b
7187 csISO92JISC62991984b }
7188 { JIS_C6229-1984-b-add iso-ir-93 jp-ocr-b-add csISO93JIS62291984badd }
7189 { JIS_C6229-1984-hand iso-ir-94 jp-ocr-hand csISO94JIS62291984hand }
7190 { JIS_C6229-1984-hand-add iso-ir-95 jp-ocr-hand-add
7191 csISO95JIS62291984handadd }
7192 { JIS_C6229-1984-kana iso-ir-96 csISO96JISC62291984kana }
7193 { ISO_2033-1983 iso-ir-98 e13b csISO2033 }
7194 { ANSI_X3.110-1983 iso-ir-99 CSA_T500-1983 NAPLPS csISO99NAPLPS }
7195 { ISO_8859-1:1987 iso-ir-100 ISO_8859-1 ISO-8859-1 latin1 l1 IBM819
7196 CP819 csISOLatin1 }
7197 { ISO_8859-2:1987 iso-ir-101 ISO_8859-2 ISO-8859-2 latin2 l2 csISOLatin2 }
7198 { T.61-7bit iso-ir-102 csISO102T617bit }
7199 { T.61-8bit T.61 iso-ir-103 csISO103T618bit }
7200 { ISO_8859-3:1988 iso-ir-109 ISO_8859-3 ISO-8859-3 latin3 l3 csISOLatin3 }
7201 { ISO_8859-4:1988 iso-ir-110 ISO_8859-4 ISO-8859-4 latin4 l4 csISOLatin4 }
7202 { ECMA-cyrillic iso-ir-111 KOI8-E csISO111ECMACyrillic }
7203 { CSA_Z243.4-1985-1 iso-ir-121 ISO646-CA csa7-1 ca csISO121Canadian1 }
7204 { CSA_Z243.4-1985-2 iso-ir-122 ISO646-CA2 csa7-2 csISO122Canadian2 }
7205 { CSA_Z243.4-1985-gr iso-ir-123 csISO123CSAZ24341985gr }
7206 { ISO_8859-6:1987 iso-ir-127 ISO_8859-6 ISO-8859-6 ECMA-114 ASMO-708
7207 arabic csISOLatinArabic }
7208 { ISO_8859-6-E csISO88596E ISO-8859-6-E }
7209 { ISO_8859-6-I csISO88596I ISO-8859-6-I }
7210 { ISO_8859-7:1987 iso-ir-126 ISO_8859-7 ISO-8859-7 ELOT_928 ECMA-118
7211 greek greek8 csISOLatinGreek }
7212 { T.101-G2 iso-ir-128 csISO128T101G2 }
7213 { ISO_8859-8:1988 iso-ir-138 ISO_8859-8 ISO-8859-8 hebrew
7214 csISOLatinHebrew }
7215 { ISO_8859-8-E csISO88598E ISO-8859-8-E }
7216 { ISO_8859-8-I csISO88598I ISO-8859-8-I }
7217 { CSN_369103 iso-ir-139 csISO139CSN369103 }
7218 { JUS_I.B1.002 iso-ir-141 ISO646-YU js yu csISO141JUSIB1002 }
7219 { ISO_6937-2-add iso-ir-142 csISOTextComm }
7220 { IEC_P27-1 iso-ir-143 csISO143IECP271 }
7221 { ISO_8859-5:1988 iso-ir-144 ISO_8859-5 ISO-8859-5 cyrillic
7222 csISOLatinCyrillic }
7223 { JUS_I.B1.003-serb iso-ir-146 serbian csISO146Serbian }
7224 { JUS_I.B1.003-mac macedonian iso-ir-147 csISO147Macedonian }
7225 { ISO_8859-9:1989 iso-ir-148 ISO_8859-9 ISO-8859-9 latin5 l5 csISOLatin5 }
7226 { greek-ccitt iso-ir-150 csISO150 csISO150GreekCCITT }
7227 { NC_NC00-10:81 cuba iso-ir-151 ISO646-CU csISO151Cuba }
7228 { ISO_6937-2-25 iso-ir-152 csISO6937Add }
7229 { GOST_19768-74 ST_SEV_358-88 iso-ir-153 csISO153GOST1976874 }
7230 { ISO_8859-supp iso-ir-154 latin1-2-5 csISO8859Supp }
7231 { ISO_10367-box iso-ir-155 csISO10367Box }
7232 { ISO-8859-10 iso-ir-157 l6 ISO_8859-10:1992 csISOLatin6 latin6 }
7233 { latin-lap lap iso-ir-158 csISO158Lap }
7234 { JIS_X0212-1990 x0212 iso-ir-159 csISO159JISX02121990 }
7235 { DS_2089 DS2089 ISO646-DK dk csISO646Danish }
7236 { us-dk csUSDK }
7237 { dk-us csDKUS }
7238 { JIS_X0201 X0201 csHalfWidthKatakana }
7239 { KSC5636 ISO646-KR csKSC5636 }
7240 { ISO-10646-UCS-2 csUnicode }
7241 { ISO-10646-UCS-4 csUCS4 }
7242 { DEC-MCS dec csDECMCS }
7243 { hp-roman8 roman8 r8 csHPRoman8 }
7244 { macintosh mac csMacintosh }
7245 { IBM037 cp037 ebcdic-cp-us ebcdic-cp-ca ebcdic-cp-wt ebcdic-cp-nl
7246 csIBM037 }
7247 { IBM038 EBCDIC-INT cp038 csIBM038 }
7248 { IBM273 CP273 csIBM273 }
7249 { IBM274 EBCDIC-BE CP274 csIBM274 }
7250 { IBM275 EBCDIC-BR cp275 csIBM275 }
7251 { IBM277 EBCDIC-CP-DK EBCDIC-CP-NO csIBM277 }
7252 { IBM278 CP278 ebcdic-cp-fi ebcdic-cp-se csIBM278 }
7253 { IBM280 CP280 ebcdic-cp-it csIBM280 }
7254 { IBM281 EBCDIC-JP-E cp281 csIBM281 }
7255 { IBM284 CP284 ebcdic-cp-es csIBM284 }
7256 { IBM285 CP285 ebcdic-cp-gb csIBM285 }
7257 { IBM290 cp290 EBCDIC-JP-kana csIBM290 }
7258 { IBM297 cp297 ebcdic-cp-fr csIBM297 }
7259 { IBM420 cp420 ebcdic-cp-ar1 csIBM420 }
7260 { IBM423 cp423 ebcdic-cp-gr csIBM423 }
7261 { IBM424 cp424 ebcdic-cp-he csIBM424 }
7262 { IBM437 cp437 437 csPC8CodePage437 }
7263 { IBM500 CP500 ebcdic-cp-be ebcdic-cp-ch csIBM500 }
7264 { IBM775 cp775 csPC775Baltic }
7265 { IBM850 cp850 850 csPC850Multilingual }
7266 { IBM851 cp851 851 csIBM851 }
7267 { IBM852 cp852 852 csPCp852 }
7268 { IBM855 cp855 855 csIBM855 }
7269 { IBM857 cp857 857 csIBM857 }
7270 { IBM860 cp860 860 csIBM860 }
7271 { IBM861 cp861 861 cp-is csIBM861 }
7272 { IBM862 cp862 862 csPC862LatinHebrew }
7273 { IBM863 cp863 863 csIBM863 }
7274 { IBM864 cp864 csIBM864 }
7275 { IBM865 cp865 865 csIBM865 }
7276 { IBM866 cp866 866 csIBM866 }
7277 { IBM868 CP868 cp-ar csIBM868 }
7278 { IBM869 cp869 869 cp-gr csIBM869 }
7279 { IBM870 CP870 ebcdic-cp-roece ebcdic-cp-yu csIBM870 }
7280 { IBM871 CP871 ebcdic-cp-is csIBM871 }
7281 { IBM880 cp880 EBCDIC-Cyrillic csIBM880 }
7282 { IBM891 cp891 csIBM891 }
7283 { IBM903 cp903 csIBM903 }
7284 { IBM904 cp904 904 csIBBM904 }
7285 { IBM905 CP905 ebcdic-cp-tr csIBM905 }
7286 { IBM918 CP918 ebcdic-cp-ar2 csIBM918 }
7287 { IBM1026 CP1026 csIBM1026 }
7288 { EBCDIC-AT-DE csIBMEBCDICATDE }
7289 { EBCDIC-AT-DE-A csEBCDICATDEA }
7290 { EBCDIC-CA-FR csEBCDICCAFR }
7291 { EBCDIC-DK-NO csEBCDICDKNO }
7292 { EBCDIC-DK-NO-A csEBCDICDKNOA }
7293 { EBCDIC-FI-SE csEBCDICFISE }
7294 { EBCDIC-FI-SE-A csEBCDICFISEA }
7295 { EBCDIC-FR csEBCDICFR }
7296 { EBCDIC-IT csEBCDICIT }
7297 { EBCDIC-PT csEBCDICPT }
7298 { EBCDIC-ES csEBCDICES }
7299 { EBCDIC-ES-A csEBCDICESA }
7300 { EBCDIC-ES-S csEBCDICESS }
7301 { EBCDIC-UK csEBCDICUK }
7302 { EBCDIC-US csEBCDICUS }
7303 { UNKNOWN-8BIT csUnknown8BiT }
7304 { MNEMONIC csMnemonic }
7305 { MNEM csMnem }
7306 { VISCII csVISCII }
7307 { VIQR csVIQR }
7308 { KOI8-R csKOI8R }
7309 { IBM00858 CCSID00858 CP00858 PC-Multilingual-850+euro }
7310 { IBM00924 CCSID00924 CP00924 ebcdic-Latin9--euro }
7311 { IBM01140 CCSID01140 CP01140 ebcdic-us-37+euro }
7312 { IBM01141 CCSID01141 CP01141 ebcdic-de-273+euro }
7313 { IBM01142 CCSID01142 CP01142 ebcdic-dk-277+euro ebcdic-no-277+euro }
7314 { IBM01143 CCSID01143 CP01143 ebcdic-fi-278+euro ebcdic-se-278+euro }
7315 { IBM01144 CCSID01144 CP01144 ebcdic-it-280+euro }
7316 { IBM01145 CCSID01145 CP01145 ebcdic-es-284+euro }
7317 { IBM01146 CCSID01146 CP01146 ebcdic-gb-285+euro }
7318 { IBM01147 CCSID01147 CP01147 ebcdic-fr-297+euro }
7319 { IBM01148 CCSID01148 CP01148 ebcdic-international-500+euro }
7320 { IBM01149 CCSID01149 CP01149 ebcdic-is-871+euro }
7321 { IBM1047 IBM-1047 }
7322 { PTCP154 csPTCP154 PT154 CP154 Cyrillic-Asian }
7323 { Amiga-1251 Ami1251 Amiga1251 Ami-1251 }
7324 { UNICODE-1-1 csUnicode11 }
7325 { CESU-8 csCESU-8 }
7326 { BOCU-1 csBOCU-1 }
7327 { UNICODE-1-1-UTF-7 csUnicode11UTF7 }
7328 { ISO-8859-14 iso-ir-199 ISO_8859-14:1998 ISO_8859-14 latin8 iso-celtic
7329 l8 }
7330 { ISO-8859-15 ISO_8859-15 Latin-9 }
7331 { ISO-8859-16 iso-ir-226 ISO_8859-16:2001 ISO_8859-16 latin10 l10 }
7332 { GBK CP936 MS936 windows-936 }
7333 { JIS_Encoding csJISEncoding }
7334 { Shift_JIS MS_Kanji csShiftJIS }
7335 { Extended_UNIX_Code_Packed_Format_for_Japanese csEUCPkdFmtJapanese
7336 EUC-JP }
7337 { Extended_UNIX_Code_Fixed_Width_for_Japanese csEUCFixWidJapanese }
7338 { ISO-10646-UCS-Basic csUnicodeASCII }
7339 { ISO-10646-Unicode-Latin1 csUnicodeLatin1 ISO-10646 }
7340 { ISO-Unicode-IBM-1261 csUnicodeIBM1261 }
7341 { ISO-Unicode-IBM-1268 csUnicodeIBM1268 }
7342 { ISO-Unicode-IBM-1276 csUnicodeIBM1276 }
7343 { ISO-Unicode-IBM-1264 csUnicodeIBM1264 }
7344 { ISO-Unicode-IBM-1265 csUnicodeIBM1265 }
7345 { ISO-8859-1-Windows-3.0-Latin-1 csWindows30Latin1 }
7346 { ISO-8859-1-Windows-3.1-Latin-1 csWindows31Latin1 }
7347 { ISO-8859-2-Windows-Latin-2 csWindows31Latin2 }
7348 { ISO-8859-9-Windows-Latin-5 csWindows31Latin5 }
7349 { Adobe-Standard-Encoding csAdobeStandardEncoding }
7350 { Ventura-US csVenturaUS }
7351 { Ventura-International csVenturaInternational }
7352 { PC8-Danish-Norwegian csPC8DanishNorwegian }
7353 { PC8-Turkish csPC8Turkish }
7354 { IBM-Symbols csIBMSymbols }
7355 { IBM-Thai csIBMThai }
7356 { HP-Legal csHPLegal }
7357 { HP-Pi-font csHPPiFont }
7358 { HP-Math8 csHPMath8 }
7359 { Adobe-Symbol-Encoding csHPPSMath }
7360 { HP-DeskTop csHPDesktop }
7361 { Ventura-Math csVenturaMath }
7362 { Microsoft-Publishing csMicrosoftPublishing }
7363 { Windows-31J csWindows31J }
7364 { GB2312 csGB2312 }
7365 { Big5 csBig5 }
7368 proc tcl_encoding {enc} {
7369 global encoding_aliases
7370 set names [encoding names]
7371 set lcnames [string tolower $names]
7372 set enc [string tolower $enc]
7373 set i [lsearch -exact $lcnames $enc]
7374 if {$i < 0} {
7375 # look for "isonnn" instead of "iso-nnn" or "iso_nnn"
7376 if {[regsub {^iso[-_]} $enc iso encx]} {
7377 set i [lsearch -exact $lcnames $encx]
7380 if {$i < 0} {
7381 foreach l $encoding_aliases {
7382 set ll [string tolower $l]
7383 if {[lsearch -exact $ll $enc] < 0} continue
7384 # look through the aliases for one that tcl knows about
7385 foreach e $ll {
7386 set i [lsearch -exact $lcnames $e]
7387 if {$i < 0} {
7388 if {[regsub {^iso[-_]} $e iso ex]} {
7389 set i [lsearch -exact $lcnames $ex]
7392 if {$i >= 0} break
7394 break
7397 if {$i >= 0} {
7398 return [lindex $names $i]
7400 return {}
7403 # defaults...
7404 set datemode 0
7405 set diffopts "-U 5 -p"
7406 set wrcomcmd "git diff-tree --stdin -p --pretty"
7408 set gitencoding {}
7409 catch {
7410 set gitencoding [exec git config --get i18n.commitencoding]
7412 if {$gitencoding == ""} {
7413 set gitencoding "utf-8"
7415 set tclencoding [tcl_encoding $gitencoding]
7416 if {$tclencoding == {}} {
7417 puts stderr "Warning: encoding $gitencoding is not supported by Tcl/Tk"
7420 set mainfont {Helvetica 9}
7421 set textfont {Courier 9}
7422 set uifont {Helvetica 9 bold}
7423 set tabstop 8
7424 set findmergefiles 0
7425 set maxgraphpct 50
7426 set maxwidth 16
7427 set revlistorder 0
7428 set fastdate 0
7429 set uparrowlen 7
7430 set downarrowlen 7
7431 set mingaplen 30
7432 set cmitmode "patch"
7433 set wrapcomment "none"
7434 set showneartags 1
7435 set maxrefs 20
7436 set maxlinelen 200
7437 set showlocalchanges 1
7439 set colors {green red blue magenta darkgrey brown orange}
7440 set bgcolor white
7441 set fgcolor black
7442 set diffcolors {red "#00a000" blue}
7443 set selectbgcolor gray85
7445 catch {source ~/.gitk}
7447 font create optionfont -family sans-serif -size -12
7449 set revtreeargs {}
7450 foreach arg $argv {
7451 switch -regexp -- $arg {
7452 "^$" { }
7453 "^-d" { set datemode 1 }
7454 default {
7455 lappend revtreeargs $arg
7460 # check that we can find a .git directory somewhere...
7461 set gitdir [gitdir]
7462 if {![file isdirectory $gitdir]} {
7463 show_error {} . "Cannot find the git directory \"$gitdir\"."
7464 exit 1
7467 set cmdline_files {}
7468 set i [lsearch -exact $revtreeargs "--"]
7469 if {$i >= 0} {
7470 set cmdline_files [lrange $revtreeargs [expr {$i + 1}] end]
7471 set revtreeargs [lrange $revtreeargs 0 [expr {$i - 1}]]
7472 } elseif {$revtreeargs ne {}} {
7473 if {[catch {
7474 set f [eval exec git rev-parse --no-revs --no-flags $revtreeargs]
7475 set cmdline_files [split $f "\n"]
7476 set n [llength $cmdline_files]
7477 set revtreeargs [lrange $revtreeargs 0 end-$n]
7478 } err]} {
7479 # unfortunately we get both stdout and stderr in $err,
7480 # so look for "fatal:".
7481 set i [string first "fatal:" $err]
7482 if {$i > 0} {
7483 set err [string range $err [expr {$i + 6}] end]
7485 show_error {} . "Bad arguments to gitk:\n$err"
7486 exit 1
7490 set nullid "0000000000000000000000000000000000000000"
7492 set runq {}
7493 set history {}
7494 set historyindex 0
7495 set fh_serial 0
7496 set nhl_names {}
7497 set highlight_paths {}
7498 set searchdirn -forwards
7499 set boldrows {}
7500 set boldnamerows {}
7501 set diffelide {0 0}
7502 set markingmatches 0
7504 set optim_delay 16
7506 set nextviewnum 1
7507 set curview 0
7508 set selectedview 0
7509 set selectedhlview None
7510 set viewfiles(0) {}
7511 set viewperm(0) 0
7512 set viewargs(0) {}
7514 set cmdlineok 0
7515 set stopped 0
7516 set stuffsaved 0
7517 set patchnum 0
7518 set lookingforhead 0
7519 set localrow -1
7520 set lserial 0
7521 setcoords
7522 makewindow
7523 wm title . "[file tail $argv0]: [file tail [pwd]]"
7524 readrefs
7526 if {$cmdline_files ne {} || $revtreeargs ne {}} {
7527 # create a view for the files/dirs specified on the command line
7528 set curview 1
7529 set selectedview 1
7530 set nextviewnum 2
7531 set viewname(1) "Command line"
7532 set viewfiles(1) $cmdline_files
7533 set viewargs(1) $revtreeargs
7534 set viewperm(1) 0
7535 addviewmenu 1
7536 .bar.view entryconf Edit* -state normal
7537 .bar.view entryconf Delete* -state normal
7540 if {[info exists permviews]} {
7541 foreach v $permviews {
7542 set n $nextviewnum
7543 incr nextviewnum
7544 set viewname($n) [lindex $v 0]
7545 set viewfiles($n) [lindex $v 1]
7546 set viewargs($n) [lindex $v 2]
7547 set viewperm($n) 1
7548 addviewmenu $n
7551 getcommits