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