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