gitk: New infrastructure for working out branches & previous/next tags
[git.git] / gitk
blob5948ec37c5c08883ebfed88af53731fffea521e1
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 proc start_rev_list {view} {
20 global startmsecs nextupdate
21 global commfd leftover tclencoding datemode
22 global viewargs viewfiles commitidx
24 set startmsecs [clock clicks -milliseconds]
25 set nextupdate [expr {$startmsecs + 100}]
26 set commitidx($view) 0
27 set args $viewargs($view)
28 if {$viewfiles($view) ne {}} {
29 set args [concat $args "--" $viewfiles($view)]
31 set order "--topo-order"
32 if {$datemode} {
33 set order "--date-order"
35 if {[catch {
36 set fd [open [concat | git rev-list --header $order \
37 --parents --boundary --default HEAD $args] r]
38 } err]} {
39 puts stderr "Error executing git rev-list: $err"
40 exit 1
42 set commfd($view) $fd
43 set leftover($view) {}
44 fconfigure $fd -blocking 0 -translation lf
45 if {$tclencoding != {}} {
46 fconfigure $fd -encoding $tclencoding
48 fileevent $fd readable [list getcommitlines $fd $view]
49 nowbusy $view
52 proc stop_rev_list {} {
53 global commfd curview
55 if {![info exists commfd($curview)]} return
56 set fd $commfd($curview)
57 catch {
58 set pid [pid $fd]
59 exec kill $pid
61 catch {close $fd}
62 unset commfd($curview)
65 proc getcommits {} {
66 global phase canv mainfont curview
68 set phase getcommits
69 initlayout
70 start_rev_list $curview
71 show_status "Reading commits..."
74 proc getcommitlines {fd view} {
75 global commitlisted nextupdate
76 global leftover commfd
77 global displayorder commitidx commitrow commitdata
78 global parentlist childlist children curview hlview
79 global vparentlist vchildlist vdisporder vcmitlisted
81 set stuff [read $fd 500000]
82 if {$stuff == {}} {
83 if {![eof $fd]} return
84 global viewname
85 unset commfd($view)
86 notbusy $view
87 # set it blocking so we wait for the process to terminate
88 fconfigure $fd -blocking 1
89 if {[catch {close $fd} err]} {
90 set fv {}
91 if {$view != $curview} {
92 set fv " for the \"$viewname($view)\" view"
94 if {[string range $err 0 4] == "usage"} {
95 set err "Gitk: error reading commits$fv:\
96 bad arguments to git rev-list."
97 if {$viewname($view) eq "Command line"} {
98 append err \
99 " (Note: arguments to gitk are passed to git rev-list\
100 to allow selection of commits to be displayed.)"
102 } else {
103 set err "Error reading commits$fv: $err"
105 error_popup $err
107 if {$view == $curview} {
108 after idle finishcommits
110 return
112 set start 0
113 set gotsome 0
114 while 1 {
115 set i [string first "\0" $stuff $start]
116 if {$i < 0} {
117 append leftover($view) [string range $stuff $start end]
118 break
120 if {$start == 0} {
121 set cmit $leftover($view)
122 append cmit [string range $stuff 0 [expr {$i - 1}]]
123 set leftover($view) {}
124 } else {
125 set cmit [string range $stuff $start [expr {$i - 1}]]
127 set start [expr {$i + 1}]
128 set j [string first "\n" $cmit]
129 set ok 0
130 set listed 1
131 if {$j >= 0} {
132 set ids [string range $cmit 0 [expr {$j - 1}]]
133 if {[string range $ids 0 0] == "-"} {
134 set listed 0
135 set ids [string range $ids 1 end]
137 set ok 1
138 foreach id $ids {
139 if {[string length $id] != 40} {
140 set ok 0
141 break
145 if {!$ok} {
146 set shortcmit $cmit
147 if {[string length $shortcmit] > 80} {
148 set shortcmit "[string range $shortcmit 0 80]..."
150 error_popup "Can't parse git rev-list output: {$shortcmit}"
151 exit 1
153 set id [lindex $ids 0]
154 if {$listed} {
155 set olds [lrange $ids 1 end]
156 set i 0
157 foreach p $olds {
158 if {$i == 0 || [lsearch -exact $olds $p] >= $i} {
159 lappend children($view,$p) $id
161 incr i
163 } else {
164 set olds {}
166 if {![info exists children($view,$id)]} {
167 set children($view,$id) {}
169 set commitdata($id) [string range $cmit [expr {$j + 1}] end]
170 set commitrow($view,$id) $commitidx($view)
171 incr commitidx($view)
172 if {$view == $curview} {
173 lappend parentlist $olds
174 lappend childlist $children($view,$id)
175 lappend displayorder $id
176 lappend commitlisted $listed
177 } else {
178 lappend vparentlist($view) $olds
179 lappend vchildlist($view) $children($view,$id)
180 lappend vdisporder($view) $id
181 lappend vcmitlisted($view) $listed
183 set gotsome 1
185 if {$gotsome} {
186 if {$view == $curview} {
187 while {[layoutmore $nextupdate]} doupdate
188 } elseif {[info exists hlview] && $view == $hlview} {
189 vhighlightmore
192 if {[clock clicks -milliseconds] >= $nextupdate} {
193 doupdate
197 proc doupdate {} {
198 global commfd nextupdate numcommits
200 foreach v [array names commfd] {
201 fileevent $commfd($v) readable {}
203 update
204 set nextupdate [expr {[clock clicks -milliseconds] + 100}]
205 foreach v [array names commfd] {
206 set fd $commfd($v)
207 fileevent $fd readable [list getcommitlines $fd $v]
211 proc readcommit {id} {
212 if {[catch {set contents [exec git cat-file commit $id]}]} return
213 parsecommit $id $contents 0
216 proc updatecommits {} {
217 global viewdata curview phase displayorder
218 global children commitrow selectedline thickerline
220 if {$phase ne {}} {
221 stop_rev_list
222 set phase {}
224 set n $curview
225 foreach id $displayorder {
226 catch {unset children($n,$id)}
227 catch {unset commitrow($n,$id)}
229 set curview -1
230 catch {unset selectedline}
231 catch {unset thickerline}
232 catch {unset viewdata($n)}
233 readrefs
234 changedrefs
235 regetallcommits
236 showview $n
239 proc parsecommit {id contents listed} {
240 global commitinfo cdate
242 set inhdr 1
243 set comment {}
244 set headline {}
245 set auname {}
246 set audate {}
247 set comname {}
248 set comdate {}
249 set hdrend [string first "\n\n" $contents]
250 if {$hdrend < 0} {
251 # should never happen...
252 set hdrend [string length $contents]
254 set header [string range $contents 0 [expr {$hdrend - 1}]]
255 set comment [string range $contents [expr {$hdrend + 2}] end]
256 foreach line [split $header "\n"] {
257 set tag [lindex $line 0]
258 if {$tag == "author"} {
259 set audate [lindex $line end-1]
260 set auname [lrange $line 1 end-2]
261 } elseif {$tag == "committer"} {
262 set comdate [lindex $line end-1]
263 set comname [lrange $line 1 end-2]
266 set headline {}
267 # take the first line of the comment as the headline
268 set i [string first "\n" $comment]
269 if {$i >= 0} {
270 set headline [string trim [string range $comment 0 $i]]
271 } else {
272 set headline $comment
274 if {!$listed} {
275 # git rev-list indents the comment by 4 spaces;
276 # if we got this via git cat-file, add the indentation
277 set newcomment {}
278 foreach line [split $comment "\n"] {
279 append newcomment " "
280 append newcomment $line
281 append newcomment "\n"
283 set comment $newcomment
285 if {$comdate != {}} {
286 set cdate($id) $comdate
288 set commitinfo($id) [list $headline $auname $audate \
289 $comname $comdate $comment]
292 proc getcommit {id} {
293 global commitdata commitinfo
295 if {[info exists commitdata($id)]} {
296 parsecommit $id $commitdata($id) 1
297 } else {
298 readcommit $id
299 if {![info exists commitinfo($id)]} {
300 set commitinfo($id) {"No commit information available"}
303 return 1
306 proc readrefs {} {
307 global tagids idtags headids idheads tagcontents
308 global otherrefids idotherrefs mainhead
310 foreach v {tagids idtags headids idheads otherrefids idotherrefs} {
311 catch {unset $v}
313 set refd [open [list | git show-ref] r]
314 while {0 <= [set n [gets $refd line]]} {
315 if {![regexp {^([0-9a-f]{40}) refs/([^^]*)$} $line \
316 match id path]} {
317 continue
319 if {[regexp {^remotes/.*/HEAD$} $path match]} {
320 continue
322 if {![regexp {^(tags|heads)/(.*)$} $path match type name]} {
323 set type others
324 set name $path
326 if {[regexp {^remotes/} $path match]} {
327 set type heads
329 if {$type == "tags"} {
330 set tagids($name) $id
331 lappend idtags($id) $name
332 set obj {}
333 set type {}
334 set tag {}
335 catch {
336 set commit [exec git rev-parse "$id^0"]
337 if {$commit != $id} {
338 set tagids($name) $commit
339 lappend idtags($commit) $name
342 catch {
343 set tagcontents($name) [exec git cat-file tag $id]
345 } elseif { $type == "heads" } {
346 set headids($name) $id
347 lappend idheads($id) $name
348 } else {
349 set otherrefids($name) $id
350 lappend idotherrefs($id) $name
353 close $refd
354 set mainhead {}
355 catch {
356 set thehead [exec git symbolic-ref HEAD]
357 if {[string match "refs/heads/*" $thehead]} {
358 set mainhead [string range $thehead 11 end]
363 # update things for a head moved to a child of its previous location
364 proc movehead {id name} {
365 global headids idheads
367 removehead $headids($name) $name
368 set headids($name) $id
369 lappend idheads($id) $name
372 # update things when a head has been removed
373 proc removehead {id name} {
374 global headids idheads
376 if {$idheads($id) eq $name} {
377 unset idheads($id)
378 } else {
379 set i [lsearch -exact $idheads($id) $name]
380 if {$i >= 0} {
381 set idheads($id) [lreplace $idheads($id) $i $i]
384 unset headids($name)
387 proc show_error {w top msg} {
388 message $w.m -text $msg -justify center -aspect 400
389 pack $w.m -side top -fill x -padx 20 -pady 20
390 button $w.ok -text OK -command "destroy $top"
391 pack $w.ok -side bottom -fill x
392 bind $top <Visibility> "grab $top; focus $top"
393 bind $top <Key-Return> "destroy $top"
394 tkwait window $top
397 proc error_popup msg {
398 set w .error
399 toplevel $w
400 wm transient $w .
401 show_error $w $w $msg
404 proc confirm_popup msg {
405 global confirm_ok
406 set confirm_ok 0
407 set w .confirm
408 toplevel $w
409 wm transient $w .
410 message $w.m -text $msg -justify center -aspect 400
411 pack $w.m -side top -fill x -padx 20 -pady 20
412 button $w.ok -text OK -command "set confirm_ok 1; destroy $w"
413 pack $w.ok -side left -fill x
414 button $w.cancel -text Cancel -command "destroy $w"
415 pack $w.cancel -side right -fill x
416 bind $w <Visibility> "grab $w; focus $w"
417 tkwait window $w
418 return $confirm_ok
421 proc makewindow {} {
422 global canv canv2 canv3 linespc charspc ctext cflist
423 global textfont mainfont uifont tabstop
424 global findtype findtypemenu findloc findstring fstring geometry
425 global entries sha1entry sha1string sha1but
426 global maincursor textcursor curtextcursor
427 global rowctxmenu mergemax wrapcomment
428 global highlight_files gdttype
429 global searchstring sstring
430 global bgcolor fgcolor bglist fglist diffcolors selectbgcolor
431 global headctxmenu
433 menu .bar
434 .bar add cascade -label "File" -menu .bar.file
435 .bar configure -font $uifont
436 menu .bar.file
437 .bar.file add command -label "Update" -command updatecommits
438 .bar.file add command -label "Reread references" -command rereadrefs
439 .bar.file add command -label "Quit" -command doquit
440 .bar.file configure -font $uifont
441 menu .bar.edit
442 .bar add cascade -label "Edit" -menu .bar.edit
443 .bar.edit add command -label "Preferences" -command doprefs
444 .bar.edit configure -font $uifont
446 menu .bar.view -font $uifont
447 .bar add cascade -label "View" -menu .bar.view
448 .bar.view add command -label "New view..." -command {newview 0}
449 .bar.view add command -label "Edit view..." -command editview \
450 -state disabled
451 .bar.view add command -label "Delete view" -command delview -state disabled
452 .bar.view add separator
453 .bar.view add radiobutton -label "All files" -command {showview 0} \
454 -variable selectedview -value 0
456 menu .bar.help
457 .bar add cascade -label "Help" -menu .bar.help
458 .bar.help add command -label "About gitk" -command about
459 .bar.help add command -label "Key bindings" -command keys
460 .bar.help configure -font $uifont
461 . configure -menu .bar
463 # the gui has upper and lower half, parts of a paned window.
464 panedwindow .ctop -orient vertical
466 # possibly use assumed geometry
467 if {![info exists geometry(pwsash0)]} {
468 set geometry(topheight) [expr {15 * $linespc}]
469 set geometry(topwidth) [expr {80 * $charspc}]
470 set geometry(botheight) [expr {15 * $linespc}]
471 set geometry(botwidth) [expr {50 * $charspc}]
472 set geometry(pwsash0) "[expr {40 * $charspc}] 2"
473 set geometry(pwsash1) "[expr {60 * $charspc}] 2"
476 # the upper half will have a paned window, a scroll bar to the right, and some stuff below
477 frame .tf -height $geometry(topheight) -width $geometry(topwidth)
478 frame .tf.histframe
479 panedwindow .tf.histframe.pwclist -orient horizontal -sashpad 0 -handlesize 4
481 # create three canvases
482 set cscroll .tf.histframe.csb
483 set canv .tf.histframe.pwclist.canv
484 canvas $canv \
485 -selectbackground $selectbgcolor \
486 -background $bgcolor -bd 0 \
487 -yscrollincr $linespc -yscrollcommand "scrollcanv $cscroll"
488 .tf.histframe.pwclist add $canv
489 set canv2 .tf.histframe.pwclist.canv2
490 canvas $canv2 \
491 -selectbackground $selectbgcolor \
492 -background $bgcolor -bd 0 -yscrollincr $linespc
493 .tf.histframe.pwclist add $canv2
494 set canv3 .tf.histframe.pwclist.canv3
495 canvas $canv3 \
496 -selectbackground $selectbgcolor \
497 -background $bgcolor -bd 0 -yscrollincr $linespc
498 .tf.histframe.pwclist add $canv3
499 eval .tf.histframe.pwclist sash place 0 $geometry(pwsash0)
500 eval .tf.histframe.pwclist sash place 1 $geometry(pwsash1)
502 # a scroll bar to rule them
503 scrollbar $cscroll -command {allcanvs yview} -highlightthickness 0
504 pack $cscroll -side right -fill y
505 bind .tf.histframe.pwclist <Configure> {resizeclistpanes %W %w}
506 lappend bglist $canv $canv2 $canv3
507 pack .tf.histframe.pwclist -fill both -expand 1 -side left
509 # we have two button bars at bottom of top frame. Bar 1
510 frame .tf.bar
511 frame .tf.lbar -height 15
513 set sha1entry .tf.bar.sha1
514 set entries $sha1entry
515 set sha1but .tf.bar.sha1label
516 button $sha1but -text "SHA1 ID: " -state disabled -relief flat \
517 -command gotocommit -width 8 -font $uifont
518 $sha1but conf -disabledforeground [$sha1but cget -foreground]
519 pack .tf.bar.sha1label -side left
520 entry $sha1entry -width 40 -font $textfont -textvariable sha1string
521 trace add variable sha1string write sha1change
522 pack $sha1entry -side left -pady 2
524 image create bitmap bm-left -data {
525 #define left_width 16
526 #define left_height 16
527 static unsigned char left_bits[] = {
528 0x00, 0x00, 0xc0, 0x01, 0xe0, 0x00, 0x70, 0x00, 0x38, 0x00, 0x1c, 0x00,
529 0x0e, 0x00, 0xff, 0x7f, 0xff, 0x7f, 0xff, 0x7f, 0x0e, 0x00, 0x1c, 0x00,
530 0x38, 0x00, 0x70, 0x00, 0xe0, 0x00, 0xc0, 0x01};
532 image create bitmap bm-right -data {
533 #define right_width 16
534 #define right_height 16
535 static unsigned char right_bits[] = {
536 0x00, 0x00, 0xc0, 0x01, 0x80, 0x03, 0x00, 0x07, 0x00, 0x0e, 0x00, 0x1c,
537 0x00, 0x38, 0xff, 0x7f, 0xff, 0x7f, 0xff, 0x7f, 0x00, 0x38, 0x00, 0x1c,
538 0x00, 0x0e, 0x00, 0x07, 0x80, 0x03, 0xc0, 0x01};
540 button .tf.bar.leftbut -image bm-left -command goback \
541 -state disabled -width 26
542 pack .tf.bar.leftbut -side left -fill y
543 button .tf.bar.rightbut -image bm-right -command goforw \
544 -state disabled -width 26
545 pack .tf.bar.rightbut -side left -fill y
547 button .tf.bar.findbut -text "Find" -command dofind -font $uifont
548 pack .tf.bar.findbut -side left
549 set findstring {}
550 set fstring .tf.bar.findstring
551 lappend entries $fstring
552 entry $fstring -width 30 -font $textfont -textvariable findstring
553 trace add variable findstring write find_change
554 pack $fstring -side left -expand 1 -fill x -in .tf.bar
555 set findtype Exact
556 set findtypemenu [tk_optionMenu .tf.bar.findtype \
557 findtype Exact IgnCase Regexp]
558 trace add variable findtype write find_change
559 .tf.bar.findtype configure -font $uifont
560 .tf.bar.findtype.menu configure -font $uifont
561 set findloc "All fields"
562 tk_optionMenu .tf.bar.findloc findloc "All fields" Headline \
563 Comments Author Committer
564 trace add variable findloc write find_change
565 .tf.bar.findloc configure -font $uifont
566 .tf.bar.findloc.menu configure -font $uifont
567 pack .tf.bar.findloc -side right
568 pack .tf.bar.findtype -side right
570 # build up the bottom bar of upper window
571 label .tf.lbar.flabel -text "Highlight: Commits " \
572 -font $uifont
573 pack .tf.lbar.flabel -side left -fill y
574 set gdttype "touching paths:"
575 set gm [tk_optionMenu .tf.lbar.gdttype gdttype "touching paths:" \
576 "adding/removing string:"]
577 trace add variable gdttype write hfiles_change
578 $gm conf -font $uifont
579 .tf.lbar.gdttype conf -font $uifont
580 pack .tf.lbar.gdttype -side left -fill y
581 entry .tf.lbar.fent -width 25 -font $textfont \
582 -textvariable highlight_files
583 trace add variable highlight_files write hfiles_change
584 lappend entries .tf.lbar.fent
585 pack .tf.lbar.fent -side left -fill x -expand 1
586 label .tf.lbar.vlabel -text " OR in view" -font $uifont
587 pack .tf.lbar.vlabel -side left -fill y
588 global viewhlmenu selectedhlview
589 set viewhlmenu [tk_optionMenu .tf.lbar.vhl selectedhlview None]
590 $viewhlmenu entryconf None -command delvhighlight
591 $viewhlmenu conf -font $uifont
592 .tf.lbar.vhl conf -font $uifont
593 pack .tf.lbar.vhl -side left -fill y
594 label .tf.lbar.rlabel -text " OR " -font $uifont
595 pack .tf.lbar.rlabel -side left -fill y
596 global highlight_related
597 set m [tk_optionMenu .tf.lbar.relm highlight_related None \
598 "Descendent" "Not descendent" "Ancestor" "Not ancestor"]
599 $m conf -font $uifont
600 .tf.lbar.relm conf -font $uifont
601 trace add variable highlight_related write vrel_change
602 pack .tf.lbar.relm -side left -fill y
604 # Finish putting the upper half of the viewer together
605 pack .tf.lbar -in .tf -side bottom -fill x
606 pack .tf.bar -in .tf -side bottom -fill x
607 pack .tf.histframe -fill both -side top -expand 1
608 .ctop add .tf
609 .ctop paneconfigure .tf -height $geometry(topheight)
610 .ctop paneconfigure .tf -width $geometry(topwidth)
612 # now build up the bottom
613 panedwindow .pwbottom -orient horizontal
615 # lower left, a text box over search bar, scroll bar to the right
616 # if we know window height, then that will set the lower text height, otherwise
617 # we set lower text height which will drive window height
618 if {[info exists geometry(main)]} {
619 frame .bleft -width $geometry(botwidth)
620 } else {
621 frame .bleft -width $geometry(botwidth) -height $geometry(botheight)
623 frame .bleft.top
624 frame .bleft.mid
626 button .bleft.top.search -text "Search" -command dosearch \
627 -font $uifont
628 pack .bleft.top.search -side left -padx 5
629 set sstring .bleft.top.sstring
630 entry $sstring -width 20 -font $textfont -textvariable searchstring
631 lappend entries $sstring
632 trace add variable searchstring write incrsearch
633 pack $sstring -side left -expand 1 -fill x
634 radiobutton .bleft.mid.diff -text "Diff" \
635 -command changediffdisp -variable diffelide -value {0 0}
636 radiobutton .bleft.mid.old -text "Old version" \
637 -command changediffdisp -variable diffelide -value {0 1}
638 radiobutton .bleft.mid.new -text "New version" \
639 -command changediffdisp -variable diffelide -value {1 0}
640 pack .bleft.mid.diff .bleft.mid.old .bleft.mid.new -side left
641 set ctext .bleft.ctext
642 text $ctext -background $bgcolor -foreground $fgcolor \
643 -tabs "[expr {$tabstop * $charspc}]" \
644 -state disabled -font $textfont \
645 -yscrollcommand scrolltext -wrap none
646 scrollbar .bleft.sb -command "$ctext yview"
647 pack .bleft.top -side top -fill x
648 pack .bleft.mid -side top -fill x
649 pack .bleft.sb -side right -fill y
650 pack $ctext -side left -fill both -expand 1
651 lappend bglist $ctext
652 lappend fglist $ctext
654 $ctext tag conf comment -wrap $wrapcomment
655 $ctext tag conf filesep -font [concat $textfont bold] -back "#aaaaaa"
656 $ctext tag conf hunksep -fore [lindex $diffcolors 2]
657 $ctext tag conf d0 -fore [lindex $diffcolors 0]
658 $ctext tag conf d1 -fore [lindex $diffcolors 1]
659 $ctext tag conf m0 -fore red
660 $ctext tag conf m1 -fore blue
661 $ctext tag conf m2 -fore green
662 $ctext tag conf m3 -fore purple
663 $ctext tag conf m4 -fore brown
664 $ctext tag conf m5 -fore "#009090"
665 $ctext tag conf m6 -fore magenta
666 $ctext tag conf m7 -fore "#808000"
667 $ctext tag conf m8 -fore "#009000"
668 $ctext tag conf m9 -fore "#ff0080"
669 $ctext tag conf m10 -fore cyan
670 $ctext tag conf m11 -fore "#b07070"
671 $ctext tag conf m12 -fore "#70b0f0"
672 $ctext tag conf m13 -fore "#70f0b0"
673 $ctext tag conf m14 -fore "#f0b070"
674 $ctext tag conf m15 -fore "#ff70b0"
675 $ctext tag conf mmax -fore darkgrey
676 set mergemax 16
677 $ctext tag conf mresult -font [concat $textfont bold]
678 $ctext tag conf msep -font [concat $textfont bold]
679 $ctext tag conf found -back yellow
681 .pwbottom add .bleft
682 .pwbottom paneconfigure .bleft -width $geometry(botwidth)
684 # lower right
685 frame .bright
686 frame .bright.mode
687 radiobutton .bright.mode.patch -text "Patch" \
688 -command reselectline -variable cmitmode -value "patch"
689 .bright.mode.patch configure -font $uifont
690 radiobutton .bright.mode.tree -text "Tree" \
691 -command reselectline -variable cmitmode -value "tree"
692 .bright.mode.tree configure -font $uifont
693 grid .bright.mode.patch .bright.mode.tree -sticky ew
694 pack .bright.mode -side top -fill x
695 set cflist .bright.cfiles
696 set indent [font measure $mainfont "nn"]
697 text $cflist \
698 -selectbackground $selectbgcolor \
699 -background $bgcolor -foreground $fgcolor \
700 -font $mainfont \
701 -tabs [list $indent [expr {2 * $indent}]] \
702 -yscrollcommand ".bright.sb set" \
703 -cursor [. cget -cursor] \
704 -spacing1 1 -spacing3 1
705 lappend bglist $cflist
706 lappend fglist $cflist
707 scrollbar .bright.sb -command "$cflist yview"
708 pack .bright.sb -side right -fill y
709 pack $cflist -side left -fill both -expand 1
710 $cflist tag configure highlight \
711 -background [$cflist cget -selectbackground]
712 $cflist tag configure bold -font [concat $mainfont bold]
714 .pwbottom add .bright
715 .ctop add .pwbottom
717 # restore window position if known
718 if {[info exists geometry(main)]} {
719 wm geometry . "$geometry(main)"
722 bind .pwbottom <Configure> {resizecdetpanes %W %w}
723 pack .ctop -fill both -expand 1
724 bindall <1> {selcanvline %W %x %y}
725 #bindall <B1-Motion> {selcanvline %W %x %y}
726 bindall <ButtonRelease-4> "allcanvs yview scroll -5 units"
727 bindall <ButtonRelease-5> "allcanvs yview scroll 5 units"
728 bindall <2> "canvscan mark %W %x %y"
729 bindall <B2-Motion> "canvscan dragto %W %x %y"
730 bindkey <Home> selfirstline
731 bindkey <End> sellastline
732 bind . <Key-Up> "selnextline -1"
733 bind . <Key-Down> "selnextline 1"
734 bind . <Shift-Key-Up> "next_highlight -1"
735 bind . <Shift-Key-Down> "next_highlight 1"
736 bindkey <Key-Right> "goforw"
737 bindkey <Key-Left> "goback"
738 bind . <Key-Prior> "selnextpage -1"
739 bind . <Key-Next> "selnextpage 1"
740 bind . <Control-Home> "allcanvs yview moveto 0.0"
741 bind . <Control-End> "allcanvs yview moveto 1.0"
742 bind . <Control-Key-Up> "allcanvs yview scroll -1 units"
743 bind . <Control-Key-Down> "allcanvs yview scroll 1 units"
744 bind . <Control-Key-Prior> "allcanvs yview scroll -1 pages"
745 bind . <Control-Key-Next> "allcanvs yview scroll 1 pages"
746 bindkey <Key-Delete> "$ctext yview scroll -1 pages"
747 bindkey <Key-BackSpace> "$ctext yview scroll -1 pages"
748 bindkey <Key-space> "$ctext yview scroll 1 pages"
749 bindkey p "selnextline -1"
750 bindkey n "selnextline 1"
751 bindkey z "goback"
752 bindkey x "goforw"
753 bindkey i "selnextline -1"
754 bindkey k "selnextline 1"
755 bindkey j "goback"
756 bindkey l "goforw"
757 bindkey b "$ctext yview scroll -1 pages"
758 bindkey d "$ctext yview scroll 18 units"
759 bindkey u "$ctext yview scroll -18 units"
760 bindkey / {findnext 1}
761 bindkey <Key-Return> {findnext 0}
762 bindkey ? findprev
763 bindkey f nextfile
764 bindkey <F5> updatecommits
765 bind . <Control-q> doquit
766 bind . <Control-f> dofind
767 bind . <Control-g> {findnext 0}
768 bind . <Control-r> dosearchback
769 bind . <Control-s> dosearch
770 bind . <Control-equal> {incrfont 1}
771 bind . <Control-KP_Add> {incrfont 1}
772 bind . <Control-minus> {incrfont -1}
773 bind . <Control-KP_Subtract> {incrfont -1}
774 wm protocol . WM_DELETE_WINDOW doquit
775 bind . <Button-1> "click %W"
776 bind $fstring <Key-Return> dofind
777 bind $sha1entry <Key-Return> gotocommit
778 bind $sha1entry <<PasteSelection>> clearsha1
779 bind $cflist <1> {sel_flist %W %x %y; break}
780 bind $cflist <B1-Motion> {sel_flist %W %x %y; break}
781 bind $cflist <ButtonRelease-1> {treeclick %W %x %y}
783 set maincursor [. cget -cursor]
784 set textcursor [$ctext cget -cursor]
785 set curtextcursor $textcursor
787 set rowctxmenu .rowctxmenu
788 menu $rowctxmenu -tearoff 0
789 $rowctxmenu add command -label "Diff this -> selected" \
790 -command {diffvssel 0}
791 $rowctxmenu add command -label "Diff selected -> this" \
792 -command {diffvssel 1}
793 $rowctxmenu add command -label "Make patch" -command mkpatch
794 $rowctxmenu add command -label "Create tag" -command mktag
795 $rowctxmenu add command -label "Write commit to file" -command writecommit
796 $rowctxmenu add command -label "Create new branch" -command mkbranch
797 $rowctxmenu add command -label "Cherry-pick this commit" \
798 -command cherrypick
800 set headctxmenu .headctxmenu
801 menu $headctxmenu -tearoff 0
802 $headctxmenu add command -label "Check out this branch" \
803 -command cobranch
804 $headctxmenu add command -label "Remove this branch" \
805 -command rmbranch
808 # mouse-2 makes all windows scan vertically, but only the one
809 # the cursor is in scans horizontally
810 proc canvscan {op w x y} {
811 global canv canv2 canv3
812 foreach c [list $canv $canv2 $canv3] {
813 if {$c == $w} {
814 $c scan $op $x $y
815 } else {
816 $c scan $op 0 $y
821 proc scrollcanv {cscroll f0 f1} {
822 $cscroll set $f0 $f1
823 drawfrac $f0 $f1
824 flushhighlights
827 # when we make a key binding for the toplevel, make sure
828 # it doesn't get triggered when that key is pressed in the
829 # find string entry widget.
830 proc bindkey {ev script} {
831 global entries
832 bind . $ev $script
833 set escript [bind Entry $ev]
834 if {$escript == {}} {
835 set escript [bind Entry <Key>]
837 foreach e $entries {
838 bind $e $ev "$escript; break"
842 # set the focus back to the toplevel for any click outside
843 # the entry widgets
844 proc click {w} {
845 global entries
846 foreach e $entries {
847 if {$w == $e} return
849 focus .
852 proc savestuff {w} {
853 global canv canv2 canv3 ctext cflist mainfont textfont uifont tabstop
854 global stuffsaved findmergefiles maxgraphpct
855 global maxwidth showneartags
856 global viewname viewfiles viewargs viewperm nextviewnum
857 global cmitmode wrapcomment
858 global colors bgcolor fgcolor diffcolors selectbgcolor
860 if {$stuffsaved} return
861 if {![winfo viewable .]} return
862 catch {
863 set f [open "~/.gitk-new" w]
864 puts $f [list set mainfont $mainfont]
865 puts $f [list set textfont $textfont]
866 puts $f [list set uifont $uifont]
867 puts $f [list set tabstop $tabstop]
868 puts $f [list set findmergefiles $findmergefiles]
869 puts $f [list set maxgraphpct $maxgraphpct]
870 puts $f [list set maxwidth $maxwidth]
871 puts $f [list set cmitmode $cmitmode]
872 puts $f [list set wrapcomment $wrapcomment]
873 puts $f [list set showneartags $showneartags]
874 puts $f [list set bgcolor $bgcolor]
875 puts $f [list set fgcolor $fgcolor]
876 puts $f [list set colors $colors]
877 puts $f [list set diffcolors $diffcolors]
878 puts $f [list set selectbgcolor $selectbgcolor]
880 puts $f "set geometry(main) [wm geometry .]"
881 puts $f "set geometry(topwidth) [winfo width .tf]"
882 puts $f "set geometry(topheight) [winfo height .tf]"
883 puts $f "set geometry(pwsash0) \"[.tf.histframe.pwclist sash coord 0]\""
884 puts $f "set geometry(pwsash1) \"[.tf.histframe.pwclist sash coord 1]\""
885 puts $f "set geometry(botwidth) [winfo width .bleft]"
886 puts $f "set geometry(botheight) [winfo height .bleft]"
888 puts -nonewline $f "set permviews {"
889 for {set v 0} {$v < $nextviewnum} {incr v} {
890 if {$viewperm($v)} {
891 puts $f "{[list $viewname($v) $viewfiles($v) $viewargs($v)]}"
894 puts $f "}"
895 close $f
896 file rename -force "~/.gitk-new" "~/.gitk"
898 set stuffsaved 1
901 proc resizeclistpanes {win w} {
902 global oldwidth
903 if {[info exists oldwidth($win)]} {
904 set s0 [$win sash coord 0]
905 set s1 [$win sash coord 1]
906 if {$w < 60} {
907 set sash0 [expr {int($w/2 - 2)}]
908 set sash1 [expr {int($w*5/6 - 2)}]
909 } else {
910 set factor [expr {1.0 * $w / $oldwidth($win)}]
911 set sash0 [expr {int($factor * [lindex $s0 0])}]
912 set sash1 [expr {int($factor * [lindex $s1 0])}]
913 if {$sash0 < 30} {
914 set sash0 30
916 if {$sash1 < $sash0 + 20} {
917 set sash1 [expr {$sash0 + 20}]
919 if {$sash1 > $w - 10} {
920 set sash1 [expr {$w - 10}]
921 if {$sash0 > $sash1 - 20} {
922 set sash0 [expr {$sash1 - 20}]
926 $win sash place 0 $sash0 [lindex $s0 1]
927 $win sash place 1 $sash1 [lindex $s1 1]
929 set oldwidth($win) $w
932 proc resizecdetpanes {win w} {
933 global oldwidth
934 if {[info exists oldwidth($win)]} {
935 set s0 [$win sash coord 0]
936 if {$w < 60} {
937 set sash0 [expr {int($w*3/4 - 2)}]
938 } else {
939 set factor [expr {1.0 * $w / $oldwidth($win)}]
940 set sash0 [expr {int($factor * [lindex $s0 0])}]
941 if {$sash0 < 45} {
942 set sash0 45
944 if {$sash0 > $w - 15} {
945 set sash0 [expr {$w - 15}]
948 $win sash place 0 $sash0 [lindex $s0 1]
950 set oldwidth($win) $w
953 proc allcanvs args {
954 global canv canv2 canv3
955 eval $canv $args
956 eval $canv2 $args
957 eval $canv3 $args
960 proc bindall {event action} {
961 global canv canv2 canv3
962 bind $canv $event $action
963 bind $canv2 $event $action
964 bind $canv3 $event $action
967 proc about {} {
968 global uifont
969 set w .about
970 if {[winfo exists $w]} {
971 raise $w
972 return
974 toplevel $w
975 wm title $w "About gitk"
976 message $w.m -text {
977 Gitk - a commit viewer for git
979 Copyright © 2005-2006 Paul Mackerras
981 Use and redistribute under the terms of the GNU General Public License} \
982 -justify center -aspect 400 -border 2 -bg white -relief groove
983 pack $w.m -side top -fill x -padx 2 -pady 2
984 $w.m configure -font $uifont
985 button $w.ok -text Close -command "destroy $w" -default active
986 pack $w.ok -side bottom
987 $w.ok configure -font $uifont
988 bind $w <Visibility> "focus $w.ok"
989 bind $w <Key-Escape> "destroy $w"
990 bind $w <Key-Return> "destroy $w"
993 proc keys {} {
994 global uifont
995 set w .keys
996 if {[winfo exists $w]} {
997 raise $w
998 return
1000 toplevel $w
1001 wm title $w "Gitk key bindings"
1002 message $w.m -text {
1003 Gitk key bindings:
1005 <Ctrl-Q> Quit
1006 <Home> Move to first commit
1007 <End> Move to last commit
1008 <Up>, p, i Move up one commit
1009 <Down>, n, k Move down one commit
1010 <Left>, z, j Go back in history list
1011 <Right>, x, l Go forward in history list
1012 <PageUp> Move up one page in commit list
1013 <PageDown> Move down one page in commit list
1014 <Ctrl-Home> Scroll to top of commit list
1015 <Ctrl-End> Scroll to bottom of commit list
1016 <Ctrl-Up> Scroll commit list up one line
1017 <Ctrl-Down> Scroll commit list down one line
1018 <Ctrl-PageUp> Scroll commit list up one page
1019 <Ctrl-PageDown> Scroll commit list down one page
1020 <Shift-Up> Move to previous highlighted line
1021 <Shift-Down> Move to next highlighted line
1022 <Delete>, b Scroll diff view up one page
1023 <Backspace> Scroll diff view up one page
1024 <Space> Scroll diff view down one page
1025 u Scroll diff view up 18 lines
1026 d Scroll diff view down 18 lines
1027 <Ctrl-F> Find
1028 <Ctrl-G> Move to next find hit
1029 <Return> Move to next find hit
1030 / Move to next find hit, or redo find
1031 ? Move to previous find hit
1032 f Scroll diff view to next file
1033 <Ctrl-S> Search for next hit in diff view
1034 <Ctrl-R> Search for previous hit in diff view
1035 <Ctrl-KP+> Increase font size
1036 <Ctrl-plus> Increase font size
1037 <Ctrl-KP-> Decrease font size
1038 <Ctrl-minus> Decrease font size
1039 <F5> Update
1041 -justify left -bg white -border 2 -relief groove
1042 pack $w.m -side top -fill both -padx 2 -pady 2
1043 $w.m configure -font $uifont
1044 button $w.ok -text Close -command "destroy $w" -default active
1045 pack $w.ok -side bottom
1046 $w.ok configure -font $uifont
1047 bind $w <Visibility> "focus $w.ok"
1048 bind $w <Key-Escape> "destroy $w"
1049 bind $w <Key-Return> "destroy $w"
1052 # Procedures for manipulating the file list window at the
1053 # bottom right of the overall window.
1055 proc treeview {w l openlevs} {
1056 global treecontents treediropen treeheight treeparent treeindex
1058 set ix 0
1059 set treeindex() 0
1060 set lev 0
1061 set prefix {}
1062 set prefixend -1
1063 set prefendstack {}
1064 set htstack {}
1065 set ht 0
1066 set treecontents() {}
1067 $w conf -state normal
1068 foreach f $l {
1069 while {[string range $f 0 $prefixend] ne $prefix} {
1070 if {$lev <= $openlevs} {
1071 $w mark set e:$treeindex($prefix) "end -1c"
1072 $w mark gravity e:$treeindex($prefix) left
1074 set treeheight($prefix) $ht
1075 incr ht [lindex $htstack end]
1076 set htstack [lreplace $htstack end end]
1077 set prefixend [lindex $prefendstack end]
1078 set prefendstack [lreplace $prefendstack end end]
1079 set prefix [string range $prefix 0 $prefixend]
1080 incr lev -1
1082 set tail [string range $f [expr {$prefixend+1}] end]
1083 while {[set slash [string first "/" $tail]] >= 0} {
1084 lappend htstack $ht
1085 set ht 0
1086 lappend prefendstack $prefixend
1087 incr prefixend [expr {$slash + 1}]
1088 set d [string range $tail 0 $slash]
1089 lappend treecontents($prefix) $d
1090 set oldprefix $prefix
1091 append prefix $d
1092 set treecontents($prefix) {}
1093 set treeindex($prefix) [incr ix]
1094 set treeparent($prefix) $oldprefix
1095 set tail [string range $tail [expr {$slash+1}] end]
1096 if {$lev <= $openlevs} {
1097 set ht 1
1098 set treediropen($prefix) [expr {$lev < $openlevs}]
1099 set bm [expr {$lev == $openlevs? "tri-rt": "tri-dn"}]
1100 $w mark set d:$ix "end -1c"
1101 $w mark gravity d:$ix left
1102 set str "\n"
1103 for {set i 0} {$i < $lev} {incr i} {append str "\t"}
1104 $w insert end $str
1105 $w image create end -align center -image $bm -padx 1 \
1106 -name a:$ix
1107 $w insert end $d [highlight_tag $prefix]
1108 $w mark set s:$ix "end -1c"
1109 $w mark gravity s:$ix left
1111 incr lev
1113 if {$tail ne {}} {
1114 if {$lev <= $openlevs} {
1115 incr ht
1116 set str "\n"
1117 for {set i 0} {$i < $lev} {incr i} {append str "\t"}
1118 $w insert end $str
1119 $w insert end $tail [highlight_tag $f]
1121 lappend treecontents($prefix) $tail
1124 while {$htstack ne {}} {
1125 set treeheight($prefix) $ht
1126 incr ht [lindex $htstack end]
1127 set htstack [lreplace $htstack end end]
1129 $w conf -state disabled
1132 proc linetoelt {l} {
1133 global treeheight treecontents
1135 set y 2
1136 set prefix {}
1137 while {1} {
1138 foreach e $treecontents($prefix) {
1139 if {$y == $l} {
1140 return "$prefix$e"
1142 set n 1
1143 if {[string index $e end] eq "/"} {
1144 set n $treeheight($prefix$e)
1145 if {$y + $n > $l} {
1146 append prefix $e
1147 incr y
1148 break
1151 incr y $n
1156 proc highlight_tree {y prefix} {
1157 global treeheight treecontents cflist
1159 foreach e $treecontents($prefix) {
1160 set path $prefix$e
1161 if {[highlight_tag $path] ne {}} {
1162 $cflist tag add bold $y.0 "$y.0 lineend"
1164 incr y
1165 if {[string index $e end] eq "/" && $treeheight($path) > 1} {
1166 set y [highlight_tree $y $path]
1169 return $y
1172 proc treeclosedir {w dir} {
1173 global treediropen treeheight treeparent treeindex
1175 set ix $treeindex($dir)
1176 $w conf -state normal
1177 $w delete s:$ix e:$ix
1178 set treediropen($dir) 0
1179 $w image configure a:$ix -image tri-rt
1180 $w conf -state disabled
1181 set n [expr {1 - $treeheight($dir)}]
1182 while {$dir ne {}} {
1183 incr treeheight($dir) $n
1184 set dir $treeparent($dir)
1188 proc treeopendir {w dir} {
1189 global treediropen treeheight treeparent treecontents treeindex
1191 set ix $treeindex($dir)
1192 $w conf -state normal
1193 $w image configure a:$ix -image tri-dn
1194 $w mark set e:$ix s:$ix
1195 $w mark gravity e:$ix right
1196 set lev 0
1197 set str "\n"
1198 set n [llength $treecontents($dir)]
1199 for {set x $dir} {$x ne {}} {set x $treeparent($x)} {
1200 incr lev
1201 append str "\t"
1202 incr treeheight($x) $n
1204 foreach e $treecontents($dir) {
1205 set de $dir$e
1206 if {[string index $e end] eq "/"} {
1207 set iy $treeindex($de)
1208 $w mark set d:$iy e:$ix
1209 $w mark gravity d:$iy left
1210 $w insert e:$ix $str
1211 set treediropen($de) 0
1212 $w image create e:$ix -align center -image tri-rt -padx 1 \
1213 -name a:$iy
1214 $w insert e:$ix $e [highlight_tag $de]
1215 $w mark set s:$iy e:$ix
1216 $w mark gravity s:$iy left
1217 set treeheight($de) 1
1218 } else {
1219 $w insert e:$ix $str
1220 $w insert e:$ix $e [highlight_tag $de]
1223 $w mark gravity e:$ix left
1224 $w conf -state disabled
1225 set treediropen($dir) 1
1226 set top [lindex [split [$w index @0,0] .] 0]
1227 set ht [$w cget -height]
1228 set l [lindex [split [$w index s:$ix] .] 0]
1229 if {$l < $top} {
1230 $w yview $l.0
1231 } elseif {$l + $n + 1 > $top + $ht} {
1232 set top [expr {$l + $n + 2 - $ht}]
1233 if {$l < $top} {
1234 set top $l
1236 $w yview $top.0
1240 proc treeclick {w x y} {
1241 global treediropen cmitmode ctext cflist cflist_top
1243 if {$cmitmode ne "tree"} return
1244 if {![info exists cflist_top]} return
1245 set l [lindex [split [$w index "@$x,$y"] "."] 0]
1246 $cflist tag remove highlight $cflist_top.0 "$cflist_top.0 lineend"
1247 $cflist tag add highlight $l.0 "$l.0 lineend"
1248 set cflist_top $l
1249 if {$l == 1} {
1250 $ctext yview 1.0
1251 return
1253 set e [linetoelt $l]
1254 if {[string index $e end] ne "/"} {
1255 showfile $e
1256 } elseif {$treediropen($e)} {
1257 treeclosedir $w $e
1258 } else {
1259 treeopendir $w $e
1263 proc setfilelist {id} {
1264 global treefilelist cflist
1266 treeview $cflist $treefilelist($id) 0
1269 image create bitmap tri-rt -background black -foreground blue -data {
1270 #define tri-rt_width 13
1271 #define tri-rt_height 13
1272 static unsigned char tri-rt_bits[] = {
1273 0x00, 0x00, 0x00, 0x00, 0x10, 0x00, 0x30, 0x00, 0x70, 0x00, 0xf0, 0x00,
1274 0xf0, 0x01, 0xf0, 0x00, 0x70, 0x00, 0x30, 0x00, 0x10, 0x00, 0x00, 0x00,
1275 0x00, 0x00};
1276 } -maskdata {
1277 #define tri-rt-mask_width 13
1278 #define tri-rt-mask_height 13
1279 static unsigned char tri-rt-mask_bits[] = {
1280 0x08, 0x00, 0x18, 0x00, 0x38, 0x00, 0x78, 0x00, 0xf8, 0x00, 0xf8, 0x01,
1281 0xf8, 0x03, 0xf8, 0x01, 0xf8, 0x00, 0x78, 0x00, 0x38, 0x00, 0x18, 0x00,
1282 0x08, 0x00};
1284 image create bitmap tri-dn -background black -foreground blue -data {
1285 #define tri-dn_width 13
1286 #define tri-dn_height 13
1287 static unsigned char tri-dn_bits[] = {
1288 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0xfc, 0x07, 0xf8, 0x03,
1289 0xf0, 0x01, 0xe0, 0x00, 0x40, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
1290 0x00, 0x00};
1291 } -maskdata {
1292 #define tri-dn-mask_width 13
1293 #define tri-dn-mask_height 13
1294 static unsigned char tri-dn-mask_bits[] = {
1295 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0xff, 0x1f, 0xfe, 0x0f, 0xfc, 0x07,
1296 0xf8, 0x03, 0xf0, 0x01, 0xe0, 0x00, 0x40, 0x00, 0x00, 0x00, 0x00, 0x00,
1297 0x00, 0x00};
1300 proc init_flist {first} {
1301 global cflist cflist_top selectedline difffilestart
1303 $cflist conf -state normal
1304 $cflist delete 0.0 end
1305 if {$first ne {}} {
1306 $cflist insert end $first
1307 set cflist_top 1
1308 $cflist tag add highlight 1.0 "1.0 lineend"
1309 } else {
1310 catch {unset cflist_top}
1312 $cflist conf -state disabled
1313 set difffilestart {}
1316 proc highlight_tag {f} {
1317 global highlight_paths
1319 foreach p $highlight_paths {
1320 if {[string match $p $f]} {
1321 return "bold"
1324 return {}
1327 proc highlight_filelist {} {
1328 global cmitmode cflist
1330 $cflist conf -state normal
1331 if {$cmitmode ne "tree"} {
1332 set end [lindex [split [$cflist index end] .] 0]
1333 for {set l 2} {$l < $end} {incr l} {
1334 set line [$cflist get $l.0 "$l.0 lineend"]
1335 if {[highlight_tag $line] ne {}} {
1336 $cflist tag add bold $l.0 "$l.0 lineend"
1339 } else {
1340 highlight_tree 2 {}
1342 $cflist conf -state disabled
1345 proc unhighlight_filelist {} {
1346 global cflist
1348 $cflist conf -state normal
1349 $cflist tag remove bold 1.0 end
1350 $cflist conf -state disabled
1353 proc add_flist {fl} {
1354 global cflist
1356 $cflist conf -state normal
1357 foreach f $fl {
1358 $cflist insert end "\n"
1359 $cflist insert end $f [highlight_tag $f]
1361 $cflist conf -state disabled
1364 proc sel_flist {w x y} {
1365 global ctext difffilestart cflist cflist_top cmitmode
1367 if {$cmitmode eq "tree"} return
1368 if {![info exists cflist_top]} return
1369 set l [lindex [split [$w index "@$x,$y"] "."] 0]
1370 $cflist tag remove highlight $cflist_top.0 "$cflist_top.0 lineend"
1371 $cflist tag add highlight $l.0 "$l.0 lineend"
1372 set cflist_top $l
1373 if {$l == 1} {
1374 $ctext yview 1.0
1375 } else {
1376 catch {$ctext yview [lindex $difffilestart [expr {$l - 2}]]}
1380 # Functions for adding and removing shell-type quoting
1382 proc shellquote {str} {
1383 if {![string match "*\['\"\\ \t]*" $str]} {
1384 return $str
1386 if {![string match "*\['\"\\]*" $str]} {
1387 return "\"$str\""
1389 if {![string match "*'*" $str]} {
1390 return "'$str'"
1392 return "\"[string map {\" \\\" \\ \\\\} $str]\""
1395 proc shellarglist {l} {
1396 set str {}
1397 foreach a $l {
1398 if {$str ne {}} {
1399 append str " "
1401 append str [shellquote $a]
1403 return $str
1406 proc shelldequote {str} {
1407 set ret {}
1408 set used -1
1409 while {1} {
1410 incr used
1411 if {![regexp -start $used -indices "\['\"\\\\ \t]" $str first]} {
1412 append ret [string range $str $used end]
1413 set used [string length $str]
1414 break
1416 set first [lindex $first 0]
1417 set ch [string index $str $first]
1418 if {$first > $used} {
1419 append ret [string range $str $used [expr {$first - 1}]]
1420 set used $first
1422 if {$ch eq " " || $ch eq "\t"} break
1423 incr used
1424 if {$ch eq "'"} {
1425 set first [string first "'" $str $used]
1426 if {$first < 0} {
1427 error "unmatched single-quote"
1429 append ret [string range $str $used [expr {$first - 1}]]
1430 set used $first
1431 continue
1433 if {$ch eq "\\"} {
1434 if {$used >= [string length $str]} {
1435 error "trailing backslash"
1437 append ret [string index $str $used]
1438 continue
1440 # here ch == "\""
1441 while {1} {
1442 if {![regexp -start $used -indices "\[\"\\\\]" $str first]} {
1443 error "unmatched double-quote"
1445 set first [lindex $first 0]
1446 set ch [string index $str $first]
1447 if {$first > $used} {
1448 append ret [string range $str $used [expr {$first - 1}]]
1449 set used $first
1451 if {$ch eq "\""} break
1452 incr used
1453 append ret [string index $str $used]
1454 incr used
1457 return [list $used $ret]
1460 proc shellsplit {str} {
1461 set l {}
1462 while {1} {
1463 set str [string trimleft $str]
1464 if {$str eq {}} break
1465 set dq [shelldequote $str]
1466 set n [lindex $dq 0]
1467 set word [lindex $dq 1]
1468 set str [string range $str $n end]
1469 lappend l $word
1471 return $l
1474 # Code to implement multiple views
1476 proc newview {ishighlight} {
1477 global nextviewnum newviewname newviewperm uifont newishighlight
1478 global newviewargs revtreeargs
1480 set newishighlight $ishighlight
1481 set top .gitkview
1482 if {[winfo exists $top]} {
1483 raise $top
1484 return
1486 set newviewname($nextviewnum) "View $nextviewnum"
1487 set newviewperm($nextviewnum) 0
1488 set newviewargs($nextviewnum) [shellarglist $revtreeargs]
1489 vieweditor $top $nextviewnum "Gitk view definition"
1492 proc editview {} {
1493 global curview
1494 global viewname viewperm newviewname newviewperm
1495 global viewargs newviewargs
1497 set top .gitkvedit-$curview
1498 if {[winfo exists $top]} {
1499 raise $top
1500 return
1502 set newviewname($curview) $viewname($curview)
1503 set newviewperm($curview) $viewperm($curview)
1504 set newviewargs($curview) [shellarglist $viewargs($curview)]
1505 vieweditor $top $curview "Gitk: edit view $viewname($curview)"
1508 proc vieweditor {top n title} {
1509 global newviewname newviewperm viewfiles
1510 global uifont
1512 toplevel $top
1513 wm title $top $title
1514 label $top.nl -text "Name" -font $uifont
1515 entry $top.name -width 20 -textvariable newviewname($n) -font $uifont
1516 grid $top.nl $top.name -sticky w -pady 5
1517 checkbutton $top.perm -text "Remember this view" -variable newviewperm($n) \
1518 -font $uifont
1519 grid $top.perm - -pady 5 -sticky w
1520 message $top.al -aspect 1000 -font $uifont \
1521 -text "Commits to include (arguments to git rev-list):"
1522 grid $top.al - -sticky w -pady 5
1523 entry $top.args -width 50 -textvariable newviewargs($n) \
1524 -background white -font $uifont
1525 grid $top.args - -sticky ew -padx 5
1526 message $top.l -aspect 1000 -font $uifont \
1527 -text "Enter files and directories to include, one per line:"
1528 grid $top.l - -sticky w
1529 text $top.t -width 40 -height 10 -background white -font $uifont
1530 if {[info exists viewfiles($n)]} {
1531 foreach f $viewfiles($n) {
1532 $top.t insert end $f
1533 $top.t insert end "\n"
1535 $top.t delete {end - 1c} end
1536 $top.t mark set insert 0.0
1538 grid $top.t - -sticky ew -padx 5
1539 frame $top.buts
1540 button $top.buts.ok -text "OK" -command [list newviewok $top $n] \
1541 -font $uifont
1542 button $top.buts.can -text "Cancel" -command [list destroy $top] \
1543 -font $uifont
1544 grid $top.buts.ok $top.buts.can
1545 grid columnconfigure $top.buts 0 -weight 1 -uniform a
1546 grid columnconfigure $top.buts 1 -weight 1 -uniform a
1547 grid $top.buts - -pady 10 -sticky ew
1548 focus $top.t
1551 proc doviewmenu {m first cmd op argv} {
1552 set nmenu [$m index end]
1553 for {set i $first} {$i <= $nmenu} {incr i} {
1554 if {[$m entrycget $i -command] eq $cmd} {
1555 eval $m $op $i $argv
1556 break
1561 proc allviewmenus {n op args} {
1562 global viewhlmenu
1564 doviewmenu .bar.view 5 [list showview $n] $op $args
1565 doviewmenu $viewhlmenu 1 [list addvhighlight $n] $op $args
1568 proc newviewok {top n} {
1569 global nextviewnum newviewperm newviewname newishighlight
1570 global viewname viewfiles viewperm selectedview curview
1571 global viewargs newviewargs viewhlmenu
1573 if {[catch {
1574 set newargs [shellsplit $newviewargs($n)]
1575 } err]} {
1576 error_popup "Error in commit selection arguments: $err"
1577 wm raise $top
1578 focus $top
1579 return
1581 set files {}
1582 foreach f [split [$top.t get 0.0 end] "\n"] {
1583 set ft [string trim $f]
1584 if {$ft ne {}} {
1585 lappend files $ft
1588 if {![info exists viewfiles($n)]} {
1589 # creating a new view
1590 incr nextviewnum
1591 set viewname($n) $newviewname($n)
1592 set viewperm($n) $newviewperm($n)
1593 set viewfiles($n) $files
1594 set viewargs($n) $newargs
1595 addviewmenu $n
1596 if {!$newishighlight} {
1597 after idle showview $n
1598 } else {
1599 after idle addvhighlight $n
1601 } else {
1602 # editing an existing view
1603 set viewperm($n) $newviewperm($n)
1604 if {$newviewname($n) ne $viewname($n)} {
1605 set viewname($n) $newviewname($n)
1606 doviewmenu .bar.view 5 [list showview $n] \
1607 entryconf [list -label $viewname($n)]
1608 doviewmenu $viewhlmenu 1 [list addvhighlight $n] \
1609 entryconf [list -label $viewname($n) -value $viewname($n)]
1611 if {$files ne $viewfiles($n) || $newargs ne $viewargs($n)} {
1612 set viewfiles($n) $files
1613 set viewargs($n) $newargs
1614 if {$curview == $n} {
1615 after idle updatecommits
1619 catch {destroy $top}
1622 proc delview {} {
1623 global curview viewdata viewperm hlview selectedhlview
1625 if {$curview == 0} return
1626 if {[info exists hlview] && $hlview == $curview} {
1627 set selectedhlview None
1628 unset hlview
1630 allviewmenus $curview delete
1631 set viewdata($curview) {}
1632 set viewperm($curview) 0
1633 showview 0
1636 proc addviewmenu {n} {
1637 global viewname viewhlmenu
1639 .bar.view add radiobutton -label $viewname($n) \
1640 -command [list showview $n] -variable selectedview -value $n
1641 $viewhlmenu add radiobutton -label $viewname($n) \
1642 -command [list addvhighlight $n] -variable selectedhlview
1645 proc flatten {var} {
1646 global $var
1648 set ret {}
1649 foreach i [array names $var] {
1650 lappend ret $i [set $var\($i\)]
1652 return $ret
1655 proc unflatten {var l} {
1656 global $var
1658 catch {unset $var}
1659 foreach {i v} $l {
1660 set $var\($i\) $v
1664 proc showview {n} {
1665 global curview viewdata viewfiles
1666 global displayorder parentlist childlist rowidlist rowoffsets
1667 global colormap rowtextx commitrow nextcolor canvxmax
1668 global numcommits rowrangelist commitlisted idrowranges
1669 global selectedline currentid canv canvy0
1670 global matchinglines treediffs
1671 global pending_select phase
1672 global commitidx rowlaidout rowoptim linesegends
1673 global commfd nextupdate
1674 global selectedview
1675 global vparentlist vchildlist vdisporder vcmitlisted
1676 global hlview selectedhlview
1678 if {$n == $curview} return
1679 set selid {}
1680 if {[info exists selectedline]} {
1681 set selid $currentid
1682 set y [yc $selectedline]
1683 set ymax [lindex [$canv cget -scrollregion] 3]
1684 set span [$canv yview]
1685 set ytop [expr {[lindex $span 0] * $ymax}]
1686 set ybot [expr {[lindex $span 1] * $ymax}]
1687 if {$ytop < $y && $y < $ybot} {
1688 set yscreen [expr {$y - $ytop}]
1689 } else {
1690 set yscreen [expr {($ybot - $ytop) / 2}]
1693 unselectline
1694 normalline
1695 stopfindproc
1696 if {$curview >= 0} {
1697 set vparentlist($curview) $parentlist
1698 set vchildlist($curview) $childlist
1699 set vdisporder($curview) $displayorder
1700 set vcmitlisted($curview) $commitlisted
1701 if {$phase ne {}} {
1702 set viewdata($curview) \
1703 [list $phase $rowidlist $rowoffsets $rowrangelist \
1704 [flatten idrowranges] [flatten idinlist] \
1705 $rowlaidout $rowoptim $numcommits $linesegends]
1706 } elseif {![info exists viewdata($curview)]
1707 || [lindex $viewdata($curview) 0] ne {}} {
1708 set viewdata($curview) \
1709 [list {} $rowidlist $rowoffsets $rowrangelist]
1712 catch {unset matchinglines}
1713 catch {unset treediffs}
1714 clear_display
1715 if {[info exists hlview] && $hlview == $n} {
1716 unset hlview
1717 set selectedhlview None
1720 set curview $n
1721 set selectedview $n
1722 .bar.view entryconf Edit* -state [expr {$n == 0? "disabled": "normal"}]
1723 .bar.view entryconf Delete* -state [expr {$n == 0? "disabled": "normal"}]
1725 if {![info exists viewdata($n)]} {
1726 set pending_select $selid
1727 getcommits
1728 return
1731 set v $viewdata($n)
1732 set phase [lindex $v 0]
1733 set displayorder $vdisporder($n)
1734 set parentlist $vparentlist($n)
1735 set childlist $vchildlist($n)
1736 set commitlisted $vcmitlisted($n)
1737 set rowidlist [lindex $v 1]
1738 set rowoffsets [lindex $v 2]
1739 set rowrangelist [lindex $v 3]
1740 if {$phase eq {}} {
1741 set numcommits [llength $displayorder]
1742 catch {unset idrowranges}
1743 } else {
1744 unflatten idrowranges [lindex $v 4]
1745 unflatten idinlist [lindex $v 5]
1746 set rowlaidout [lindex $v 6]
1747 set rowoptim [lindex $v 7]
1748 set numcommits [lindex $v 8]
1749 set linesegends [lindex $v 9]
1752 catch {unset colormap}
1753 catch {unset rowtextx}
1754 set nextcolor 0
1755 set canvxmax [$canv cget -width]
1756 set curview $n
1757 set row 0
1758 setcanvscroll
1759 set yf 0
1760 set row 0
1761 if {$selid ne {} && [info exists commitrow($n,$selid)]} {
1762 set row $commitrow($n,$selid)
1763 # try to get the selected row in the same position on the screen
1764 set ymax [lindex [$canv cget -scrollregion] 3]
1765 set ytop [expr {[yc $row] - $yscreen}]
1766 if {$ytop < 0} {
1767 set ytop 0
1769 set yf [expr {$ytop * 1.0 / $ymax}]
1771 allcanvs yview moveto $yf
1772 drawvisible
1773 selectline $row 0
1774 if {$phase ne {}} {
1775 if {$phase eq "getcommits"} {
1776 show_status "Reading commits..."
1778 if {[info exists commfd($n)]} {
1779 layoutmore {}
1780 } else {
1781 finishcommits
1783 } elseif {$numcommits == 0} {
1784 show_status "No commits selected"
1788 # Stuff relating to the highlighting facility
1790 proc ishighlighted {row} {
1791 global vhighlights fhighlights nhighlights rhighlights
1793 if {[info exists nhighlights($row)] && $nhighlights($row) > 0} {
1794 return $nhighlights($row)
1796 if {[info exists vhighlights($row)] && $vhighlights($row) > 0} {
1797 return $vhighlights($row)
1799 if {[info exists fhighlights($row)] && $fhighlights($row) > 0} {
1800 return $fhighlights($row)
1802 if {[info exists rhighlights($row)] && $rhighlights($row) > 0} {
1803 return $rhighlights($row)
1805 return 0
1808 proc bolden {row font} {
1809 global canv linehtag selectedline boldrows
1811 lappend boldrows $row
1812 $canv itemconf $linehtag($row) -font $font
1813 if {[info exists selectedline] && $row == $selectedline} {
1814 $canv delete secsel
1815 set t [eval $canv create rect [$canv bbox $linehtag($row)] \
1816 -outline {{}} -tags secsel \
1817 -fill [$canv cget -selectbackground]]
1818 $canv lower $t
1822 proc bolden_name {row font} {
1823 global canv2 linentag selectedline boldnamerows
1825 lappend boldnamerows $row
1826 $canv2 itemconf $linentag($row) -font $font
1827 if {[info exists selectedline] && $row == $selectedline} {
1828 $canv2 delete secsel
1829 set t [eval $canv2 create rect [$canv2 bbox $linentag($row)] \
1830 -outline {{}} -tags secsel \
1831 -fill [$canv2 cget -selectbackground]]
1832 $canv2 lower $t
1836 proc unbolden {} {
1837 global mainfont boldrows
1839 set stillbold {}
1840 foreach row $boldrows {
1841 if {![ishighlighted $row]} {
1842 bolden $row $mainfont
1843 } else {
1844 lappend stillbold $row
1847 set boldrows $stillbold
1850 proc addvhighlight {n} {
1851 global hlview curview viewdata vhl_done vhighlights commitidx
1853 if {[info exists hlview]} {
1854 delvhighlight
1856 set hlview $n
1857 if {$n != $curview && ![info exists viewdata($n)]} {
1858 set viewdata($n) [list getcommits {{}} {{}} {} {} {} 0 0 0 {}]
1859 set vparentlist($n) {}
1860 set vchildlist($n) {}
1861 set vdisporder($n) {}
1862 set vcmitlisted($n) {}
1863 start_rev_list $n
1865 set vhl_done $commitidx($hlview)
1866 if {$vhl_done > 0} {
1867 drawvisible
1871 proc delvhighlight {} {
1872 global hlview vhighlights
1874 if {![info exists hlview]} return
1875 unset hlview
1876 catch {unset vhighlights}
1877 unbolden
1880 proc vhighlightmore {} {
1881 global hlview vhl_done commitidx vhighlights
1882 global displayorder vdisporder curview mainfont
1884 set font [concat $mainfont bold]
1885 set max $commitidx($hlview)
1886 if {$hlview == $curview} {
1887 set disp $displayorder
1888 } else {
1889 set disp $vdisporder($hlview)
1891 set vr [visiblerows]
1892 set r0 [lindex $vr 0]
1893 set r1 [lindex $vr 1]
1894 for {set i $vhl_done} {$i < $max} {incr i} {
1895 set id [lindex $disp $i]
1896 if {[info exists commitrow($curview,$id)]} {
1897 set row $commitrow($curview,$id)
1898 if {$r0 <= $row && $row <= $r1} {
1899 if {![highlighted $row]} {
1900 bolden $row $font
1902 set vhighlights($row) 1
1906 set vhl_done $max
1909 proc askvhighlight {row id} {
1910 global hlview vhighlights commitrow iddrawn mainfont
1912 if {[info exists commitrow($hlview,$id)]} {
1913 if {[info exists iddrawn($id)] && ![ishighlighted $row]} {
1914 bolden $row [concat $mainfont bold]
1916 set vhighlights($row) 1
1917 } else {
1918 set vhighlights($row) 0
1922 proc hfiles_change {name ix op} {
1923 global highlight_files filehighlight fhighlights fh_serial
1924 global mainfont highlight_paths
1926 if {[info exists filehighlight]} {
1927 # delete previous highlights
1928 catch {close $filehighlight}
1929 unset filehighlight
1930 catch {unset fhighlights}
1931 unbolden
1932 unhighlight_filelist
1934 set highlight_paths {}
1935 after cancel do_file_hl $fh_serial
1936 incr fh_serial
1937 if {$highlight_files ne {}} {
1938 after 300 do_file_hl $fh_serial
1942 proc makepatterns {l} {
1943 set ret {}
1944 foreach e $l {
1945 set ee [string map {"*" "\\*" "?" "\\?" "\[" "\\\[" "\\" "\\\\"} $e]
1946 if {[string index $ee end] eq "/"} {
1947 lappend ret "$ee*"
1948 } else {
1949 lappend ret $ee
1950 lappend ret "$ee/*"
1953 return $ret
1956 proc do_file_hl {serial} {
1957 global highlight_files filehighlight highlight_paths gdttype fhl_list
1959 if {$gdttype eq "touching paths:"} {
1960 if {[catch {set paths [shellsplit $highlight_files]}]} return
1961 set highlight_paths [makepatterns $paths]
1962 highlight_filelist
1963 set gdtargs [concat -- $paths]
1964 } else {
1965 set gdtargs [list "-S$highlight_files"]
1967 set cmd [concat | git diff-tree -r -s --stdin $gdtargs]
1968 set filehighlight [open $cmd r+]
1969 fconfigure $filehighlight -blocking 0
1970 fileevent $filehighlight readable readfhighlight
1971 set fhl_list {}
1972 drawvisible
1973 flushhighlights
1976 proc flushhighlights {} {
1977 global filehighlight fhl_list
1979 if {[info exists filehighlight]} {
1980 lappend fhl_list {}
1981 puts $filehighlight ""
1982 flush $filehighlight
1986 proc askfilehighlight {row id} {
1987 global filehighlight fhighlights fhl_list
1989 lappend fhl_list $id
1990 set fhighlights($row) -1
1991 puts $filehighlight $id
1994 proc readfhighlight {} {
1995 global filehighlight fhighlights commitrow curview mainfont iddrawn
1996 global fhl_list
1998 while {[gets $filehighlight line] >= 0} {
1999 set line [string trim $line]
2000 set i [lsearch -exact $fhl_list $line]
2001 if {$i < 0} continue
2002 for {set j 0} {$j < $i} {incr j} {
2003 set id [lindex $fhl_list $j]
2004 if {[info exists commitrow($curview,$id)]} {
2005 set fhighlights($commitrow($curview,$id)) 0
2008 set fhl_list [lrange $fhl_list [expr {$i+1}] end]
2009 if {$line eq {}} continue
2010 if {![info exists commitrow($curview,$line)]} continue
2011 set row $commitrow($curview,$line)
2012 if {[info exists iddrawn($line)] && ![ishighlighted $row]} {
2013 bolden $row [concat $mainfont bold]
2015 set fhighlights($row) 1
2017 if {[eof $filehighlight]} {
2018 # strange...
2019 puts "oops, git diff-tree died"
2020 catch {close $filehighlight}
2021 unset filehighlight
2023 next_hlcont
2026 proc find_change {name ix op} {
2027 global nhighlights mainfont boldnamerows
2028 global findstring findpattern findtype
2030 # delete previous highlights, if any
2031 foreach row $boldnamerows {
2032 bolden_name $row $mainfont
2034 set boldnamerows {}
2035 catch {unset nhighlights}
2036 unbolden
2037 if {$findtype ne "Regexp"} {
2038 set e [string map {"*" "\\*" "?" "\\?" "\[" "\\\[" "\\" "\\\\"} \
2039 $findstring]
2040 set findpattern "*$e*"
2042 drawvisible
2045 proc askfindhighlight {row id} {
2046 global nhighlights commitinfo iddrawn mainfont
2047 global findstring findtype findloc findpattern
2049 if {![info exists commitinfo($id)]} {
2050 getcommit $id
2052 set info $commitinfo($id)
2053 set isbold 0
2054 set fldtypes {Headline Author Date Committer CDate Comments}
2055 foreach f $info ty $fldtypes {
2056 if {$findloc ne "All fields" && $findloc ne $ty} {
2057 continue
2059 if {$findtype eq "Regexp"} {
2060 set doesmatch [regexp $findstring $f]
2061 } elseif {$findtype eq "IgnCase"} {
2062 set doesmatch [string match -nocase $findpattern $f]
2063 } else {
2064 set doesmatch [string match $findpattern $f]
2066 if {$doesmatch} {
2067 if {$ty eq "Author"} {
2068 set isbold 2
2069 } else {
2070 set isbold 1
2074 if {[info exists iddrawn($id)]} {
2075 if {$isbold && ![ishighlighted $row]} {
2076 bolden $row [concat $mainfont bold]
2078 if {$isbold >= 2} {
2079 bolden_name $row [concat $mainfont bold]
2082 set nhighlights($row) $isbold
2085 proc vrel_change {name ix op} {
2086 global highlight_related
2088 rhighlight_none
2089 if {$highlight_related ne "None"} {
2090 after idle drawvisible
2094 # prepare for testing whether commits are descendents or ancestors of a
2095 proc rhighlight_sel {a} {
2096 global descendent desc_todo ancestor anc_todo
2097 global highlight_related rhighlights
2099 catch {unset descendent}
2100 set desc_todo [list $a]
2101 catch {unset ancestor}
2102 set anc_todo [list $a]
2103 if {$highlight_related ne "None"} {
2104 rhighlight_none
2105 after idle drawvisible
2109 proc rhighlight_none {} {
2110 global rhighlights
2112 catch {unset rhighlights}
2113 unbolden
2116 proc is_descendent {a} {
2117 global curview children commitrow descendent desc_todo
2119 set v $curview
2120 set la $commitrow($v,$a)
2121 set todo $desc_todo
2122 set leftover {}
2123 set done 0
2124 for {set i 0} {$i < [llength $todo]} {incr i} {
2125 set do [lindex $todo $i]
2126 if {$commitrow($v,$do) < $la} {
2127 lappend leftover $do
2128 continue
2130 foreach nk $children($v,$do) {
2131 if {![info exists descendent($nk)]} {
2132 set descendent($nk) 1
2133 lappend todo $nk
2134 if {$nk eq $a} {
2135 set done 1
2139 if {$done} {
2140 set desc_todo [concat $leftover [lrange $todo [expr {$i+1}] end]]
2141 return
2144 set descendent($a) 0
2145 set desc_todo $leftover
2148 proc is_ancestor {a} {
2149 global curview parentlist commitrow ancestor anc_todo
2151 set v $curview
2152 set la $commitrow($v,$a)
2153 set todo $anc_todo
2154 set leftover {}
2155 set done 0
2156 for {set i 0} {$i < [llength $todo]} {incr i} {
2157 set do [lindex $todo $i]
2158 if {![info exists commitrow($v,$do)] || $commitrow($v,$do) > $la} {
2159 lappend leftover $do
2160 continue
2162 foreach np [lindex $parentlist $commitrow($v,$do)] {
2163 if {![info exists ancestor($np)]} {
2164 set ancestor($np) 1
2165 lappend todo $np
2166 if {$np eq $a} {
2167 set done 1
2171 if {$done} {
2172 set anc_todo [concat $leftover [lrange $todo [expr {$i+1}] end]]
2173 return
2176 set ancestor($a) 0
2177 set anc_todo $leftover
2180 proc askrelhighlight {row id} {
2181 global descendent highlight_related iddrawn mainfont rhighlights
2182 global selectedline ancestor
2184 if {![info exists selectedline]} return
2185 set isbold 0
2186 if {$highlight_related eq "Descendent" ||
2187 $highlight_related eq "Not descendent"} {
2188 if {![info exists descendent($id)]} {
2189 is_descendent $id
2191 if {$descendent($id) == ($highlight_related eq "Descendent")} {
2192 set isbold 1
2194 } elseif {$highlight_related eq "Ancestor" ||
2195 $highlight_related eq "Not ancestor"} {
2196 if {![info exists ancestor($id)]} {
2197 is_ancestor $id
2199 if {$ancestor($id) == ($highlight_related eq "Ancestor")} {
2200 set isbold 1
2203 if {[info exists iddrawn($id)]} {
2204 if {$isbold && ![ishighlighted $row]} {
2205 bolden $row [concat $mainfont bold]
2208 set rhighlights($row) $isbold
2211 proc next_hlcont {} {
2212 global fhl_row fhl_dirn displayorder numcommits
2213 global vhighlights fhighlights nhighlights rhighlights
2214 global hlview filehighlight findstring highlight_related
2216 if {![info exists fhl_dirn] || $fhl_dirn == 0} return
2217 set row $fhl_row
2218 while {1} {
2219 if {$row < 0 || $row >= $numcommits} {
2220 bell
2221 set fhl_dirn 0
2222 return
2224 set id [lindex $displayorder $row]
2225 if {[info exists hlview]} {
2226 if {![info exists vhighlights($row)]} {
2227 askvhighlight $row $id
2229 if {$vhighlights($row) > 0} break
2231 if {$findstring ne {}} {
2232 if {![info exists nhighlights($row)]} {
2233 askfindhighlight $row $id
2235 if {$nhighlights($row) > 0} break
2237 if {$highlight_related ne "None"} {
2238 if {![info exists rhighlights($row)]} {
2239 askrelhighlight $row $id
2241 if {$rhighlights($row) > 0} break
2243 if {[info exists filehighlight]} {
2244 if {![info exists fhighlights($row)]} {
2245 # ask for a few more while we're at it...
2246 set r $row
2247 for {set n 0} {$n < 100} {incr n} {
2248 if {![info exists fhighlights($r)]} {
2249 askfilehighlight $r [lindex $displayorder $r]
2251 incr r $fhl_dirn
2252 if {$r < 0 || $r >= $numcommits} break
2254 flushhighlights
2256 if {$fhighlights($row) < 0} {
2257 set fhl_row $row
2258 return
2260 if {$fhighlights($row) > 0} break
2262 incr row $fhl_dirn
2264 set fhl_dirn 0
2265 selectline $row 1
2268 proc next_highlight {dirn} {
2269 global selectedline fhl_row fhl_dirn
2270 global hlview filehighlight findstring highlight_related
2272 if {![info exists selectedline]} return
2273 if {!([info exists hlview] || $findstring ne {} ||
2274 $highlight_related ne "None" || [info exists filehighlight])} return
2275 set fhl_row [expr {$selectedline + $dirn}]
2276 set fhl_dirn $dirn
2277 next_hlcont
2280 proc cancel_next_highlight {} {
2281 global fhl_dirn
2283 set fhl_dirn 0
2286 # Graph layout functions
2288 proc shortids {ids} {
2289 set res {}
2290 foreach id $ids {
2291 if {[llength $id] > 1} {
2292 lappend res [shortids $id]
2293 } elseif {[regexp {^[0-9a-f]{40}$} $id]} {
2294 lappend res [string range $id 0 7]
2295 } else {
2296 lappend res $id
2299 return $res
2302 proc incrange {l x o} {
2303 set n [llength $l]
2304 while {$x < $n} {
2305 set e [lindex $l $x]
2306 if {$e ne {}} {
2307 lset l $x [expr {$e + $o}]
2309 incr x
2311 return $l
2314 proc ntimes {n o} {
2315 set ret {}
2316 for {} {$n > 0} {incr n -1} {
2317 lappend ret $o
2319 return $ret
2322 proc usedinrange {id l1 l2} {
2323 global children commitrow childlist curview
2325 if {[info exists commitrow($curview,$id)]} {
2326 set r $commitrow($curview,$id)
2327 if {$l1 <= $r && $r <= $l2} {
2328 return [expr {$r - $l1 + 1}]
2330 set kids [lindex $childlist $r]
2331 } else {
2332 set kids $children($curview,$id)
2334 foreach c $kids {
2335 set r $commitrow($curview,$c)
2336 if {$l1 <= $r && $r <= $l2} {
2337 return [expr {$r - $l1 + 1}]
2340 return 0
2343 proc sanity {row {full 0}} {
2344 global rowidlist rowoffsets
2346 set col -1
2347 set ids [lindex $rowidlist $row]
2348 foreach id $ids {
2349 incr col
2350 if {$id eq {}} continue
2351 if {$col < [llength $ids] - 1 &&
2352 [lsearch -exact -start [expr {$col+1}] $ids $id] >= 0} {
2353 puts "oops: [shortids $id] repeated in row $row col $col: {[shortids [lindex $rowidlist $row]]}"
2355 set o [lindex $rowoffsets $row $col]
2356 set y $row
2357 set x $col
2358 while {$o ne {}} {
2359 incr y -1
2360 incr x $o
2361 if {[lindex $rowidlist $y $x] != $id} {
2362 puts "oops: rowoffsets wrong at row [expr {$y+1}] col [expr {$x-$o}]"
2363 puts " id=[shortids $id] check started at row $row"
2364 for {set i $row} {$i >= $y} {incr i -1} {
2365 puts " row $i ids={[shortids [lindex $rowidlist $i]]} offs={[lindex $rowoffsets $i]}"
2367 break
2369 if {!$full} break
2370 set o [lindex $rowoffsets $y $x]
2375 proc makeuparrow {oid x y z} {
2376 global rowidlist rowoffsets uparrowlen idrowranges
2378 for {set i 1} {$i < $uparrowlen && $y > 1} {incr i} {
2379 incr y -1
2380 incr x $z
2381 set off0 [lindex $rowoffsets $y]
2382 for {set x0 $x} {1} {incr x0} {
2383 if {$x0 >= [llength $off0]} {
2384 set x0 [llength [lindex $rowoffsets [expr {$y-1}]]]
2385 break
2387 set z [lindex $off0 $x0]
2388 if {$z ne {}} {
2389 incr x0 $z
2390 break
2393 set z [expr {$x0 - $x}]
2394 lset rowidlist $y [linsert [lindex $rowidlist $y] $x $oid]
2395 lset rowoffsets $y [linsert [lindex $rowoffsets $y] $x $z]
2397 set tmp [lreplace [lindex $rowoffsets $y] $x $x {}]
2398 lset rowoffsets $y [incrange $tmp [expr {$x+1}] -1]
2399 lappend idrowranges($oid) $y
2402 proc initlayout {} {
2403 global rowidlist rowoffsets displayorder commitlisted
2404 global rowlaidout rowoptim
2405 global idinlist rowchk rowrangelist idrowranges
2406 global numcommits canvxmax canv
2407 global nextcolor
2408 global parentlist childlist children
2409 global colormap rowtextx
2410 global linesegends
2412 set numcommits 0
2413 set displayorder {}
2414 set commitlisted {}
2415 set parentlist {}
2416 set childlist {}
2417 set rowrangelist {}
2418 set nextcolor 0
2419 set rowidlist {{}}
2420 set rowoffsets {{}}
2421 catch {unset idinlist}
2422 catch {unset rowchk}
2423 set rowlaidout 0
2424 set rowoptim 0
2425 set canvxmax [$canv cget -width]
2426 catch {unset colormap}
2427 catch {unset rowtextx}
2428 catch {unset idrowranges}
2429 set linesegends {}
2432 proc setcanvscroll {} {
2433 global canv canv2 canv3 numcommits linespc canvxmax canvy0
2435 set ymax [expr {$canvy0 + ($numcommits - 0.5) * $linespc + 2}]
2436 $canv conf -scrollregion [list 0 0 $canvxmax $ymax]
2437 $canv2 conf -scrollregion [list 0 0 0 $ymax]
2438 $canv3 conf -scrollregion [list 0 0 0 $ymax]
2441 proc visiblerows {} {
2442 global canv numcommits linespc
2444 set ymax [lindex [$canv cget -scrollregion] 3]
2445 if {$ymax eq {} || $ymax == 0} return
2446 set f [$canv yview]
2447 set y0 [expr {int([lindex $f 0] * $ymax)}]
2448 set r0 [expr {int(($y0 - 3) / $linespc) - 1}]
2449 if {$r0 < 0} {
2450 set r0 0
2452 set y1 [expr {int([lindex $f 1] * $ymax)}]
2453 set r1 [expr {int(($y1 - 3) / $linespc) + 1}]
2454 if {$r1 >= $numcommits} {
2455 set r1 [expr {$numcommits - 1}]
2457 return [list $r0 $r1]
2460 proc layoutmore {tmax} {
2461 global rowlaidout rowoptim commitidx numcommits optim_delay
2462 global uparrowlen curview
2464 while {1} {
2465 if {$rowoptim - $optim_delay > $numcommits} {
2466 showstuff [expr {$rowoptim - $optim_delay}]
2467 } elseif {$rowlaidout - $uparrowlen - 1 > $rowoptim} {
2468 set nr [expr {$rowlaidout - $uparrowlen - 1 - $rowoptim}]
2469 if {$nr > 100} {
2470 set nr 100
2472 optimize_rows $rowoptim 0 [expr {$rowoptim + $nr}]
2473 incr rowoptim $nr
2474 } elseif {$commitidx($curview) > $rowlaidout} {
2475 set nr [expr {$commitidx($curview) - $rowlaidout}]
2476 # may need to increase this threshold if uparrowlen or
2477 # mingaplen are increased...
2478 if {$nr > 150} {
2479 set nr 150
2481 set row $rowlaidout
2482 set rowlaidout [layoutrows $row [expr {$row + $nr}] 0]
2483 if {$rowlaidout == $row} {
2484 return 0
2486 } else {
2487 return 0
2489 if {$tmax ne {} && [clock clicks -milliseconds] >= $tmax} {
2490 return 1
2495 proc showstuff {canshow} {
2496 global numcommits commitrow pending_select selectedline
2497 global linesegends idrowranges idrangedrawn curview
2499 if {$numcommits == 0} {
2500 global phase
2501 set phase "incrdraw"
2502 allcanvs delete all
2504 set row $numcommits
2505 set numcommits $canshow
2506 setcanvscroll
2507 set rows [visiblerows]
2508 set r0 [lindex $rows 0]
2509 set r1 [lindex $rows 1]
2510 set selrow -1
2511 for {set r $row} {$r < $canshow} {incr r} {
2512 foreach id [lindex $linesegends [expr {$r+1}]] {
2513 set i -1
2514 foreach {s e} [rowranges $id] {
2515 incr i
2516 if {$e ne {} && $e < $numcommits && $s <= $r1 && $e >= $r0
2517 && ![info exists idrangedrawn($id,$i)]} {
2518 drawlineseg $id $i
2519 set idrangedrawn($id,$i) 1
2524 if {$canshow > $r1} {
2525 set canshow $r1
2527 while {$row < $canshow} {
2528 drawcmitrow $row
2529 incr row
2531 if {[info exists pending_select] &&
2532 [info exists commitrow($curview,$pending_select)] &&
2533 $commitrow($curview,$pending_select) < $numcommits} {
2534 selectline $commitrow($curview,$pending_select) 1
2536 if {![info exists selectedline] && ![info exists pending_select]} {
2537 selectline 0 1
2541 proc layoutrows {row endrow last} {
2542 global rowidlist rowoffsets displayorder
2543 global uparrowlen downarrowlen maxwidth mingaplen
2544 global childlist parentlist
2545 global idrowranges linesegends
2546 global commitidx curview
2547 global idinlist rowchk rowrangelist
2549 set idlist [lindex $rowidlist $row]
2550 set offs [lindex $rowoffsets $row]
2551 while {$row < $endrow} {
2552 set id [lindex $displayorder $row]
2553 set oldolds {}
2554 set newolds {}
2555 foreach p [lindex $parentlist $row] {
2556 if {![info exists idinlist($p)]} {
2557 lappend newolds $p
2558 } elseif {!$idinlist($p)} {
2559 lappend oldolds $p
2562 set lse {}
2563 set nev [expr {[llength $idlist] + [llength $newolds]
2564 + [llength $oldolds] - $maxwidth + 1}]
2565 if {$nev > 0} {
2566 if {!$last &&
2567 $row + $uparrowlen + $mingaplen >= $commitidx($curview)} break
2568 for {set x [llength $idlist]} {[incr x -1] >= 0} {} {
2569 set i [lindex $idlist $x]
2570 if {![info exists rowchk($i)] || $row >= $rowchk($i)} {
2571 set r [usedinrange $i [expr {$row - $downarrowlen}] \
2572 [expr {$row + $uparrowlen + $mingaplen}]]
2573 if {$r == 0} {
2574 set idlist [lreplace $idlist $x $x]
2575 set offs [lreplace $offs $x $x]
2576 set offs [incrange $offs $x 1]
2577 set idinlist($i) 0
2578 set rm1 [expr {$row - 1}]
2579 lappend lse $i
2580 lappend idrowranges($i) $rm1
2581 if {[incr nev -1] <= 0} break
2582 continue
2584 set rowchk($id) [expr {$row + $r}]
2587 lset rowidlist $row $idlist
2588 lset rowoffsets $row $offs
2590 lappend linesegends $lse
2591 set col [lsearch -exact $idlist $id]
2592 if {$col < 0} {
2593 set col [llength $idlist]
2594 lappend idlist $id
2595 lset rowidlist $row $idlist
2596 set z {}
2597 if {[lindex $childlist $row] ne {}} {
2598 set z [expr {[llength [lindex $rowidlist [expr {$row-1}]]] - $col}]
2599 unset idinlist($id)
2601 lappend offs $z
2602 lset rowoffsets $row $offs
2603 if {$z ne {}} {
2604 makeuparrow $id $col $row $z
2606 } else {
2607 unset idinlist($id)
2609 set ranges {}
2610 if {[info exists idrowranges($id)]} {
2611 set ranges $idrowranges($id)
2612 lappend ranges $row
2613 unset idrowranges($id)
2615 lappend rowrangelist $ranges
2616 incr row
2617 set offs [ntimes [llength $idlist] 0]
2618 set l [llength $newolds]
2619 set idlist [eval lreplace \$idlist $col $col $newolds]
2620 set o 0
2621 if {$l != 1} {
2622 set offs [lrange $offs 0 [expr {$col - 1}]]
2623 foreach x $newolds {
2624 lappend offs {}
2625 incr o -1
2627 incr o
2628 set tmp [expr {[llength $idlist] - [llength $offs]}]
2629 if {$tmp > 0} {
2630 set offs [concat $offs [ntimes $tmp $o]]
2632 } else {
2633 lset offs $col {}
2635 foreach i $newolds {
2636 set idinlist($i) 1
2637 set idrowranges($i) $row
2639 incr col $l
2640 foreach oid $oldolds {
2641 set idinlist($oid) 1
2642 set idlist [linsert $idlist $col $oid]
2643 set offs [linsert $offs $col $o]
2644 makeuparrow $oid $col $row $o
2645 incr col
2647 lappend rowidlist $idlist
2648 lappend rowoffsets $offs
2650 return $row
2653 proc addextraid {id row} {
2654 global displayorder commitrow commitinfo
2655 global commitidx commitlisted
2656 global parentlist childlist children curview
2658 incr commitidx($curview)
2659 lappend displayorder $id
2660 lappend commitlisted 0
2661 lappend parentlist {}
2662 set commitrow($curview,$id) $row
2663 readcommit $id
2664 if {![info exists commitinfo($id)]} {
2665 set commitinfo($id) {"No commit information available"}
2667 if {![info exists children($curview,$id)]} {
2668 set children($curview,$id) {}
2670 lappend childlist $children($curview,$id)
2673 proc layouttail {} {
2674 global rowidlist rowoffsets idinlist commitidx curview
2675 global idrowranges rowrangelist
2677 set row $commitidx($curview)
2678 set idlist [lindex $rowidlist $row]
2679 while {$idlist ne {}} {
2680 set col [expr {[llength $idlist] - 1}]
2681 set id [lindex $idlist $col]
2682 addextraid $id $row
2683 unset idinlist($id)
2684 lappend idrowranges($id) $row
2685 lappend rowrangelist $idrowranges($id)
2686 unset idrowranges($id)
2687 incr row
2688 set offs [ntimes $col 0]
2689 set idlist [lreplace $idlist $col $col]
2690 lappend rowidlist $idlist
2691 lappend rowoffsets $offs
2694 foreach id [array names idinlist] {
2695 addextraid $id $row
2696 lset rowidlist $row [list $id]
2697 lset rowoffsets $row 0
2698 makeuparrow $id 0 $row 0
2699 lappend idrowranges($id) $row
2700 lappend rowrangelist $idrowranges($id)
2701 unset idrowranges($id)
2702 incr row
2703 lappend rowidlist {}
2704 lappend rowoffsets {}
2708 proc insert_pad {row col npad} {
2709 global rowidlist rowoffsets
2711 set pad [ntimes $npad {}]
2712 lset rowidlist $row [eval linsert [list [lindex $rowidlist $row]] $col $pad]
2713 set tmp [eval linsert [list [lindex $rowoffsets $row]] $col $pad]
2714 lset rowoffsets $row [incrange $tmp [expr {$col + $npad}] [expr {-$npad}]]
2717 proc optimize_rows {row col endrow} {
2718 global rowidlist rowoffsets idrowranges displayorder
2720 for {} {$row < $endrow} {incr row} {
2721 set idlist [lindex $rowidlist $row]
2722 set offs [lindex $rowoffsets $row]
2723 set haspad 0
2724 for {} {$col < [llength $offs]} {incr col} {
2725 if {[lindex $idlist $col] eq {}} {
2726 set haspad 1
2727 continue
2729 set z [lindex $offs $col]
2730 if {$z eq {}} continue
2731 set isarrow 0
2732 set x0 [expr {$col + $z}]
2733 set y0 [expr {$row - 1}]
2734 set z0 [lindex $rowoffsets $y0 $x0]
2735 if {$z0 eq {}} {
2736 set id [lindex $idlist $col]
2737 set ranges [rowranges $id]
2738 if {$ranges ne {} && $y0 > [lindex $ranges 0]} {
2739 set isarrow 1
2742 if {$z < -1 || ($z < 0 && $isarrow)} {
2743 set npad [expr {-1 - $z + $isarrow}]
2744 set offs [incrange $offs $col $npad]
2745 insert_pad $y0 $x0 $npad
2746 if {$y0 > 0} {
2747 optimize_rows $y0 $x0 $row
2749 set z [lindex $offs $col]
2750 set x0 [expr {$col + $z}]
2751 set z0 [lindex $rowoffsets $y0 $x0]
2752 } elseif {$z > 1 || ($z > 0 && $isarrow)} {
2753 set npad [expr {$z - 1 + $isarrow}]
2754 set y1 [expr {$row + 1}]
2755 set offs2 [lindex $rowoffsets $y1]
2756 set x1 -1
2757 foreach z $offs2 {
2758 incr x1
2759 if {$z eq {} || $x1 + $z < $col} continue
2760 if {$x1 + $z > $col} {
2761 incr npad
2763 lset rowoffsets $y1 [incrange $offs2 $x1 $npad]
2764 break
2766 set pad [ntimes $npad {}]
2767 set idlist [eval linsert \$idlist $col $pad]
2768 set tmp [eval linsert \$offs $col $pad]
2769 incr col $npad
2770 set offs [incrange $tmp $col [expr {-$npad}]]
2771 set z [lindex $offs $col]
2772 set haspad 1
2774 if {$z0 eq {} && !$isarrow} {
2775 # this line links to its first child on row $row-2
2776 set rm2 [expr {$row - 2}]
2777 set id [lindex $displayorder $rm2]
2778 set xc [lsearch -exact [lindex $rowidlist $rm2] $id]
2779 if {$xc >= 0} {
2780 set z0 [expr {$xc - $x0}]
2783 if {$z0 ne {} && $z < 0 && $z0 > 0} {
2784 insert_pad $y0 $x0 1
2785 set offs [incrange $offs $col 1]
2786 optimize_rows $y0 [expr {$x0 + 1}] $row
2789 if {!$haspad} {
2790 set o {}
2791 for {set col [llength $idlist]} {[incr col -1] >= 0} {} {
2792 set o [lindex $offs $col]
2793 if {$o eq {}} {
2794 # check if this is the link to the first child
2795 set id [lindex $idlist $col]
2796 set ranges [rowranges $id]
2797 if {$ranges ne {} && $row == [lindex $ranges 0]} {
2798 # it is, work out offset to child
2799 set y0 [expr {$row - 1}]
2800 set id [lindex $displayorder $y0]
2801 set x0 [lsearch -exact [lindex $rowidlist $y0] $id]
2802 if {$x0 >= 0} {
2803 set o [expr {$x0 - $col}]
2807 if {$o eq {} || $o <= 0} break
2809 if {$o ne {} && [incr col] < [llength $idlist]} {
2810 set y1 [expr {$row + 1}]
2811 set offs2 [lindex $rowoffsets $y1]
2812 set x1 -1
2813 foreach z $offs2 {
2814 incr x1
2815 if {$z eq {} || $x1 + $z < $col} continue
2816 lset rowoffsets $y1 [incrange $offs2 $x1 1]
2817 break
2819 set idlist [linsert $idlist $col {}]
2820 set tmp [linsert $offs $col {}]
2821 incr col
2822 set offs [incrange $tmp $col -1]
2825 lset rowidlist $row $idlist
2826 lset rowoffsets $row $offs
2827 set col 0
2831 proc xc {row col} {
2832 global canvx0 linespc
2833 return [expr {$canvx0 + $col * $linespc}]
2836 proc yc {row} {
2837 global canvy0 linespc
2838 return [expr {$canvy0 + $row * $linespc}]
2841 proc linewidth {id} {
2842 global thickerline lthickness
2844 set wid $lthickness
2845 if {[info exists thickerline] && $id eq $thickerline} {
2846 set wid [expr {2 * $lthickness}]
2848 return $wid
2851 proc rowranges {id} {
2852 global phase idrowranges commitrow rowlaidout rowrangelist curview
2854 set ranges {}
2855 if {$phase eq {} ||
2856 ([info exists commitrow($curview,$id)]
2857 && $commitrow($curview,$id) < $rowlaidout)} {
2858 set ranges [lindex $rowrangelist $commitrow($curview,$id)]
2859 } elseif {[info exists idrowranges($id)]} {
2860 set ranges $idrowranges($id)
2862 return $ranges
2865 proc drawlineseg {id i} {
2866 global rowoffsets rowidlist
2867 global displayorder
2868 global canv colormap linespc
2869 global numcommits commitrow curview
2871 set ranges [rowranges $id]
2872 set downarrow 1
2873 if {[info exists commitrow($curview,$id)]
2874 && $commitrow($curview,$id) < $numcommits} {
2875 set downarrow [expr {$i < [llength $ranges] / 2 - 1}]
2876 } else {
2877 set downarrow 1
2879 set startrow [lindex $ranges [expr {2 * $i}]]
2880 set row [lindex $ranges [expr {2 * $i + 1}]]
2881 if {$startrow == $row} return
2882 assigncolor $id
2883 set coords {}
2884 set col [lsearch -exact [lindex $rowidlist $row] $id]
2885 if {$col < 0} {
2886 puts "oops: drawline: id $id not on row $row"
2887 return
2889 set lasto {}
2890 set ns 0
2891 while {1} {
2892 set o [lindex $rowoffsets $row $col]
2893 if {$o eq {}} break
2894 if {$o ne $lasto} {
2895 # changing direction
2896 set x [xc $row $col]
2897 set y [yc $row]
2898 lappend coords $x $y
2899 set lasto $o
2901 incr col $o
2902 incr row -1
2904 set x [xc $row $col]
2905 set y [yc $row]
2906 lappend coords $x $y
2907 if {$i == 0} {
2908 # draw the link to the first child as part of this line
2909 incr row -1
2910 set child [lindex $displayorder $row]
2911 set ccol [lsearch -exact [lindex $rowidlist $row] $child]
2912 if {$ccol >= 0} {
2913 set x [xc $row $ccol]
2914 set y [yc $row]
2915 if {$ccol < $col - 1} {
2916 lappend coords [xc $row [expr {$col - 1}]] [yc $row]
2917 } elseif {$ccol > $col + 1} {
2918 lappend coords [xc $row [expr {$col + 1}]] [yc $row]
2920 lappend coords $x $y
2923 if {[llength $coords] < 4} return
2924 if {$downarrow} {
2925 # This line has an arrow at the lower end: check if the arrow is
2926 # on a diagonal segment, and if so, work around the Tk 8.4
2927 # refusal to draw arrows on diagonal lines.
2928 set x0 [lindex $coords 0]
2929 set x1 [lindex $coords 2]
2930 if {$x0 != $x1} {
2931 set y0 [lindex $coords 1]
2932 set y1 [lindex $coords 3]
2933 if {$y0 - $y1 <= 2 * $linespc && $x1 == [lindex $coords 4]} {
2934 # we have a nearby vertical segment, just trim off the diag bit
2935 set coords [lrange $coords 2 end]
2936 } else {
2937 set slope [expr {($x0 - $x1) / ($y0 - $y1)}]
2938 set xi [expr {$x0 - $slope * $linespc / 2}]
2939 set yi [expr {$y0 - $linespc / 2}]
2940 set coords [lreplace $coords 0 1 $xi $y0 $xi $yi]
2944 set arrow [expr {2 * ($i > 0) + $downarrow}]
2945 set arrow [lindex {none first last both} $arrow]
2946 set t [$canv create line $coords -width [linewidth $id] \
2947 -fill $colormap($id) -tags lines.$id -arrow $arrow]
2948 $canv lower $t
2949 bindline $t $id
2952 proc drawparentlinks {id row col olds} {
2953 global rowidlist canv colormap
2955 set row2 [expr {$row + 1}]
2956 set x [xc $row $col]
2957 set y [yc $row]
2958 set y2 [yc $row2]
2959 set ids [lindex $rowidlist $row2]
2960 # rmx = right-most X coord used
2961 set rmx 0
2962 foreach p $olds {
2963 set i [lsearch -exact $ids $p]
2964 if {$i < 0} {
2965 puts "oops, parent $p of $id not in list"
2966 continue
2968 set x2 [xc $row2 $i]
2969 if {$x2 > $rmx} {
2970 set rmx $x2
2972 set ranges [rowranges $p]
2973 if {$ranges ne {} && $row2 == [lindex $ranges 0]
2974 && $row2 < [lindex $ranges 1]} {
2975 # drawlineseg will do this one for us
2976 continue
2978 assigncolor $p
2979 # should handle duplicated parents here...
2980 set coords [list $x $y]
2981 if {$i < $col - 1} {
2982 lappend coords [xc $row [expr {$i + 1}]] $y
2983 } elseif {$i > $col + 1} {
2984 lappend coords [xc $row [expr {$i - 1}]] $y
2986 lappend coords $x2 $y2
2987 set t [$canv create line $coords -width [linewidth $p] \
2988 -fill $colormap($p) -tags lines.$p]
2989 $canv lower $t
2990 bindline $t $p
2992 return $rmx
2995 proc drawlines {id} {
2996 global colormap canv
2997 global idrangedrawn
2998 global children iddrawn commitrow rowidlist curview
3000 $canv delete lines.$id
3001 set nr [expr {[llength [rowranges $id]] / 2}]
3002 for {set i 0} {$i < $nr} {incr i} {
3003 if {[info exists idrangedrawn($id,$i)]} {
3004 drawlineseg $id $i
3007 foreach child $children($curview,$id) {
3008 if {[info exists iddrawn($child)]} {
3009 set row $commitrow($curview,$child)
3010 set col [lsearch -exact [lindex $rowidlist $row] $child]
3011 if {$col >= 0} {
3012 drawparentlinks $child $row $col [list $id]
3018 proc drawcmittext {id row col rmx} {
3019 global linespc canv canv2 canv3 canvy0 fgcolor
3020 global commitlisted commitinfo rowidlist
3021 global rowtextx idpos idtags idheads idotherrefs
3022 global linehtag linentag linedtag
3023 global mainfont canvxmax boldrows boldnamerows fgcolor
3025 set ofill [expr {[lindex $commitlisted $row]? "blue": "white"}]
3026 set x [xc $row $col]
3027 set y [yc $row]
3028 set orad [expr {$linespc / 3}]
3029 set t [$canv create oval [expr {$x - $orad}] [expr {$y - $orad}] \
3030 [expr {$x + $orad - 1}] [expr {$y + $orad - 1}] \
3031 -fill $ofill -outline $fgcolor -width 1 -tags circle]
3032 $canv raise $t
3033 $canv bind $t <1> {selcanvline {} %x %y}
3034 set xt [xc $row [llength [lindex $rowidlist $row]]]
3035 if {$xt < $rmx} {
3036 set xt $rmx
3038 set rowtextx($row) $xt
3039 set idpos($id) [list $x $xt $y]
3040 if {[info exists idtags($id)] || [info exists idheads($id)]
3041 || [info exists idotherrefs($id)]} {
3042 set xt [drawtags $id $x $xt $y]
3044 set headline [lindex $commitinfo($id) 0]
3045 set name [lindex $commitinfo($id) 1]
3046 set date [lindex $commitinfo($id) 2]
3047 set date [formatdate $date]
3048 set font $mainfont
3049 set nfont $mainfont
3050 set isbold [ishighlighted $row]
3051 if {$isbold > 0} {
3052 lappend boldrows $row
3053 lappend font bold
3054 if {$isbold > 1} {
3055 lappend boldnamerows $row
3056 lappend nfont bold
3059 set linehtag($row) [$canv create text $xt $y -anchor w -fill $fgcolor \
3060 -text $headline -font $font -tags text]
3061 $canv bind $linehtag($row) <Button-3> "rowmenu %X %Y $id"
3062 set linentag($row) [$canv2 create text 3 $y -anchor w -fill $fgcolor \
3063 -text $name -font $nfont -tags text]
3064 set linedtag($row) [$canv3 create text 3 $y -anchor w -fill $fgcolor \
3065 -text $date -font $mainfont -tags text]
3066 set xr [expr {$xt + [font measure $mainfont $headline]}]
3067 if {$xr > $canvxmax} {
3068 set canvxmax $xr
3069 setcanvscroll
3073 proc drawcmitrow {row} {
3074 global displayorder rowidlist
3075 global idrangedrawn iddrawn
3076 global commitinfo parentlist numcommits
3077 global filehighlight fhighlights findstring nhighlights
3078 global hlview vhighlights
3079 global highlight_related rhighlights
3081 if {$row >= $numcommits} return
3082 foreach id [lindex $rowidlist $row] {
3083 if {$id eq {}} continue
3084 set i -1
3085 foreach {s e} [rowranges $id] {
3086 incr i
3087 if {$row < $s} continue
3088 if {$e eq {}} break
3089 if {$row <= $e} {
3090 if {$e < $numcommits && ![info exists idrangedrawn($id,$i)]} {
3091 drawlineseg $id $i
3092 set idrangedrawn($id,$i) 1
3094 break
3099 set id [lindex $displayorder $row]
3100 if {[info exists hlview] && ![info exists vhighlights($row)]} {
3101 askvhighlight $row $id
3103 if {[info exists filehighlight] && ![info exists fhighlights($row)]} {
3104 askfilehighlight $row $id
3106 if {$findstring ne {} && ![info exists nhighlights($row)]} {
3107 askfindhighlight $row $id
3109 if {$highlight_related ne "None" && ![info exists rhighlights($row)]} {
3110 askrelhighlight $row $id
3112 if {[info exists iddrawn($id)]} return
3113 set col [lsearch -exact [lindex $rowidlist $row] $id]
3114 if {$col < 0} {
3115 puts "oops, row $row id $id not in list"
3116 return
3118 if {![info exists commitinfo($id)]} {
3119 getcommit $id
3121 assigncolor $id
3122 set olds [lindex $parentlist $row]
3123 if {$olds ne {}} {
3124 set rmx [drawparentlinks $id $row $col $olds]
3125 } else {
3126 set rmx 0
3128 drawcmittext $id $row $col $rmx
3129 set iddrawn($id) 1
3132 proc drawfrac {f0 f1} {
3133 global numcommits canv
3134 global linespc
3136 set ymax [lindex [$canv cget -scrollregion] 3]
3137 if {$ymax eq {} || $ymax == 0} return
3138 set y0 [expr {int($f0 * $ymax)}]
3139 set row [expr {int(($y0 - 3) / $linespc) - 1}]
3140 if {$row < 0} {
3141 set row 0
3143 set y1 [expr {int($f1 * $ymax)}]
3144 set endrow [expr {int(($y1 - 3) / $linespc) + 1}]
3145 if {$endrow >= $numcommits} {
3146 set endrow [expr {$numcommits - 1}]
3148 for {} {$row <= $endrow} {incr row} {
3149 drawcmitrow $row
3153 proc drawvisible {} {
3154 global canv
3155 eval drawfrac [$canv yview]
3158 proc clear_display {} {
3159 global iddrawn idrangedrawn
3160 global vhighlights fhighlights nhighlights rhighlights
3162 allcanvs delete all
3163 catch {unset iddrawn}
3164 catch {unset idrangedrawn}
3165 catch {unset vhighlights}
3166 catch {unset fhighlights}
3167 catch {unset nhighlights}
3168 catch {unset rhighlights}
3171 proc findcrossings {id} {
3172 global rowidlist parentlist numcommits rowoffsets displayorder
3174 set cross {}
3175 set ccross {}
3176 foreach {s e} [rowranges $id] {
3177 if {$e >= $numcommits} {
3178 set e [expr {$numcommits - 1}]
3180 if {$e <= $s} continue
3181 set x [lsearch -exact [lindex $rowidlist $e] $id]
3182 if {$x < 0} {
3183 puts "findcrossings: oops, no [shortids $id] in row $e"
3184 continue
3186 for {set row $e} {[incr row -1] >= $s} {} {
3187 set olds [lindex $parentlist $row]
3188 set kid [lindex $displayorder $row]
3189 set kidx [lsearch -exact [lindex $rowidlist $row] $kid]
3190 if {$kidx < 0} continue
3191 set nextrow [lindex $rowidlist [expr {$row + 1}]]
3192 foreach p $olds {
3193 set px [lsearch -exact $nextrow $p]
3194 if {$px < 0} continue
3195 if {($kidx < $x && $x < $px) || ($px < $x && $x < $kidx)} {
3196 if {[lsearch -exact $ccross $p] >= 0} continue
3197 if {$x == $px + ($kidx < $px? -1: 1)} {
3198 lappend ccross $p
3199 } elseif {[lsearch -exact $cross $p] < 0} {
3200 lappend cross $p
3204 set inc [lindex $rowoffsets $row $x]
3205 if {$inc eq {}} break
3206 incr x $inc
3209 return [concat $ccross {{}} $cross]
3212 proc assigncolor {id} {
3213 global colormap colors nextcolor
3214 global commitrow parentlist children children curview
3216 if {[info exists colormap($id)]} return
3217 set ncolors [llength $colors]
3218 if {[info exists children($curview,$id)]} {
3219 set kids $children($curview,$id)
3220 } else {
3221 set kids {}
3223 if {[llength $kids] == 1} {
3224 set child [lindex $kids 0]
3225 if {[info exists colormap($child)]
3226 && [llength [lindex $parentlist $commitrow($curview,$child)]] == 1} {
3227 set colormap($id) $colormap($child)
3228 return
3231 set badcolors {}
3232 set origbad {}
3233 foreach x [findcrossings $id] {
3234 if {$x eq {}} {
3235 # delimiter between corner crossings and other crossings
3236 if {[llength $badcolors] >= $ncolors - 1} break
3237 set origbad $badcolors
3239 if {[info exists colormap($x)]
3240 && [lsearch -exact $badcolors $colormap($x)] < 0} {
3241 lappend badcolors $colormap($x)
3244 if {[llength $badcolors] >= $ncolors} {
3245 set badcolors $origbad
3247 set origbad $badcolors
3248 if {[llength $badcolors] < $ncolors - 1} {
3249 foreach child $kids {
3250 if {[info exists colormap($child)]
3251 && [lsearch -exact $badcolors $colormap($child)] < 0} {
3252 lappend badcolors $colormap($child)
3254 foreach p [lindex $parentlist $commitrow($curview,$child)] {
3255 if {[info exists colormap($p)]
3256 && [lsearch -exact $badcolors $colormap($p)] < 0} {
3257 lappend badcolors $colormap($p)
3261 if {[llength $badcolors] >= $ncolors} {
3262 set badcolors $origbad
3265 for {set i 0} {$i <= $ncolors} {incr i} {
3266 set c [lindex $colors $nextcolor]
3267 if {[incr nextcolor] >= $ncolors} {
3268 set nextcolor 0
3270 if {[lsearch -exact $badcolors $c]} break
3272 set colormap($id) $c
3275 proc bindline {t id} {
3276 global canv
3278 $canv bind $t <Enter> "lineenter %x %y $id"
3279 $canv bind $t <Motion> "linemotion %x %y $id"
3280 $canv bind $t <Leave> "lineleave $id"
3281 $canv bind $t <Button-1> "lineclick %x %y $id 1"
3284 proc drawtags {id x xt y1} {
3285 global idtags idheads idotherrefs mainhead
3286 global linespc lthickness
3287 global canv mainfont commitrow rowtextx curview fgcolor bgcolor
3289 set marks {}
3290 set ntags 0
3291 set nheads 0
3292 if {[info exists idtags($id)]} {
3293 set marks $idtags($id)
3294 set ntags [llength $marks]
3296 if {[info exists idheads($id)]} {
3297 set marks [concat $marks $idheads($id)]
3298 set nheads [llength $idheads($id)]
3300 if {[info exists idotherrefs($id)]} {
3301 set marks [concat $marks $idotherrefs($id)]
3303 if {$marks eq {}} {
3304 return $xt
3307 set delta [expr {int(0.5 * ($linespc - $lthickness))}]
3308 set yt [expr {$y1 - 0.5 * $linespc}]
3309 set yb [expr {$yt + $linespc - 1}]
3310 set xvals {}
3311 set wvals {}
3312 set i -1
3313 foreach tag $marks {
3314 incr i
3315 if {$i >= $ntags && $i < $ntags + $nheads && $tag eq $mainhead} {
3316 set wid [font measure [concat $mainfont bold] $tag]
3317 } else {
3318 set wid [font measure $mainfont $tag]
3320 lappend xvals $xt
3321 lappend wvals $wid
3322 set xt [expr {$xt + $delta + $wid + $lthickness + $linespc}]
3324 set t [$canv create line $x $y1 [lindex $xvals end] $y1 \
3325 -width $lthickness -fill black -tags tag.$id]
3326 $canv lower $t
3327 foreach tag $marks x $xvals wid $wvals {
3328 set xl [expr {$x + $delta}]
3329 set xr [expr {$x + $delta + $wid + $lthickness}]
3330 set font $mainfont
3331 if {[incr ntags -1] >= 0} {
3332 # draw a tag
3333 set t [$canv create polygon $x [expr {$yt + $delta}] $xl $yt \
3334 $xr $yt $xr $yb $xl $yb $x [expr {$yb - $delta}] \
3335 -width 1 -outline black -fill yellow -tags tag.$id]
3336 $canv bind $t <1> [list showtag $tag 1]
3337 set rowtextx($commitrow($curview,$id)) [expr {$xr + $linespc}]
3338 } else {
3339 # draw a head or other ref
3340 if {[incr nheads -1] >= 0} {
3341 set col green
3342 if {$tag eq $mainhead} {
3343 lappend font bold
3345 } else {
3346 set col "#ddddff"
3348 set xl [expr {$xl - $delta/2}]
3349 $canv create polygon $x $yt $xr $yt $xr $yb $x $yb \
3350 -width 1 -outline black -fill $col -tags tag.$id
3351 if {[regexp {^(remotes/.*/|remotes/)} $tag match remoteprefix]} {
3352 set rwid [font measure $mainfont $remoteprefix]
3353 set xi [expr {$x + 1}]
3354 set yti [expr {$yt + 1}]
3355 set xri [expr {$x + $rwid}]
3356 $canv create polygon $xi $yti $xri $yti $xri $yb $xi $yb \
3357 -width 0 -fill "#ffddaa" -tags tag.$id
3360 set t [$canv create text $xl $y1 -anchor w -text $tag -fill $fgcolor \
3361 -font $font -tags [list tag.$id text]]
3362 if {$ntags >= 0} {
3363 $canv bind $t <1> [list showtag $tag 1]
3364 } elseif {$nheads >= 0} {
3365 $canv bind $t <Button-3> [list headmenu %X %Y $id $tag]
3368 return $xt
3371 proc xcoord {i level ln} {
3372 global canvx0 xspc1 xspc2
3374 set x [expr {$canvx0 + $i * $xspc1($ln)}]
3375 if {$i > 0 && $i == $level} {
3376 set x [expr {$x + 0.5 * ($xspc2 - $xspc1($ln))}]
3377 } elseif {$i > $level} {
3378 set x [expr {$x + $xspc2 - $xspc1($ln)}]
3380 return $x
3383 proc show_status {msg} {
3384 global canv mainfont fgcolor
3386 clear_display
3387 $canv create text 3 3 -anchor nw -text $msg -font $mainfont \
3388 -tags text -fill $fgcolor
3391 proc finishcommits {} {
3392 global commitidx phase curview
3393 global pending_select
3395 if {$commitidx($curview) > 0} {
3396 drawrest
3397 } else {
3398 show_status "No commits selected"
3400 set phase {}
3401 catch {unset pending_select}
3404 # Insert a new commit as the child of the commit on row $row.
3405 # The new commit will be displayed on row $row and the commits
3406 # on that row and below will move down one row.
3407 proc insertrow {row newcmit} {
3408 global displayorder parentlist childlist commitlisted
3409 global commitrow curview rowidlist rowoffsets numcommits
3410 global rowrangelist idrowranges rowlaidout rowoptim numcommits
3411 global linesegends selectedline
3413 if {$row >= $numcommits} {
3414 puts "oops, inserting new row $row but only have $numcommits rows"
3415 return
3417 set p [lindex $displayorder $row]
3418 set displayorder [linsert $displayorder $row $newcmit]
3419 set parentlist [linsert $parentlist $row $p]
3420 set kids [lindex $childlist $row]
3421 lappend kids $newcmit
3422 lset childlist $row $kids
3423 set childlist [linsert $childlist $row {}]
3424 set commitlisted [linsert $commitlisted $row 1]
3425 set l [llength $displayorder]
3426 for {set r $row} {$r < $l} {incr r} {
3427 set id [lindex $displayorder $r]
3428 set commitrow($curview,$id) $r
3431 set idlist [lindex $rowidlist $row]
3432 set offs [lindex $rowoffsets $row]
3433 set newoffs {}
3434 foreach x $idlist {
3435 if {$x eq {} || ($x eq $p && [llength $kids] == 1)} {
3436 lappend newoffs {}
3437 } else {
3438 lappend newoffs 0
3441 if {[llength $kids] == 1} {
3442 set col [lsearch -exact $idlist $p]
3443 lset idlist $col $newcmit
3444 } else {
3445 set col [llength $idlist]
3446 lappend idlist $newcmit
3447 lappend offs {}
3448 lset rowoffsets $row $offs
3450 set rowidlist [linsert $rowidlist $row $idlist]
3451 set rowoffsets [linsert $rowoffsets [expr {$row+1}] $newoffs]
3453 set rowrangelist [linsert $rowrangelist $row {}]
3454 set l [llength $rowrangelist]
3455 for {set r 0} {$r < $l} {incr r} {
3456 set ranges [lindex $rowrangelist $r]
3457 if {$ranges ne {} && [lindex $ranges end] >= $row} {
3458 set newranges {}
3459 foreach x $ranges {
3460 if {$x >= $row} {
3461 lappend newranges [expr {$x + 1}]
3462 } else {
3463 lappend newranges $x
3466 lset rowrangelist $r $newranges
3469 if {[llength $kids] > 1} {
3470 set rp1 [expr {$row + 1}]
3471 set ranges [lindex $rowrangelist $rp1]
3472 if {$ranges eq {}} {
3473 set ranges [list $row $rp1]
3474 } elseif {[lindex $ranges end-1] == $rp1} {
3475 lset ranges end-1 $row
3477 lset rowrangelist $rp1 $ranges
3479 foreach id [array names idrowranges] {
3480 set ranges $idrowranges($id)
3481 if {$ranges ne {} && [lindex $ranges end] >= $row} {
3482 set newranges {}
3483 foreach x $ranges {
3484 if {$x >= $row} {
3485 lappend newranges [expr {$x + 1}]
3486 } else {
3487 lappend newranges $x
3490 set idrowranges($id) $newranges
3494 set linesegends [linsert $linesegends $row {}]
3496 incr rowlaidout
3497 incr rowoptim
3498 incr numcommits
3500 if {[info exists selectedline] && $selectedline >= $row} {
3501 incr selectedline
3503 redisplay
3506 # Don't change the text pane cursor if it is currently the hand cursor,
3507 # showing that we are over a sha1 ID link.
3508 proc settextcursor {c} {
3509 global ctext curtextcursor
3511 if {[$ctext cget -cursor] == $curtextcursor} {
3512 $ctext config -cursor $c
3514 set curtextcursor $c
3517 proc nowbusy {what} {
3518 global isbusy
3520 if {[array names isbusy] eq {}} {
3521 . config -cursor watch
3522 settextcursor watch
3524 set isbusy($what) 1
3527 proc notbusy {what} {
3528 global isbusy maincursor textcursor
3530 catch {unset isbusy($what)}
3531 if {[array names isbusy] eq {}} {
3532 . config -cursor $maincursor
3533 settextcursor $textcursor
3537 proc drawrest {} {
3538 global startmsecs
3539 global rowlaidout commitidx curview
3540 global pending_select
3542 set row $rowlaidout
3543 layoutrows $rowlaidout $commitidx($curview) 1
3544 layouttail
3545 optimize_rows $row 0 $commitidx($curview)
3546 showstuff $commitidx($curview)
3547 if {[info exists pending_select]} {
3548 selectline 0 1
3551 set drawmsecs [expr {[clock clicks -milliseconds] - $startmsecs}]
3552 #global numcommits
3553 #puts "overall $drawmsecs ms for $numcommits commits"
3556 proc findmatches {f} {
3557 global findtype foundstring foundstrlen
3558 if {$findtype == "Regexp"} {
3559 set matches [regexp -indices -all -inline $foundstring $f]
3560 } else {
3561 if {$findtype == "IgnCase"} {
3562 set str [string tolower $f]
3563 } else {
3564 set str $f
3566 set matches {}
3567 set i 0
3568 while {[set j [string first $foundstring $str $i]] >= 0} {
3569 lappend matches [list $j [expr {$j+$foundstrlen-1}]]
3570 set i [expr {$j + $foundstrlen}]
3573 return $matches
3576 proc dofind {} {
3577 global findtype findloc findstring markedmatches commitinfo
3578 global numcommits displayorder linehtag linentag linedtag
3579 global mainfont canv canv2 canv3 selectedline
3580 global matchinglines foundstring foundstrlen matchstring
3581 global commitdata
3583 stopfindproc
3584 unmarkmatches
3585 cancel_next_highlight
3586 focus .
3587 set matchinglines {}
3588 if {$findtype == "IgnCase"} {
3589 set foundstring [string tolower $findstring]
3590 } else {
3591 set foundstring $findstring
3593 set foundstrlen [string length $findstring]
3594 if {$foundstrlen == 0} return
3595 regsub -all {[*?\[\\]} $foundstring {\\&} matchstring
3596 set matchstring "*$matchstring*"
3597 if {![info exists selectedline]} {
3598 set oldsel -1
3599 } else {
3600 set oldsel $selectedline
3602 set didsel 0
3603 set fldtypes {Headline Author Date Committer CDate Comments}
3604 set l -1
3605 foreach id $displayorder {
3606 set d $commitdata($id)
3607 incr l
3608 if {$findtype == "Regexp"} {
3609 set doesmatch [regexp $foundstring $d]
3610 } elseif {$findtype == "IgnCase"} {
3611 set doesmatch [string match -nocase $matchstring $d]
3612 } else {
3613 set doesmatch [string match $matchstring $d]
3615 if {!$doesmatch} continue
3616 if {![info exists commitinfo($id)]} {
3617 getcommit $id
3619 set info $commitinfo($id)
3620 set doesmatch 0
3621 foreach f $info ty $fldtypes {
3622 if {$findloc != "All fields" && $findloc != $ty} {
3623 continue
3625 set matches [findmatches $f]
3626 if {$matches == {}} continue
3627 set doesmatch 1
3628 if {$ty == "Headline"} {
3629 drawcmitrow $l
3630 markmatches $canv $l $f $linehtag($l) $matches $mainfont
3631 } elseif {$ty == "Author"} {
3632 drawcmitrow $l
3633 markmatches $canv2 $l $f $linentag($l) $matches $mainfont
3634 } elseif {$ty == "Date"} {
3635 drawcmitrow $l
3636 markmatches $canv3 $l $f $linedtag($l) $matches $mainfont
3639 if {$doesmatch} {
3640 lappend matchinglines $l
3641 if {!$didsel && $l > $oldsel} {
3642 findselectline $l
3643 set didsel 1
3647 if {$matchinglines == {}} {
3648 bell
3649 } elseif {!$didsel} {
3650 findselectline [lindex $matchinglines 0]
3654 proc findselectline {l} {
3655 global findloc commentend ctext
3656 selectline $l 1
3657 if {$findloc == "All fields" || $findloc == "Comments"} {
3658 # highlight the matches in the comments
3659 set f [$ctext get 1.0 $commentend]
3660 set matches [findmatches $f]
3661 foreach match $matches {
3662 set start [lindex $match 0]
3663 set end [expr {[lindex $match 1] + 1}]
3664 $ctext tag add found "1.0 + $start c" "1.0 + $end c"
3669 proc findnext {restart} {
3670 global matchinglines selectedline
3671 if {![info exists matchinglines]} {
3672 if {$restart} {
3673 dofind
3675 return
3677 if {![info exists selectedline]} return
3678 foreach l $matchinglines {
3679 if {$l > $selectedline} {
3680 findselectline $l
3681 return
3684 bell
3687 proc findprev {} {
3688 global matchinglines selectedline
3689 if {![info exists matchinglines]} {
3690 dofind
3691 return
3693 if {![info exists selectedline]} return
3694 set prev {}
3695 foreach l $matchinglines {
3696 if {$l >= $selectedline} break
3697 set prev $l
3699 if {$prev != {}} {
3700 findselectline $prev
3701 } else {
3702 bell
3706 proc stopfindproc {{done 0}} {
3707 global findprocpid findprocfile findids
3708 global ctext findoldcursor phase maincursor textcursor
3709 global findinprogress
3711 catch {unset findids}
3712 if {[info exists findprocpid]} {
3713 if {!$done} {
3714 catch {exec kill $findprocpid}
3716 catch {close $findprocfile}
3717 unset findprocpid
3719 catch {unset findinprogress}
3720 notbusy find
3723 # mark a commit as matching by putting a yellow background
3724 # behind the headline
3725 proc markheadline {l id} {
3726 global canv mainfont linehtag
3728 drawcmitrow $l
3729 set bbox [$canv bbox $linehtag($l)]
3730 set t [$canv create rect $bbox -outline {} -tags matches -fill yellow]
3731 $canv lower $t
3734 # mark the bits of a headline, author or date that match a find string
3735 proc markmatches {canv l str tag matches font} {
3736 set bbox [$canv bbox $tag]
3737 set x0 [lindex $bbox 0]
3738 set y0 [lindex $bbox 1]
3739 set y1 [lindex $bbox 3]
3740 foreach match $matches {
3741 set start [lindex $match 0]
3742 set end [lindex $match 1]
3743 if {$start > $end} continue
3744 set xoff [font measure $font [string range $str 0 [expr {$start-1}]]]
3745 set xlen [font measure $font [string range $str 0 [expr {$end}]]]
3746 set t [$canv create rect [expr {$x0+$xoff}] $y0 \
3747 [expr {$x0+$xlen+2}] $y1 \
3748 -outline {} -tags matches -fill yellow]
3749 $canv lower $t
3753 proc unmarkmatches {} {
3754 global matchinglines findids
3755 allcanvs delete matches
3756 catch {unset matchinglines}
3757 catch {unset findids}
3760 proc selcanvline {w x y} {
3761 global canv canvy0 ctext linespc
3762 global rowtextx
3763 set ymax [lindex [$canv cget -scrollregion] 3]
3764 if {$ymax == {}} return
3765 set yfrac [lindex [$canv yview] 0]
3766 set y [expr {$y + $yfrac * $ymax}]
3767 set l [expr {int(($y - $canvy0) / $linespc + 0.5)}]
3768 if {$l < 0} {
3769 set l 0
3771 if {$w eq $canv} {
3772 if {![info exists rowtextx($l)] || $x < $rowtextx($l)} return
3774 unmarkmatches
3775 selectline $l 1
3778 proc commit_descriptor {p} {
3779 global commitinfo
3780 if {![info exists commitinfo($p)]} {
3781 getcommit $p
3783 set l "..."
3784 if {[llength $commitinfo($p)] > 1} {
3785 set l [lindex $commitinfo($p) 0]
3787 return "$p ($l)\n"
3790 # append some text to the ctext widget, and make any SHA1 ID
3791 # that we know about be a clickable link.
3792 proc appendwithlinks {text tags} {
3793 global ctext commitrow linknum curview
3795 set start [$ctext index "end - 1c"]
3796 $ctext insert end $text $tags
3797 set links [regexp -indices -all -inline {[0-9a-f]{40}} $text]
3798 foreach l $links {
3799 set s [lindex $l 0]
3800 set e [lindex $l 1]
3801 set linkid [string range $text $s $e]
3802 if {![info exists commitrow($curview,$linkid)]} continue
3803 incr e
3804 $ctext tag add link "$start + $s c" "$start + $e c"
3805 $ctext tag add link$linknum "$start + $s c" "$start + $e c"
3806 $ctext tag bind link$linknum <1> \
3807 [list selectline $commitrow($curview,$linkid) 1]
3808 incr linknum
3810 $ctext tag conf link -foreground blue -underline 1
3811 $ctext tag bind link <Enter> { %W configure -cursor hand2 }
3812 $ctext tag bind link <Leave> { %W configure -cursor $curtextcursor }
3815 proc viewnextline {dir} {
3816 global canv linespc
3818 $canv delete hover
3819 set ymax [lindex [$canv cget -scrollregion] 3]
3820 set wnow [$canv yview]
3821 set wtop [expr {[lindex $wnow 0] * $ymax}]
3822 set newtop [expr {$wtop + $dir * $linespc}]
3823 if {$newtop < 0} {
3824 set newtop 0
3825 } elseif {$newtop > $ymax} {
3826 set newtop $ymax
3828 allcanvs yview moveto [expr {$newtop * 1.0 / $ymax}]
3831 # add a list of tag or branch names at position pos
3832 # returns the number of names inserted
3833 proc appendrefs {pos ids var} {
3834 global ctext commitrow linknum curview $var
3836 if {[catch {$ctext index $pos}]} {
3837 return 0
3839 $ctext conf -state normal
3840 $ctext delete $pos "$pos lineend"
3841 set tags {}
3842 foreach id $ids {
3843 foreach tag [set $var\($id\)] {
3844 lappend tags [list $tag $id]
3847 set tags [lsort -index 0 -decreasing $tags]
3848 set sep {}
3849 foreach ti $tags {
3850 set id [lindex $ti 1]
3851 set lk link$linknum
3852 incr linknum
3853 $ctext tag delete $lk
3854 $ctext insert $pos $sep
3855 $ctext insert $pos [lindex $ti 0] $lk
3856 if {[info exists commitrow($curview,$id)]} {
3857 $ctext tag conf $lk -foreground blue
3858 $ctext tag bind $lk <1> \
3859 [list selectline $commitrow($curview,$id) 1]
3860 $ctext tag conf $lk -underline 1
3861 $ctext tag bind $lk <Enter> { %W configure -cursor hand2 }
3862 $ctext tag bind $lk <Leave> { %W configure -cursor $curtextcursor }
3864 set sep ", "
3866 $ctext conf -state disabled
3867 return [llength $tags]
3870 # called when we have finished computing the nearby tags
3871 proc dispneartags {delay} {
3872 global selectedline currentid showneartags tagphase
3874 if {![info exists selectedline] || !$showneartags} return
3875 after cancel dispnexttag
3876 if {$delay} {
3877 after 200 dispnexttag
3878 set tagphase -1
3879 } else {
3880 after idle dispnexttag
3881 set tagphase 0
3885 proc dispnexttag {} {
3886 global selectedline currentid showneartags tagphase ctext
3888 if {![info exists selectedline] || !$showneartags} return
3889 switch -- $tagphase {
3891 set dtags [desctags $currentid]
3892 if {$dtags ne {}} {
3893 appendrefs precedes $dtags idtags
3897 set atags [anctags $currentid]
3898 if {$atags ne {}} {
3899 appendrefs follows $atags idtags
3903 set dheads [descheads $currentid]
3904 if {$dheads ne {}} {
3905 if {[appendrefs branch $dheads idheads] > 1
3906 && [$ctext get "branch -3c"] eq "h"} {
3907 # turn "Branch" into "Branches"
3908 $ctext conf -state normal
3909 $ctext insert "branch -2c" "es"
3910 $ctext conf -state disabled
3915 if {[incr tagphase] <= 2} {
3916 after idle dispnexttag
3920 proc selectline {l isnew} {
3921 global canv canv2 canv3 ctext commitinfo selectedline
3922 global displayorder linehtag linentag linedtag
3923 global canvy0 linespc parentlist childlist
3924 global currentid sha1entry
3925 global commentend idtags linknum
3926 global mergemax numcommits pending_select
3927 global cmitmode showneartags allcommits
3929 catch {unset pending_select}
3930 $canv delete hover
3931 normalline
3932 cancel_next_highlight
3933 if {$l < 0 || $l >= $numcommits} return
3934 set y [expr {$canvy0 + $l * $linespc}]
3935 set ymax [lindex [$canv cget -scrollregion] 3]
3936 set ytop [expr {$y - $linespc - 1}]
3937 set ybot [expr {$y + $linespc + 1}]
3938 set wnow [$canv yview]
3939 set wtop [expr {[lindex $wnow 0] * $ymax}]
3940 set wbot [expr {[lindex $wnow 1] * $ymax}]
3941 set wh [expr {$wbot - $wtop}]
3942 set newtop $wtop
3943 if {$ytop < $wtop} {
3944 if {$ybot < $wtop} {
3945 set newtop [expr {$y - $wh / 2.0}]
3946 } else {
3947 set newtop $ytop
3948 if {$newtop > $wtop - $linespc} {
3949 set newtop [expr {$wtop - $linespc}]
3952 } elseif {$ybot > $wbot} {
3953 if {$ytop > $wbot} {
3954 set newtop [expr {$y - $wh / 2.0}]
3955 } else {
3956 set newtop [expr {$ybot - $wh}]
3957 if {$newtop < $wtop + $linespc} {
3958 set newtop [expr {$wtop + $linespc}]
3962 if {$newtop != $wtop} {
3963 if {$newtop < 0} {
3964 set newtop 0
3966 allcanvs yview moveto [expr {$newtop * 1.0 / $ymax}]
3967 drawvisible
3970 if {![info exists linehtag($l)]} return
3971 $canv delete secsel
3972 set t [eval $canv create rect [$canv bbox $linehtag($l)] -outline {{}} \
3973 -tags secsel -fill [$canv cget -selectbackground]]
3974 $canv lower $t
3975 $canv2 delete secsel
3976 set t [eval $canv2 create rect [$canv2 bbox $linentag($l)] -outline {{}} \
3977 -tags secsel -fill [$canv2 cget -selectbackground]]
3978 $canv2 lower $t
3979 $canv3 delete secsel
3980 set t [eval $canv3 create rect [$canv3 bbox $linedtag($l)] -outline {{}} \
3981 -tags secsel -fill [$canv3 cget -selectbackground]]
3982 $canv3 lower $t
3984 if {$isnew} {
3985 addtohistory [list selectline $l 0]
3988 set selectedline $l
3990 set id [lindex $displayorder $l]
3991 set currentid $id
3992 $sha1entry delete 0 end
3993 $sha1entry insert 0 $id
3994 $sha1entry selection from 0
3995 $sha1entry selection to end
3996 rhighlight_sel $id
3998 $ctext conf -state normal
3999 clear_ctext
4000 set linknum 0
4001 set info $commitinfo($id)
4002 set date [formatdate [lindex $info 2]]
4003 $ctext insert end "Author: [lindex $info 1] $date\n"
4004 set date [formatdate [lindex $info 4]]
4005 $ctext insert end "Committer: [lindex $info 3] $date\n"
4006 if {[info exists idtags($id)]} {
4007 $ctext insert end "Tags:"
4008 foreach tag $idtags($id) {
4009 $ctext insert end " $tag"
4011 $ctext insert end "\n"
4014 set headers {}
4015 set olds [lindex $parentlist $l]
4016 if {[llength $olds] > 1} {
4017 set np 0
4018 foreach p $olds {
4019 if {$np >= $mergemax} {
4020 set tag mmax
4021 } else {
4022 set tag m$np
4024 $ctext insert end "Parent: " $tag
4025 appendwithlinks [commit_descriptor $p] {}
4026 incr np
4028 } else {
4029 foreach p $olds {
4030 append headers "Parent: [commit_descriptor $p]"
4034 foreach c [lindex $childlist $l] {
4035 append headers "Child: [commit_descriptor $c]"
4038 # make anything that looks like a SHA1 ID be a clickable link
4039 appendwithlinks $headers {}
4040 if {$showneartags} {
4041 if {![info exists allcommits]} {
4042 getallcommits
4044 $ctext insert end "Branch: "
4045 $ctext mark set branch "end -1c"
4046 $ctext mark gravity branch left
4047 $ctext insert end "\nFollows: "
4048 $ctext mark set follows "end -1c"
4049 $ctext mark gravity follows left
4050 $ctext insert end "\nPrecedes: "
4051 $ctext mark set precedes "end -1c"
4052 $ctext mark gravity precedes left
4053 $ctext insert end "\n"
4054 dispneartags 1
4056 $ctext insert end "\n"
4057 appendwithlinks [lindex $info 5] {comment}
4059 $ctext tag delete Comments
4060 $ctext tag remove found 1.0 end
4061 $ctext conf -state disabled
4062 set commentend [$ctext index "end - 1c"]
4064 init_flist "Comments"
4065 if {$cmitmode eq "tree"} {
4066 gettree $id
4067 } elseif {[llength $olds] <= 1} {
4068 startdiff $id
4069 } else {
4070 mergediff $id $l
4074 proc selfirstline {} {
4075 unmarkmatches
4076 selectline 0 1
4079 proc sellastline {} {
4080 global numcommits
4081 unmarkmatches
4082 set l [expr {$numcommits - 1}]
4083 selectline $l 1
4086 proc selnextline {dir} {
4087 global selectedline
4088 if {![info exists selectedline]} return
4089 set l [expr {$selectedline + $dir}]
4090 unmarkmatches
4091 selectline $l 1
4094 proc selnextpage {dir} {
4095 global canv linespc selectedline numcommits
4097 set lpp [expr {([winfo height $canv] - 2) / $linespc}]
4098 if {$lpp < 1} {
4099 set lpp 1
4101 allcanvs yview scroll [expr {$dir * $lpp}] units
4102 drawvisible
4103 if {![info exists selectedline]} return
4104 set l [expr {$selectedline + $dir * $lpp}]
4105 if {$l < 0} {
4106 set l 0
4107 } elseif {$l >= $numcommits} {
4108 set l [expr $numcommits - 1]
4110 unmarkmatches
4111 selectline $l 1
4114 proc unselectline {} {
4115 global selectedline currentid
4117 catch {unset selectedline}
4118 catch {unset currentid}
4119 allcanvs delete secsel
4120 rhighlight_none
4121 cancel_next_highlight
4124 proc reselectline {} {
4125 global selectedline
4127 if {[info exists selectedline]} {
4128 selectline $selectedline 0
4132 proc addtohistory {cmd} {
4133 global history historyindex curview
4135 set elt [list $curview $cmd]
4136 if {$historyindex > 0
4137 && [lindex $history [expr {$historyindex - 1}]] == $elt} {
4138 return
4141 if {$historyindex < [llength $history]} {
4142 set history [lreplace $history $historyindex end $elt]
4143 } else {
4144 lappend history $elt
4146 incr historyindex
4147 if {$historyindex > 1} {
4148 .tf.bar.leftbut conf -state normal
4149 } else {
4150 .tf.bar.leftbut conf -state disabled
4152 .tf.bar.rightbut conf -state disabled
4155 proc godo {elt} {
4156 global curview
4158 set view [lindex $elt 0]
4159 set cmd [lindex $elt 1]
4160 if {$curview != $view} {
4161 showview $view
4163 eval $cmd
4166 proc goback {} {
4167 global history historyindex
4169 if {$historyindex > 1} {
4170 incr historyindex -1
4171 godo [lindex $history [expr {$historyindex - 1}]]
4172 .tf.bar.rightbut conf -state normal
4174 if {$historyindex <= 1} {
4175 .tf.bar.leftbut conf -state disabled
4179 proc goforw {} {
4180 global history historyindex
4182 if {$historyindex < [llength $history]} {
4183 set cmd [lindex $history $historyindex]
4184 incr historyindex
4185 godo $cmd
4186 .tf.bar.leftbut conf -state normal
4188 if {$historyindex >= [llength $history]} {
4189 .tf.bar.rightbut conf -state disabled
4193 proc gettree {id} {
4194 global treefilelist treeidlist diffids diffmergeid treepending
4196 set diffids $id
4197 catch {unset diffmergeid}
4198 if {![info exists treefilelist($id)]} {
4199 if {![info exists treepending]} {
4200 if {[catch {set gtf [open [concat | git ls-tree -r $id] r]}]} {
4201 return
4203 set treepending $id
4204 set treefilelist($id) {}
4205 set treeidlist($id) {}
4206 fconfigure $gtf -blocking 0
4207 fileevent $gtf readable [list gettreeline $gtf $id]
4209 } else {
4210 setfilelist $id
4214 proc gettreeline {gtf id} {
4215 global treefilelist treeidlist treepending cmitmode diffids
4217 while {[gets $gtf line] >= 0} {
4218 if {[lindex $line 1] ne "blob"} continue
4219 set sha1 [lindex $line 2]
4220 set fname [lindex $line 3]
4221 lappend treefilelist($id) $fname
4222 lappend treeidlist($id) $sha1
4224 if {![eof $gtf]} return
4225 close $gtf
4226 unset treepending
4227 if {$cmitmode ne "tree"} {
4228 if {![info exists diffmergeid]} {
4229 gettreediffs $diffids
4231 } elseif {$id ne $diffids} {
4232 gettree $diffids
4233 } else {
4234 setfilelist $id
4238 proc showfile {f} {
4239 global treefilelist treeidlist diffids
4240 global ctext commentend
4242 set i [lsearch -exact $treefilelist($diffids) $f]
4243 if {$i < 0} {
4244 puts "oops, $f not in list for id $diffids"
4245 return
4247 set blob [lindex $treeidlist($diffids) $i]
4248 if {[catch {set bf [open [concat | git cat-file blob $blob] r]} err]} {
4249 puts "oops, error reading blob $blob: $err"
4250 return
4252 fconfigure $bf -blocking 0
4253 fileevent $bf readable [list getblobline $bf $diffids]
4254 $ctext config -state normal
4255 clear_ctext $commentend
4256 $ctext insert end "\n"
4257 $ctext insert end "$f\n" filesep
4258 $ctext config -state disabled
4259 $ctext yview $commentend
4262 proc getblobline {bf id} {
4263 global diffids cmitmode ctext
4265 if {$id ne $diffids || $cmitmode ne "tree"} {
4266 catch {close $bf}
4267 return
4269 $ctext config -state normal
4270 while {[gets $bf line] >= 0} {
4271 $ctext insert end "$line\n"
4273 if {[eof $bf]} {
4274 # delete last newline
4275 $ctext delete "end - 2c" "end - 1c"
4276 close $bf
4278 $ctext config -state disabled
4281 proc mergediff {id l} {
4282 global diffmergeid diffopts mdifffd
4283 global diffids
4284 global parentlist
4286 set diffmergeid $id
4287 set diffids $id
4288 # this doesn't seem to actually affect anything...
4289 set env(GIT_DIFF_OPTS) $diffopts
4290 set cmd [concat | git diff-tree --no-commit-id --cc $id]
4291 if {[catch {set mdf [open $cmd r]} err]} {
4292 error_popup "Error getting merge diffs: $err"
4293 return
4295 fconfigure $mdf -blocking 0
4296 set mdifffd($id) $mdf
4297 set np [llength [lindex $parentlist $l]]
4298 fileevent $mdf readable [list getmergediffline $mdf $id $np]
4299 set nextupdate [expr {[clock clicks -milliseconds] + 100}]
4302 proc getmergediffline {mdf id np} {
4303 global diffmergeid ctext cflist nextupdate mergemax
4304 global difffilestart mdifffd
4306 set n [gets $mdf line]
4307 if {$n < 0} {
4308 if {[eof $mdf]} {
4309 close $mdf
4311 return
4313 if {![info exists diffmergeid] || $id != $diffmergeid
4314 || $mdf != $mdifffd($id)} {
4315 return
4317 $ctext conf -state normal
4318 if {[regexp {^diff --cc (.*)} $line match fname]} {
4319 # start of a new file
4320 $ctext insert end "\n"
4321 set here [$ctext index "end - 1c"]
4322 lappend difffilestart $here
4323 add_flist [list $fname]
4324 set l [expr {(78 - [string length $fname]) / 2}]
4325 set pad [string range "----------------------------------------" 1 $l]
4326 $ctext insert end "$pad $fname $pad\n" filesep
4327 } elseif {[regexp {^@@} $line]} {
4328 $ctext insert end "$line\n" hunksep
4329 } elseif {[regexp {^[0-9a-f]{40}$} $line] || [regexp {^index} $line]} {
4330 # do nothing
4331 } else {
4332 # parse the prefix - one ' ', '-' or '+' for each parent
4333 set spaces {}
4334 set minuses {}
4335 set pluses {}
4336 set isbad 0
4337 for {set j 0} {$j < $np} {incr j} {
4338 set c [string range $line $j $j]
4339 if {$c == " "} {
4340 lappend spaces $j
4341 } elseif {$c == "-"} {
4342 lappend minuses $j
4343 } elseif {$c == "+"} {
4344 lappend pluses $j
4345 } else {
4346 set isbad 1
4347 break
4350 set tags {}
4351 set num {}
4352 if {!$isbad && $minuses ne {} && $pluses eq {}} {
4353 # line doesn't appear in result, parents in $minuses have the line
4354 set num [lindex $minuses 0]
4355 } elseif {!$isbad && $pluses ne {} && $minuses eq {}} {
4356 # line appears in result, parents in $pluses don't have the line
4357 lappend tags mresult
4358 set num [lindex $spaces 0]
4360 if {$num ne {}} {
4361 if {$num >= $mergemax} {
4362 set num "max"
4364 lappend tags m$num
4366 $ctext insert end "$line\n" $tags
4368 $ctext conf -state disabled
4369 if {[clock clicks -milliseconds] >= $nextupdate} {
4370 incr nextupdate 100
4371 fileevent $mdf readable {}
4372 update
4373 fileevent $mdf readable [list getmergediffline $mdf $id $np]
4377 proc startdiff {ids} {
4378 global treediffs diffids treepending diffmergeid
4380 set diffids $ids
4381 catch {unset diffmergeid}
4382 if {![info exists treediffs($ids)]} {
4383 if {![info exists treepending]} {
4384 gettreediffs $ids
4386 } else {
4387 addtocflist $ids
4391 proc addtocflist {ids} {
4392 global treediffs cflist
4393 add_flist $treediffs($ids)
4394 getblobdiffs $ids
4397 proc gettreediffs {ids} {
4398 global treediff treepending
4399 set treepending $ids
4400 set treediff {}
4401 if {[catch \
4402 {set gdtf [open [concat | git diff-tree --no-commit-id -r $ids] r]} \
4403 ]} return
4404 fconfigure $gdtf -blocking 0
4405 fileevent $gdtf readable [list gettreediffline $gdtf $ids]
4408 proc gettreediffline {gdtf ids} {
4409 global treediff treediffs treepending diffids diffmergeid
4410 global cmitmode
4412 set n [gets $gdtf line]
4413 if {$n < 0} {
4414 if {![eof $gdtf]} return
4415 close $gdtf
4416 set treediffs($ids) $treediff
4417 unset treepending
4418 if {$cmitmode eq "tree"} {
4419 gettree $diffids
4420 } elseif {$ids != $diffids} {
4421 if {![info exists diffmergeid]} {
4422 gettreediffs $diffids
4424 } else {
4425 addtocflist $ids
4427 return
4429 set file [lindex $line 5]
4430 lappend treediff $file
4433 proc getblobdiffs {ids} {
4434 global diffopts blobdifffd diffids env curdifftag curtagstart
4435 global nextupdate diffinhdr treediffs
4437 set env(GIT_DIFF_OPTS) $diffopts
4438 set cmd [concat | git diff-tree --no-commit-id -r -p -C $ids]
4439 if {[catch {set bdf [open $cmd r]} err]} {
4440 puts "error getting diffs: $err"
4441 return
4443 set diffinhdr 0
4444 fconfigure $bdf -blocking 0
4445 set blobdifffd($ids) $bdf
4446 set curdifftag Comments
4447 set curtagstart 0.0
4448 fileevent $bdf readable [list getblobdiffline $bdf $diffids]
4449 set nextupdate [expr {[clock clicks -milliseconds] + 100}]
4452 proc setinlist {var i val} {
4453 global $var
4455 while {[llength [set $var]] < $i} {
4456 lappend $var {}
4458 if {[llength [set $var]] == $i} {
4459 lappend $var $val
4460 } else {
4461 lset $var $i $val
4465 proc getblobdiffline {bdf ids} {
4466 global diffids blobdifffd ctext curdifftag curtagstart
4467 global diffnexthead diffnextnote difffilestart
4468 global nextupdate diffinhdr treediffs
4470 set n [gets $bdf line]
4471 if {$n < 0} {
4472 if {[eof $bdf]} {
4473 close $bdf
4474 if {$ids == $diffids && $bdf == $blobdifffd($ids)} {
4475 $ctext tag add $curdifftag $curtagstart end
4478 return
4480 if {$ids != $diffids || $bdf != $blobdifffd($ids)} {
4481 return
4483 $ctext conf -state normal
4484 if {[regexp {^diff --git a/(.*) b/(.*)} $line match fname newname]} {
4485 # start of a new file
4486 $ctext insert end "\n"
4487 $ctext tag add $curdifftag $curtagstart end
4488 set here [$ctext index "end - 1c"]
4489 set curtagstart $here
4490 set header $newname
4491 set i [lsearch -exact $treediffs($ids) $fname]
4492 if {$i >= 0} {
4493 setinlist difffilestart $i $here
4495 if {$newname ne $fname} {
4496 set i [lsearch -exact $treediffs($ids) $newname]
4497 if {$i >= 0} {
4498 setinlist difffilestart $i $here
4501 set curdifftag "f:$fname"
4502 $ctext tag delete $curdifftag
4503 set l [expr {(78 - [string length $header]) / 2}]
4504 set pad [string range "----------------------------------------" 1 $l]
4505 $ctext insert end "$pad $header $pad\n" filesep
4506 set diffinhdr 1
4507 } elseif {$diffinhdr && [string compare -length 3 $line "---"] == 0} {
4508 # do nothing
4509 } elseif {$diffinhdr && [string compare -length 3 $line "+++"] == 0} {
4510 set diffinhdr 0
4511 } elseif {[regexp {^@@ -([0-9]+),([0-9]+) \+([0-9]+),([0-9]+) @@(.*)} \
4512 $line match f1l f1c f2l f2c rest]} {
4513 $ctext insert end "$line\n" hunksep
4514 set diffinhdr 0
4515 } else {
4516 set x [string range $line 0 0]
4517 if {$x == "-" || $x == "+"} {
4518 set tag [expr {$x == "+"}]
4519 $ctext insert end "$line\n" d$tag
4520 } elseif {$x == " "} {
4521 $ctext insert end "$line\n"
4522 } elseif {$diffinhdr || $x == "\\"} {
4523 # e.g. "\ No newline at end of file"
4524 $ctext insert end "$line\n" filesep
4525 } else {
4526 # Something else we don't recognize
4527 if {$curdifftag != "Comments"} {
4528 $ctext insert end "\n"
4529 $ctext tag add $curdifftag $curtagstart end
4530 set curtagstart [$ctext index "end - 1c"]
4531 set curdifftag Comments
4533 $ctext insert end "$line\n" filesep
4536 $ctext conf -state disabled
4537 if {[clock clicks -milliseconds] >= $nextupdate} {
4538 incr nextupdate 100
4539 fileevent $bdf readable {}
4540 update
4541 fileevent $bdf readable "getblobdiffline $bdf {$ids}"
4545 proc changediffdisp {} {
4546 global ctext diffelide
4548 $ctext tag conf d0 -elide [lindex $diffelide 0]
4549 $ctext tag conf d1 -elide [lindex $diffelide 1]
4552 proc prevfile {} {
4553 global difffilestart ctext
4554 set prev [lindex $difffilestart 0]
4555 set here [$ctext index @0,0]
4556 foreach loc $difffilestart {
4557 if {[$ctext compare $loc >= $here]} {
4558 $ctext yview $prev
4559 return
4561 set prev $loc
4563 $ctext yview $prev
4566 proc nextfile {} {
4567 global difffilestart ctext
4568 set here [$ctext index @0,0]
4569 foreach loc $difffilestart {
4570 if {[$ctext compare $loc > $here]} {
4571 $ctext yview $loc
4572 return
4577 proc clear_ctext {{first 1.0}} {
4578 global ctext smarktop smarkbot
4580 set l [lindex [split $first .] 0]
4581 if {![info exists smarktop] || [$ctext compare $first < $smarktop.0]} {
4582 set smarktop $l
4584 if {![info exists smarkbot] || [$ctext compare $first < $smarkbot.0]} {
4585 set smarkbot $l
4587 $ctext delete $first end
4590 proc incrsearch {name ix op} {
4591 global ctext searchstring searchdirn
4593 $ctext tag remove found 1.0 end
4594 if {[catch {$ctext index anchor}]} {
4595 # no anchor set, use start of selection, or of visible area
4596 set sel [$ctext tag ranges sel]
4597 if {$sel ne {}} {
4598 $ctext mark set anchor [lindex $sel 0]
4599 } elseif {$searchdirn eq "-forwards"} {
4600 $ctext mark set anchor @0,0
4601 } else {
4602 $ctext mark set anchor @0,[winfo height $ctext]
4605 if {$searchstring ne {}} {
4606 set here [$ctext search $searchdirn -- $searchstring anchor]
4607 if {$here ne {}} {
4608 $ctext see $here
4610 searchmarkvisible 1
4614 proc dosearch {} {
4615 global sstring ctext searchstring searchdirn
4617 focus $sstring
4618 $sstring icursor end
4619 set searchdirn -forwards
4620 if {$searchstring ne {}} {
4621 set sel [$ctext tag ranges sel]
4622 if {$sel ne {}} {
4623 set start "[lindex $sel 0] + 1c"
4624 } elseif {[catch {set start [$ctext index anchor]}]} {
4625 set start "@0,0"
4627 set match [$ctext search -count mlen -- $searchstring $start]
4628 $ctext tag remove sel 1.0 end
4629 if {$match eq {}} {
4630 bell
4631 return
4633 $ctext see $match
4634 set mend "$match + $mlen c"
4635 $ctext tag add sel $match $mend
4636 $ctext mark unset anchor
4640 proc dosearchback {} {
4641 global sstring ctext searchstring searchdirn
4643 focus $sstring
4644 $sstring icursor end
4645 set searchdirn -backwards
4646 if {$searchstring ne {}} {
4647 set sel [$ctext tag ranges sel]
4648 if {$sel ne {}} {
4649 set start [lindex $sel 0]
4650 } elseif {[catch {set start [$ctext index anchor]}]} {
4651 set start @0,[winfo height $ctext]
4653 set match [$ctext search -backwards -count ml -- $searchstring $start]
4654 $ctext tag remove sel 1.0 end
4655 if {$match eq {}} {
4656 bell
4657 return
4659 $ctext see $match
4660 set mend "$match + $ml c"
4661 $ctext tag add sel $match $mend
4662 $ctext mark unset anchor
4666 proc searchmark {first last} {
4667 global ctext searchstring
4669 set mend $first.0
4670 while {1} {
4671 set match [$ctext search -count mlen -- $searchstring $mend $last.end]
4672 if {$match eq {}} break
4673 set mend "$match + $mlen c"
4674 $ctext tag add found $match $mend
4678 proc searchmarkvisible {doall} {
4679 global ctext smarktop smarkbot
4681 set topline [lindex [split [$ctext index @0,0] .] 0]
4682 set botline [lindex [split [$ctext index @0,[winfo height $ctext]] .] 0]
4683 if {$doall || $botline < $smarktop || $topline > $smarkbot} {
4684 # no overlap with previous
4685 searchmark $topline $botline
4686 set smarktop $topline
4687 set smarkbot $botline
4688 } else {
4689 if {$topline < $smarktop} {
4690 searchmark $topline [expr {$smarktop-1}]
4691 set smarktop $topline
4693 if {$botline > $smarkbot} {
4694 searchmark [expr {$smarkbot+1}] $botline
4695 set smarkbot $botline
4700 proc scrolltext {f0 f1} {
4701 global searchstring
4703 .bleft.sb set $f0 $f1
4704 if {$searchstring ne {}} {
4705 searchmarkvisible 0
4709 proc setcoords {} {
4710 global linespc charspc canvx0 canvy0 mainfont
4711 global xspc1 xspc2 lthickness
4713 set linespc [font metrics $mainfont -linespace]
4714 set charspc [font measure $mainfont "m"]
4715 set canvy0 [expr {int(3 + 0.5 * $linespc)}]
4716 set canvx0 [expr {int(3 + 0.5 * $linespc)}]
4717 set lthickness [expr {int($linespc / 9) + 1}]
4718 set xspc1(0) $linespc
4719 set xspc2 $linespc
4722 proc redisplay {} {
4723 global canv
4724 global selectedline
4726 set ymax [lindex [$canv cget -scrollregion] 3]
4727 if {$ymax eq {} || $ymax == 0} return
4728 set span [$canv yview]
4729 clear_display
4730 setcanvscroll
4731 allcanvs yview moveto [lindex $span 0]
4732 drawvisible
4733 if {[info exists selectedline]} {
4734 selectline $selectedline 0
4735 allcanvs yview moveto [lindex $span 0]
4739 proc incrfont {inc} {
4740 global mainfont textfont ctext canv phase cflist
4741 global charspc tabstop
4742 global stopped entries
4743 unmarkmatches
4744 set mainfont [lreplace $mainfont 1 1 [expr {[lindex $mainfont 1] + $inc}]]
4745 set textfont [lreplace $textfont 1 1 [expr {[lindex $textfont 1] + $inc}]]
4746 setcoords
4747 $ctext conf -font $textfont -tabs "[expr {$tabstop * $charspc}]"
4748 $cflist conf -font $textfont
4749 $ctext tag conf filesep -font [concat $textfont bold]
4750 foreach e $entries {
4751 $e conf -font $mainfont
4753 if {$phase eq "getcommits"} {
4754 $canv itemconf textitems -font $mainfont
4756 redisplay
4759 proc clearsha1 {} {
4760 global sha1entry sha1string
4761 if {[string length $sha1string] == 40} {
4762 $sha1entry delete 0 end
4766 proc sha1change {n1 n2 op} {
4767 global sha1string currentid sha1but
4768 if {$sha1string == {}
4769 || ([info exists currentid] && $sha1string == $currentid)} {
4770 set state disabled
4771 } else {
4772 set state normal
4774 if {[$sha1but cget -state] == $state} return
4775 if {$state == "normal"} {
4776 $sha1but conf -state normal -relief raised -text "Goto: "
4777 } else {
4778 $sha1but conf -state disabled -relief flat -text "SHA1 ID: "
4782 proc gotocommit {} {
4783 global sha1string currentid commitrow tagids headids
4784 global displayorder numcommits curview
4786 if {$sha1string == {}
4787 || ([info exists currentid] && $sha1string == $currentid)} return
4788 if {[info exists tagids($sha1string)]} {
4789 set id $tagids($sha1string)
4790 } elseif {[info exists headids($sha1string)]} {
4791 set id $headids($sha1string)
4792 } else {
4793 set id [string tolower $sha1string]
4794 if {[regexp {^[0-9a-f]{4,39}$} $id]} {
4795 set matches {}
4796 foreach i $displayorder {
4797 if {[string match $id* $i]} {
4798 lappend matches $i
4801 if {$matches ne {}} {
4802 if {[llength $matches] > 1} {
4803 error_popup "Short SHA1 id $id is ambiguous"
4804 return
4806 set id [lindex $matches 0]
4810 if {[info exists commitrow($curview,$id)]} {
4811 selectline $commitrow($curview,$id) 1
4812 return
4814 if {[regexp {^[0-9a-fA-F]{4,}$} $sha1string]} {
4815 set type "SHA1 id"
4816 } else {
4817 set type "Tag/Head"
4819 error_popup "$type $sha1string is not known"
4822 proc lineenter {x y id} {
4823 global hoverx hovery hoverid hovertimer
4824 global commitinfo canv
4826 if {![info exists commitinfo($id)] && ![getcommit $id]} return
4827 set hoverx $x
4828 set hovery $y
4829 set hoverid $id
4830 if {[info exists hovertimer]} {
4831 after cancel $hovertimer
4833 set hovertimer [after 500 linehover]
4834 $canv delete hover
4837 proc linemotion {x y id} {
4838 global hoverx hovery hoverid hovertimer
4840 if {[info exists hoverid] && $id == $hoverid} {
4841 set hoverx $x
4842 set hovery $y
4843 if {[info exists hovertimer]} {
4844 after cancel $hovertimer
4846 set hovertimer [after 500 linehover]
4850 proc lineleave {id} {
4851 global hoverid hovertimer canv
4853 if {[info exists hoverid] && $id == $hoverid} {
4854 $canv delete hover
4855 if {[info exists hovertimer]} {
4856 after cancel $hovertimer
4857 unset hovertimer
4859 unset hoverid
4863 proc linehover {} {
4864 global hoverx hovery hoverid hovertimer
4865 global canv linespc lthickness
4866 global commitinfo mainfont
4868 set text [lindex $commitinfo($hoverid) 0]
4869 set ymax [lindex [$canv cget -scrollregion] 3]
4870 if {$ymax == {}} return
4871 set yfrac [lindex [$canv yview] 0]
4872 set x [expr {$hoverx + 2 * $linespc}]
4873 set y [expr {$hovery + $yfrac * $ymax - $linespc / 2}]
4874 set x0 [expr {$x - 2 * $lthickness}]
4875 set y0 [expr {$y - 2 * $lthickness}]
4876 set x1 [expr {$x + [font measure $mainfont $text] + 2 * $lthickness}]
4877 set y1 [expr {$y + $linespc + 2 * $lthickness}]
4878 set t [$canv create rectangle $x0 $y0 $x1 $y1 \
4879 -fill \#ffff80 -outline black -width 1 -tags hover]
4880 $canv raise $t
4881 set t [$canv create text $x $y -anchor nw -text $text -tags hover \
4882 -font $mainfont]
4883 $canv raise $t
4886 proc clickisonarrow {id y} {
4887 global lthickness
4889 set ranges [rowranges $id]
4890 set thresh [expr {2 * $lthickness + 6}]
4891 set n [expr {[llength $ranges] - 1}]
4892 for {set i 1} {$i < $n} {incr i} {
4893 set row [lindex $ranges $i]
4894 if {abs([yc $row] - $y) < $thresh} {
4895 return $i
4898 return {}
4901 proc arrowjump {id n y} {
4902 global canv
4904 # 1 <-> 2, 3 <-> 4, etc...
4905 set n [expr {(($n - 1) ^ 1) + 1}]
4906 set row [lindex [rowranges $id] $n]
4907 set yt [yc $row]
4908 set ymax [lindex [$canv cget -scrollregion] 3]
4909 if {$ymax eq {} || $ymax <= 0} return
4910 set view [$canv yview]
4911 set yspan [expr {[lindex $view 1] - [lindex $view 0]}]
4912 set yfrac [expr {$yt / $ymax - $yspan / 2}]
4913 if {$yfrac < 0} {
4914 set yfrac 0
4916 allcanvs yview moveto $yfrac
4919 proc lineclick {x y id isnew} {
4920 global ctext commitinfo children canv thickerline curview
4922 if {![info exists commitinfo($id)] && ![getcommit $id]} return
4923 unmarkmatches
4924 unselectline
4925 normalline
4926 $canv delete hover
4927 # draw this line thicker than normal
4928 set thickerline $id
4929 drawlines $id
4930 if {$isnew} {
4931 set ymax [lindex [$canv cget -scrollregion] 3]
4932 if {$ymax eq {}} return
4933 set yfrac [lindex [$canv yview] 0]
4934 set y [expr {$y + $yfrac * $ymax}]
4936 set dirn [clickisonarrow $id $y]
4937 if {$dirn ne {}} {
4938 arrowjump $id $dirn $y
4939 return
4942 if {$isnew} {
4943 addtohistory [list lineclick $x $y $id 0]
4945 # fill the details pane with info about this line
4946 $ctext conf -state normal
4947 clear_ctext
4948 $ctext tag conf link -foreground blue -underline 1
4949 $ctext tag bind link <Enter> { %W configure -cursor hand2 }
4950 $ctext tag bind link <Leave> { %W configure -cursor $curtextcursor }
4951 $ctext insert end "Parent:\t"
4952 $ctext insert end $id [list link link0]
4953 $ctext tag bind link0 <1> [list selbyid $id]
4954 set info $commitinfo($id)
4955 $ctext insert end "\n\t[lindex $info 0]\n"
4956 $ctext insert end "\tAuthor:\t[lindex $info 1]\n"
4957 set date [formatdate [lindex $info 2]]
4958 $ctext insert end "\tDate:\t$date\n"
4959 set kids $children($curview,$id)
4960 if {$kids ne {}} {
4961 $ctext insert end "\nChildren:"
4962 set i 0
4963 foreach child $kids {
4964 incr i
4965 if {![info exists commitinfo($child)] && ![getcommit $child]} continue
4966 set info $commitinfo($child)
4967 $ctext insert end "\n\t"
4968 $ctext insert end $child [list link link$i]
4969 $ctext tag bind link$i <1> [list selbyid $child]
4970 $ctext insert end "\n\t[lindex $info 0]"
4971 $ctext insert end "\n\tAuthor:\t[lindex $info 1]"
4972 set date [formatdate [lindex $info 2]]
4973 $ctext insert end "\n\tDate:\t$date\n"
4976 $ctext conf -state disabled
4977 init_flist {}
4980 proc normalline {} {
4981 global thickerline
4982 if {[info exists thickerline]} {
4983 set id $thickerline
4984 unset thickerline
4985 drawlines $id
4989 proc selbyid {id} {
4990 global commitrow curview
4991 if {[info exists commitrow($curview,$id)]} {
4992 selectline $commitrow($curview,$id) 1
4996 proc mstime {} {
4997 global startmstime
4998 if {![info exists startmstime]} {
4999 set startmstime [clock clicks -milliseconds]
5001 return [format "%.3f" [expr {([clock click -milliseconds] - $startmstime) / 1000.0}]]
5004 proc rowmenu {x y id} {
5005 global rowctxmenu commitrow selectedline rowmenuid curview
5007 if {![info exists selectedline]
5008 || $commitrow($curview,$id) eq $selectedline} {
5009 set state disabled
5010 } else {
5011 set state normal
5013 $rowctxmenu entryconfigure "Diff this*" -state $state
5014 $rowctxmenu entryconfigure "Diff selected*" -state $state
5015 $rowctxmenu entryconfigure "Make patch" -state $state
5016 set rowmenuid $id
5017 tk_popup $rowctxmenu $x $y
5020 proc diffvssel {dirn} {
5021 global rowmenuid selectedline displayorder
5023 if {![info exists selectedline]} return
5024 if {$dirn} {
5025 set oldid [lindex $displayorder $selectedline]
5026 set newid $rowmenuid
5027 } else {
5028 set oldid $rowmenuid
5029 set newid [lindex $displayorder $selectedline]
5031 addtohistory [list doseldiff $oldid $newid]
5032 doseldiff $oldid $newid
5035 proc doseldiff {oldid newid} {
5036 global ctext
5037 global commitinfo
5039 $ctext conf -state normal
5040 clear_ctext
5041 init_flist "Top"
5042 $ctext insert end "From "
5043 $ctext tag conf link -foreground blue -underline 1
5044 $ctext tag bind link <Enter> { %W configure -cursor hand2 }
5045 $ctext tag bind link <Leave> { %W configure -cursor $curtextcursor }
5046 $ctext tag bind link0 <1> [list selbyid $oldid]
5047 $ctext insert end $oldid [list link link0]
5048 $ctext insert end "\n "
5049 $ctext insert end [lindex $commitinfo($oldid) 0]
5050 $ctext insert end "\n\nTo "
5051 $ctext tag bind link1 <1> [list selbyid $newid]
5052 $ctext insert end $newid [list link link1]
5053 $ctext insert end "\n "
5054 $ctext insert end [lindex $commitinfo($newid) 0]
5055 $ctext insert end "\n"
5056 $ctext conf -state disabled
5057 $ctext tag delete Comments
5058 $ctext tag remove found 1.0 end
5059 startdiff [list $oldid $newid]
5062 proc mkpatch {} {
5063 global rowmenuid currentid commitinfo patchtop patchnum
5065 if {![info exists currentid]} return
5066 set oldid $currentid
5067 set oldhead [lindex $commitinfo($oldid) 0]
5068 set newid $rowmenuid
5069 set newhead [lindex $commitinfo($newid) 0]
5070 set top .patch
5071 set patchtop $top
5072 catch {destroy $top}
5073 toplevel $top
5074 label $top.title -text "Generate patch"
5075 grid $top.title - -pady 10
5076 label $top.from -text "From:"
5077 entry $top.fromsha1 -width 40 -relief flat
5078 $top.fromsha1 insert 0 $oldid
5079 $top.fromsha1 conf -state readonly
5080 grid $top.from $top.fromsha1 -sticky w
5081 entry $top.fromhead -width 60 -relief flat
5082 $top.fromhead insert 0 $oldhead
5083 $top.fromhead conf -state readonly
5084 grid x $top.fromhead -sticky w
5085 label $top.to -text "To:"
5086 entry $top.tosha1 -width 40 -relief flat
5087 $top.tosha1 insert 0 $newid
5088 $top.tosha1 conf -state readonly
5089 grid $top.to $top.tosha1 -sticky w
5090 entry $top.tohead -width 60 -relief flat
5091 $top.tohead insert 0 $newhead
5092 $top.tohead conf -state readonly
5093 grid x $top.tohead -sticky w
5094 button $top.rev -text "Reverse" -command mkpatchrev -padx 5
5095 grid $top.rev x -pady 10
5096 label $top.flab -text "Output file:"
5097 entry $top.fname -width 60
5098 $top.fname insert 0 [file normalize "patch$patchnum.patch"]
5099 incr patchnum
5100 grid $top.flab $top.fname -sticky w
5101 frame $top.buts
5102 button $top.buts.gen -text "Generate" -command mkpatchgo
5103 button $top.buts.can -text "Cancel" -command mkpatchcan
5104 grid $top.buts.gen $top.buts.can
5105 grid columnconfigure $top.buts 0 -weight 1 -uniform a
5106 grid columnconfigure $top.buts 1 -weight 1 -uniform a
5107 grid $top.buts - -pady 10 -sticky ew
5108 focus $top.fname
5111 proc mkpatchrev {} {
5112 global patchtop
5114 set oldid [$patchtop.fromsha1 get]
5115 set oldhead [$patchtop.fromhead get]
5116 set newid [$patchtop.tosha1 get]
5117 set newhead [$patchtop.tohead get]
5118 foreach e [list fromsha1 fromhead tosha1 tohead] \
5119 v [list $newid $newhead $oldid $oldhead] {
5120 $patchtop.$e conf -state normal
5121 $patchtop.$e delete 0 end
5122 $patchtop.$e insert 0 $v
5123 $patchtop.$e conf -state readonly
5127 proc mkpatchgo {} {
5128 global patchtop
5130 set oldid [$patchtop.fromsha1 get]
5131 set newid [$patchtop.tosha1 get]
5132 set fname [$patchtop.fname get]
5133 if {[catch {exec git diff-tree -p $oldid $newid >$fname &} err]} {
5134 error_popup "Error creating patch: $err"
5136 catch {destroy $patchtop}
5137 unset patchtop
5140 proc mkpatchcan {} {
5141 global patchtop
5143 catch {destroy $patchtop}
5144 unset patchtop
5147 proc mktag {} {
5148 global rowmenuid mktagtop commitinfo
5150 set top .maketag
5151 set mktagtop $top
5152 catch {destroy $top}
5153 toplevel $top
5154 label $top.title -text "Create tag"
5155 grid $top.title - -pady 10
5156 label $top.id -text "ID:"
5157 entry $top.sha1 -width 40 -relief flat
5158 $top.sha1 insert 0 $rowmenuid
5159 $top.sha1 conf -state readonly
5160 grid $top.id $top.sha1 -sticky w
5161 entry $top.head -width 60 -relief flat
5162 $top.head insert 0 [lindex $commitinfo($rowmenuid) 0]
5163 $top.head conf -state readonly
5164 grid x $top.head -sticky w
5165 label $top.tlab -text "Tag name:"
5166 entry $top.tag -width 60
5167 grid $top.tlab $top.tag -sticky w
5168 frame $top.buts
5169 button $top.buts.gen -text "Create" -command mktaggo
5170 button $top.buts.can -text "Cancel" -command mktagcan
5171 grid $top.buts.gen $top.buts.can
5172 grid columnconfigure $top.buts 0 -weight 1 -uniform a
5173 grid columnconfigure $top.buts 1 -weight 1 -uniform a
5174 grid $top.buts - -pady 10 -sticky ew
5175 focus $top.tag
5178 proc domktag {} {
5179 global mktagtop env tagids idtags
5181 set id [$mktagtop.sha1 get]
5182 set tag [$mktagtop.tag get]
5183 if {$tag == {}} {
5184 error_popup "No tag name specified"
5185 return
5187 if {[info exists tagids($tag)]} {
5188 error_popup "Tag \"$tag\" already exists"
5189 return
5191 if {[catch {
5192 set dir [gitdir]
5193 set fname [file join $dir "refs/tags" $tag]
5194 set f [open $fname w]
5195 puts $f $id
5196 close $f
5197 } err]} {
5198 error_popup "Error creating tag: $err"
5199 return
5202 set tagids($tag) $id
5203 lappend idtags($id) $tag
5204 redrawtags $id
5205 addedtag $id
5208 proc redrawtags {id} {
5209 global canv linehtag commitrow idpos selectedline curview
5210 global mainfont canvxmax
5212 if {![info exists commitrow($curview,$id)]} return
5213 drawcmitrow $commitrow($curview,$id)
5214 $canv delete tag.$id
5215 set xt [eval drawtags $id $idpos($id)]
5216 $canv coords $linehtag($commitrow($curview,$id)) $xt [lindex $idpos($id) 2]
5217 set text [$canv itemcget $linehtag($commitrow($curview,$id)) -text]
5218 set xr [expr {$xt + [font measure $mainfont $text]}]
5219 if {$xr > $canvxmax} {
5220 set canvxmax $xr
5221 setcanvscroll
5223 if {[info exists selectedline]
5224 && $selectedline == $commitrow($curview,$id)} {
5225 selectline $selectedline 0
5229 proc mktagcan {} {
5230 global mktagtop
5232 catch {destroy $mktagtop}
5233 unset mktagtop
5236 proc mktaggo {} {
5237 domktag
5238 mktagcan
5241 proc writecommit {} {
5242 global rowmenuid wrcomtop commitinfo wrcomcmd
5244 set top .writecommit
5245 set wrcomtop $top
5246 catch {destroy $top}
5247 toplevel $top
5248 label $top.title -text "Write commit to file"
5249 grid $top.title - -pady 10
5250 label $top.id -text "ID:"
5251 entry $top.sha1 -width 40 -relief flat
5252 $top.sha1 insert 0 $rowmenuid
5253 $top.sha1 conf -state readonly
5254 grid $top.id $top.sha1 -sticky w
5255 entry $top.head -width 60 -relief flat
5256 $top.head insert 0 [lindex $commitinfo($rowmenuid) 0]
5257 $top.head conf -state readonly
5258 grid x $top.head -sticky w
5259 label $top.clab -text "Command:"
5260 entry $top.cmd -width 60 -textvariable wrcomcmd
5261 grid $top.clab $top.cmd -sticky w -pady 10
5262 label $top.flab -text "Output file:"
5263 entry $top.fname -width 60
5264 $top.fname insert 0 [file normalize "commit-[string range $rowmenuid 0 6]"]
5265 grid $top.flab $top.fname -sticky w
5266 frame $top.buts
5267 button $top.buts.gen -text "Write" -command wrcomgo
5268 button $top.buts.can -text "Cancel" -command wrcomcan
5269 grid $top.buts.gen $top.buts.can
5270 grid columnconfigure $top.buts 0 -weight 1 -uniform a
5271 grid columnconfigure $top.buts 1 -weight 1 -uniform a
5272 grid $top.buts - -pady 10 -sticky ew
5273 focus $top.fname
5276 proc wrcomgo {} {
5277 global wrcomtop
5279 set id [$wrcomtop.sha1 get]
5280 set cmd "echo $id | [$wrcomtop.cmd get]"
5281 set fname [$wrcomtop.fname get]
5282 if {[catch {exec sh -c $cmd >$fname &} err]} {
5283 error_popup "Error writing commit: $err"
5285 catch {destroy $wrcomtop}
5286 unset wrcomtop
5289 proc wrcomcan {} {
5290 global wrcomtop
5292 catch {destroy $wrcomtop}
5293 unset wrcomtop
5296 proc mkbranch {} {
5297 global rowmenuid mkbrtop
5299 set top .makebranch
5300 catch {destroy $top}
5301 toplevel $top
5302 label $top.title -text "Create new branch"
5303 grid $top.title - -pady 10
5304 label $top.id -text "ID:"
5305 entry $top.sha1 -width 40 -relief flat
5306 $top.sha1 insert 0 $rowmenuid
5307 $top.sha1 conf -state readonly
5308 grid $top.id $top.sha1 -sticky w
5309 label $top.nlab -text "Name:"
5310 entry $top.name -width 40
5311 grid $top.nlab $top.name -sticky w
5312 frame $top.buts
5313 button $top.buts.go -text "Create" -command [list mkbrgo $top]
5314 button $top.buts.can -text "Cancel" -command "catch {destroy $top}"
5315 grid $top.buts.go $top.buts.can
5316 grid columnconfigure $top.buts 0 -weight 1 -uniform a
5317 grid columnconfigure $top.buts 1 -weight 1 -uniform a
5318 grid $top.buts - -pady 10 -sticky ew
5319 focus $top.name
5322 proc mkbrgo {top} {
5323 global headids idheads
5325 set name [$top.name get]
5326 set id [$top.sha1 get]
5327 if {$name eq {}} {
5328 error_popup "Please specify a name for the new branch"
5329 return
5331 catch {destroy $top}
5332 nowbusy newbranch
5333 update
5334 if {[catch {
5335 exec git branch $name $id
5336 } err]} {
5337 notbusy newbranch
5338 error_popup $err
5339 } else {
5340 set headids($name) $id
5341 lappend idheads($id) $name
5342 addedhead $id $name
5343 notbusy newbranch
5344 redrawtags $id
5345 dispneartags 0
5349 proc cherrypick {} {
5350 global rowmenuid curview commitrow
5351 global mainhead
5353 set oldhead [exec git rev-parse HEAD]
5354 set dheads [descheads $rowmenuid]
5355 if {$dheads ne {} && [lsearch -exact $dheads $oldhead] >= 0} {
5356 set ok [confirm_popup "Commit [string range $rowmenuid 0 7] is already\
5357 included in branch $mainhead -- really re-apply it?"]
5358 if {!$ok} return
5360 nowbusy cherrypick
5361 update
5362 # Unfortunately git-cherry-pick writes stuff to stderr even when
5363 # no error occurs, and exec takes that as an indication of error...
5364 if {[catch {exec sh -c "git cherry-pick -r $rowmenuid 2>&1"} err]} {
5365 notbusy cherrypick
5366 error_popup $err
5367 return
5369 set newhead [exec git rev-parse HEAD]
5370 if {$newhead eq $oldhead} {
5371 notbusy cherrypick
5372 error_popup "No changes committed"
5373 return
5375 addnewchild $newhead $oldhead
5376 if {[info exists commitrow($curview,$oldhead)]} {
5377 insertrow $commitrow($curview,$oldhead) $newhead
5378 if {$mainhead ne {}} {
5379 movehead $newhead $mainhead
5380 movedhead $newhead $mainhead
5382 redrawtags $oldhead
5383 redrawtags $newhead
5385 notbusy cherrypick
5388 # context menu for a head
5389 proc headmenu {x y id head} {
5390 global headmenuid headmenuhead headctxmenu
5392 set headmenuid $id
5393 set headmenuhead $head
5394 tk_popup $headctxmenu $x $y
5397 proc cobranch {} {
5398 global headmenuid headmenuhead mainhead headids
5400 # check the tree is clean first??
5401 set oldmainhead $mainhead
5402 nowbusy checkout
5403 update
5404 if {[catch {
5405 exec git checkout -q $headmenuhead
5406 } err]} {
5407 notbusy checkout
5408 error_popup $err
5409 } else {
5410 notbusy checkout
5411 set mainhead $headmenuhead
5412 if {[info exists headids($oldmainhead)]} {
5413 redrawtags $headids($oldmainhead)
5415 redrawtags $headmenuid
5419 proc rmbranch {} {
5420 global headmenuid headmenuhead mainhead
5421 global headids idheads
5423 set head $headmenuhead
5424 set id $headmenuid
5425 if {$head eq $mainhead} {
5426 error_popup "Cannot delete the currently checked-out branch"
5427 return
5429 set dheads [descheads $id]
5430 if {$dheads eq $headids($head)} {
5431 # the stuff on this branch isn't on any other branch
5432 if {![confirm_popup "The commits on branch $head aren't on any other\
5433 branch.\nReally delete branch $head?"]} return
5435 nowbusy rmbranch
5436 update
5437 if {[catch {exec git branch -D $head} err]} {
5438 notbusy rmbranch
5439 error_popup $err
5440 return
5442 removehead $id $head
5443 removedhead $id $head
5444 redrawtags $id
5445 notbusy rmbranch
5446 dispneartags 0
5449 # Stuff for finding nearby tags
5450 proc getallcommits {} {
5451 global allcommits allids nbmp nextarc seeds
5453 set allids {}
5454 set nbmp 0
5455 set nextarc 0
5456 set allcommits 0
5457 set seeds {}
5458 regetallcommits
5461 # Called when the graph might have changed
5462 proc regetallcommits {} {
5463 global allcommits seeds
5465 set cmd [concat | git rev-list --all --parents]
5466 foreach id $seeds {
5467 lappend cmd "^$id"
5469 set fd [open $cmd r]
5470 fconfigure $fd -blocking 0
5471 incr allcommits
5472 nowbusy allcommits
5473 restartgetall $fd
5476 proc restartgetall {fd} {
5477 fileevent $fd readable [list getallclines $fd]
5480 # Since most commits have 1 parent and 1 child, we group strings of
5481 # such commits into "arcs" joining branch/merge points (BMPs), which
5482 # are commits that either don't have 1 parent or don't have 1 child.
5484 # arcnos(id) - incoming arcs for BMP, arc we're on for other nodes
5485 # arcout(id) - outgoing arcs for BMP
5486 # arcids(a) - list of IDs on arc including end but not start
5487 # arcstart(a) - BMP ID at start of arc
5488 # arcend(a) - BMP ID at end of arc
5489 # growing(a) - arc a is still growing
5490 # arctags(a) - IDs out of arcids (excluding end) that have tags
5491 # archeads(a) - IDs out of arcids (excluding end) that have heads
5492 # The start of an arc is at the descendent end, so "incoming" means
5493 # coming from descendents, and "outgoing" means going towards ancestors.
5495 proc getallclines {fd} {
5496 global allids allparents allchildren idtags nextarc nbmp
5497 global arcnos arcids arctags arcout arcend arcstart archeads growing
5498 global seeds allcommits allcstart
5500 if {![info exists allcstart]} {
5501 set allcstart [clock clicks -milliseconds]
5503 set nid 0
5504 while {[gets $fd line] >= 0} {
5505 set id [lindex $line 0]
5506 if {[info exists allparents($id)]} {
5507 # seen it already
5508 continue
5510 lappend allids $id
5511 set olds [lrange $line 1 end]
5512 set allparents($id) $olds
5513 if {![info exists allchildren($id)]} {
5514 set allchildren($id) {}
5515 set arcnos($id) {}
5516 lappend seeds $id
5517 } else {
5518 set a $arcnos($id)
5519 if {[llength $olds] == 1 && [llength $a] == 1} {
5520 lappend arcids($a) $id
5521 if {[info exists idtags($id)]} {
5522 lappend arctags($a) $id
5524 if {[info exists idheads($id)]} {
5525 lappend archeads($a) $id
5527 if {[info exists allparents($olds)]} {
5528 # seen parent already
5529 if {![info exists arcout($olds)]} {
5530 splitarc $olds
5532 lappend arcids($a) $olds
5533 set arcend($a) $olds
5534 unset growing($a)
5536 lappend allchildren($olds) $id
5537 lappend arcnos($olds) $a
5538 continue
5541 incr nbmp
5542 foreach a $arcnos($id) {
5543 lappend arcids($a) $id
5544 set arcend($a) $id
5545 unset growing($a)
5548 set ao {}
5549 foreach p $olds {
5550 lappend allchildren($p) $id
5551 set a [incr nextarc]
5552 set arcstart($a) $id
5553 set archeads($a) {}
5554 set arctags($a) {}
5555 set archeads($a) {}
5556 set arcids($a) {}
5557 lappend ao $a
5558 set growing($a) 1
5559 if {[info exists allparents($p)]} {
5560 # seen it already, may need to make a new branch
5561 if {![info exists arcout($p)]} {
5562 splitarc $p
5564 lappend arcids($a) $p
5565 set arcend($a) $p
5566 unset growing($a)
5568 lappend arcnos($p) $a
5570 set arcout($id) $ao
5571 if {[incr nid] >= 50} {
5572 set nid 0
5573 if {[clock clicks -milliseconds] - $allcstart >= 50} {
5574 fileevent $fd readable {}
5575 after idle restartgetall $fd
5576 unset allcstart
5577 return
5581 if {![eof $fd]} return
5582 close $fd
5583 if {[incr allcommits -1] == 0} {
5584 notbusy allcommits
5586 dispneartags 0
5589 proc recalcarc {a} {
5590 global arctags archeads arcids idtags idheads
5592 set at {}
5593 set ah {}
5594 foreach id [lrange $arcids($a) 0 end-1] {
5595 if {[info exists idtags($id)]} {
5596 lappend at $id
5598 if {[info exists idheads($id)]} {
5599 lappend ah $id
5602 set arctags($a) $at
5603 set archeads($a) $ah
5606 proc splitarc {p} {
5607 global arcnos arcids nextarc nbmp arctags archeads idtags idheads
5608 global arcstart arcend arcout allparents growing
5610 set a $arcnos($p)
5611 if {[llength $a] != 1} {
5612 puts "oops splitarc called but [llength $a] arcs already"
5613 return
5615 set a [lindex $a 0]
5616 set i [lsearch -exact $arcids($a) $p]
5617 if {$i < 0} {
5618 puts "oops splitarc $p not in arc $a"
5619 return
5621 set na [incr nextarc]
5622 if {[info exists arcend($a)]} {
5623 set arcend($na) $arcend($a)
5624 } else {
5625 set l [lindex $allparents([lindex $arcids($a) end]) 0]
5626 set j [lsearch -exact $arcnos($l) $a]
5627 set arcnos($l) [lreplace $arcnos($l) $j $j $na]
5629 set tail [lrange $arcids($a) [expr {$i+1}] end]
5630 set arcids($a) [lrange $arcids($a) 0 $i]
5631 set arcend($a) $p
5632 set arcstart($na) $p
5633 set arcout($p) $na
5634 set arcids($na) $tail
5635 if {[info exists growing($a)]} {
5636 set growing($na) 1
5637 unset growing($a)
5639 incr nbmp
5641 foreach id $tail {
5642 if {[llength $arcnos($id)] == 1} {
5643 set arcnos($id) $na
5644 } else {
5645 set j [lsearch -exact $arcnos($id) $a]
5646 set arcnos($id) [lreplace $arcnos($id) $j $j $na]
5650 # reconstruct tags and heads lists
5651 if {$arctags($a) ne {} || $archeads($a) ne {}} {
5652 recalcarc $a
5653 recalcarc $na
5654 } else {
5655 set arctags($na) {}
5656 set archeads($na) {}
5660 # Update things for a new commit added that is a child of one
5661 # existing commit. Used when cherry-picking.
5662 proc addnewchild {id p} {
5663 global allids allparents allchildren idtags nextarc nbmp
5664 global arcnos arcids arctags arcout arcend arcstart archeads growing
5665 global seeds
5667 lappend allids $id
5668 set allparents($id) [list $p]
5669 set allchildren($id) {}
5670 set arcnos($id) {}
5671 lappend seeds $id
5672 incr nbmp
5673 lappend allchildren($p) $id
5674 set a [incr nextarc]
5675 set arcstart($a) $id
5676 set archeads($a) {}
5677 set arctags($a) {}
5678 set arcids($a) [list $p]
5679 set arcend($a) $p
5680 if {![info exists arcout($p)]} {
5681 splitarc $p
5683 lappend arcnos($p) $a
5684 set arcout($id) [list $a]
5687 # Returns 1 if a is an ancestor of b, -1 if b is an ancestor of a,
5688 # or 0 if neither is true.
5689 proc anc_or_desc {a b} {
5690 global arcout arcstart arcend arcnos cached_isanc
5692 if {$arcnos($a) eq $arcnos($b)} {
5693 # Both are on the same arc(s); either both are the same BMP,
5694 # or if one is not a BMP, the other is also not a BMP or is
5695 # the BMP at end of the arc (and it only has 1 incoming arc).
5696 if {$a eq $b} {
5697 return 0
5699 # assert {[llength $arcnos($a)] == 1}
5700 set arc [lindex $arcnos($a) 0]
5701 set i [lsearch -exact $arcids($arc) $a]
5702 set j [lsearch -exact $arcids($arc) $b]
5703 if {$i < 0 || $i > $j} {
5704 return 1
5705 } else {
5706 return -1
5710 if {![info exists arcout($a)]} {
5711 set arc [lindex $arcnos($a) 0]
5712 if {[info exists arcend($arc)]} {
5713 set aend $arcend($arc)
5714 } else {
5715 set aend {}
5717 set a $arcstart($arc)
5718 } else {
5719 set aend $a
5721 if {![info exists arcout($b)]} {
5722 set arc [lindex $arcnos($b) 0]
5723 if {[info exists arcend($arc)]} {
5724 set bend $arcend($arc)
5725 } else {
5726 set bend {}
5728 set b $arcstart($arc)
5729 } else {
5730 set bend $b
5732 if {$a eq $bend} {
5733 return 1
5735 if {$b eq $aend} {
5736 return -1
5738 if {[info exists cached_isanc($a,$bend)]} {
5739 if {$cached_isanc($a,$bend)} {
5740 return 1
5743 if {[info exists cached_isanc($b,$aend)]} {
5744 if {$cached_isanc($b,$aend)} {
5745 return -1
5747 if {[info exists cached_isanc($a,$bend)]} {
5748 return 0
5752 set todo [list $a $b]
5753 set anc($a) a
5754 set anc($b) b
5755 for {set i 0} {$i < [llength $todo]} {incr i} {
5756 set x [lindex $todo $i]
5757 if {$anc($x) eq {}} {
5758 continue
5760 foreach arc $arcnos($x) {
5761 set xd $arcstart($arc)
5762 if {$xd eq $bend} {
5763 set cached_isanc($a,$bend) 1
5764 set cached_isanc($b,$aend) 0
5765 return 1
5766 } elseif {$xd eq $aend} {
5767 set cached_isanc($b,$aend) 1
5768 set cached_isanc($a,$bend) 0
5769 return -1
5771 if {![info exists anc($xd)]} {
5772 set anc($xd) $anc($x)
5773 lappend todo $xd
5774 } elseif {$anc($xd) ne $anc($x)} {
5775 set anc($xd) {}
5779 set cached_isanc($a,$bend) 0
5780 set cached_isanc($b,$aend) 0
5781 return 0
5784 # This identifies whether $desc has an ancestor that is
5785 # a growing tip of the graph and which is not an ancestor of $anc
5786 # and returns 0 if so and 1 if not.
5787 # If we subsequently discover a tag on such a growing tip, and that
5788 # turns out to be a descendent of $anc (which it could, since we
5789 # don't necessarily see children before parents), then $desc
5790 # isn't a good choice to display as a descendent tag of
5791 # $anc (since it is the descendent of another tag which is
5792 # a descendent of $anc). Similarly, $anc isn't a good choice to
5793 # display as a ancestor tag of $desc.
5795 proc is_certain {desc anc} {
5796 global arcnos arcout arcstart arcend growing problems
5798 set certain {}
5799 if {[llength $arcnos($anc)] == 1} {
5800 # tags on the same arc are certain
5801 if {$arcnos($desc) eq $arcnos($anc)} {
5802 return 1
5804 if {![info exists arcout($anc)]} {
5805 # if $anc is partway along an arc, use the start of the arc instead
5806 set a [lindex $arcnos($anc) 0]
5807 set anc $arcstart($a)
5810 if {[llength $arcnos($desc)] > 1 || [info exists arcout($desc)]} {
5811 set x $desc
5812 } else {
5813 set a [lindex $arcnos($desc) 0]
5814 set x $arcend($a)
5816 if {$x == $anc} {
5817 return 1
5819 set anclist [list $x]
5820 set dl($x) 1
5821 set nnh 1
5822 set ngrowanc 0
5823 for {set i 0} {$i < [llength $anclist] && ($nnh > 0 || $ngrowanc > 0)} {incr i} {
5824 set x [lindex $anclist $i]
5825 if {$dl($x)} {
5826 incr nnh -1
5828 set done($x) 1
5829 foreach a $arcout($x) {
5830 if {[info exists growing($a)]} {
5831 if {![info exists growanc($x)] && $dl($x)} {
5832 set growanc($x) 1
5833 incr ngrowanc
5835 } else {
5836 set y $arcend($a)
5837 if {[info exists dl($y)]} {
5838 if {$dl($y)} {
5839 if {!$dl($x)} {
5840 set dl($y) 0
5841 if {![info exists done($y)]} {
5842 incr nnh -1
5844 if {[info exists growanc($x)]} {
5845 incr ngrowanc -1
5847 set xl [list $y]
5848 for {set k 0} {$k < [llength $xl]} {incr k} {
5849 set z [lindex $xl $k]
5850 foreach c $arcout($z) {
5851 if {[info exists arcend($c)]} {
5852 set v $arcend($c)
5853 if {[info exists dl($v)] && $dl($v)} {
5854 set dl($v) 0
5855 if {![info exists done($v)]} {
5856 incr nnh -1
5858 if {[info exists growanc($v)]} {
5859 incr ngrowanc -1
5861 lappend xl $v
5868 } elseif {$y eq $anc || !$dl($x)} {
5869 set dl($y) 0
5870 lappend anclist $y
5871 } else {
5872 set dl($y) 1
5873 lappend anclist $y
5874 incr nnh
5879 foreach x [array names growanc] {
5880 if {$dl($x)} {
5881 return 0
5884 return 1
5887 proc validate_arctags {a} {
5888 global arctags idtags
5890 set i -1
5891 set na $arctags($a)
5892 foreach id $arctags($a) {
5893 incr i
5894 if {![info exists idtags($id)]} {
5895 set na [lreplace $na $i $i]
5896 incr i -1
5899 set arctags($a) $na
5902 proc validate_archeads {a} {
5903 global archeads idheads
5905 set i -1
5906 set na $archeads($a)
5907 foreach id $archeads($a) {
5908 incr i
5909 if {![info exists idheads($id)]} {
5910 set na [lreplace $na $i $i]
5911 incr i -1
5914 set archeads($a) $na
5917 # Return the list of IDs that have tags that are descendents of id,
5918 # ignoring IDs that are descendents of IDs already reported.
5919 proc desctags {id} {
5920 global arcnos arcstart arcids arctags idtags allparents
5921 global growing cached_dtags
5923 if {![info exists allparents($id)]} {
5924 return {}
5926 set t1 [clock clicks -milliseconds]
5927 set argid $id
5928 if {[llength $arcnos($id)] == 1 && [llength $allparents($id)] == 1} {
5929 # part-way along an arc; check that arc first
5930 set a [lindex $arcnos($id) 0]
5931 if {$arctags($a) ne {}} {
5932 validate_arctags $a
5933 set i [lsearch -exact $arcids($a) $id]
5934 set tid {}
5935 foreach t $arctags($a) {
5936 set j [lsearch -exact $arcids($a) $t]
5937 if {$j >= $i} break
5938 set tid $t
5940 if {$tid ne {}} {
5941 return $tid
5944 set id $arcstart($a)
5945 if {[info exists idtags($id)]} {
5946 return $id
5949 if {[info exists cached_dtags($id)]} {
5950 return $cached_dtags($id)
5953 set origid $id
5954 set todo [list $id]
5955 set queued($id) 1
5956 set nc 1
5957 for {set i 0} {$i < [llength $todo] && $nc > 0} {incr i} {
5958 set id [lindex $todo $i]
5959 set done($id) 1
5960 set ta [info exists hastaggedancestor($id)]
5961 if {!$ta} {
5962 incr nc -1
5964 # ignore tags on starting node
5965 if {!$ta && $i > 0} {
5966 if {[info exists idtags($id)]} {
5967 set tagloc($id) $id
5968 set ta 1
5969 } elseif {[info exists cached_dtags($id)]} {
5970 set tagloc($id) $cached_dtags($id)
5971 set ta 1
5974 foreach a $arcnos($id) {
5975 set d $arcstart($a)
5976 if {!$ta && $arctags($a) ne {}} {
5977 validate_arctags $a
5978 if {$arctags($a) ne {}} {
5979 lappend tagloc($id) [lindex $arctags($a) end]
5982 if {$ta || $arctags($a) ne {}} {
5983 set tomark [list $d]
5984 for {set j 0} {$j < [llength $tomark]} {incr j} {
5985 set dd [lindex $tomark $j]
5986 if {![info exists hastaggedancestor($dd)]} {
5987 if {[info exists done($dd)]} {
5988 foreach b $arcnos($dd) {
5989 lappend tomark $arcstart($b)
5991 if {[info exists tagloc($dd)]} {
5992 unset tagloc($dd)
5994 } elseif {[info exists queued($dd)]} {
5995 incr nc -1
5997 set hastaggedancestor($dd) 1
6001 if {![info exists queued($d)]} {
6002 lappend todo $d
6003 set queued($d) 1
6004 if {![info exists hastaggedancestor($d)]} {
6005 incr nc
6010 set tags {}
6011 foreach id [array names tagloc] {
6012 if {![info exists hastaggedancestor($id)]} {
6013 foreach t $tagloc($id) {
6014 if {[lsearch -exact $tags $t] < 0} {
6015 lappend tags $t
6020 set t2 [clock clicks -milliseconds]
6021 set loopix $i
6023 # remove tags that are descendents of other tags
6024 for {set i 0} {$i < [llength $tags]} {incr i} {
6025 set a [lindex $tags $i]
6026 for {set j 0} {$j < $i} {incr j} {
6027 set b [lindex $tags $j]
6028 set r [anc_or_desc $a $b]
6029 if {$r == 1} {
6030 set tags [lreplace $tags $j $j]
6031 incr j -1
6032 incr i -1
6033 } elseif {$r == -1} {
6034 set tags [lreplace $tags $i $i]
6035 incr i -1
6036 break
6041 if {[array names growing] ne {}} {
6042 # graph isn't finished, need to check if any tag could get
6043 # eclipsed by another tag coming later. Simply ignore any
6044 # tags that could later get eclipsed.
6045 set ctags {}
6046 foreach t $tags {
6047 if {[is_certain $t $origid]} {
6048 lappend ctags $t
6051 if {$tags eq $ctags} {
6052 set cached_dtags($origid) $tags
6053 } else {
6054 set tags $ctags
6056 } else {
6057 set cached_dtags($origid) $tags
6059 set t3 [clock clicks -milliseconds]
6060 if {0 && $t3 - $t1 >= 100} {
6061 puts "iterating descendents ($loopix/[llength $todo] nodes) took\
6062 [expr {$t2-$t1}]+[expr {$t3-$t2}]ms, $nc candidates left"
6064 return $tags
6067 proc anctags {id} {
6068 global arcnos arcids arcout arcend arctags idtags allparents
6069 global growing cached_atags
6071 if {![info exists allparents($id)]} {
6072 return {}
6074 set t1 [clock clicks -milliseconds]
6075 set argid $id
6076 if {[llength $arcnos($id)] == 1 && [llength $allparents($id)] == 1} {
6077 # part-way along an arc; check that arc first
6078 set a [lindex $arcnos($id) 0]
6079 if {$arctags($a) ne {}} {
6080 validate_arctags $a
6081 set i [lsearch -exact $arcids($a) $id]
6082 foreach t $arctags($a) {
6083 set j [lsearch -exact $arcids($a) $t]
6084 if {$j > $i} {
6085 return $t
6089 if {![info exists arcend($a)]} {
6090 return {}
6092 set id $arcend($a)
6093 if {[info exists idtags($id)]} {
6094 return $id
6097 if {[info exists cached_atags($id)]} {
6098 return $cached_atags($id)
6101 set origid $id
6102 set todo [list $id]
6103 set queued($id) 1
6104 set taglist {}
6105 set nc 1
6106 for {set i 0} {$i < [llength $todo] && $nc > 0} {incr i} {
6107 set id [lindex $todo $i]
6108 set done($id) 1
6109 set td [info exists hastaggeddescendent($id)]
6110 if {!$td} {
6111 incr nc -1
6113 # ignore tags on starting node
6114 if {!$td && $i > 0} {
6115 if {[info exists idtags($id)]} {
6116 set tagloc($id) $id
6117 set td 1
6118 } elseif {[info exists cached_atags($id)]} {
6119 set tagloc($id) $cached_atags($id)
6120 set td 1
6123 foreach a $arcout($id) {
6124 if {!$td && $arctags($a) ne {}} {
6125 validate_arctags $a
6126 if {$arctags($a) ne {}} {
6127 lappend tagloc($id) [lindex $arctags($a) 0]
6130 if {![info exists arcend($a)]} continue
6131 set d $arcend($a)
6132 if {$td || $arctags($a) ne {}} {
6133 set tomark [list $d]
6134 for {set j 0} {$j < [llength $tomark]} {incr j} {
6135 set dd [lindex $tomark $j]
6136 if {![info exists hastaggeddescendent($dd)]} {
6137 if {[info exists done($dd)]} {
6138 foreach b $arcout($dd) {
6139 if {[info exists arcend($b)]} {
6140 lappend tomark $arcend($b)
6143 if {[info exists tagloc($dd)]} {
6144 unset tagloc($dd)
6146 } elseif {[info exists queued($dd)]} {
6147 incr nc -1
6149 set hastaggeddescendent($dd) 1
6153 if {![info exists queued($d)]} {
6154 lappend todo $d
6155 set queued($d) 1
6156 if {![info exists hastaggeddescendent($d)]} {
6157 incr nc
6162 set t2 [clock clicks -milliseconds]
6163 set loopix $i
6164 set tags {}
6165 foreach id [array names tagloc] {
6166 if {![info exists hastaggeddescendent($id)]} {
6167 foreach t $tagloc($id) {
6168 if {[lsearch -exact $tags $t] < 0} {
6169 lappend tags $t
6175 # remove tags that are ancestors of other tags
6176 for {set i 0} {$i < [llength $tags]} {incr i} {
6177 set a [lindex $tags $i]
6178 for {set j 0} {$j < $i} {incr j} {
6179 set b [lindex $tags $j]
6180 set r [anc_or_desc $a $b]
6181 if {$r == -1} {
6182 set tags [lreplace $tags $j $j]
6183 incr j -1
6184 incr i -1
6185 } elseif {$r == 1} {
6186 set tags [lreplace $tags $i $i]
6187 incr i -1
6188 break
6193 if {[array names growing] ne {}} {
6194 # graph isn't finished, need to check if any tag could get
6195 # eclipsed by another tag coming later. Simply ignore any
6196 # tags that could later get eclipsed.
6197 set ctags {}
6198 foreach t $tags {
6199 if {[is_certain $origid $t]} {
6200 lappend ctags $t
6203 if {$tags eq $ctags} {
6204 set cached_atags($origid) $tags
6205 } else {
6206 set tags $ctags
6208 } else {
6209 set cached_atags($origid) $tags
6211 set t3 [clock clicks -milliseconds]
6212 if {0 && $t3 - $t1 >= 100} {
6213 puts "iterating ancestors ($loopix/[llength $todo] nodes) took\
6214 [expr {$t2-$t1}]+[expr {$t3-$t2}]ms, $nc candidates left"
6216 return $tags
6219 # Return the list of IDs that have heads that are descendents of id,
6220 # including id itself if it has a head.
6221 proc descheads {id} {
6222 global arcnos arcstart arcids archeads idheads cached_dheads
6223 global allparents
6225 if {![info exists allparents($id)]} {
6226 return {}
6228 set ret {}
6229 if {[llength $arcnos($id)] == 1 && [llength $allparents($id)] == 1} {
6230 # part-way along an arc; check it first
6231 set a [lindex $arcnos($id) 0]
6232 if {$archeads($a) ne {}} {
6233 validate_archeads $a
6234 set i [lsearch -exact $arcids($a) $id]
6235 foreach t $archeads($a) {
6236 set j [lsearch -exact $arcids($a) $t]
6237 if {$j > $i} break
6238 lappend $ret $t
6241 set id $arcstart($a)
6243 set origid $id
6244 set todo [list $id]
6245 set seen($id) 1
6246 for {set i 0} {$i < [llength $todo]} {incr i} {
6247 set id [lindex $todo $i]
6248 if {[info exists cached_dheads($id)]} {
6249 set ret [concat $ret $cached_dheads($id)]
6250 } else {
6251 if {[info exists idheads($id)]} {
6252 lappend ret $id
6254 foreach a $arcnos($id) {
6255 if {$archeads($a) ne {}} {
6256 set ret [concat $ret $archeads($a)]
6258 set d $arcstart($a)
6259 if {![info exists seen($d)]} {
6260 lappend todo $d
6261 set seen($d) 1
6266 set ret [lsort -unique $ret]
6267 set cached_dheads($origid) $ret
6270 proc addedtag {id} {
6271 global arcnos arcout cached_dtags cached_atags
6273 if {![info exists arcnos($id)]} return
6274 if {![info exists arcout($id)]} {
6275 recalcarc [lindex $arcnos($id) 0]
6277 catch {unset cached_dtags}
6278 catch {unset cached_atags}
6281 proc addedhead {hid head} {
6282 global arcnos arcout cached_dheads
6284 if {![info exists arcnos($hid)]} return
6285 if {![info exists arcout($hid)]} {
6286 recalcarc [lindex $arcnos($hid) 0]
6288 catch {unset cached_dheads}
6291 proc removedhead {hid head} {
6292 global cached_dheads
6294 catch {unset cached_dheads}
6297 proc movedhead {hid head} {
6298 global arcnos arcout cached_dheads
6300 if {![info exists arcnos($hid)]} return
6301 if {![info exists arcout($hid)]} {
6302 recalcarc [lindex $arcnos($hid) 0]
6304 catch {unset cached_dheads}
6307 proc changedrefs {} {
6308 global cached_dheads cached_dtags cached_atags
6309 global arctags archeads arcnos arcout idheads idtags
6311 foreach id [concat [array names idheads] [array names idtags]] {
6312 if {[info exists arcnos($id)] && ![info exists arcout($id)]} {
6313 set a [lindex $arcnos($id) 0]
6314 if {![info exists donearc($a)]} {
6315 recalcarc $a
6316 set donearc($a) 1
6320 catch {unset cached_dtags}
6321 catch {unset cached_atags}
6322 catch {unset cached_dheads}
6325 proc rereadrefs {} {
6326 global idtags idheads idotherrefs mainhead
6328 set refids [concat [array names idtags] \
6329 [array names idheads] [array names idotherrefs]]
6330 foreach id $refids {
6331 if {![info exists ref($id)]} {
6332 set ref($id) [listrefs $id]
6335 set oldmainhead $mainhead
6336 readrefs
6337 changedrefs
6338 set refids [lsort -unique [concat $refids [array names idtags] \
6339 [array names idheads] [array names idotherrefs]]]
6340 foreach id $refids {
6341 set v [listrefs $id]
6342 if {![info exists ref($id)] || $ref($id) != $v ||
6343 ($id eq $oldmainhead && $id ne $mainhead) ||
6344 ($id eq $mainhead && $id ne $oldmainhead)} {
6345 redrawtags $id
6350 proc listrefs {id} {
6351 global idtags idheads idotherrefs
6353 set x {}
6354 if {[info exists idtags($id)]} {
6355 set x $idtags($id)
6357 set y {}
6358 if {[info exists idheads($id)]} {
6359 set y $idheads($id)
6361 set z {}
6362 if {[info exists idotherrefs($id)]} {
6363 set z $idotherrefs($id)
6365 return [list $x $y $z]
6368 proc showtag {tag isnew} {
6369 global ctext tagcontents tagids linknum
6371 if {$isnew} {
6372 addtohistory [list showtag $tag 0]
6374 $ctext conf -state normal
6375 clear_ctext
6376 set linknum 0
6377 if {[info exists tagcontents($tag)]} {
6378 set text $tagcontents($tag)
6379 } else {
6380 set text "Tag: $tag\nId: $tagids($tag)"
6382 appendwithlinks $text {}
6383 $ctext conf -state disabled
6384 init_flist {}
6387 proc doquit {} {
6388 global stopped
6389 set stopped 100
6390 savestuff .
6391 destroy .
6394 proc doprefs {} {
6395 global maxwidth maxgraphpct diffopts
6396 global oldprefs prefstop showneartags
6397 global bgcolor fgcolor ctext diffcolors selectbgcolor
6398 global uifont tabstop
6400 set top .gitkprefs
6401 set prefstop $top
6402 if {[winfo exists $top]} {
6403 raise $top
6404 return
6406 foreach v {maxwidth maxgraphpct diffopts showneartags} {
6407 set oldprefs($v) [set $v]
6409 toplevel $top
6410 wm title $top "Gitk preferences"
6411 label $top.ldisp -text "Commit list display options"
6412 $top.ldisp configure -font $uifont
6413 grid $top.ldisp - -sticky w -pady 10
6414 label $top.spacer -text " "
6415 label $top.maxwidthl -text "Maximum graph width (lines)" \
6416 -font optionfont
6417 spinbox $top.maxwidth -from 0 -to 100 -width 4 -textvariable maxwidth
6418 grid $top.spacer $top.maxwidthl $top.maxwidth -sticky w
6419 label $top.maxpctl -text "Maximum graph width (% of pane)" \
6420 -font optionfont
6421 spinbox $top.maxpct -from 1 -to 100 -width 4 -textvariable maxgraphpct
6422 grid x $top.maxpctl $top.maxpct -sticky w
6424 label $top.ddisp -text "Diff display options"
6425 $top.ddisp configure -font $uifont
6426 grid $top.ddisp - -sticky w -pady 10
6427 label $top.diffoptl -text "Options for diff program" \
6428 -font optionfont
6429 entry $top.diffopt -width 20 -textvariable diffopts
6430 grid x $top.diffoptl $top.diffopt -sticky w
6431 frame $top.ntag
6432 label $top.ntag.l -text "Display nearby tags" -font optionfont
6433 checkbutton $top.ntag.b -variable showneartags
6434 pack $top.ntag.b $top.ntag.l -side left
6435 grid x $top.ntag -sticky w
6436 label $top.tabstopl -text "tabstop" -font optionfont
6437 entry $top.tabstop -width 10 -textvariable tabstop
6438 grid x $top.tabstopl $top.tabstop -sticky w
6440 label $top.cdisp -text "Colors: press to choose"
6441 $top.cdisp configure -font $uifont
6442 grid $top.cdisp - -sticky w -pady 10
6443 label $top.bg -padx 40 -relief sunk -background $bgcolor
6444 button $top.bgbut -text "Background" -font optionfont \
6445 -command [list choosecolor bgcolor 0 $top.bg background setbg]
6446 grid x $top.bgbut $top.bg -sticky w
6447 label $top.fg -padx 40 -relief sunk -background $fgcolor
6448 button $top.fgbut -text "Foreground" -font optionfont \
6449 -command [list choosecolor fgcolor 0 $top.fg foreground setfg]
6450 grid x $top.fgbut $top.fg -sticky w
6451 label $top.diffold -padx 40 -relief sunk -background [lindex $diffcolors 0]
6452 button $top.diffoldbut -text "Diff: old lines" -font optionfont \
6453 -command [list choosecolor diffcolors 0 $top.diffold "diff old lines" \
6454 [list $ctext tag conf d0 -foreground]]
6455 grid x $top.diffoldbut $top.diffold -sticky w
6456 label $top.diffnew -padx 40 -relief sunk -background [lindex $diffcolors 1]
6457 button $top.diffnewbut -text "Diff: new lines" -font optionfont \
6458 -command [list choosecolor diffcolors 1 $top.diffnew "diff new lines" \
6459 [list $ctext tag conf d1 -foreground]]
6460 grid x $top.diffnewbut $top.diffnew -sticky w
6461 label $top.hunksep -padx 40 -relief sunk -background [lindex $diffcolors 2]
6462 button $top.hunksepbut -text "Diff: hunk header" -font optionfont \
6463 -command [list choosecolor diffcolors 2 $top.hunksep \
6464 "diff hunk header" \
6465 [list $ctext tag conf hunksep -foreground]]
6466 grid x $top.hunksepbut $top.hunksep -sticky w
6467 label $top.selbgsep -padx 40 -relief sunk -background $selectbgcolor
6468 button $top.selbgbut -text "Select bg" -font optionfont \
6469 -command [list choosecolor selectbgcolor 0 $top.bg background setselbg]
6470 grid x $top.selbgbut $top.selbgsep -sticky w
6472 frame $top.buts
6473 button $top.buts.ok -text "OK" -command prefsok -default active
6474 $top.buts.ok configure -font $uifont
6475 button $top.buts.can -text "Cancel" -command prefscan -default normal
6476 $top.buts.can configure -font $uifont
6477 grid $top.buts.ok $top.buts.can
6478 grid columnconfigure $top.buts 0 -weight 1 -uniform a
6479 grid columnconfigure $top.buts 1 -weight 1 -uniform a
6480 grid $top.buts - - -pady 10 -sticky ew
6481 bind $top <Visibility> "focus $top.buts.ok"
6484 proc choosecolor {v vi w x cmd} {
6485 global $v
6487 set c [tk_chooseColor -initialcolor [lindex [set $v] $vi] \
6488 -title "Gitk: choose color for $x"]
6489 if {$c eq {}} return
6490 $w conf -background $c
6491 lset $v $vi $c
6492 eval $cmd $c
6495 proc setselbg {c} {
6496 global bglist cflist
6497 foreach w $bglist {
6498 $w configure -selectbackground $c
6500 $cflist tag configure highlight \
6501 -background [$cflist cget -selectbackground]
6502 allcanvs itemconf secsel -fill $c
6505 proc setbg {c} {
6506 global bglist
6508 foreach w $bglist {
6509 $w conf -background $c
6513 proc setfg {c} {
6514 global fglist canv
6516 foreach w $fglist {
6517 $w conf -foreground $c
6519 allcanvs itemconf text -fill $c
6520 $canv itemconf circle -outline $c
6523 proc prefscan {} {
6524 global maxwidth maxgraphpct diffopts
6525 global oldprefs prefstop showneartags
6527 foreach v {maxwidth maxgraphpct diffopts showneartags} {
6528 set $v $oldprefs($v)
6530 catch {destroy $prefstop}
6531 unset prefstop
6534 proc prefsok {} {
6535 global maxwidth maxgraphpct
6536 global oldprefs prefstop showneartags
6537 global charspc ctext tabstop
6539 catch {destroy $prefstop}
6540 unset prefstop
6541 $ctext configure -tabs "[expr {$tabstop * $charspc}]"
6542 if {$maxwidth != $oldprefs(maxwidth)
6543 || $maxgraphpct != $oldprefs(maxgraphpct)} {
6544 redisplay
6545 } elseif {$showneartags != $oldprefs(showneartags)} {
6546 reselectline
6550 proc formatdate {d} {
6551 return [clock format $d -format "%Y-%m-%d %H:%M:%S"]
6554 # This list of encoding names and aliases is distilled from
6555 # http://www.iana.org/assignments/character-sets.
6556 # Not all of them are supported by Tcl.
6557 set encoding_aliases {
6558 { ANSI_X3.4-1968 iso-ir-6 ANSI_X3.4-1986 ISO_646.irv:1991 ASCII
6559 ISO646-US US-ASCII us IBM367 cp367 csASCII }
6560 { ISO-10646-UTF-1 csISO10646UTF1 }
6561 { ISO_646.basic:1983 ref csISO646basic1983 }
6562 { INVARIANT csINVARIANT }
6563 { ISO_646.irv:1983 iso-ir-2 irv csISO2IntlRefVersion }
6564 { BS_4730 iso-ir-4 ISO646-GB gb uk csISO4UnitedKingdom }
6565 { NATS-SEFI iso-ir-8-1 csNATSSEFI }
6566 { NATS-SEFI-ADD iso-ir-8-2 csNATSSEFIADD }
6567 { NATS-DANO iso-ir-9-1 csNATSDANO }
6568 { NATS-DANO-ADD iso-ir-9-2 csNATSDANOADD }
6569 { SEN_850200_B iso-ir-10 FI ISO646-FI ISO646-SE se csISO10Swedish }
6570 { SEN_850200_C iso-ir-11 ISO646-SE2 se2 csISO11SwedishForNames }
6571 { KS_C_5601-1987 iso-ir-149 KS_C_5601-1989 KSC_5601 korean csKSC56011987 }
6572 { ISO-2022-KR csISO2022KR }
6573 { EUC-KR csEUCKR }
6574 { ISO-2022-JP csISO2022JP }
6575 { ISO-2022-JP-2 csISO2022JP2 }
6576 { JIS_C6220-1969-jp JIS_C6220-1969 iso-ir-13 katakana x0201-7
6577 csISO13JISC6220jp }
6578 { JIS_C6220-1969-ro iso-ir-14 jp ISO646-JP csISO14JISC6220ro }
6579 { IT iso-ir-15 ISO646-IT csISO15Italian }
6580 { PT iso-ir-16 ISO646-PT csISO16Portuguese }
6581 { ES iso-ir-17 ISO646-ES csISO17Spanish }
6582 { greek7-old iso-ir-18 csISO18Greek7Old }
6583 { latin-greek iso-ir-19 csISO19LatinGreek }
6584 { DIN_66003 iso-ir-21 de ISO646-DE csISO21German }
6585 { NF_Z_62-010_(1973) iso-ir-25 ISO646-FR1 csISO25French }
6586 { Latin-greek-1 iso-ir-27 csISO27LatinGreek1 }
6587 { ISO_5427 iso-ir-37 csISO5427Cyrillic }
6588 { JIS_C6226-1978 iso-ir-42 csISO42JISC62261978 }
6589 { BS_viewdata iso-ir-47 csISO47BSViewdata }
6590 { INIS iso-ir-49 csISO49INIS }
6591 { INIS-8 iso-ir-50 csISO50INIS8 }
6592 { INIS-cyrillic iso-ir-51 csISO51INISCyrillic }
6593 { ISO_5427:1981 iso-ir-54 ISO5427Cyrillic1981 }
6594 { ISO_5428:1980 iso-ir-55 csISO5428Greek }
6595 { GB_1988-80 iso-ir-57 cn ISO646-CN csISO57GB1988 }
6596 { GB_2312-80 iso-ir-58 chinese csISO58GB231280 }
6597 { NS_4551-1 iso-ir-60 ISO646-NO no csISO60DanishNorwegian
6598 csISO60Norwegian1 }
6599 { NS_4551-2 ISO646-NO2 iso-ir-61 no2 csISO61Norwegian2 }
6600 { NF_Z_62-010 iso-ir-69 ISO646-FR fr csISO69French }
6601 { videotex-suppl iso-ir-70 csISO70VideotexSupp1 }
6602 { PT2 iso-ir-84 ISO646-PT2 csISO84Portuguese2 }
6603 { ES2 iso-ir-85 ISO646-ES2 csISO85Spanish2 }
6604 { MSZ_7795.3 iso-ir-86 ISO646-HU hu csISO86Hungarian }
6605 { JIS_C6226-1983 iso-ir-87 x0208 JIS_X0208-1983 csISO87JISX0208 }
6606 { greek7 iso-ir-88 csISO88Greek7 }
6607 { ASMO_449 ISO_9036 arabic7 iso-ir-89 csISO89ASMO449 }
6608 { iso-ir-90 csISO90 }
6609 { JIS_C6229-1984-a iso-ir-91 jp-ocr-a csISO91JISC62291984a }
6610 { JIS_C6229-1984-b iso-ir-92 ISO646-JP-OCR-B jp-ocr-b
6611 csISO92JISC62991984b }
6612 { JIS_C6229-1984-b-add iso-ir-93 jp-ocr-b-add csISO93JIS62291984badd }
6613 { JIS_C6229-1984-hand iso-ir-94 jp-ocr-hand csISO94JIS62291984hand }
6614 { JIS_C6229-1984-hand-add iso-ir-95 jp-ocr-hand-add
6615 csISO95JIS62291984handadd }
6616 { JIS_C6229-1984-kana iso-ir-96 csISO96JISC62291984kana }
6617 { ISO_2033-1983 iso-ir-98 e13b csISO2033 }
6618 { ANSI_X3.110-1983 iso-ir-99 CSA_T500-1983 NAPLPS csISO99NAPLPS }
6619 { ISO_8859-1:1987 iso-ir-100 ISO_8859-1 ISO-8859-1 latin1 l1 IBM819
6620 CP819 csISOLatin1 }
6621 { ISO_8859-2:1987 iso-ir-101 ISO_8859-2 ISO-8859-2 latin2 l2 csISOLatin2 }
6622 { T.61-7bit iso-ir-102 csISO102T617bit }
6623 { T.61-8bit T.61 iso-ir-103 csISO103T618bit }
6624 { ISO_8859-3:1988 iso-ir-109 ISO_8859-3 ISO-8859-3 latin3 l3 csISOLatin3 }
6625 { ISO_8859-4:1988 iso-ir-110 ISO_8859-4 ISO-8859-4 latin4 l4 csISOLatin4 }
6626 { ECMA-cyrillic iso-ir-111 KOI8-E csISO111ECMACyrillic }
6627 { CSA_Z243.4-1985-1 iso-ir-121 ISO646-CA csa7-1 ca csISO121Canadian1 }
6628 { CSA_Z243.4-1985-2 iso-ir-122 ISO646-CA2 csa7-2 csISO122Canadian2 }
6629 { CSA_Z243.4-1985-gr iso-ir-123 csISO123CSAZ24341985gr }
6630 { ISO_8859-6:1987 iso-ir-127 ISO_8859-6 ISO-8859-6 ECMA-114 ASMO-708
6631 arabic csISOLatinArabic }
6632 { ISO_8859-6-E csISO88596E ISO-8859-6-E }
6633 { ISO_8859-6-I csISO88596I ISO-8859-6-I }
6634 { ISO_8859-7:1987 iso-ir-126 ISO_8859-7 ISO-8859-7 ELOT_928 ECMA-118
6635 greek greek8 csISOLatinGreek }
6636 { T.101-G2 iso-ir-128 csISO128T101G2 }
6637 { ISO_8859-8:1988 iso-ir-138 ISO_8859-8 ISO-8859-8 hebrew
6638 csISOLatinHebrew }
6639 { ISO_8859-8-E csISO88598E ISO-8859-8-E }
6640 { ISO_8859-8-I csISO88598I ISO-8859-8-I }
6641 { CSN_369103 iso-ir-139 csISO139CSN369103 }
6642 { JUS_I.B1.002 iso-ir-141 ISO646-YU js yu csISO141JUSIB1002 }
6643 { ISO_6937-2-add iso-ir-142 csISOTextComm }
6644 { IEC_P27-1 iso-ir-143 csISO143IECP271 }
6645 { ISO_8859-5:1988 iso-ir-144 ISO_8859-5 ISO-8859-5 cyrillic
6646 csISOLatinCyrillic }
6647 { JUS_I.B1.003-serb iso-ir-146 serbian csISO146Serbian }
6648 { JUS_I.B1.003-mac macedonian iso-ir-147 csISO147Macedonian }
6649 { ISO_8859-9:1989 iso-ir-148 ISO_8859-9 ISO-8859-9 latin5 l5 csISOLatin5 }
6650 { greek-ccitt iso-ir-150 csISO150 csISO150GreekCCITT }
6651 { NC_NC00-10:81 cuba iso-ir-151 ISO646-CU csISO151Cuba }
6652 { ISO_6937-2-25 iso-ir-152 csISO6937Add }
6653 { GOST_19768-74 ST_SEV_358-88 iso-ir-153 csISO153GOST1976874 }
6654 { ISO_8859-supp iso-ir-154 latin1-2-5 csISO8859Supp }
6655 { ISO_10367-box iso-ir-155 csISO10367Box }
6656 { ISO-8859-10 iso-ir-157 l6 ISO_8859-10:1992 csISOLatin6 latin6 }
6657 { latin-lap lap iso-ir-158 csISO158Lap }
6658 { JIS_X0212-1990 x0212 iso-ir-159 csISO159JISX02121990 }
6659 { DS_2089 DS2089 ISO646-DK dk csISO646Danish }
6660 { us-dk csUSDK }
6661 { dk-us csDKUS }
6662 { JIS_X0201 X0201 csHalfWidthKatakana }
6663 { KSC5636 ISO646-KR csKSC5636 }
6664 { ISO-10646-UCS-2 csUnicode }
6665 { ISO-10646-UCS-4 csUCS4 }
6666 { DEC-MCS dec csDECMCS }
6667 { hp-roman8 roman8 r8 csHPRoman8 }
6668 { macintosh mac csMacintosh }
6669 { IBM037 cp037 ebcdic-cp-us ebcdic-cp-ca ebcdic-cp-wt ebcdic-cp-nl
6670 csIBM037 }
6671 { IBM038 EBCDIC-INT cp038 csIBM038 }
6672 { IBM273 CP273 csIBM273 }
6673 { IBM274 EBCDIC-BE CP274 csIBM274 }
6674 { IBM275 EBCDIC-BR cp275 csIBM275 }
6675 { IBM277 EBCDIC-CP-DK EBCDIC-CP-NO csIBM277 }
6676 { IBM278 CP278 ebcdic-cp-fi ebcdic-cp-se csIBM278 }
6677 { IBM280 CP280 ebcdic-cp-it csIBM280 }
6678 { IBM281 EBCDIC-JP-E cp281 csIBM281 }
6679 { IBM284 CP284 ebcdic-cp-es csIBM284 }
6680 { IBM285 CP285 ebcdic-cp-gb csIBM285 }
6681 { IBM290 cp290 EBCDIC-JP-kana csIBM290 }
6682 { IBM297 cp297 ebcdic-cp-fr csIBM297 }
6683 { IBM420 cp420 ebcdic-cp-ar1 csIBM420 }
6684 { IBM423 cp423 ebcdic-cp-gr csIBM423 }
6685 { IBM424 cp424 ebcdic-cp-he csIBM424 }
6686 { IBM437 cp437 437 csPC8CodePage437 }
6687 { IBM500 CP500 ebcdic-cp-be ebcdic-cp-ch csIBM500 }
6688 { IBM775 cp775 csPC775Baltic }
6689 { IBM850 cp850 850 csPC850Multilingual }
6690 { IBM851 cp851 851 csIBM851 }
6691 { IBM852 cp852 852 csPCp852 }
6692 { IBM855 cp855 855 csIBM855 }
6693 { IBM857 cp857 857 csIBM857 }
6694 { IBM860 cp860 860 csIBM860 }
6695 { IBM861 cp861 861 cp-is csIBM861 }
6696 { IBM862 cp862 862 csPC862LatinHebrew }
6697 { IBM863 cp863 863 csIBM863 }
6698 { IBM864 cp864 csIBM864 }
6699 { IBM865 cp865 865 csIBM865 }
6700 { IBM866 cp866 866 csIBM866 }
6701 { IBM868 CP868 cp-ar csIBM868 }
6702 { IBM869 cp869 869 cp-gr csIBM869 }
6703 { IBM870 CP870 ebcdic-cp-roece ebcdic-cp-yu csIBM870 }
6704 { IBM871 CP871 ebcdic-cp-is csIBM871 }
6705 { IBM880 cp880 EBCDIC-Cyrillic csIBM880 }
6706 { IBM891 cp891 csIBM891 }
6707 { IBM903 cp903 csIBM903 }
6708 { IBM904 cp904 904 csIBBM904 }
6709 { IBM905 CP905 ebcdic-cp-tr csIBM905 }
6710 { IBM918 CP918 ebcdic-cp-ar2 csIBM918 }
6711 { IBM1026 CP1026 csIBM1026 }
6712 { EBCDIC-AT-DE csIBMEBCDICATDE }
6713 { EBCDIC-AT-DE-A csEBCDICATDEA }
6714 { EBCDIC-CA-FR csEBCDICCAFR }
6715 { EBCDIC-DK-NO csEBCDICDKNO }
6716 { EBCDIC-DK-NO-A csEBCDICDKNOA }
6717 { EBCDIC-FI-SE csEBCDICFISE }
6718 { EBCDIC-FI-SE-A csEBCDICFISEA }
6719 { EBCDIC-FR csEBCDICFR }
6720 { EBCDIC-IT csEBCDICIT }
6721 { EBCDIC-PT csEBCDICPT }
6722 { EBCDIC-ES csEBCDICES }
6723 { EBCDIC-ES-A csEBCDICESA }
6724 { EBCDIC-ES-S csEBCDICESS }
6725 { EBCDIC-UK csEBCDICUK }
6726 { EBCDIC-US csEBCDICUS }
6727 { UNKNOWN-8BIT csUnknown8BiT }
6728 { MNEMONIC csMnemonic }
6729 { MNEM csMnem }
6730 { VISCII csVISCII }
6731 { VIQR csVIQR }
6732 { KOI8-R csKOI8R }
6733 { IBM00858 CCSID00858 CP00858 PC-Multilingual-850+euro }
6734 { IBM00924 CCSID00924 CP00924 ebcdic-Latin9--euro }
6735 { IBM01140 CCSID01140 CP01140 ebcdic-us-37+euro }
6736 { IBM01141 CCSID01141 CP01141 ebcdic-de-273+euro }
6737 { IBM01142 CCSID01142 CP01142 ebcdic-dk-277+euro ebcdic-no-277+euro }
6738 { IBM01143 CCSID01143 CP01143 ebcdic-fi-278+euro ebcdic-se-278+euro }
6739 { IBM01144 CCSID01144 CP01144 ebcdic-it-280+euro }
6740 { IBM01145 CCSID01145 CP01145 ebcdic-es-284+euro }
6741 { IBM01146 CCSID01146 CP01146 ebcdic-gb-285+euro }
6742 { IBM01147 CCSID01147 CP01147 ebcdic-fr-297+euro }
6743 { IBM01148 CCSID01148 CP01148 ebcdic-international-500+euro }
6744 { IBM01149 CCSID01149 CP01149 ebcdic-is-871+euro }
6745 { IBM1047 IBM-1047 }
6746 { PTCP154 csPTCP154 PT154 CP154 Cyrillic-Asian }
6747 { Amiga-1251 Ami1251 Amiga1251 Ami-1251 }
6748 { UNICODE-1-1 csUnicode11 }
6749 { CESU-8 csCESU-8 }
6750 { BOCU-1 csBOCU-1 }
6751 { UNICODE-1-1-UTF-7 csUnicode11UTF7 }
6752 { ISO-8859-14 iso-ir-199 ISO_8859-14:1998 ISO_8859-14 latin8 iso-celtic
6753 l8 }
6754 { ISO-8859-15 ISO_8859-15 Latin-9 }
6755 { ISO-8859-16 iso-ir-226 ISO_8859-16:2001 ISO_8859-16 latin10 l10 }
6756 { GBK CP936 MS936 windows-936 }
6757 { JIS_Encoding csJISEncoding }
6758 { Shift_JIS MS_Kanji csShiftJIS }
6759 { Extended_UNIX_Code_Packed_Format_for_Japanese csEUCPkdFmtJapanese
6760 EUC-JP }
6761 { Extended_UNIX_Code_Fixed_Width_for_Japanese csEUCFixWidJapanese }
6762 { ISO-10646-UCS-Basic csUnicodeASCII }
6763 { ISO-10646-Unicode-Latin1 csUnicodeLatin1 ISO-10646 }
6764 { ISO-Unicode-IBM-1261 csUnicodeIBM1261 }
6765 { ISO-Unicode-IBM-1268 csUnicodeIBM1268 }
6766 { ISO-Unicode-IBM-1276 csUnicodeIBM1276 }
6767 { ISO-Unicode-IBM-1264 csUnicodeIBM1264 }
6768 { ISO-Unicode-IBM-1265 csUnicodeIBM1265 }
6769 { ISO-8859-1-Windows-3.0-Latin-1 csWindows30Latin1 }
6770 { ISO-8859-1-Windows-3.1-Latin-1 csWindows31Latin1 }
6771 { ISO-8859-2-Windows-Latin-2 csWindows31Latin2 }
6772 { ISO-8859-9-Windows-Latin-5 csWindows31Latin5 }
6773 { Adobe-Standard-Encoding csAdobeStandardEncoding }
6774 { Ventura-US csVenturaUS }
6775 { Ventura-International csVenturaInternational }
6776 { PC8-Danish-Norwegian csPC8DanishNorwegian }
6777 { PC8-Turkish csPC8Turkish }
6778 { IBM-Symbols csIBMSymbols }
6779 { IBM-Thai csIBMThai }
6780 { HP-Legal csHPLegal }
6781 { HP-Pi-font csHPPiFont }
6782 { HP-Math8 csHPMath8 }
6783 { Adobe-Symbol-Encoding csHPPSMath }
6784 { HP-DeskTop csHPDesktop }
6785 { Ventura-Math csVenturaMath }
6786 { Microsoft-Publishing csMicrosoftPublishing }
6787 { Windows-31J csWindows31J }
6788 { GB2312 csGB2312 }
6789 { Big5 csBig5 }
6792 proc tcl_encoding {enc} {
6793 global encoding_aliases
6794 set names [encoding names]
6795 set lcnames [string tolower $names]
6796 set enc [string tolower $enc]
6797 set i [lsearch -exact $lcnames $enc]
6798 if {$i < 0} {
6799 # look for "isonnn" instead of "iso-nnn" or "iso_nnn"
6800 if {[regsub {^iso[-_]} $enc iso encx]} {
6801 set i [lsearch -exact $lcnames $encx]
6804 if {$i < 0} {
6805 foreach l $encoding_aliases {
6806 set ll [string tolower $l]
6807 if {[lsearch -exact $ll $enc] < 0} continue
6808 # look through the aliases for one that tcl knows about
6809 foreach e $ll {
6810 set i [lsearch -exact $lcnames $e]
6811 if {$i < 0} {
6812 if {[regsub {^iso[-_]} $e iso ex]} {
6813 set i [lsearch -exact $lcnames $ex]
6816 if {$i >= 0} break
6818 break
6821 if {$i >= 0} {
6822 return [lindex $names $i]
6824 return {}
6827 # defaults...
6828 set datemode 0
6829 set diffopts "-U 5 -p"
6830 set wrcomcmd "git diff-tree --stdin -p --pretty"
6832 set gitencoding {}
6833 catch {
6834 set gitencoding [exec git config --get i18n.commitencoding]
6836 if {$gitencoding == ""} {
6837 set gitencoding "utf-8"
6839 set tclencoding [tcl_encoding $gitencoding]
6840 if {$tclencoding == {}} {
6841 puts stderr "Warning: encoding $gitencoding is not supported by Tcl/Tk"
6844 set mainfont {Helvetica 9}
6845 set textfont {Courier 9}
6846 set uifont {Helvetica 9 bold}
6847 set tabstop 8
6848 set findmergefiles 0
6849 set maxgraphpct 50
6850 set maxwidth 16
6851 set revlistorder 0
6852 set fastdate 0
6853 set uparrowlen 7
6854 set downarrowlen 7
6855 set mingaplen 30
6856 set cmitmode "patch"
6857 set wrapcomment "none"
6858 set showneartags 1
6860 set colors {green red blue magenta darkgrey brown orange}
6861 set bgcolor white
6862 set fgcolor black
6863 set diffcolors {red "#00a000" blue}
6864 set selectbgcolor gray85
6866 catch {source ~/.gitk}
6868 font create optionfont -family sans-serif -size -12
6870 set revtreeargs {}
6871 foreach arg $argv {
6872 switch -regexp -- $arg {
6873 "^$" { }
6874 "^-d" { set datemode 1 }
6875 default {
6876 lappend revtreeargs $arg
6881 # check that we can find a .git directory somewhere...
6882 set gitdir [gitdir]
6883 if {![file isdirectory $gitdir]} {
6884 show_error {} . "Cannot find the git directory \"$gitdir\"."
6885 exit 1
6888 set cmdline_files {}
6889 set i [lsearch -exact $revtreeargs "--"]
6890 if {$i >= 0} {
6891 set cmdline_files [lrange $revtreeargs [expr {$i + 1}] end]
6892 set revtreeargs [lrange $revtreeargs 0 [expr {$i - 1}]]
6893 } elseif {$revtreeargs ne {}} {
6894 if {[catch {
6895 set f [eval exec git rev-parse --no-revs --no-flags $revtreeargs]
6896 set cmdline_files [split $f "\n"]
6897 set n [llength $cmdline_files]
6898 set revtreeargs [lrange $revtreeargs 0 end-$n]
6899 } err]} {
6900 # unfortunately we get both stdout and stderr in $err,
6901 # so look for "fatal:".
6902 set i [string first "fatal:" $err]
6903 if {$i > 0} {
6904 set err [string range $err [expr {$i + 6}] end]
6906 show_error {} . "Bad arguments to gitk:\n$err"
6907 exit 1
6911 set history {}
6912 set historyindex 0
6913 set fh_serial 0
6914 set nhl_names {}
6915 set highlight_paths {}
6916 set searchdirn -forwards
6917 set boldrows {}
6918 set boldnamerows {}
6919 set diffelide {0 0}
6921 set optim_delay 16
6923 set nextviewnum 1
6924 set curview 0
6925 set selectedview 0
6926 set selectedhlview None
6927 set viewfiles(0) {}
6928 set viewperm(0) 0
6929 set viewargs(0) {}
6931 set cmdlineok 0
6932 set stopped 0
6933 set stuffsaved 0
6934 set patchnum 0
6935 setcoords
6936 makewindow
6937 wm title . "[file tail $argv0]: [file tail [pwd]]"
6938 readrefs
6940 if {$cmdline_files ne {} || $revtreeargs ne {}} {
6941 # create a view for the files/dirs specified on the command line
6942 set curview 1
6943 set selectedview 1
6944 set nextviewnum 2
6945 set viewname(1) "Command line"
6946 set viewfiles(1) $cmdline_files
6947 set viewargs(1) $revtreeargs
6948 set viewperm(1) 0
6949 addviewmenu 1
6950 .bar.view entryconf Edit* -state normal
6951 .bar.view entryconf Delete* -state normal
6954 if {[info exists permviews]} {
6955 foreach v $permviews {
6956 set n $nextviewnum
6957 incr nextviewnum
6958 set viewname($n) [lindex $v 0]
6959 set viewfiles($n) [lindex $v 1]
6960 set viewargs($n) [lindex $v 2]
6961 set viewperm($n) 1
6962 addviewmenu $n
6965 getcommits