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