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