1#!/bin/sh
2# Tcl ignores the next line -*- tcl -*- \
3exec wish "$0" -- "$@"
4
5# Copyright © 2005-2009 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
10package require Tk
11
12proc gitdir {} {
13 global env
14 if {[info exists env(GIT_DIR)]} {
15 return $env(GIT_DIR)
16 } else {
17 return [exec git rev-parse --git-dir]
18 }
19}
20
21# A simple scheduler for compute-intensive stuff.
22# The aim is to make sure that event handlers for GUI actions can
23# run at least every 50-100 ms. Unfortunately fileevent handlers are
24# run before X event handlers, so reading from a fast source can
25# make the GUI completely unresponsive.
26proc run args {
27 global isonrunq runq currunq
28
29 set script $args
30 if {[info exists isonrunq($script)]} return
31 if {$runq eq {} && ![info exists currunq]} {
32 after idle dorunq
33 }
34 lappend runq [list {} $script]
35 set isonrunq($script) 1
36}
37
38proc filerun {fd script} {
39 fileevent $fd readable [list filereadable $fd $script]
40}
41
42proc filereadable {fd script} {
43 global runq currunq
44
45 fileevent $fd readable {}
46 if {$runq eq {} && ![info exists currunq]} {
47 after idle dorunq
48 }
49 lappend runq [list $fd $script]
50}
51
52proc nukefile {fd} {
53 global runq
54
55 for {set i 0} {$i < [llength $runq]} {} {
56 if {[lindex $runq $i 0] eq $fd} {
57 set runq [lreplace $runq $i $i]
58 } else {
59 incr i
60 }
61 }
62}
63
64proc dorunq {} {
65 global isonrunq runq currunq
66
67 set tstart [clock clicks -milliseconds]
68 set t0 $tstart
69 while {[llength $runq] > 0} {
70 set fd [lindex $runq 0 0]
71 set script [lindex $runq 0 1]
72 set currunq [lindex $runq 0]
73 set runq [lrange $runq 1 end]
74 set repeat [eval $script]
75 unset currunq
76 set t1 [clock clicks -milliseconds]
77 set t [expr {$t1 - $t0}]
78 if {$repeat ne {} && $repeat} {
79 if {$fd eq {} || $repeat == 2} {
80 # script returns 1 if it wants to be readded
81 # file readers return 2 if they could do more straight away
82 lappend runq [list $fd $script]
83 } else {
84 fileevent $fd readable [list filereadable $fd $script]
85 }
86 } elseif {$fd eq {}} {
87 unset isonrunq($script)
88 }
89 set t0 $t1
90 if {$t1 - $tstart >= 80} break
91 }
92 if {$runq ne {}} {
93 after idle dorunq
94 }
95}
96
97proc reg_instance {fd} {
98 global commfd leftover loginstance
99
100 set i [incr loginstance]
101 set commfd($i) $fd
102 set leftover($i) {}
103 return $i
104}
105
106proc unmerged_files {files} {
107 global nr_unmerged
108
109 # find the list of unmerged files
110 set mlist {}
111 set nr_unmerged 0
112 if {[catch {
113 set fd [open "| git ls-files -u" r]
114 } err]} {
115 show_error {} . "[mc "Couldn't get list of unmerged files:"] $err"
116 exit 1
117 }
118 while {[gets $fd line] >= 0} {
119 set i [string first "\t" $line]
120 if {$i < 0} continue
121 set fname [string range $line [expr {$i+1}] end]
122 if {[lsearch -exact $mlist $fname] >= 0} continue
123 incr nr_unmerged
124 if {$files eq {} || [path_filter $files $fname]} {
125 lappend mlist $fname
126 }
127 }
128 catch {close $fd}
129 return $mlist
130}
131
132proc parseviewargs {n arglist} {
133 global vdatemode vmergeonly vflags vdflags vrevs vfiltered vorigargs
134
135 set vdatemode($n) 0
136 set vmergeonly($n) 0
137 set glflags {}
138 set diffargs {}
139 set nextisval 0
140 set revargs {}
141 set origargs $arglist
142 set allknown 1
143 set filtered 0
144 set i -1
145 foreach arg $arglist {
146 incr i
147 if {$nextisval} {
148 lappend glflags $arg
149 set nextisval 0
150 continue
151 }
152 switch -glob -- $arg {
153 "-d" -
154 "--date-order" {
155 set vdatemode($n) 1
156 # remove from origargs in case we hit an unknown option
157 set origargs [lreplace $origargs $i $i]
158 incr i -1
159 }
160 "-[puabwcrRBMC]" -
161 "--no-renames" - "--full-index" - "--binary" - "--abbrev=*" -
162 "--find-copies-harder" - "-l*" - "--ext-diff" - "--no-ext-diff" -
163 "--src-prefix=*" - "--dst-prefix=*" - "--no-prefix" -
164 "-O*" - "--text" - "--full-diff" - "--ignore-space-at-eol" -
165 "--ignore-space-change" - "-U*" - "--unified=*" {
166 # These request or affect diff output, which we don't want.
167 # Some could be used to set our defaults for diff display.
168 lappend diffargs $arg
169 }
170 "--raw" - "--patch-with-raw" - "--patch-with-stat" -
171 "--name-only" - "--name-status" - "--color" - "--color-words" -
172 "--log-size" - "--pretty=*" - "--decorate" - "--abbrev-commit" -
173 "--cc" - "-z" - "--header" - "--parents" - "--boundary" -
174 "--no-color" - "-g" - "--walk-reflogs" - "--no-walk" -
175 "--timestamp" - "relative-date" - "--date=*" - "--stdin" -
176 "--objects" - "--objects-edge" - "--reverse" {
177 # These cause our parsing of git log's output to fail, or else
178 # they're options we want to set ourselves, so ignore them.
179 }
180 "--stat=*" - "--numstat" - "--shortstat" - "--summary" -
181 "--check" - "--exit-code" - "--quiet" - "--topo-order" -
182 "--full-history" - "--dense" - "--sparse" -
183 "--follow" - "--left-right" - "--encoding=*" {
184 # These are harmless, and some are even useful
185 lappend glflags $arg
186 }
187 "--diff-filter=*" - "--no-merges" - "--unpacked" -
188 "--max-count=*" - "--skip=*" - "--since=*" - "--after=*" -
189 "--until=*" - "--before=*" - "--max-age=*" - "--min-age=*" -
190 "--author=*" - "--committer=*" - "--grep=*" - "-[iE]" -
191 "--remove-empty" - "--first-parent" - "--cherry-pick" -
192 "-S*" - "--pickaxe-all" - "--pickaxe-regex" -
193 "--simplify-by-decoration" {
194 # These mean that we get a subset of the commits
195 set filtered 1
196 lappend glflags $arg
197 }
198 "-n" {
199 # This appears to be the only one that has a value as a
200 # separate word following it
201 set filtered 1
202 set nextisval 1
203 lappend glflags $arg
204 }
205 "--not" - "--all" {
206 lappend revargs $arg
207 }
208 "--merge" {
209 set vmergeonly($n) 1
210 # git rev-parse doesn't understand --merge
211 lappend revargs --gitk-symmetric-diff-marker MERGE_HEAD...HEAD
212 }
213 "-*" {
214 # Other flag arguments including -<n>
215 if {[string is digit -strict [string range $arg 1 end]]} {
216 set filtered 1
217 } else {
218 # a flag argument that we don't recognize;
219 # that means we can't optimize
220 set allknown 0
221 }
222 lappend glflags $arg
223 }
224 default {
225 # Non-flag arguments specify commits or ranges of commits
226 if {[string match "*...*" $arg]} {
227 lappend revargs --gitk-symmetric-diff-marker
228 }
229 lappend revargs $arg
230 }
231 }
232 }
233 set vdflags($n) $diffargs
234 set vflags($n) $glflags
235 set vrevs($n) $revargs
236 set vfiltered($n) $filtered
237 set vorigargs($n) $origargs
238 return $allknown
239}
240
241proc parseviewrevs {view revs} {
242 global vposids vnegids
243
244 if {$revs eq {}} {
245 set revs HEAD
246 }
247 if {[catch {set ids [eval exec git rev-parse $revs]} err]} {
248 # we get stdout followed by stderr in $err
249 # for an unknown rev, git rev-parse echoes it and then errors out
250 set errlines [split $err "\n"]
251 set badrev {}
252 for {set l 0} {$l < [llength $errlines]} {incr l} {
253 set line [lindex $errlines $l]
254 if {!([string length $line] == 40 && [string is xdigit $line])} {
255 if {[string match "fatal:*" $line]} {
256 if {[string match "fatal: ambiguous argument*" $line]
257 && $badrev ne {}} {
258 if {[llength $badrev] == 1} {
259 set err "unknown revision $badrev"
260 } else {
261 set err "unknown revisions: [join $badrev ", "]"
262 }
263 } else {
264 set err [join [lrange $errlines $l end] "\n"]
265 }
266 break
267 }
268 lappend badrev $line
269 }
270 }
271 error_popup "[mc "Error parsing revisions:"] $err"
272 return {}
273 }
274 set ret {}
275 set pos {}
276 set neg {}
277 set sdm 0
278 foreach id [split $ids "\n"] {
279 if {$id eq "--gitk-symmetric-diff-marker"} {
280 set sdm 4
281 } elseif {[string match "^*" $id]} {
282 if {$sdm != 1} {
283 lappend ret $id
284 if {$sdm == 3} {
285 set sdm 0
286 }
287 }
288 lappend neg [string range $id 1 end]
289 } else {
290 if {$sdm != 2} {
291 lappend ret $id
292 } else {
293 lset ret end $id...[lindex $ret end]
294 }
295 lappend pos $id
296 }
297 incr sdm -1
298 }
299 set vposids($view) $pos
300 set vnegids($view) $neg
301 return $ret
302}
303
304# Start off a git log process and arrange to read its output
305proc start_rev_list {view} {
306 global startmsecs commitidx viewcomplete curview
307 global tclencoding
308 global viewargs viewargscmd viewfiles vfilelimit
309 global showlocalchanges
310 global viewactive viewinstances vmergeonly
311 global mainheadid viewmainheadid viewmainheadid_orig
312 global vcanopt vflags vrevs vorigargs
313
314 set startmsecs [clock clicks -milliseconds]
315 set commitidx($view) 0
316 # these are set this way for the error exits
317 set viewcomplete($view) 1
318 set viewactive($view) 0
319 varcinit $view
320
321 set args $viewargs($view)
322 if {$viewargscmd($view) ne {}} {
323 if {[catch {
324 set str [exec sh -c $viewargscmd($view)]
325 } err]} {
326 error_popup "[mc "Error executing --argscmd command:"] $err"
327 return 0
328 }
329 set args [concat $args [split $str "\n"]]
330 }
331 set vcanopt($view) [parseviewargs $view $args]
332
333 set files $viewfiles($view)
334 if {$vmergeonly($view)} {
335 set files [unmerged_files $files]
336 if {$files eq {}} {
337 global nr_unmerged
338 if {$nr_unmerged == 0} {
339 error_popup [mc "No files selected: --merge specified but\
340 no files are unmerged."]
341 } else {
342 error_popup [mc "No files selected: --merge specified but\
343 no unmerged files are within file limit."]
344 }
345 return 0
346 }
347 }
348 set vfilelimit($view) $files
349
350 if {$vcanopt($view)} {
351 set revs [parseviewrevs $view $vrevs($view)]
352 if {$revs eq {}} {
353 return 0
354 }
355 set args [concat $vflags($view) $revs]
356 } else {
357 set args $vorigargs($view)
358 }
359
360 if {[catch {
361 set fd [open [concat | git log --no-color -z --pretty=raw --parents \
362 --boundary $args "--" $files] r]
363 } err]} {
364 error_popup "[mc "Error executing git log:"] $err"
365 return 0
366 }
367 set i [reg_instance $fd]
368 set viewinstances($view) [list $i]
369 set viewmainheadid($view) $mainheadid
370 set viewmainheadid_orig($view) $mainheadid
371 if {$files ne {} && $mainheadid ne {}} {
372 get_viewmainhead $view
373 }
374 if {$showlocalchanges && $viewmainheadid($view) ne {}} {
375 interestedin $viewmainheadid($view) dodiffindex
376 }
377 fconfigure $fd -blocking 0 -translation lf -eofchar {}
378 if {$tclencoding != {}} {
379 fconfigure $fd -encoding $tclencoding
380 }
381 filerun $fd [list getcommitlines $fd $i $view 0]
382 nowbusy $view [mc "Reading"]
383 set viewcomplete($view) 0
384 set viewactive($view) 1
385 return 1
386}
387
388proc stop_instance {inst} {
389 global commfd leftover
390
391 set fd $commfd($inst)
392 catch {
393 set pid [pid $fd]
394
395 if {$::tcl_platform(platform) eq {windows}} {
396 exec kill -f $pid
397 } else {
398 exec kill $pid
399 }
400 }
401 catch {close $fd}
402 nukefile $fd
403 unset commfd($inst)
404 unset leftover($inst)
405}
406
407proc stop_backends {} {
408 global commfd
409
410 foreach inst [array names commfd] {
411 stop_instance $inst
412 }
413}
414
415proc stop_rev_list {view} {
416 global viewinstances
417
418 foreach inst $viewinstances($view) {
419 stop_instance $inst
420 }
421 set viewinstances($view) {}
422}
423
424proc reset_pending_select {selid} {
425 global pending_select mainheadid selectheadid
426
427 if {$selid ne {}} {
428 set pending_select $selid
429 } elseif {$selectheadid ne {}} {
430 set pending_select $selectheadid
431 } else {
432 set pending_select $mainheadid
433 }
434}
435
436proc getcommits {selid} {
437 global canv curview need_redisplay viewactive
438
439 initlayout
440 if {[start_rev_list $curview]} {
441 reset_pending_select $selid
442 show_status [mc "Reading commits..."]
443 set need_redisplay 1
444 } else {
445 show_status [mc "No commits selected"]
446 }
447}
448
449proc updatecommits {} {
450 global curview vcanopt vorigargs vfilelimit viewinstances
451 global viewactive viewcomplete tclencoding
452 global startmsecs showneartags showlocalchanges
453 global mainheadid viewmainheadid viewmainheadid_orig pending_select
454 global isworktree
455 global varcid vposids vnegids vflags vrevs
456
457 set isworktree [expr {[exec git rev-parse --is-inside-work-tree] == "true"}]
458 rereadrefs
459 set view $curview
460 if {$mainheadid ne $viewmainheadid_orig($view)} {
461 if {$showlocalchanges} {
462 dohidelocalchanges
463 }
464 set viewmainheadid($view) $mainheadid
465 set viewmainheadid_orig($view) $mainheadid
466 if {$vfilelimit($view) ne {}} {
467 get_viewmainhead $view
468 }
469 }
470 if {$showlocalchanges} {
471 doshowlocalchanges
472 }
473 if {$vcanopt($view)} {
474 set oldpos $vposids($view)
475 set oldneg $vnegids($view)
476 set revs [parseviewrevs $view $vrevs($view)]
477 if {$revs eq {}} {
478 return
479 }
480 # note: getting the delta when negative refs change is hard,
481 # and could require multiple git log invocations, so in that
482 # case we ask git log for all the commits (not just the delta)
483 if {$oldneg eq $vnegids($view)} {
484 set newrevs {}
485 set npos 0
486 # take out positive refs that we asked for before or
487 # that we have already seen
488 foreach rev $revs {
489 if {[string length $rev] == 40} {
490 if {[lsearch -exact $oldpos $rev] < 0
491 && ![info exists varcid($view,$rev)]} {
492 lappend newrevs $rev
493 incr npos
494 }
495 } else {
496 lappend $newrevs $rev
497 }
498 }
499 if {$npos == 0} return
500 set revs $newrevs
501 set vposids($view) [lsort -unique [concat $oldpos $vposids($view)]]
502 }
503 set args [concat $vflags($view) $revs --not $oldpos]
504 } else {
505 set args $vorigargs($view)
506 }
507 if {[catch {
508 set fd [open [concat | git log --no-color -z --pretty=raw --parents \
509 --boundary $args "--" $vfilelimit($view)] r]
510 } err]} {
511 error_popup "[mc "Error executing git log:"] $err"
512 return
513 }
514 if {$viewactive($view) == 0} {
515 set startmsecs [clock clicks -milliseconds]
516 }
517 set i [reg_instance $fd]
518 lappend viewinstances($view) $i
519 fconfigure $fd -blocking 0 -translation lf -eofchar {}
520 if {$tclencoding != {}} {
521 fconfigure $fd -encoding $tclencoding
522 }
523 filerun $fd [list getcommitlines $fd $i $view 1]
524 incr viewactive($view)
525 set viewcomplete($view) 0
526 reset_pending_select {}
527 nowbusy $view [mc "Reading"]
528 if {$showneartags} {
529 getallcommits
530 }
531}
532
533proc reloadcommits {} {
534 global curview viewcomplete selectedline currentid thickerline
535 global showneartags treediffs commitinterest cached_commitrow
536 global targetid
537
538 set selid {}
539 if {$selectedline ne {}} {
540 set selid $currentid
541 }
542
543 if {!$viewcomplete($curview)} {
544 stop_rev_list $curview
545 }
546 resetvarcs $curview
547 set selectedline {}
548 catch {unset currentid}
549 catch {unset thickerline}
550 catch {unset treediffs}
551 readrefs
552 changedrefs
553 if {$showneartags} {
554 getallcommits
555 }
556 clear_display
557 catch {unset commitinterest}
558 catch {unset cached_commitrow}
559 catch {unset targetid}
560 setcanvscroll
561 getcommits $selid
562 return 0
563}
564
565# This makes a string representation of a positive integer which
566# sorts as a string in numerical order
567proc strrep {n} {
568 if {$n < 16} {
569 return [format "%x" $n]
570 } elseif {$n < 256} {
571 return [format "x%.2x" $n]
572 } elseif {$n < 65536} {
573 return [format "y%.4x" $n]
574 }
575 return [format "z%.8x" $n]
576}
577
578# Procedures used in reordering commits from git log (without
579# --topo-order) into the order for display.
580
581proc varcinit {view} {
582 global varcstart vupptr vdownptr vleftptr vbackptr varctok varcrow
583 global vtokmod varcmod vrowmod varcix vlastins
584
585 set varcstart($view) {{}}
586 set vupptr($view) {0}
587 set vdownptr($view) {0}
588 set vleftptr($view) {0}
589 set vbackptr($view) {0}
590 set varctok($view) {{}}
591 set varcrow($view) {{}}
592 set vtokmod($view) {}
593 set varcmod($view) 0
594 set vrowmod($view) 0
595 set varcix($view) {{}}
596 set vlastins($view) {0}
597}
598
599proc resetvarcs {view} {
600 global varcid varccommits parents children vseedcount ordertok
601
602 foreach vid [array names varcid $view,*] {
603 unset varcid($vid)
604 unset children($vid)
605 unset parents($vid)
606 }
607 # some commits might have children but haven't been seen yet
608 foreach vid [array names children $view,*] {
609 unset children($vid)
610 }
611 foreach va [array names varccommits $view,*] {
612 unset varccommits($va)
613 }
614 foreach vd [array names vseedcount $view,*] {
615 unset vseedcount($vd)
616 }
617 catch {unset ordertok}
618}
619
620# returns a list of the commits with no children
621proc seeds {v} {
622 global vdownptr vleftptr varcstart
623
624 set ret {}
625 set a [lindex $vdownptr($v) 0]
626 while {$a != 0} {
627 lappend ret [lindex $varcstart($v) $a]
628 set a [lindex $vleftptr($v) $a]
629 }
630 return $ret
631}
632
633proc newvarc {view id} {
634 global varcid varctok parents children vdatemode
635 global vupptr vdownptr vleftptr vbackptr varcrow varcix varcstart
636 global commitdata commitinfo vseedcount varccommits vlastins
637
638 set a [llength $varctok($view)]
639 set vid $view,$id
640 if {[llength $children($vid)] == 0 || $vdatemode($view)} {
641 if {![info exists commitinfo($id)]} {
642 parsecommit $id $commitdata($id) 1
643 }
644 set cdate [lindex $commitinfo($id) 4]
645 if {![string is integer -strict $cdate]} {
646 set cdate 0
647 }
648 if {![info exists vseedcount($view,$cdate)]} {
649 set vseedcount($view,$cdate) -1
650 }
651 set c [incr vseedcount($view,$cdate)]
652 set cdate [expr {$cdate ^ 0xffffffff}]
653 set tok "s[strrep $cdate][strrep $c]"
654 } else {
655 set tok {}
656 }
657 set ka 0
658 if {[llength $children($vid)] > 0} {
659 set kid [lindex $children($vid) end]
660 set k $varcid($view,$kid)
661 if {[string compare [lindex $varctok($view) $k] $tok] > 0} {
662 set ki $kid
663 set ka $k
664 set tok [lindex $varctok($view) $k]
665 }
666 }
667 if {$ka != 0} {
668 set i [lsearch -exact $parents($view,$ki) $id]
669 set j [expr {[llength $parents($view,$ki)] - 1 - $i}]
670 append tok [strrep $j]
671 }
672 set c [lindex $vlastins($view) $ka]
673 if {$c == 0 || [string compare $tok [lindex $varctok($view) $c]] < 0} {
674 set c $ka
675 set b [lindex $vdownptr($view) $ka]
676 } else {
677 set b [lindex $vleftptr($view) $c]
678 }
679 while {$b != 0 && [string compare $tok [lindex $varctok($view) $b]] >= 0} {
680 set c $b
681 set b [lindex $vleftptr($view) $c]
682 }
683 if {$c == $ka} {
684 lset vdownptr($view) $ka $a
685 lappend vbackptr($view) 0
686 } else {
687 lset vleftptr($view) $c $a
688 lappend vbackptr($view) $c
689 }
690 lset vlastins($view) $ka $a
691 lappend vupptr($view) $ka
692 lappend vleftptr($view) $b
693 if {$b != 0} {
694 lset vbackptr($view) $b $a
695 }
696 lappend varctok($view) $tok
697 lappend varcstart($view) $id
698 lappend vdownptr($view) 0
699 lappend varcrow($view) {}
700 lappend varcix($view) {}
701 set varccommits($view,$a) {}
702 lappend vlastins($view) 0
703 return $a
704}
705
706proc splitvarc {p v} {
707 global varcid varcstart varccommits varctok vtokmod
708 global vupptr vdownptr vleftptr vbackptr varcix varcrow vlastins
709
710 set oa $varcid($v,$p)
711 set otok [lindex $varctok($v) $oa]
712 set ac $varccommits($v,$oa)
713 set i [lsearch -exact $varccommits($v,$oa) $p]
714 if {$i <= 0} return
715 set na [llength $varctok($v)]
716 # "%" sorts before "0"...
717 set tok "$otok%[strrep $i]"
718 lappend varctok($v) $tok
719 lappend varcrow($v) {}
720 lappend varcix($v) {}
721 set varccommits($v,$oa) [lrange $ac 0 [expr {$i - 1}]]
722 set varccommits($v,$na) [lrange $ac $i end]
723 lappend varcstart($v) $p
724 foreach id $varccommits($v,$na) {
725 set varcid($v,$id) $na
726 }
727 lappend vdownptr($v) [lindex $vdownptr($v) $oa]
728 lappend vlastins($v) [lindex $vlastins($v) $oa]
729 lset vdownptr($v) $oa $na
730 lset vlastins($v) $oa 0
731 lappend vupptr($v) $oa
732 lappend vleftptr($v) 0
733 lappend vbackptr($v) 0
734 for {set b [lindex $vdownptr($v) $na]} {$b != 0} {set b [lindex $vleftptr($v) $b]} {
735 lset vupptr($v) $b $na
736 }
737 if {[string compare $otok $vtokmod($v)] <= 0} {
738 modify_arc $v $oa
739 }
740}
741
742proc renumbervarc {a v} {
743 global parents children varctok varcstart varccommits
744 global vupptr vdownptr vleftptr vbackptr vlastins varcid vtokmod vdatemode
745
746 set t1 [clock clicks -milliseconds]
747 set todo {}
748 set isrelated($a) 1
749 set kidchanged($a) 1
750 set ntot 0
751 while {$a != 0} {
752 if {[info exists isrelated($a)]} {
753 lappend todo $a
754 set id [lindex $varccommits($v,$a) end]
755 foreach p $parents($v,$id) {
756 if {[info exists varcid($v,$p)]} {
757 set isrelated($varcid($v,$p)) 1
758 }
759 }
760 }
761 incr ntot
762 set b [lindex $vdownptr($v) $a]
763 if {$b == 0} {
764 while {$a != 0} {
765 set b [lindex $vleftptr($v) $a]
766 if {$b != 0} break
767 set a [lindex $vupptr($v) $a]
768 }
769 }
770 set a $b
771 }
772 foreach a $todo {
773 if {![info exists kidchanged($a)]} continue
774 set id [lindex $varcstart($v) $a]
775 if {[llength $children($v,$id)] > 1} {
776 set children($v,$id) [lsort -command [list vtokcmp $v] \
777 $children($v,$id)]
778 }
779 set oldtok [lindex $varctok($v) $a]
780 if {!$vdatemode($v)} {
781 set tok {}
782 } else {
783 set tok $oldtok
784 }
785 set ka 0
786 set kid [last_real_child $v,$id]
787 if {$kid ne {}} {
788 set k $varcid($v,$kid)
789 if {[string compare [lindex $varctok($v) $k] $tok] > 0} {
790 set ki $kid
791 set ka $k
792 set tok [lindex $varctok($v) $k]
793 }
794 }
795 if {$ka != 0} {
796 set i [lsearch -exact $parents($v,$ki) $id]
797 set j [expr {[llength $parents($v,$ki)] - 1 - $i}]
798 append tok [strrep $j]
799 }
800 if {$tok eq $oldtok} {
801 continue
802 }
803 set id [lindex $varccommits($v,$a) end]
804 foreach p $parents($v,$id) {
805 if {[info exists varcid($v,$p)]} {
806 set kidchanged($varcid($v,$p)) 1
807 } else {
808 set sortkids($p) 1
809 }
810 }
811 lset varctok($v) $a $tok
812 set b [lindex $vupptr($v) $a]
813 if {$b != $ka} {
814 if {[string compare [lindex $varctok($v) $ka] $vtokmod($v)] < 0} {
815 modify_arc $v $ka
816 }
817 if {[string compare [lindex $varctok($v) $b] $vtokmod($v)] < 0} {
818 modify_arc $v $b
819 }
820 set c [lindex $vbackptr($v) $a]
821 set d [lindex $vleftptr($v) $a]
822 if {$c == 0} {
823 lset vdownptr($v) $b $d
824 } else {
825 lset vleftptr($v) $c $d
826 }
827 if {$d != 0} {
828 lset vbackptr($v) $d $c
829 }
830 if {[lindex $vlastins($v) $b] == $a} {
831 lset vlastins($v) $b $c
832 }
833 lset vupptr($v) $a $ka
834 set c [lindex $vlastins($v) $ka]
835 if {$c == 0 || \
836 [string compare $tok [lindex $varctok($v) $c]] < 0} {
837 set c $ka
838 set b [lindex $vdownptr($v) $ka]
839 } else {
840 set b [lindex $vleftptr($v) $c]
841 }
842 while {$b != 0 && \
843 [string compare $tok [lindex $varctok($v) $b]] >= 0} {
844 set c $b
845 set b [lindex $vleftptr($v) $c]
846 }
847 if {$c == $ka} {
848 lset vdownptr($v) $ka $a
849 lset vbackptr($v) $a 0
850 } else {
851 lset vleftptr($v) $c $a
852 lset vbackptr($v) $a $c
853 }
854 lset vleftptr($v) $a $b
855 if {$b != 0} {
856 lset vbackptr($v) $b $a
857 }
858 lset vlastins($v) $ka $a
859 }
860 }
861 foreach id [array names sortkids] {
862 if {[llength $children($v,$id)] > 1} {
863 set children($v,$id) [lsort -command [list vtokcmp $v] \
864 $children($v,$id)]
865 }
866 }
867 set t2 [clock clicks -milliseconds]
868 #puts "renumbervarc did [llength $todo] of $ntot arcs in [expr {$t2-$t1}]ms"
869}
870
871# Fix up the graph after we have found out that in view $v,
872# $p (a commit that we have already seen) is actually the parent
873# of the last commit in arc $a.
874proc fix_reversal {p a v} {
875 global varcid varcstart varctok vupptr
876
877 set pa $varcid($v,$p)
878 if {$p ne [lindex $varcstart($v) $pa]} {
879 splitvarc $p $v
880 set pa $varcid($v,$p)
881 }
882 # seeds always need to be renumbered
883 if {[lindex $vupptr($v) $pa] == 0 ||
884 [string compare [lindex $varctok($v) $a] \
885 [lindex $varctok($v) $pa]] > 0} {
886 renumbervarc $pa $v
887 }
888}
889
890proc insertrow {id p v} {
891 global cmitlisted children parents varcid varctok vtokmod
892 global varccommits ordertok commitidx numcommits curview
893 global targetid targetrow
894
895 readcommit $id
896 set vid $v,$id
897 set cmitlisted($vid) 1
898 set children($vid) {}
899 set parents($vid) [list $p]
900 set a [newvarc $v $id]
901 set varcid($vid) $a
902 if {[string compare [lindex $varctok($v) $a] $vtokmod($v)] < 0} {
903 modify_arc $v $a
904 }
905 lappend varccommits($v,$a) $id
906 set vp $v,$p
907 if {[llength [lappend children($vp) $id]] > 1} {
908 set children($vp) [lsort -command [list vtokcmp $v] $children($vp)]
909 catch {unset ordertok}
910 }
911 fix_reversal $p $a $v
912 incr commitidx($v)
913 if {$v == $curview} {
914 set numcommits $commitidx($v)
915 setcanvscroll
916 if {[info exists targetid]} {
917 if {![comes_before $targetid $p]} {
918 incr targetrow
919 }
920 }
921 }
922}
923
924proc insertfakerow {id p} {
925 global varcid varccommits parents children cmitlisted
926 global commitidx varctok vtokmod targetid targetrow curview numcommits
927
928 set v $curview
929 set a $varcid($v,$p)
930 set i [lsearch -exact $varccommits($v,$a) $p]
931 if {$i < 0} {
932 puts "oops: insertfakerow can't find [shortids $p] on arc $a"
933 return
934 }
935 set children($v,$id) {}
936 set parents($v,$id) [list $p]
937 set varcid($v,$id) $a
938 lappend children($v,$p) $id
939 set cmitlisted($v,$id) 1
940 set numcommits [incr commitidx($v)]
941 # note we deliberately don't update varcstart($v) even if $i == 0
942 set varccommits($v,$a) [linsert $varccommits($v,$a) $i $id]
943 modify_arc $v $a $i
944 if {[info exists targetid]} {
945 if {![comes_before $targetid $p]} {
946 incr targetrow
947 }
948 }
949 setcanvscroll
950 drawvisible
951}
952
953proc removefakerow {id} {
954 global varcid varccommits parents children commitidx
955 global varctok vtokmod cmitlisted currentid selectedline
956 global targetid curview numcommits
957
958 set v $curview
959 if {[llength $parents($v,$id)] != 1} {
960 puts "oops: removefakerow [shortids $id] has [llength $parents($v,$id)] parents"
961 return
962 }
963 set p [lindex $parents($v,$id) 0]
964 set a $varcid($v,$id)
965 set i [lsearch -exact $varccommits($v,$a) $id]
966 if {$i < 0} {
967 puts "oops: removefakerow can't find [shortids $id] on arc $a"
968 return
969 }
970 unset varcid($v,$id)
971 set varccommits($v,$a) [lreplace $varccommits($v,$a) $i $i]
972 unset parents($v,$id)
973 unset children($v,$id)
974 unset cmitlisted($v,$id)
975 set numcommits [incr commitidx($v) -1]
976 set j [lsearch -exact $children($v,$p) $id]
977 if {$j >= 0} {
978 set children($v,$p) [lreplace $children($v,$p) $j $j]
979 }
980 modify_arc $v $a $i
981 if {[info exist currentid] && $id eq $currentid} {
982 unset currentid
983 set selectedline {}
984 }
985 if {[info exists targetid] && $targetid eq $id} {
986 set targetid $p
987 }
988 setcanvscroll
989 drawvisible
990}
991
992proc real_children {vp} {
993 global children nullid nullid2
994
995 set kids {}
996 foreach id $children($vp) {
997 if {$id ne $nullid && $id ne $nullid2} {
998 lappend kids $id
999 }
1000 }
1001 return $kids
1002}
1003
1004proc first_real_child {vp} {
1005 global children nullid nullid2
1006
1007 foreach id $children($vp) {
1008 if {$id ne $nullid && $id ne $nullid2} {
1009 return $id
1010 }
1011 }
1012 return {}
1013}
1014
1015proc last_real_child {vp} {
1016 global children nullid nullid2
1017
1018 set kids $children($vp)
1019 for {set i [llength $kids]} {[incr i -1] >= 0} {} {
1020 set id [lindex $kids $i]
1021 if {$id ne $nullid && $id ne $nullid2} {
1022 return $id
1023 }
1024 }
1025 return {}
1026}
1027
1028proc vtokcmp {v a b} {
1029 global varctok varcid
1030
1031 return [string compare [lindex $varctok($v) $varcid($v,$a)] \
1032 [lindex $varctok($v) $varcid($v,$b)]]
1033}
1034
1035# This assumes that if lim is not given, the caller has checked that
1036# arc a's token is less than $vtokmod($v)
1037proc modify_arc {v a {lim {}}} {
1038 global varctok vtokmod varcmod varcrow vupptr curview vrowmod varccommits
1039
1040 if {$lim ne {}} {
1041 set c [string compare [lindex $varctok($v) $a] $vtokmod($v)]
1042 if {$c > 0} return
1043 if {$c == 0} {
1044 set r [lindex $varcrow($v) $a]
1045 if {$r ne {} && $vrowmod($v) <= $r + $lim} return
1046 }
1047 }
1048 set vtokmod($v) [lindex $varctok($v) $a]
1049 set varcmod($v) $a
1050 if {$v == $curview} {
1051 while {$a != 0 && [lindex $varcrow($v) $a] eq {}} {
1052 set a [lindex $vupptr($v) $a]
1053 set lim {}
1054 }
1055 set r 0
1056 if {$a != 0} {
1057 if {$lim eq {}} {
1058 set lim [llength $varccommits($v,$a)]
1059 }
1060 set r [expr {[lindex $varcrow($v) $a] + $lim}]
1061 }
1062 set vrowmod($v) $r
1063 undolayout $r
1064 }
1065}
1066
1067proc update_arcrows {v} {
1068 global vtokmod varcmod vrowmod varcrow commitidx currentid selectedline
1069 global varcid vrownum varcorder varcix varccommits
1070 global vupptr vdownptr vleftptr varctok
1071 global displayorder parentlist curview cached_commitrow
1072
1073 if {$vrowmod($v) == $commitidx($v)} return
1074 if {$v == $curview} {
1075 if {[llength $displayorder] > $vrowmod($v)} {
1076 set displayorder [lrange $displayorder 0 [expr {$vrowmod($v) - 1}]]
1077 set parentlist [lrange $parentlist 0 [expr {$vrowmod($v) - 1}]]
1078 }
1079 catch {unset cached_commitrow}
1080 }
1081 set narctot [expr {[llength $varctok($v)] - 1}]
1082 set a $varcmod($v)
1083 while {$a != 0 && [lindex $varcix($v) $a] eq {}} {
1084 # go up the tree until we find something that has a row number,
1085 # or we get to a seed
1086 set a [lindex $vupptr($v) $a]
1087 }
1088 if {$a == 0} {
1089 set a [lindex $vdownptr($v) 0]
1090 if {$a == 0} return
1091 set vrownum($v) {0}
1092 set varcorder($v) [list $a]
1093 lset varcix($v) $a 0
1094 lset varcrow($v) $a 0
1095 set arcn 0
1096 set row 0
1097 } else {
1098 set arcn [lindex $varcix($v) $a]
1099 if {[llength $vrownum($v)] > $arcn + 1} {
1100 set vrownum($v) [lrange $vrownum($v) 0 $arcn]
1101 set varcorder($v) [lrange $varcorder($v) 0 $arcn]
1102 }
1103 set row [lindex $varcrow($v) $a]
1104 }
1105 while {1} {
1106 set p $a
1107 incr row [llength $varccommits($v,$a)]
1108 # go down if possible
1109 set b [lindex $vdownptr($v) $a]
1110 if {$b == 0} {
1111 # if not, go left, or go up until we can go left
1112 while {$a != 0} {
1113 set b [lindex $vleftptr($v) $a]
1114 if {$b != 0} break
1115 set a [lindex $vupptr($v) $a]
1116 }
1117 if {$a == 0} break
1118 }
1119 set a $b
1120 incr arcn
1121 lappend vrownum($v) $row
1122 lappend varcorder($v) $a
1123 lset varcix($v) $a $arcn
1124 lset varcrow($v) $a $row
1125 }
1126 set vtokmod($v) [lindex $varctok($v) $p]
1127 set varcmod($v) $p
1128 set vrowmod($v) $row
1129 if {[info exists currentid]} {
1130 set selectedline [rowofcommit $currentid]
1131 }
1132}
1133
1134# Test whether view $v contains commit $id
1135proc commitinview {id v} {
1136 global varcid
1137
1138 return [info exists varcid($v,$id)]
1139}
1140
1141# Return the row number for commit $id in the current view
1142proc rowofcommit {id} {
1143 global varcid varccommits varcrow curview cached_commitrow
1144 global varctok vtokmod
1145
1146 set v $curview
1147 if {![info exists varcid($v,$id)]} {
1148 puts "oops rowofcommit no arc for [shortids $id]"
1149 return {}
1150 }
1151 set a $varcid($v,$id)
1152 if {[string compare [lindex $varctok($v) $a] $vtokmod($v)] >= 0} {
1153 update_arcrows $v
1154 }
1155 if {[info exists cached_commitrow($id)]} {
1156 return $cached_commitrow($id)
1157 }
1158 set i [lsearch -exact $varccommits($v,$a) $id]
1159 if {$i < 0} {
1160 puts "oops didn't find commit [shortids $id] in arc $a"
1161 return {}
1162 }
1163 incr i [lindex $varcrow($v) $a]
1164 set cached_commitrow($id) $i
1165 return $i
1166}
1167
1168# Returns 1 if a is on an earlier row than b, otherwise 0
1169proc comes_before {a b} {
1170 global varcid varctok curview
1171
1172 set v $curview
1173 if {$a eq $b || ![info exists varcid($v,$a)] || \
1174 ![info exists varcid($v,$b)]} {
1175 return 0
1176 }
1177 if {$varcid($v,$a) != $varcid($v,$b)} {
1178 return [expr {[string compare [lindex $varctok($v) $varcid($v,$a)] \
1179 [lindex $varctok($v) $varcid($v,$b)]] < 0}]
1180 }
1181 return [expr {[rowofcommit $a] < [rowofcommit $b]}]
1182}
1183
1184proc bsearch {l elt} {
1185 if {[llength $l] == 0 || $elt <= [lindex $l 0]} {
1186 return 0
1187 }
1188 set lo 0
1189 set hi [llength $l]
1190 while {$hi - $lo > 1} {
1191 set mid [expr {int(($lo + $hi) / 2)}]
1192 set t [lindex $l $mid]
1193 if {$elt < $t} {
1194 set hi $mid
1195 } elseif {$elt > $t} {
1196 set lo $mid
1197 } else {
1198 return $mid
1199 }
1200 }
1201 return $lo
1202}
1203
1204# Make sure rows $start..$end-1 are valid in displayorder and parentlist
1205proc make_disporder {start end} {
1206 global vrownum curview commitidx displayorder parentlist
1207 global varccommits varcorder parents vrowmod varcrow
1208 global d_valid_start d_valid_end
1209
1210 if {$end > $vrowmod($curview)} {
1211 update_arcrows $curview
1212 }
1213 set ai [bsearch $vrownum($curview) $start]
1214 set start [lindex $vrownum($curview) $ai]
1215 set narc [llength $vrownum($curview)]
1216 for {set r $start} {$ai < $narc && $r < $end} {incr ai} {
1217 set a [lindex $varcorder($curview) $ai]
1218 set l [llength $displayorder]
1219 set al [llength $varccommits($curview,$a)]
1220 if {$l < $r + $al} {
1221 if {$l < $r} {
1222 set pad [ntimes [expr {$r - $l}] {}]
1223 set displayorder [concat $displayorder $pad]
1224 set parentlist [concat $parentlist $pad]
1225 } elseif {$l > $r} {
1226 set displayorder [lrange $displayorder 0 [expr {$r - 1}]]
1227 set parentlist [lrange $parentlist 0 [expr {$r - 1}]]
1228 }
1229 foreach id $varccommits($curview,$a) {
1230 lappend displayorder $id
1231 lappend parentlist $parents($curview,$id)
1232 }
1233 } elseif {[lindex $displayorder [expr {$r + $al - 1}]] eq {}} {
1234 set i $r
1235 foreach id $varccommits($curview,$a) {
1236 lset displayorder $i $id
1237 lset parentlist $i $parents($curview,$id)
1238 incr i
1239 }
1240 }
1241 incr r $al
1242 }
1243}
1244
1245proc commitonrow {row} {
1246 global displayorder
1247
1248 set id [lindex $displayorder $row]
1249 if {$id eq {}} {
1250 make_disporder $row [expr {$row + 1}]
1251 set id [lindex $displayorder $row]
1252 }
1253 return $id
1254}
1255
1256proc closevarcs {v} {
1257 global varctok varccommits varcid parents children
1258 global cmitlisted commitidx vtokmod
1259
1260 set missing_parents 0
1261 set scripts {}
1262 set narcs [llength $varctok($v)]
1263 for {set a 1} {$a < $narcs} {incr a} {
1264 set id [lindex $varccommits($v,$a) end]
1265 foreach p $parents($v,$id) {
1266 if {[info exists varcid($v,$p)]} continue
1267 # add p as a new commit
1268 incr missing_parents
1269 set cmitlisted($v,$p) 0
1270 set parents($v,$p) {}
1271 if {[llength $children($v,$p)] == 1 &&
1272 [llength $parents($v,$id)] == 1} {
1273 set b $a
1274 } else {
1275 set b [newvarc $v $p]
1276 }
1277 set varcid($v,$p) $b
1278 if {[string compare [lindex $varctok($v) $b] $vtokmod($v)] < 0} {
1279 modify_arc $v $b
1280 }
1281 lappend varccommits($v,$b) $p
1282 incr commitidx($v)
1283 set scripts [check_interest $p $scripts]
1284 }
1285 }
1286 if {$missing_parents > 0} {
1287 foreach s $scripts {
1288 eval $s
1289 }
1290 }
1291}
1292
1293# Use $rwid as a substitute for $id, i.e. reparent $id's children to $rwid
1294# Assumes we already have an arc for $rwid.
1295proc rewrite_commit {v id rwid} {
1296 global children parents varcid varctok vtokmod varccommits
1297
1298 foreach ch $children($v,$id) {
1299 # make $rwid be $ch's parent in place of $id
1300 set i [lsearch -exact $parents($v,$ch) $id]
1301 if {$i < 0} {
1302 puts "oops rewrite_commit didn't find $id in parent list for $ch"
1303 }
1304 set parents($v,$ch) [lreplace $parents($v,$ch) $i $i $rwid]
1305 # add $ch to $rwid's children and sort the list if necessary
1306 if {[llength [lappend children($v,$rwid) $ch]] > 1} {
1307 set children($v,$rwid) [lsort -command [list vtokcmp $v] \
1308 $children($v,$rwid)]
1309 }
1310 # fix the graph after joining $id to $rwid
1311 set a $varcid($v,$ch)
1312 fix_reversal $rwid $a $v
1313 # parentlist is wrong for the last element of arc $a
1314 # even if displayorder is right, hence the 3rd arg here
1315 modify_arc $v $a [expr {[llength $varccommits($v,$a)] - 1}]
1316 }
1317}
1318
1319# Mechanism for registering a command to be executed when we come
1320# across a particular commit. To handle the case when only the
1321# prefix of the commit is known, the commitinterest array is now
1322# indexed by the first 4 characters of the ID. Each element is a
1323# list of id, cmd pairs.
1324proc interestedin {id cmd} {
1325 global commitinterest
1326
1327 lappend commitinterest([string range $id 0 3]) $id $cmd
1328}
1329
1330proc check_interest {id scripts} {
1331 global commitinterest
1332
1333 set prefix [string range $id 0 3]
1334 if {[info exists commitinterest($prefix)]} {
1335 set newlist {}
1336 foreach {i script} $commitinterest($prefix) {
1337 if {[string match "$i*" $id]} {
1338 lappend scripts [string map [list "%I" $id "%P" $i] $script]
1339 } else {
1340 lappend newlist $i $script
1341 }
1342 }
1343 if {$newlist ne {}} {
1344 set commitinterest($prefix) $newlist
1345 } else {
1346 unset commitinterest($prefix)
1347 }
1348 }
1349 return $scripts
1350}
1351
1352proc getcommitlines {fd inst view updating} {
1353 global cmitlisted leftover
1354 global commitidx commitdata vdatemode
1355 global parents children curview hlview
1356 global idpending ordertok
1357 global varccommits varcid varctok vtokmod vfilelimit
1358
1359 set stuff [read $fd 500000]
1360 # git log doesn't terminate the last commit with a null...
1361 if {$stuff == {} && $leftover($inst) ne {} && [eof $fd]} {
1362 set stuff "\0"
1363 }
1364 if {$stuff == {}} {
1365 if {![eof $fd]} {
1366 return 1
1367 }
1368 global commfd viewcomplete viewactive viewname
1369 global viewinstances
1370 unset commfd($inst)
1371 set i [lsearch -exact $viewinstances($view) $inst]
1372 if {$i >= 0} {
1373 set viewinstances($view) [lreplace $viewinstances($view) $i $i]
1374 }
1375 # set it blocking so we wait for the process to terminate
1376 fconfigure $fd -blocking 1
1377 if {[catch {close $fd} err]} {
1378 set fv {}
1379 if {$view != $curview} {
1380 set fv " for the \"$viewname($view)\" view"
1381 }
1382 if {[string range $err 0 4] == "usage"} {
1383 set err "Gitk: error reading commits$fv:\
1384 bad arguments to git log."
1385 if {$viewname($view) eq "Command line"} {
1386 append err \
1387 " (Note: arguments to gitk are passed to git log\
1388 to allow selection of commits to be displayed.)"
1389 }
1390 } else {
1391 set err "Error reading commits$fv: $err"
1392 }
1393 error_popup $err
1394 }
1395 if {[incr viewactive($view) -1] <= 0} {
1396 set viewcomplete($view) 1
1397 # Check if we have seen any ids listed as parents that haven't
1398 # appeared in the list
1399 closevarcs $view
1400 notbusy $view
1401 }
1402 if {$view == $curview} {
1403 run chewcommits
1404 }
1405 return 0
1406 }
1407 set start 0
1408 set gotsome 0
1409 set scripts {}
1410 while 1 {
1411 set i [string first "\0" $stuff $start]
1412 if {$i < 0} {
1413 append leftover($inst) [string range $stuff $start end]
1414 break
1415 }
1416 if {$start == 0} {
1417 set cmit $leftover($inst)
1418 append cmit [string range $stuff 0 [expr {$i - 1}]]
1419 set leftover($inst) {}
1420 } else {
1421 set cmit [string range $stuff $start [expr {$i - 1}]]
1422 }
1423 set start [expr {$i + 1}]
1424 set j [string first "\n" $cmit]
1425 set ok 0
1426 set listed 1
1427 if {$j >= 0 && [string match "commit *" $cmit]} {
1428 set ids [string range $cmit 7 [expr {$j - 1}]]
1429 if {[string match {[-^<>]*} $ids]} {
1430 switch -- [string index $ids 0] {
1431 "-" {set listed 0}
1432 "^" {set listed 2}
1433 "<" {set listed 3}
1434 ">" {set listed 4}
1435 }
1436 set ids [string range $ids 1 end]
1437 }
1438 set ok 1
1439 foreach id $ids {
1440 if {[string length $id] != 40} {
1441 set ok 0
1442 break
1443 }
1444 }
1445 }
1446 if {!$ok} {
1447 set shortcmit $cmit
1448 if {[string length $shortcmit] > 80} {
1449 set shortcmit "[string range $shortcmit 0 80]..."
1450 }
1451 error_popup "[mc "Can't parse git log output:"] {$shortcmit}"
1452 exit 1
1453 }
1454 set id [lindex $ids 0]
1455 set vid $view,$id
1456
1457 if {!$listed && $updating && ![info exists varcid($vid)] &&
1458 $vfilelimit($view) ne {}} {
1459 # git log doesn't rewrite parents for unlisted commits
1460 # when doing path limiting, so work around that here
1461 # by working out the rewritten parent with git rev-list
1462 # and if we already know about it, using the rewritten
1463 # parent as a substitute parent for $id's children.
1464 if {![catch {
1465 set rwid [exec git rev-list --first-parent --max-count=1 \
1466 $id -- $vfilelimit($view)]
1467 }]} {
1468 if {$rwid ne {} && [info exists varcid($view,$rwid)]} {
1469 # use $rwid in place of $id
1470 rewrite_commit $view $id $rwid
1471 continue
1472 }
1473 }
1474 }
1475
1476 set a 0
1477 if {[info exists varcid($vid)]} {
1478 if {$cmitlisted($vid) || !$listed} continue
1479 set a $varcid($vid)
1480 }
1481 if {$listed} {
1482 set olds [lrange $ids 1 end]
1483 } else {
1484 set olds {}
1485 }
1486 set commitdata($id) [string range $cmit [expr {$j + 1}] end]
1487 set cmitlisted($vid) $listed
1488 set parents($vid) $olds
1489 if {![info exists children($vid)]} {
1490 set children($vid) {}
1491 } elseif {$a == 0 && [llength $children($vid)] == 1} {
1492 set k [lindex $children($vid) 0]
1493 if {[llength $parents($view,$k)] == 1 &&
1494 (!$vdatemode($view) ||
1495 $varcid($view,$k) == [llength $varctok($view)] - 1)} {
1496 set a $varcid($view,$k)
1497 }
1498 }
1499 if {$a == 0} {
1500 # new arc
1501 set a [newvarc $view $id]
1502 }
1503 if {[string compare [lindex $varctok($view) $a] $vtokmod($view)] < 0} {
1504 modify_arc $view $a
1505 }
1506 if {![info exists varcid($vid)]} {
1507 set varcid($vid) $a
1508 lappend varccommits($view,$a) $id
1509 incr commitidx($view)
1510 }
1511
1512 set i 0
1513 foreach p $olds {
1514 if {$i == 0 || [lsearch -exact $olds $p] >= $i} {
1515 set vp $view,$p
1516 if {[llength [lappend children($vp) $id]] > 1 &&
1517 [vtokcmp $view [lindex $children($vp) end-1] $id] > 0} {
1518 set children($vp) [lsort -command [list vtokcmp $view] \
1519 $children($vp)]
1520 catch {unset ordertok}
1521 }
1522 if {[info exists varcid($view,$p)]} {
1523 fix_reversal $p $a $view
1524 }
1525 }
1526 incr i
1527 }
1528
1529 set scripts [check_interest $id $scripts]
1530 set gotsome 1
1531 }
1532 if {$gotsome} {
1533 global numcommits hlview
1534
1535 if {$view == $curview} {
1536 set numcommits $commitidx($view)
1537 run chewcommits
1538 }
1539 if {[info exists hlview] && $view == $hlview} {
1540 # we never actually get here...
1541 run vhighlightmore
1542 }
1543 foreach s $scripts {
1544 eval $s
1545 }
1546 }
1547 return 2
1548}
1549
1550proc chewcommits {} {
1551 global curview hlview viewcomplete
1552 global pending_select
1553
1554 layoutmore
1555 if {$viewcomplete($curview)} {
1556 global commitidx varctok
1557 global numcommits startmsecs
1558
1559 if {[info exists pending_select]} {
1560 update
1561 reset_pending_select {}
1562
1563 if {[commitinview $pending_select $curview]} {
1564 selectline [rowofcommit $pending_select] 1
1565 } else {
1566 set row [first_real_row]
1567 selectline $row 1
1568 }
1569 }
1570 if {$commitidx($curview) > 0} {
1571 #set ms [expr {[clock clicks -milliseconds] - $startmsecs}]
1572 #puts "overall $ms ms for $numcommits commits"
1573 #puts "[llength $varctok($view)] arcs, $commitidx($view) commits"
1574 } else {
1575 show_status [mc "No commits selected"]
1576 }
1577 notbusy layout
1578 }
1579 return 0
1580}
1581
1582proc do_readcommit {id} {
1583 global tclencoding
1584
1585 # Invoke git-log to handle automatic encoding conversion
1586 set fd [open [concat | git log --no-color --pretty=raw -1 $id] r]
1587 # Read the results using i18n.logoutputencoding
1588 fconfigure $fd -translation lf -eofchar {}
1589 if {$tclencoding != {}} {
1590 fconfigure $fd -encoding $tclencoding
1591 }
1592 set contents [read $fd]
1593 close $fd
1594 # Remove the heading line
1595 regsub {^commit [0-9a-f]+\n} $contents {} contents
1596
1597 return $contents
1598}
1599
1600proc readcommit {id} {
1601 if {[catch {set contents [do_readcommit $id]}]} return
1602 parsecommit $id $contents 1
1603}
1604
1605proc parsecommit {id contents listed} {
1606 global commitinfo cdate
1607
1608 set inhdr 1
1609 set comment {}
1610 set headline {}
1611 set auname {}
1612 set audate {}
1613 set comname {}
1614 set comdate {}
1615 set hdrend [string first "\n\n" $contents]
1616 if {$hdrend < 0} {
1617 # should never happen...
1618 set hdrend [string length $contents]
1619 }
1620 set header [string range $contents 0 [expr {$hdrend - 1}]]
1621 set comment [string range $contents [expr {$hdrend + 2}] end]
1622 foreach line [split $header "\n"] {
1623 set line [split $line " "]
1624 set tag [lindex $line 0]
1625 if {$tag == "author"} {
1626 set audate [lindex $line end-1]
1627 set auname [join [lrange $line 1 end-2] " "]
1628 } elseif {$tag == "committer"} {
1629 set comdate [lindex $line end-1]
1630 set comname [join [lrange $line 1 end-2] " "]
1631 }
1632 }
1633 set headline {}
1634 # take the first non-blank line of the comment as the headline
1635 set headline [string trimleft $comment]
1636 set i [string first "\n" $headline]
1637 if {$i >= 0} {
1638 set headline [string range $headline 0 $i]
1639 }
1640 set headline [string trimright $headline]
1641 set i [string first "\r" $headline]
1642 if {$i >= 0} {
1643 set headline [string trimright [string range $headline 0 $i]]
1644 }
1645 if {!$listed} {
1646 # git log indents the comment by 4 spaces;
1647 # if we got this via git cat-file, add the indentation
1648 set newcomment {}
1649 foreach line [split $comment "\n"] {
1650 append newcomment " "
1651 append newcomment $line
1652 append newcomment "\n"
1653 }
1654 set comment $newcomment
1655 }
1656 if {$comdate != {}} {
1657 set cdate($id) $comdate
1658 }
1659 set commitinfo($id) [list $headline $auname $audate \
1660 $comname $comdate $comment]
1661}
1662
1663proc getcommit {id} {
1664 global commitdata commitinfo
1665
1666 if {[info exists commitdata($id)]} {
1667 parsecommit $id $commitdata($id) 1
1668 } else {
1669 readcommit $id
1670 if {![info exists commitinfo($id)]} {
1671 set commitinfo($id) [list [mc "No commit information available"]]
1672 }
1673 }
1674 return 1
1675}
1676
1677# Expand an abbreviated commit ID to a list of full 40-char IDs that match
1678# and are present in the current view.
1679# This is fairly slow...
1680proc longid {prefix} {
1681 global varcid curview
1682
1683 set ids {}
1684 foreach match [array names varcid "$curview,$prefix*"] {
1685 lappend ids [lindex [split $match ","] 1]
1686 }
1687 return $ids
1688}
1689
1690proc readrefs {} {
1691 global tagids idtags headids idheads tagobjid
1692 global otherrefids idotherrefs mainhead mainheadid
1693 global selecthead selectheadid
1694 global hideremotes
1695
1696 foreach v {tagids idtags headids idheads otherrefids idotherrefs} {
1697 catch {unset $v}
1698 }
1699 set refd [open [list | git show-ref -d] r]
1700 while {[gets $refd line] >= 0} {
1701 if {[string index $line 40] ne " "} continue
1702 set id [string range $line 0 39]
1703 set ref [string range $line 41 end]
1704 if {![string match "refs/*" $ref]} continue
1705 set name [string range $ref 5 end]
1706 if {[string match "remotes/*" $name]} {
1707 if {![string match "*/HEAD" $name] && !$hideremotes} {
1708 set headids($name) $id
1709 lappend idheads($id) $name
1710 }
1711 } elseif {[string match "heads/*" $name]} {
1712 set name [string range $name 6 end]
1713 set headids($name) $id
1714 lappend idheads($id) $name
1715 } elseif {[string match "tags/*" $name]} {
1716 # this lets refs/tags/foo^{} overwrite refs/tags/foo,
1717 # which is what we want since the former is the commit ID
1718 set name [string range $name 5 end]
1719 if {[string match "*^{}" $name]} {
1720 set name [string range $name 0 end-3]
1721 } else {
1722 set tagobjid($name) $id
1723 }
1724 set tagids($name) $id
1725 lappend idtags($id) $name
1726 } else {
1727 set otherrefids($name) $id
1728 lappend idotherrefs($id) $name
1729 }
1730 }
1731 catch {close $refd}
1732 set mainhead {}
1733 set mainheadid {}
1734 catch {
1735 set mainheadid [exec git rev-parse HEAD]
1736 set thehead [exec git symbolic-ref HEAD]
1737 if {[string match "refs/heads/*" $thehead]} {
1738 set mainhead [string range $thehead 11 end]
1739 }
1740 }
1741 set selectheadid {}
1742 if {$selecthead ne {}} {
1743 catch {
1744 set selectheadid [exec git rev-parse --verify $selecthead]
1745 }
1746 }
1747}
1748
1749# skip over fake commits
1750proc first_real_row {} {
1751 global nullid nullid2 numcommits
1752
1753 for {set row 0} {$row < $numcommits} {incr row} {
1754 set id [commitonrow $row]
1755 if {$id ne $nullid && $id ne $nullid2} {
1756 break
1757 }
1758 }
1759 return $row
1760}
1761
1762# update things for a head moved to a child of its previous location
1763proc movehead {id name} {
1764 global headids idheads
1765
1766 removehead $headids($name) $name
1767 set headids($name) $id
1768 lappend idheads($id) $name
1769}
1770
1771# update things when a head has been removed
1772proc removehead {id name} {
1773 global headids idheads
1774
1775 if {$idheads($id) eq $name} {
1776 unset idheads($id)
1777 } else {
1778 set i [lsearch -exact $idheads($id) $name]
1779 if {$i >= 0} {
1780 set idheads($id) [lreplace $idheads($id) $i $i]
1781 }
1782 }
1783 unset headids($name)
1784}
1785
1786proc ttk_toplevel {w args} {
1787 global use_ttk
1788 eval [linsert $args 0 ::toplevel $w]
1789 if {$use_ttk} {
1790 place [ttk::frame $w._toplevel_background] -x 0 -y 0 -relwidth 1 -relheight 1
1791 }
1792 return $w
1793}
1794
1795proc make_transient {window origin} {
1796 global have_tk85
1797
1798 # In MacOS Tk 8.4 transient appears to work by setting
1799 # overrideredirect, which is utterly useless, since the
1800 # windows get no border, and are not even kept above
1801 # the parent.
1802 if {!$have_tk85 && [tk windowingsystem] eq {aqua}} return
1803
1804 wm transient $window $origin
1805
1806 # Windows fails to place transient windows normally, so
1807 # schedule a callback to center them on the parent.
1808 if {[tk windowingsystem] eq {win32}} {
1809 after idle [list tk::PlaceWindow $window widget $origin]
1810 }
1811}
1812
1813proc show_error {w top msg} {
1814 global NS
1815 if {![info exists NS]} {set NS ""}
1816 if {[wm state $top] eq "withdrawn"} { wm deiconify $top }
1817 message $w.m -text $msg -justify center -aspect 400
1818 pack $w.m -side top -fill x -padx 20 -pady 20
1819 ${NS}::button $w.ok -default active -text [mc OK] -command "destroy $top"
1820 pack $w.ok -side bottom -fill x
1821 bind $top <Visibility> "grab $top; focus $top"
1822 bind $top <Key-Return> "destroy $top"
1823 bind $top <Key-space> "destroy $top"
1824 bind $top <Key-Escape> "destroy $top"
1825 tkwait window $top
1826}
1827
1828proc error_popup {msg {owner .}} {
1829 if {[tk windowingsystem] eq "win32"} {
1830 tk_messageBox -icon error -type ok -title [wm title .] \
1831 -parent $owner -message $msg
1832 } else {
1833 set w .error
1834 ttk_toplevel $w
1835 make_transient $w $owner
1836 show_error $w $w $msg
1837 }
1838}
1839
1840proc confirm_popup {msg {owner .}} {
1841 global confirm_ok NS
1842 set confirm_ok 0
1843 set w .confirm
1844 ttk_toplevel $w
1845 make_transient $w $owner
1846 message $w.m -text $msg -justify center -aspect 400
1847 pack $w.m -side top -fill x -padx 20 -pady 20
1848 ${NS}::button $w.ok -text [mc OK] -command "set confirm_ok 1; destroy $w"
1849 pack $w.ok -side left -fill x
1850 ${NS}::button $w.cancel -text [mc Cancel] -command "destroy $w"
1851 pack $w.cancel -side right -fill x
1852 bind $w <Visibility> "grab $w; focus $w"
1853 bind $w <Key-Return> "set confirm_ok 1; destroy $w"
1854 bind $w <Key-space> "set confirm_ok 1; destroy $w"
1855 bind $w <Key-Escape> "destroy $w"
1856 tk::PlaceWindow $w widget $owner
1857 tkwait window $w
1858 return $confirm_ok
1859}
1860
1861proc setoptions {} {
1862 if {[tk windowingsystem] ne "win32"} {
1863 option add *Panedwindow.showHandle 1 startupFile
1864 option add *Panedwindow.sashRelief raised startupFile
1865 if {[tk windowingsystem] ne "aqua"} {
1866 option add *Menu.font uifont startupFile
1867 }
1868 } else {
1869 option add *Menu.TearOff 0 startupFile
1870 }
1871 option add *Button.font uifont startupFile
1872 option add *Checkbutton.font uifont startupFile
1873 option add *Radiobutton.font uifont startupFile
1874 option add *Menubutton.font uifont startupFile
1875 option add *Label.font uifont startupFile
1876 option add *Message.font uifont startupFile
1877 option add *Entry.font uifont startupFile
1878 option add *Labelframe.font uifont startupFile
1879}
1880
1881# Make a menu and submenus.
1882# m is the window name for the menu, items is the list of menu items to add.
1883# Each item is a list {mc label type description options...}
1884# mc is ignored; it's so we can put mc there to alert xgettext
1885# label is the string that appears in the menu
1886# type is cascade, command or radiobutton (should add checkbutton)
1887# description depends on type; it's the sublist for cascade, the
1888# command to invoke for command, or {variable value} for radiobutton
1889proc makemenu {m items} {
1890 menu $m
1891 if {[tk windowingsystem] eq {aqua}} {
1892 set Meta1 Cmd
1893 } else {
1894 set Meta1 Ctrl
1895 }
1896 foreach i $items {
1897 set name [mc [lindex $i 1]]
1898 set type [lindex $i 2]
1899 set thing [lindex $i 3]
1900 set params [list $type]
1901 if {$name ne {}} {
1902 set u [string first "&" [string map {&& x} $name]]
1903 lappend params -label [string map {&& & & {}} $name]
1904 if {$u >= 0} {
1905 lappend params -underline $u
1906 }
1907 }
1908 switch -- $type {
1909 "cascade" {
1910 set submenu [string tolower [string map {& ""} [lindex $i 1]]]
1911 lappend params -menu $m.$submenu
1912 }
1913 "command" {
1914 lappend params -command $thing
1915 }
1916 "radiobutton" {
1917 lappend params -variable [lindex $thing 0] \
1918 -value [lindex $thing 1]
1919 }
1920 }
1921 set tail [lrange $i 4 end]
1922 regsub -all {\yMeta1\y} $tail $Meta1 tail
1923 eval $m add $params $tail
1924 if {$type eq "cascade"} {
1925 makemenu $m.$submenu $thing
1926 }
1927 }
1928}
1929
1930# translate string and remove ampersands
1931proc mca {str} {
1932 return [string map {&& & & {}} [mc $str]]
1933}
1934
1935proc makedroplist {w varname args} {
1936 global use_ttk
1937 if {$use_ttk} {
1938 set width 0
1939 foreach label $args {
1940 set cx [string length $label]
1941 if {$cx > $width} {set width $cx}
1942 }
1943 set gm [ttk::combobox $w -width $width -state readonly\
1944 -textvariable $varname -values $args]
1945 } else {
1946 set gm [eval [linsert $args 0 tk_optionMenu $w $varname]]
1947 }
1948 return $gm
1949}
1950
1951proc makewindow {} {
1952 global canv canv2 canv3 linespc charspc ctext cflist cscroll
1953 global tabstop
1954 global findtype findtypemenu findloc findstring fstring geometry
1955 global entries sha1entry sha1string sha1but
1956 global diffcontextstring diffcontext
1957 global ignorespace
1958 global maincursor textcursor curtextcursor
1959 global rowctxmenu fakerowmenu mergemax wrapcomment
1960 global highlight_files gdttype
1961 global searchstring sstring
1962 global bgcolor fgcolor bglist fglist diffcolors selectbgcolor
1963 global headctxmenu progresscanv progressitem progresscoords statusw
1964 global fprogitem fprogcoord lastprogupdate progupdatepending
1965 global rprogitem rprogcoord rownumsel numcommits
1966 global have_tk85 use_ttk NS
1967
1968 # The "mc" arguments here are purely so that xgettext
1969 # sees the following string as needing to be translated
1970 set file {
1971 mc "File" cascade {
1972 {mc "Update" command updatecommits -accelerator F5}
1973 {mc "Reload" command reloadcommits -accelerator Meta1-F5}
1974 {mc "Reread references" command rereadrefs}
1975 {mc "List references" command showrefs -accelerator F2}
1976 {xx "" separator}
1977 {mc "Start git gui" command {exec git gui &}}
1978 {xx "" separator}
1979 {mc "Quit" command doquit -accelerator Meta1-Q}
1980 }}
1981 set edit {
1982 mc "Edit" cascade {
1983 {mc "Preferences" command doprefs}
1984 }}
1985 set view {
1986 mc "View" cascade {
1987 {mc "New view..." command {newview 0} -accelerator Shift-F4}
1988 {mc "Edit view..." command editview -state disabled -accelerator F4}
1989 {mc "Delete view" command delview -state disabled}
1990 {xx "" separator}
1991 {mc "All files" radiobutton {selectedview 0} -command {showview 0}}
1992 }}
1993 if {[tk windowingsystem] ne "aqua"} {
1994 set help {
1995 mc "Help" cascade {
1996 {mc "About gitk" command about}
1997 {mc "Key bindings" command keys}
1998 }}
1999 set bar [list $file $edit $view $help]
2000 } else {
2001 proc ::tk::mac::ShowPreferences {} {doprefs}
2002 proc ::tk::mac::Quit {} {doquit}
2003 lset file end [lreplace [lindex $file end] end-1 end]
2004 set apple {
2005 xx "Apple" cascade {
2006 {mc "About gitk" command about}
2007 {xx "" separator}
2008 }}
2009 set help {
2010 mc "Help" cascade {
2011 {mc "Key bindings" command keys}
2012 }}
2013 set bar [list $apple $file $view $help]
2014 }
2015 makemenu .bar $bar
2016 . configure -menu .bar
2017
2018 if {$use_ttk} {
2019 # cover the non-themed toplevel with a themed frame.
2020 place [ttk::frame ._main_background] -x 0 -y 0 -relwidth 1 -relheight 1
2021 }
2022
2023 # the gui has upper and lower half, parts of a paned window.
2024 ${NS}::panedwindow .ctop -orient vertical
2025
2026 # possibly use assumed geometry
2027 if {![info exists geometry(pwsash0)]} {
2028 set geometry(topheight) [expr {15 * $linespc}]
2029 set geometry(topwidth) [expr {80 * $charspc}]
2030 set geometry(botheight) [expr {15 * $linespc}]
2031 set geometry(botwidth) [expr {50 * $charspc}]
2032 set geometry(pwsash0) [list [expr {40 * $charspc}] 2]
2033 set geometry(pwsash1) [list [expr {60 * $charspc}] 2]
2034 }
2035
2036 # the upper half will have a paned window, a scroll bar to the right, and some stuff below
2037 ${NS}::frame .tf -height $geometry(topheight) -width $geometry(topwidth)
2038 ${NS}::frame .tf.histframe
2039 ${NS}::panedwindow .tf.histframe.pwclist -orient horizontal
2040 if {!$use_ttk} {
2041 .tf.histframe.pwclist configure -sashpad 0 -handlesize 4
2042 }
2043
2044 # create three canvases
2045 set cscroll .tf.histframe.csb
2046 set canv .tf.histframe.pwclist.canv
2047 canvas $canv \
2048 -selectbackground $selectbgcolor \
2049 -background $bgcolor -bd 0 \
2050 -yscrollincr $linespc -yscrollcommand "scrollcanv $cscroll"
2051 .tf.histframe.pwclist add $canv
2052 set canv2 .tf.histframe.pwclist.canv2
2053 canvas $canv2 \
2054 -selectbackground $selectbgcolor \
2055 -background $bgcolor -bd 0 -yscrollincr $linespc
2056 .tf.histframe.pwclist add $canv2
2057 set canv3 .tf.histframe.pwclist.canv3
2058 canvas $canv3 \
2059 -selectbackground $selectbgcolor \
2060 -background $bgcolor -bd 0 -yscrollincr $linespc
2061 .tf.histframe.pwclist add $canv3
2062 if {$use_ttk} {
2063 bind .tf.histframe.pwclist <Map> {
2064 bind %W <Map> {}
2065 .tf.histframe.pwclist sashpos 1 [lindex $::geometry(pwsash1) 0]
2066 .tf.histframe.pwclist sashpos 0 [lindex $::geometry(pwsash0) 0]
2067 }
2068 } else {
2069 eval .tf.histframe.pwclist sash place 0 $geometry(pwsash0)
2070 eval .tf.histframe.pwclist sash place 1 $geometry(pwsash1)
2071 }
2072
2073 # a scroll bar to rule them
2074 ${NS}::scrollbar $cscroll -command {allcanvs yview}
2075 if {!$use_ttk} {$cscroll configure -highlightthickness 0}
2076 pack $cscroll -side right -fill y
2077 bind .tf.histframe.pwclist <Configure> {resizeclistpanes %W %w}
2078 lappend bglist $canv $canv2 $canv3
2079 pack .tf.histframe.pwclist -fill both -expand 1 -side left
2080
2081 # we have two button bars at bottom of top frame. Bar 1
2082 ${NS}::frame .tf.bar
2083 ${NS}::frame .tf.lbar -height 15
2084
2085 set sha1entry .tf.bar.sha1
2086 set entries $sha1entry
2087 set sha1but .tf.bar.sha1label
2088 button $sha1but -text [mc "SHA1 ID: "] -state disabled -relief flat \
2089 -command gotocommit -width 8
2090 $sha1but conf -disabledforeground [$sha1but cget -foreground]
2091 pack .tf.bar.sha1label -side left
2092 ${NS}::entry $sha1entry -width 40 -font textfont -textvariable sha1string
2093 trace add variable sha1string write sha1change
2094 pack $sha1entry -side left -pady 2
2095
2096 image create bitmap bm-left -data {
2097 #define left_width 16
2098 #define left_height 16
2099 static unsigned char left_bits[] = {
2100 0x00, 0x00, 0xc0, 0x01, 0xe0, 0x00, 0x70, 0x00, 0x38, 0x00, 0x1c, 0x00,
2101 0x0e, 0x00, 0xff, 0x7f, 0xff, 0x7f, 0xff, 0x7f, 0x0e, 0x00, 0x1c, 0x00,
2102 0x38, 0x00, 0x70, 0x00, 0xe0, 0x00, 0xc0, 0x01};
2103 }
2104 image create bitmap bm-right -data {
2105 #define right_width 16
2106 #define right_height 16
2107 static unsigned char right_bits[] = {
2108 0x00, 0x00, 0xc0, 0x01, 0x80, 0x03, 0x00, 0x07, 0x00, 0x0e, 0x00, 0x1c,
2109 0x00, 0x38, 0xff, 0x7f, 0xff, 0x7f, 0xff, 0x7f, 0x00, 0x38, 0x00, 0x1c,
2110 0x00, 0x0e, 0x00, 0x07, 0x80, 0x03, 0xc0, 0x01};
2111 }
2112 ${NS}::button .tf.bar.leftbut -image bm-left -command goback \
2113 -state disabled -width 26
2114 pack .tf.bar.leftbut -side left -fill y
2115 ${NS}::button .tf.bar.rightbut -image bm-right -command goforw \
2116 -state disabled -width 26
2117 pack .tf.bar.rightbut -side left -fill y
2118
2119 ${NS}::label .tf.bar.rowlabel -text [mc "Row"]
2120 set rownumsel {}
2121 ${NS}::label .tf.bar.rownum -width 7 -textvariable rownumsel \
2122 -relief sunken -anchor e
2123 ${NS}::label .tf.bar.rowlabel2 -text "/"
2124 ${NS}::label .tf.bar.numcommits -width 7 -textvariable numcommits \
2125 -relief sunken -anchor e
2126 pack .tf.bar.rowlabel .tf.bar.rownum .tf.bar.rowlabel2 .tf.bar.numcommits \
2127 -side left
2128 if {!$use_ttk} {
2129 foreach w {rownum numcommits} {.tf.bar.$w configure -font textfont}
2130 }
2131 global selectedline
2132 trace add variable selectedline write selectedline_change
2133
2134 # Status label and progress bar
2135 set statusw .tf.bar.status
2136 ${NS}::label $statusw -width 15 -relief sunken
2137 pack $statusw -side left -padx 5
2138 if {$use_ttk} {
2139 set progresscanv [ttk::progressbar .tf.bar.progress]
2140 } else {
2141 set h [expr {[font metrics uifont -linespace] + 2}]
2142 set progresscanv .tf.bar.progress
2143 canvas $progresscanv -relief sunken -height $h -borderwidth 2
2144 set progressitem [$progresscanv create rect -1 0 0 $h -fill green]
2145 set fprogitem [$progresscanv create rect -1 0 0 $h -fill yellow]
2146 set rprogitem [$progresscanv create rect -1 0 0 $h -fill red]
2147 }
2148 pack $progresscanv -side right -expand 1 -fill x -padx {0 2}
2149 set progresscoords {0 0}
2150 set fprogcoord 0
2151 set rprogcoord 0
2152 bind $progresscanv <Configure> adjustprogress
2153 set lastprogupdate [clock clicks -milliseconds]
2154 set progupdatepending 0
2155
2156 # build up the bottom bar of upper window
2157 ${NS}::label .tf.lbar.flabel -text "[mc "Find"] "
2158 ${NS}::button .tf.lbar.fnext -text [mc "next"] -command {dofind 1 1}
2159 ${NS}::button .tf.lbar.fprev -text [mc "prev"] -command {dofind -1 1}
2160 ${NS}::label .tf.lbar.flab2 -text " [mc "commit"] "
2161 pack .tf.lbar.flabel .tf.lbar.fnext .tf.lbar.fprev .tf.lbar.flab2 \
2162 -side left -fill y
2163 set gdttype [mc "containing:"]
2164 set gm [makedroplist .tf.lbar.gdttype gdttype \
2165 [mc "containing:"] \
2166 [mc "touching paths:"] \
2167 [mc "adding/removing string:"]]
2168 trace add variable gdttype write gdttype_change
2169 pack .tf.lbar.gdttype -side left -fill y
2170
2171 set findstring {}
2172 set fstring .tf.lbar.findstring
2173 lappend entries $fstring
2174 ${NS}::entry $fstring -width 30 -font textfont -textvariable findstring
2175 trace add variable findstring write find_change
2176 set findtype [mc "Exact"]
2177 set findtypemenu [makedroplist .tf.lbar.findtype \
2178 findtype [mc "Exact"] [mc "IgnCase"] [mc "Regexp"]]
2179 trace add variable findtype write findcom_change
2180 set findloc [mc "All fields"]
2181 makedroplist .tf.lbar.findloc findloc [mc "All fields"] [mc "Headline"] \
2182 [mc "Comments"] [mc "Author"] [mc "Committer"]
2183 trace add variable findloc write find_change
2184 pack .tf.lbar.findloc -side right
2185 pack .tf.lbar.findtype -side right
2186 pack $fstring -side left -expand 1 -fill x
2187
2188 # Finish putting the upper half of the viewer together
2189 pack .tf.lbar -in .tf -side bottom -fill x
2190 pack .tf.bar -in .tf -side bottom -fill x
2191 pack .tf.histframe -fill both -side top -expand 1
2192 .ctop add .tf
2193 if {!$use_ttk} {
2194 .ctop paneconfigure .tf -height $geometry(topheight)
2195 .ctop paneconfigure .tf -width $geometry(topwidth)
2196 }
2197
2198 # now build up the bottom
2199 ${NS}::panedwindow .pwbottom -orient horizontal
2200
2201 # lower left, a text box over search bar, scroll bar to the right
2202 # if we know window height, then that will set the lower text height, otherwise
2203 # we set lower text height which will drive window height
2204 if {[info exists geometry(main)]} {
2205 ${NS}::frame .bleft -width $geometry(botwidth)
2206 } else {
2207 ${NS}::frame .bleft -width $geometry(botwidth) -height $geometry(botheight)
2208 }
2209 ${NS}::frame .bleft.top
2210 ${NS}::frame .bleft.mid
2211 ${NS}::frame .bleft.bottom
2212
2213 ${NS}::button .bleft.top.search -text [mc "Search"] -command dosearch
2214 pack .bleft.top.search -side left -padx 5
2215 set sstring .bleft.top.sstring
2216 set searchstring ""
2217 ${NS}::entry $sstring -width 20 -font textfont -textvariable searchstring
2218 lappend entries $sstring
2219 trace add variable searchstring write incrsearch
2220 pack $sstring -side left -expand 1 -fill x
2221 ${NS}::radiobutton .bleft.mid.diff -text [mc "Diff"] \
2222 -command changediffdisp -variable diffelide -value {0 0}
2223 ${NS}::radiobutton .bleft.mid.old -text [mc "Old version"] \
2224 -command changediffdisp -variable diffelide -value {0 1}
2225 ${NS}::radiobutton .bleft.mid.new -text [mc "New version"] \
2226 -command changediffdisp -variable diffelide -value {1 0}
2227 ${NS}::label .bleft.mid.labeldiffcontext -text " [mc "Lines of context"]: "
2228 pack .bleft.mid.diff .bleft.mid.old .bleft.mid.new -side left
2229 spinbox .bleft.mid.diffcontext -width 5 -font textfont \
2230 -from 0 -increment 1 -to 10000000 \
2231 -validate all -validatecommand "diffcontextvalidate %P" \
2232 -textvariable diffcontextstring
2233 .bleft.mid.diffcontext set $diffcontext
2234 trace add variable diffcontextstring write diffcontextchange
2235 lappend entries .bleft.mid.diffcontext
2236 pack .bleft.mid.labeldiffcontext .bleft.mid.diffcontext -side left
2237 ${NS}::checkbutton .bleft.mid.ignspace -text [mc "Ignore space change"] \
2238 -command changeignorespace -variable ignorespace
2239 pack .bleft.mid.ignspace -side left -padx 5
2240 set ctext .bleft.bottom.ctext
2241 text $ctext -background $bgcolor -foreground $fgcolor \
2242 -state disabled -font textfont \
2243 -yscrollcommand scrolltext -wrap none \
2244 -xscrollcommand ".bleft.bottom.sbhorizontal set"
2245 if {$have_tk85} {
2246 $ctext conf -tabstyle wordprocessor
2247 }
2248 ${NS}::scrollbar .bleft.bottom.sb -command "$ctext yview"
2249 ${NS}::scrollbar .bleft.bottom.sbhorizontal -command "$ctext xview" -orient h
2250 pack .bleft.top -side top -fill x
2251 pack .bleft.mid -side top -fill x
2252 grid $ctext .bleft.bottom.sb -sticky nsew
2253 grid .bleft.bottom.sbhorizontal -sticky ew
2254 grid columnconfigure .bleft.bottom 0 -weight 1
2255 grid rowconfigure .bleft.bottom 0 -weight 1
2256 grid rowconfigure .bleft.bottom 1 -weight 0
2257 pack .bleft.bottom -side top -fill both -expand 1
2258 lappend bglist $ctext
2259 lappend fglist $ctext
2260
2261 $ctext tag conf comment -wrap $wrapcomment
2262 $ctext tag conf filesep -font textfontbold -back "#aaaaaa"
2263 $ctext tag conf hunksep -fore [lindex $diffcolors 2]
2264 $ctext tag conf d0 -fore [lindex $diffcolors 0]
2265 $ctext tag conf dresult -fore [lindex $diffcolors 1]
2266 $ctext tag conf m0 -fore red
2267 $ctext tag conf m1 -fore blue
2268 $ctext tag conf m2 -fore green
2269 $ctext tag conf m3 -fore purple
2270 $ctext tag conf m4 -fore brown
2271 $ctext tag conf m5 -fore "#009090"
2272 $ctext tag conf m6 -fore magenta
2273 $ctext tag conf m7 -fore "#808000"
2274 $ctext tag conf m8 -fore "#009000"
2275 $ctext tag conf m9 -fore "#ff0080"
2276 $ctext tag conf m10 -fore cyan
2277 $ctext tag conf m11 -fore "#b07070"
2278 $ctext tag conf m12 -fore "#70b0f0"
2279 $ctext tag conf m13 -fore "#70f0b0"
2280 $ctext tag conf m14 -fore "#f0b070"
2281 $ctext tag conf m15 -fore "#ff70b0"
2282 $ctext tag conf mmax -fore darkgrey
2283 set mergemax 16
2284 $ctext tag conf mresult -font textfontbold
2285 $ctext tag conf msep -font textfontbold
2286 $ctext tag conf found -back yellow
2287
2288 .pwbottom add .bleft
2289 if {!$use_ttk} {
2290 .pwbottom paneconfigure .bleft -width $geometry(botwidth)
2291 }
2292
2293 # lower right
2294 ${NS}::frame .bright
2295 ${NS}::frame .bright.mode
2296 ${NS}::radiobutton .bright.mode.patch -text [mc "Patch"] \
2297 -command reselectline -variable cmitmode -value "patch"
2298 ${NS}::radiobutton .bright.mode.tree -text [mc "Tree"] \
2299 -command reselectline -variable cmitmode -value "tree"
2300 grid .bright.mode.patch .bright.mode.tree -sticky ew
2301 pack .bright.mode -side top -fill x
2302 set cflist .bright.cfiles
2303 set indent [font measure mainfont "nn"]
2304 text $cflist \
2305 -selectbackground $selectbgcolor \
2306 -background $bgcolor -foreground $fgcolor \
2307 -font mainfont \
2308 -tabs [list $indent [expr {2 * $indent}]] \
2309 -yscrollcommand ".bright.sb set" \
2310 -cursor [. cget -cursor] \
2311 -spacing1 1 -spacing3 1
2312 lappend bglist $cflist
2313 lappend fglist $cflist
2314 ${NS}::scrollbar .bright.sb -command "$cflist yview"
2315 pack .bright.sb -side right -fill y
2316 pack $cflist -side left -fill both -expand 1
2317 $cflist tag configure highlight \
2318 -background [$cflist cget -selectbackground]
2319 $cflist tag configure bold -font mainfontbold
2320
2321 .pwbottom add .bright
2322 .ctop add .pwbottom
2323
2324 # restore window width & height if known
2325 if {[info exists geometry(main)]} {
2326 if {[scan $geometry(main) "%dx%d" w h] >= 2} {
2327 if {$w > [winfo screenwidth .]} {
2328 set w [winfo screenwidth .]
2329 }
2330 if {$h > [winfo screenheight .]} {
2331 set h [winfo screenheight .]
2332 }
2333 wm geometry . "${w}x$h"
2334 }
2335 }
2336
2337 if {[info exists geometry(state)] && $geometry(state) eq "zoomed"} {
2338 wm state . $geometry(state)
2339 }
2340
2341 if {[tk windowingsystem] eq {aqua}} {
2342 set M1B M1
2343 set ::BM "3"
2344 } else {
2345 set M1B Control
2346 set ::BM "2"
2347 }
2348
2349 if {$use_ttk} {
2350 bind .ctop <Map> {
2351 bind %W <Map> {}
2352 %W sashpos 0 $::geometry(topheight)
2353 }
2354 bind .pwbottom <Map> {
2355 bind %W <Map> {}
2356 %W sashpos 0 $::geometry(botwidth)
2357 }
2358 }
2359
2360 bind .pwbottom <Configure> {resizecdetpanes %W %w}
2361 pack .ctop -fill both -expand 1
2362 bindall <1> {selcanvline %W %x %y}
2363 #bindall <B1-Motion> {selcanvline %W %x %y}
2364 if {[tk windowingsystem] == "win32"} {
2365 bind . <MouseWheel> { windows_mousewheel_redirector %W %X %Y %D }
2366 bind $ctext <MouseWheel> { windows_mousewheel_redirector %W %X %Y %D ; break }
2367 } else {
2368 bindall <ButtonRelease-4> "allcanvs yview scroll -5 units"
2369 bindall <ButtonRelease-5> "allcanvs yview scroll 5 units"
2370 if {[tk windowingsystem] eq "aqua"} {
2371 bindall <MouseWheel> {
2372 set delta [expr {- (%D)}]
2373 allcanvs yview scroll $delta units
2374 }
2375 bindall <Shift-MouseWheel> {
2376 set delta [expr {- (%D)}]
2377 $canv xview scroll $delta units
2378 }
2379 }
2380 }
2381 bindall <$::BM> "canvscan mark %W %x %y"
2382 bindall <B$::BM-Motion> "canvscan dragto %W %x %y"
2383 bindkey <Home> selfirstline
2384 bindkey <End> sellastline
2385 bind . <Key-Up> "selnextline -1"
2386 bind . <Key-Down> "selnextline 1"
2387 bind . <Shift-Key-Up> "dofind -1 0"
2388 bind . <Shift-Key-Down> "dofind 1 0"
2389 bindkey <Key-Right> "goforw"
2390 bindkey <Key-Left> "goback"
2391 bind . <Key-Prior> "selnextpage -1"
2392 bind . <Key-Next> "selnextpage 1"
2393 bind . <$M1B-Home> "allcanvs yview moveto 0.0"
2394 bind . <$M1B-End> "allcanvs yview moveto 1.0"
2395 bind . <$M1B-Key-Up> "allcanvs yview scroll -1 units"
2396 bind . <$M1B-Key-Down> "allcanvs yview scroll 1 units"
2397 bind . <$M1B-Key-Prior> "allcanvs yview scroll -1 pages"
2398 bind . <$M1B-Key-Next> "allcanvs yview scroll 1 pages"
2399 bindkey <Key-Delete> "$ctext yview scroll -1 pages"
2400 bindkey <Key-BackSpace> "$ctext yview scroll -1 pages"
2401 bindkey <Key-space> "$ctext yview scroll 1 pages"
2402 bindkey p "selnextline -1"
2403 bindkey n "selnextline 1"
2404 bindkey z "goback"
2405 bindkey x "goforw"
2406 bindkey i "selnextline -1"
2407 bindkey k "selnextline 1"
2408 bindkey j "goback"
2409 bindkey l "goforw"
2410 bindkey b prevfile
2411 bindkey d "$ctext yview scroll 18 units"
2412 bindkey u "$ctext yview scroll -18 units"
2413 bindkey / {focus $fstring}
2414 bindkey <Key-KP_Divide> {focus $fstring}
2415 bindkey <Key-Return> {dofind 1 1}
2416 bindkey ? {dofind -1 1}
2417 bindkey f nextfile
2418 bind . <F5> updatecommits
2419 bind . <$M1B-F5> reloadcommits
2420 bind . <F2> showrefs
2421 bind . <Shift-F4> {newview 0}
2422 catch { bind . <Shift-Key-XF86_Switch_VT_4> {newview 0} }
2423 bind . <F4> edit_or_newview
2424 bind . <$M1B-q> doquit
2425 bind . <$M1B-f> {dofind 1 1}
2426 bind . <$M1B-g> {dofind 1 0}
2427 bind . <$M1B-r> dosearchback
2428 bind . <$M1B-s> dosearch
2429 bind . <$M1B-equal> {incrfont 1}
2430 bind . <$M1B-plus> {incrfont 1}
2431 bind . <$M1B-KP_Add> {incrfont 1}
2432 bind . <$M1B-minus> {incrfont -1}
2433 bind . <$M1B-KP_Subtract> {incrfont -1}
2434 wm protocol . WM_DELETE_WINDOW doquit
2435 bind . <Destroy> {stop_backends}
2436 bind . <Button-1> "click %W"
2437 bind $fstring <Key-Return> {dofind 1 1}
2438 bind $sha1entry <Key-Return> {gotocommit; break}
2439 bind $sha1entry <<PasteSelection>> clearsha1
2440 bind $cflist <1> {sel_flist %W %x %y; break}
2441 bind $cflist <B1-Motion> {sel_flist %W %x %y; break}
2442 bind $cflist <ButtonRelease-1> {treeclick %W %x %y}
2443 global ctxbut
2444 bind $cflist $ctxbut {pop_flist_menu %W %X %Y %x %y}
2445 bind $ctext $ctxbut {pop_diff_menu %W %X %Y %x %y}
2446
2447 set maincursor [. cget -cursor]
2448 set textcursor [$ctext cget -cursor]
2449 set curtextcursor $textcursor
2450
2451 set rowctxmenu .rowctxmenu
2452 makemenu $rowctxmenu {
2453 {mc "Diff this -> selected" command {diffvssel 0}}
2454 {mc "Diff selected -> this" command {diffvssel 1}}
2455 {mc "Make patch" command mkpatch}
2456 {mc "Create tag" command mktag}
2457 {mc "Write commit to file" command writecommit}
2458 {mc "Create new branch" command mkbranch}
2459 {mc "Cherry-pick this commit" command cherrypick}
2460 {mc "Reset HEAD branch to here" command resethead}
2461 {mc "Mark this commit" command markhere}
2462 {mc "Return to mark" command gotomark}
2463 {mc "Find descendant of this and mark" command find_common_desc}
2464 {mc "Compare with marked commit" command compare_commits}
2465 }
2466 $rowctxmenu configure -tearoff 0
2467
2468 set fakerowmenu .fakerowmenu
2469 makemenu $fakerowmenu {
2470 {mc "Diff this -> selected" command {diffvssel 0}}
2471 {mc "Diff selected -> this" command {diffvssel 1}}
2472 {mc "Make patch" command mkpatch}
2473 }
2474 $fakerowmenu configure -tearoff 0
2475
2476 set headctxmenu .headctxmenu
2477 makemenu $headctxmenu {
2478 {mc "Check out this branch" command cobranch}
2479 {mc "Remove this branch" command rmbranch}
2480 }
2481 $headctxmenu configure -tearoff 0
2482
2483 global flist_menu
2484 set flist_menu .flistctxmenu
2485 makemenu $flist_menu {
2486 {mc "Highlight this too" command {flist_hl 0}}
2487 {mc "Highlight this only" command {flist_hl 1}}
2488 {mc "External diff" command {external_diff}}
2489 {mc "Blame parent commit" command {external_blame 1}}
2490 }
2491 $flist_menu configure -tearoff 0
2492
2493 global diff_menu
2494 set diff_menu .diffctxmenu
2495 makemenu $diff_menu {
2496 {mc "Show origin of this line" command show_line_source}
2497 {mc "Run git gui blame on this line" command {external_blame_diff}}
2498 }
2499 $diff_menu configure -tearoff 0
2500}
2501
2502# Windows sends all mouse wheel events to the current focused window, not
2503# the one where the mouse hovers, so bind those events here and redirect
2504# to the correct window
2505proc windows_mousewheel_redirector {W X Y D} {
2506 global canv canv2 canv3
2507 set w [winfo containing -displayof $W $X $Y]
2508 if {$w ne ""} {
2509 set u [expr {$D < 0 ? 5 : -5}]
2510 if {$w == $canv || $w == $canv2 || $w == $canv3} {
2511 allcanvs yview scroll $u units
2512 } else {
2513 catch {
2514 $w yview scroll $u units
2515 }
2516 }
2517 }
2518}
2519
2520# Update row number label when selectedline changes
2521proc selectedline_change {n1 n2 op} {
2522 global selectedline rownumsel
2523
2524 if {$selectedline eq {}} {
2525 set rownumsel {}
2526 } else {
2527 set rownumsel [expr {$selectedline + 1}]
2528 }
2529}
2530
2531# mouse-2 makes all windows scan vertically, but only the one
2532# the cursor is in scans horizontally
2533proc canvscan {op w x y} {
2534 global canv canv2 canv3
2535 foreach c [list $canv $canv2 $canv3] {
2536 if {$c == $w} {
2537 $c scan $op $x $y
2538 } else {
2539 $c scan $op 0 $y
2540 }
2541 }
2542}
2543
2544proc scrollcanv {cscroll f0 f1} {
2545 $cscroll set $f0 $f1
2546 drawvisible
2547 flushhighlights
2548}
2549
2550# when we make a key binding for the toplevel, make sure
2551# it doesn't get triggered when that key is pressed in the
2552# find string entry widget.
2553proc bindkey {ev script} {
2554 global entries
2555 bind . $ev $script
2556 set escript [bind Entry $ev]
2557 if {$escript == {}} {
2558 set escript [bind Entry <Key>]
2559 }
2560 foreach e $entries {
2561 bind $e $ev "$escript; break"
2562 }
2563}
2564
2565# set the focus back to the toplevel for any click outside
2566# the entry widgets
2567proc click {w} {
2568 global ctext entries
2569 foreach e [concat $entries $ctext] {
2570 if {$w == $e} return
2571 }
2572 focus .
2573}
2574
2575# Adjust the progress bar for a change in requested extent or canvas size
2576proc adjustprogress {} {
2577 global progresscanv progressitem progresscoords
2578 global fprogitem fprogcoord lastprogupdate progupdatepending
2579 global rprogitem rprogcoord use_ttk
2580
2581 if {$use_ttk} {
2582 $progresscanv configure -value [expr {int($fprogcoord * 100)}]
2583 return
2584 }
2585
2586 set w [expr {[winfo width $progresscanv] - 4}]
2587 set x0 [expr {$w * [lindex $progresscoords 0]}]
2588 set x1 [expr {$w * [lindex $progresscoords 1]}]
2589 set h [winfo height $progresscanv]
2590 $progresscanv coords $progressitem $x0 0 $x1 $h
2591 $progresscanv coords $fprogitem 0 0 [expr {$w * $fprogcoord}] $h
2592 $progresscanv coords $rprogitem 0 0 [expr {$w * $rprogcoord}] $h
2593 set now [clock clicks -milliseconds]
2594 if {$now >= $lastprogupdate + 100} {
2595 set progupdatepending 0
2596 update
2597 } elseif {!$progupdatepending} {
2598 set progupdatepending 1
2599 after [expr {$lastprogupdate + 100 - $now}] doprogupdate
2600 }
2601}
2602
2603proc doprogupdate {} {
2604 global lastprogupdate progupdatepending
2605
2606 if {$progupdatepending} {
2607 set progupdatepending 0
2608 set lastprogupdate [clock clicks -milliseconds]
2609 update
2610 }
2611}
2612
2613proc savestuff {w} {
2614 global canv canv2 canv3 mainfont textfont uifont tabstop
2615 global stuffsaved findmergefiles maxgraphpct
2616 global maxwidth showneartags showlocalchanges
2617 global viewname viewfiles viewargs viewargscmd viewperm nextviewnum
2618 global cmitmode wrapcomment datetimeformat limitdiffs
2619 global colors bgcolor fgcolor diffcolors diffcontext selectbgcolor
2620 global autoselect extdifftool perfile_attrs markbgcolor use_ttk
2621 global hideremotes want_ttk
2622
2623 if {$stuffsaved} return
2624 if {![winfo viewable .]} return
2625 catch {
2626 set f [open "~/.gitk-new" w]
2627 if {$::tcl_platform(platform) eq {windows}} {
2628 file attributes "~/.gitk-new" -hidden true
2629 }
2630 puts $f [list set mainfont $mainfont]
2631 puts $f [list set textfont $textfont]
2632 puts $f [list set uifont $uifont]
2633 puts $f [list set tabstop $tabstop]
2634 puts $f [list set findmergefiles $findmergefiles]
2635 puts $f [list set maxgraphpct $maxgraphpct]
2636 puts $f [list set maxwidth $maxwidth]
2637 puts $f [list set cmitmode $cmitmode]
2638 puts $f [list set wrapcomment $wrapcomment]
2639 puts $f [list set autoselect $autoselect]
2640 puts $f [list set showneartags $showneartags]
2641 puts $f [list set hideremotes $hideremotes]
2642 puts $f [list set showlocalchanges $showlocalchanges]
2643 puts $f [list set datetimeformat $datetimeformat]
2644 puts $f [list set limitdiffs $limitdiffs]
2645 puts $f [list set want_ttk $want_ttk]
2646 puts $f [list set bgcolor $bgcolor]
2647 puts $f [list set fgcolor $fgcolor]
2648 puts $f [list set colors $colors]
2649 puts $f [list set diffcolors $diffcolors]
2650 puts $f [list set markbgcolor $markbgcolor]
2651 puts $f [list set diffcontext $diffcontext]
2652 puts $f [list set selectbgcolor $selectbgcolor]
2653 puts $f [list set extdifftool $extdifftool]
2654 puts $f [list set perfile_attrs $perfile_attrs]
2655
2656 puts $f "set geometry(main) [wm geometry .]"
2657 puts $f "set geometry(state) [wm state .]"
2658 puts $f "set geometry(topwidth) [winfo width .tf]"
2659 puts $f "set geometry(topheight) [winfo height .tf]"
2660 if {$use_ttk} {
2661 puts $f "set geometry(pwsash0) \"[.tf.histframe.pwclist sashpos 0] 1\""
2662 puts $f "set geometry(pwsash1) \"[.tf.histframe.pwclist sashpos 1] 1\""
2663 } else {
2664 puts $f "set geometry(pwsash0) \"[.tf.histframe.pwclist sash coord 0]\""
2665 puts $f "set geometry(pwsash1) \"[.tf.histframe.pwclist sash coord 1]\""
2666 }
2667 puts $f "set geometry(botwidth) [winfo width .bleft]"
2668 puts $f "set geometry(botheight) [winfo height .bleft]"
2669
2670 puts -nonewline $f "set permviews {"
2671 for {set v 0} {$v < $nextviewnum} {incr v} {
2672 if {$viewperm($v)} {
2673 puts $f "{[list $viewname($v) $viewfiles($v) $viewargs($v) $viewargscmd($v)]}"
2674 }
2675 }
2676 puts $f "}"
2677 close $f
2678 file rename -force "~/.gitk-new" "~/.gitk"
2679 }
2680 set stuffsaved 1
2681}
2682
2683proc resizeclistpanes {win w} {
2684 global oldwidth use_ttk
2685 if {[info exists oldwidth($win)]} {
2686 if {$use_ttk} {
2687 set s0 [$win sashpos 0]
2688 set s1 [$win sashpos 1]
2689 } else {
2690 set s0 [$win sash coord 0]
2691 set s1 [$win sash coord 1]
2692 }
2693 if {$w < 60} {
2694 set sash0 [expr {int($w/2 - 2)}]
2695 set sash1 [expr {int($w*5/6 - 2)}]
2696 } else {
2697 set factor [expr {1.0 * $w / $oldwidth($win)}]
2698 set sash0 [expr {int($factor * [lindex $s0 0])}]
2699 set sash1 [expr {int($factor * [lindex $s1 0])}]
2700 if {$sash0 < 30} {
2701 set sash0 30
2702 }
2703 if {$sash1 < $sash0 + 20} {
2704 set sash1 [expr {$sash0 + 20}]
2705 }
2706 if {$sash1 > $w - 10} {
2707 set sash1 [expr {$w - 10}]
2708 if {$sash0 > $sash1 - 20} {
2709 set sash0 [expr {$sash1 - 20}]
2710 }
2711 }
2712 }
2713 if {$use_ttk} {
2714 $win sashpos 0 $sash0
2715 $win sashpos 1 $sash1
2716 } else {
2717 $win sash place 0 $sash0 [lindex $s0 1]
2718 $win sash place 1 $sash1 [lindex $s1 1]
2719 }
2720 }
2721 set oldwidth($win) $w
2722}
2723
2724proc resizecdetpanes {win w} {
2725 global oldwidth use_ttk
2726 if {[info exists oldwidth($win)]} {
2727 if {$use_ttk} {
2728 set s0 [$win sashpos 0]
2729 } else {
2730 set s0 [$win sash coord 0]
2731 }
2732 if {$w < 60} {
2733 set sash0 [expr {int($w*3/4 - 2)}]
2734 } else {
2735 set factor [expr {1.0 * $w / $oldwidth($win)}]
2736 set sash0 [expr {int($factor * [lindex $s0 0])}]
2737 if {$sash0 < 45} {
2738 set sash0 45
2739 }
2740 if {$sash0 > $w - 15} {
2741 set sash0 [expr {$w - 15}]
2742 }
2743 }
2744 if {$use_ttk} {
2745 $win sashpos 0 $sash0
2746 } else {
2747 $win sash place 0 $sash0 [lindex $s0 1]
2748 }
2749 }
2750 set oldwidth($win) $w
2751}
2752
2753proc allcanvs args {
2754 global canv canv2 canv3
2755 eval $canv $args
2756 eval $canv2 $args
2757 eval $canv3 $args
2758}
2759
2760proc bindall {event action} {
2761 global canv canv2 canv3
2762 bind $canv $event $action
2763 bind $canv2 $event $action
2764 bind $canv3 $event $action
2765}
2766
2767proc about {} {
2768 global uifont NS
2769 set w .about
2770 if {[winfo exists $w]} {
2771 raise $w
2772 return
2773 }
2774 ttk_toplevel $w
2775 wm title $w [mc "About gitk"]
2776 make_transient $w .
2777 message $w.m -text [mc "
2778Gitk - a commit viewer for git
2779
2780Copyright \u00a9 2005-2009 Paul Mackerras
2781
2782Use and redistribute under the terms of the GNU General Public License"] \
2783 -justify center -aspect 400 -border 2 -bg white -relief groove
2784 pack $w.m -side top -fill x -padx 2 -pady 2
2785 ${NS}::button $w.ok -text [mc "Close"] -command "destroy $w" -default active
2786 pack $w.ok -side bottom
2787 bind $w <Visibility> "focus $w.ok"
2788 bind $w <Key-Escape> "destroy $w"
2789 bind $w <Key-Return> "destroy $w"
2790 tk::PlaceWindow $w widget .
2791}
2792
2793proc keys {} {
2794 global NS
2795 set w .keys
2796 if {[winfo exists $w]} {
2797 raise $w
2798 return
2799 }
2800 if {[tk windowingsystem] eq {aqua}} {
2801 set M1T Cmd
2802 } else {
2803 set M1T Ctrl
2804 }
2805 ttk_toplevel $w
2806 wm title $w [mc "Gitk key bindings"]
2807 make_transient $w .
2808 message $w.m -text "
2809[mc "Gitk key bindings:"]
2810
2811[mc "<%s-Q> Quit" $M1T]
2812[mc "<Home> Move to first commit"]
2813[mc "<End> Move to last commit"]
2814[mc "<Up>, p, i Move up one commit"]
2815[mc "<Down>, n, k Move down one commit"]
2816[mc "<Left>, z, j Go back in history list"]
2817[mc "<Right>, x, l Go forward in history list"]
2818[mc "<PageUp> Move up one page in commit list"]
2819[mc "<PageDown> Move down one page in commit list"]
2820[mc "<%s-Home> Scroll to top of commit list" $M1T]
2821[mc "<%s-End> Scroll to bottom of commit list" $M1T]
2822[mc "<%s-Up> Scroll commit list up one line" $M1T]
2823[mc "<%s-Down> Scroll commit list down one line" $M1T]
2824[mc "<%s-PageUp> Scroll commit list up one page" $M1T]
2825[mc "<%s-PageDown> Scroll commit list down one page" $M1T]
2826[mc "<Shift-Up> Find backwards (upwards, later commits)"]
2827[mc "<Shift-Down> Find forwards (downwards, earlier commits)"]
2828[mc "<Delete>, b Scroll diff view up one page"]
2829[mc "<Backspace> Scroll diff view up one page"]
2830[mc "<Space> Scroll diff view down one page"]
2831[mc "u Scroll diff view up 18 lines"]
2832[mc "d Scroll diff view down 18 lines"]
2833[mc "<%s-F> Find" $M1T]
2834[mc "<%s-G> Move to next find hit" $M1T]
2835[mc "<Return> Move to next find hit"]
2836[mc "/ Focus the search box"]
2837[mc "? Move to previous find hit"]
2838[mc "f Scroll diff view to next file"]
2839[mc "<%s-S> Search for next hit in diff view" $M1T]
2840[mc "<%s-R> Search for previous hit in diff view" $M1T]
2841[mc "<%s-KP+> Increase font size" $M1T]
2842[mc "<%s-plus> Increase font size" $M1T]
2843[mc "<%s-KP-> Decrease font size" $M1T]
2844[mc "<%s-minus> Decrease font size" $M1T]
2845[mc "<F5> Update"]
2846" \
2847 -justify left -bg white -border 2 -relief groove
2848 pack $w.m -side top -fill both -padx 2 -pady 2
2849 ${NS}::button $w.ok -text [mc "Close"] -command "destroy $w" -default active
2850 bind $w <Key-Escape> [list destroy $w]
2851 pack $w.ok -side bottom
2852 bind $w <Visibility> "focus $w.ok"
2853 bind $w <Key-Escape> "destroy $w"
2854 bind $w <Key-Return> "destroy $w"
2855}
2856
2857# Procedures for manipulating the file list window at the
2858# bottom right of the overall window.
2859
2860proc treeview {w l openlevs} {
2861 global treecontents treediropen treeheight treeparent treeindex
2862
2863 set ix 0
2864 set treeindex() 0
2865 set lev 0
2866 set prefix {}
2867 set prefixend -1
2868 set prefendstack {}
2869 set htstack {}
2870 set ht 0
2871 set treecontents() {}
2872 $w conf -state normal
2873 foreach f $l {
2874 while {[string range $f 0 $prefixend] ne $prefix} {
2875 if {$lev <= $openlevs} {
2876 $w mark set e:$treeindex($prefix) "end -1c"
2877 $w mark gravity e:$treeindex($prefix) left
2878 }
2879 set treeheight($prefix) $ht
2880 incr ht [lindex $htstack end]
2881 set htstack [lreplace $htstack end end]
2882 set prefixend [lindex $prefendstack end]
2883 set prefendstack [lreplace $prefendstack end end]
2884 set prefix [string range $prefix 0 $prefixend]
2885 incr lev -1
2886 }
2887 set tail [string range $f [expr {$prefixend+1}] end]
2888 while {[set slash [string first "/" $tail]] >= 0} {
2889 lappend htstack $ht
2890 set ht 0
2891 lappend prefendstack $prefixend
2892 incr prefixend [expr {$slash + 1}]
2893 set d [string range $tail 0 $slash]
2894 lappend treecontents($prefix) $d
2895 set oldprefix $prefix
2896 append prefix $d
2897 set treecontents($prefix) {}
2898 set treeindex($prefix) [incr ix]
2899 set treeparent($prefix) $oldprefix
2900 set tail [string range $tail [expr {$slash+1}] end]
2901 if {$lev <= $openlevs} {
2902 set ht 1
2903 set treediropen($prefix) [expr {$lev < $openlevs}]
2904 set bm [expr {$lev == $openlevs? "tri-rt": "tri-dn"}]
2905 $w mark set d:$ix "end -1c"
2906 $w mark gravity d:$ix left
2907 set str "\n"
2908 for {set i 0} {$i < $lev} {incr i} {append str "\t"}
2909 $w insert end $str
2910 $w image create end -align center -image $bm -padx 1 \
2911 -name a:$ix
2912 $w insert end $d [highlight_tag $prefix]
2913 $w mark set s:$ix "end -1c"
2914 $w mark gravity s:$ix left
2915 }
2916 incr lev
2917 }
2918 if {$tail ne {}} {
2919 if {$lev <= $openlevs} {
2920 incr ht
2921 set str "\n"
2922 for {set i 0} {$i < $lev} {incr i} {append str "\t"}
2923 $w insert end $str
2924 $w insert end $tail [highlight_tag $f]
2925 }
2926 lappend treecontents($prefix) $tail
2927 }
2928 }
2929 while {$htstack ne {}} {
2930 set treeheight($prefix) $ht
2931 incr ht [lindex $htstack end]
2932 set htstack [lreplace $htstack end end]
2933 set prefixend [lindex $prefendstack end]
2934 set prefendstack [lreplace $prefendstack end end]
2935 set prefix [string range $prefix 0 $prefixend]
2936 }
2937 $w conf -state disabled
2938}
2939
2940proc linetoelt {l} {
2941 global treeheight treecontents
2942
2943 set y 2
2944 set prefix {}
2945 while {1} {
2946 foreach e $treecontents($prefix) {
2947 if {$y == $l} {
2948 return "$prefix$e"
2949 }
2950 set n 1
2951 if {[string index $e end] eq "/"} {
2952 set n $treeheight($prefix$e)
2953 if {$y + $n > $l} {
2954 append prefix $e
2955 incr y
2956 break
2957 }
2958 }
2959 incr y $n
2960 }
2961 }
2962}
2963
2964proc highlight_tree {y prefix} {
2965 global treeheight treecontents cflist
2966
2967 foreach e $treecontents($prefix) {
2968 set path $prefix$e
2969 if {[highlight_tag $path] ne {}} {
2970 $cflist tag add bold $y.0 "$y.0 lineend"
2971 }
2972 incr y
2973 if {[string index $e end] eq "/" && $treeheight($path) > 1} {
2974 set y [highlight_tree $y $path]
2975 }
2976 }
2977 return $y
2978}
2979
2980proc treeclosedir {w dir} {
2981 global treediropen treeheight treeparent treeindex
2982
2983 set ix $treeindex($dir)
2984 $w conf -state normal
2985 $w delete s:$ix e:$ix
2986 set treediropen($dir) 0
2987 $w image configure a:$ix -image tri-rt
2988 $w conf -state disabled
2989 set n [expr {1 - $treeheight($dir)}]
2990 while {$dir ne {}} {
2991 incr treeheight($dir) $n
2992 set dir $treeparent($dir)
2993 }
2994}
2995
2996proc treeopendir {w dir} {
2997 global treediropen treeheight treeparent treecontents treeindex
2998
2999 set ix $treeindex($dir)
3000 $w conf -state normal
3001 $w image configure a:$ix -image tri-dn
3002 $w mark set e:$ix s:$ix
3003 $w mark gravity e:$ix right
3004 set lev 0
3005 set str "\n"
3006 set n [llength $treecontents($dir)]
3007 for {set x $dir} {$x ne {}} {set x $treeparent($x)} {
3008 incr lev
3009 append str "\t"
3010 incr treeheight($x) $n
3011 }
3012 foreach e $treecontents($dir) {
3013 set de $dir$e
3014 if {[string index $e end] eq "/"} {
3015 set iy $treeindex($de)
3016 $w mark set d:$iy e:$ix
3017 $w mark gravity d:$iy left
3018 $w insert e:$ix $str
3019 set treediropen($de) 0
3020 $w image create e:$ix -align center -image tri-rt -padx 1 \
3021 -name a:$iy
3022 $w insert e:$ix $e [highlight_tag $de]
3023 $w mark set s:$iy e:$ix
3024 $w mark gravity s:$iy left
3025 set treeheight($de) 1
3026 } else {
3027 $w insert e:$ix $str
3028 $w insert e:$ix $e [highlight_tag $de]
3029 }
3030 }
3031 $w mark gravity e:$ix right
3032 $w conf -state disabled
3033 set treediropen($dir) 1
3034 set top [lindex [split [$w index @0,0] .] 0]
3035 set ht [$w cget -height]
3036 set l [lindex [split [$w index s:$ix] .] 0]
3037 if {$l < $top} {
3038 $w yview $l.0
3039 } elseif {$l + $n + 1 > $top + $ht} {
3040 set top [expr {$l + $n + 2 - $ht}]
3041 if {$l < $top} {
3042 set top $l
3043 }
3044 $w yview $top.0
3045 }
3046}
3047
3048proc treeclick {w x y} {
3049 global treediropen cmitmode ctext cflist cflist_top
3050
3051 if {$cmitmode ne "tree"} return
3052 if {![info exists cflist_top]} return
3053 set l [lindex [split [$w index "@$x,$y"] "."] 0]
3054 $cflist tag remove highlight $cflist_top.0 "$cflist_top.0 lineend"
3055 $cflist tag add highlight $l.0 "$l.0 lineend"
3056 set cflist_top $l
3057 if {$l == 1} {
3058 $ctext yview 1.0
3059 return
3060 }
3061 set e [linetoelt $l]
3062 if {[string index $e end] ne "/"} {
3063 showfile $e
3064 } elseif {$treediropen($e)} {
3065 treeclosedir $w $e
3066 } else {
3067 treeopendir $w $e
3068 }
3069}
3070
3071proc setfilelist {id} {
3072 global treefilelist cflist jump_to_here
3073
3074 treeview $cflist $treefilelist($id) 0
3075 if {$jump_to_here ne {}} {
3076 set f [lindex $jump_to_here 0]
3077 if {[lsearch -exact $treefilelist($id) $f] >= 0} {
3078 showfile $f
3079 }
3080 }
3081}
3082
3083image create bitmap tri-rt -background black -foreground blue -data {
3084 #define tri-rt_width 13
3085 #define tri-rt_height 13
3086 static unsigned char tri-rt_bits[] = {
3087 0x00, 0x00, 0x00, 0x00, 0x10, 0x00, 0x30, 0x00, 0x70, 0x00, 0xf0, 0x00,
3088 0xf0, 0x01, 0xf0, 0x00, 0x70, 0x00, 0x30, 0x00, 0x10, 0x00, 0x00, 0x00,
3089 0x00, 0x00};
3090} -maskdata {
3091 #define tri-rt-mask_width 13
3092 #define tri-rt-mask_height 13
3093 static unsigned char tri-rt-mask_bits[] = {
3094 0x08, 0x00, 0x18, 0x00, 0x38, 0x00, 0x78, 0x00, 0xf8, 0x00, 0xf8, 0x01,
3095 0xf8, 0x03, 0xf8, 0x01, 0xf8, 0x00, 0x78, 0x00, 0x38, 0x00, 0x18, 0x00,
3096 0x08, 0x00};
3097}
3098image create bitmap tri-dn -background black -foreground blue -data {
3099 #define tri-dn_width 13
3100 #define tri-dn_height 13
3101 static unsigned char tri-dn_bits[] = {
3102 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0xfc, 0x07, 0xf8, 0x03,
3103 0xf0, 0x01, 0xe0, 0x00, 0x40, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
3104 0x00, 0x00};
3105} -maskdata {
3106 #define tri-dn-mask_width 13
3107 #define tri-dn-mask_height 13
3108 static unsigned char tri-dn-mask_bits[] = {
3109 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0xff, 0x1f, 0xfe, 0x0f, 0xfc, 0x07,
3110 0xf8, 0x03, 0xf0, 0x01, 0xe0, 0x00, 0x40, 0x00, 0x00, 0x00, 0x00, 0x00,
3111 0x00, 0x00};
3112}
3113
3114image create bitmap reficon-T -background black -foreground yellow -data {
3115 #define tagicon_width 13
3116 #define tagicon_height 9
3117 static unsigned char tagicon_bits[] = {
3118 0x00, 0x00, 0x00, 0x00, 0xf0, 0x07, 0xf8, 0x07,
3119 0xfc, 0x07, 0xf8, 0x07, 0xf0, 0x07, 0x00, 0x00, 0x00, 0x00};
3120} -maskdata {
3121 #define tagicon-mask_width 13
3122 #define tagicon-mask_height 9
3123 static unsigned char tagicon-mask_bits[] = {
3124 0x00, 0x00, 0xf0, 0x0f, 0xf8, 0x0f, 0xfc, 0x0f,
3125 0xfe, 0x0f, 0xfc, 0x0f, 0xf8, 0x0f, 0xf0, 0x0f, 0x00, 0x00};
3126}
3127set rectdata {
3128 #define headicon_width 13
3129 #define headicon_height 9
3130 static unsigned char headicon_bits[] = {
3131 0x00, 0x00, 0x00, 0x00, 0xf8, 0x07, 0xf8, 0x07,
3132 0xf8, 0x07, 0xf8, 0x07, 0xf8, 0x07, 0x00, 0x00, 0x00, 0x00};
3133}
3134set rectmask {
3135 #define headicon-mask_width 13
3136 #define headicon-mask_height 9
3137 static unsigned char headicon-mask_bits[] = {
3138 0x00, 0x00, 0xfc, 0x0f, 0xfc, 0x0f, 0xfc, 0x0f,
3139 0xfc, 0x0f, 0xfc, 0x0f, 0xfc, 0x0f, 0xfc, 0x0f, 0x00, 0x00};
3140}
3141image create bitmap reficon-H -background black -foreground green \
3142 -data $rectdata -maskdata $rectmask
3143image create bitmap reficon-o -background black -foreground "#ddddff" \
3144 -data $rectdata -maskdata $rectmask
3145
3146proc init_flist {first} {
3147 global cflist cflist_top difffilestart
3148
3149 $cflist conf -state normal
3150 $cflist delete 0.0 end
3151 if {$first ne {}} {
3152 $cflist insert end $first
3153 set cflist_top 1
3154 $cflist tag add highlight 1.0 "1.0 lineend"
3155 } else {
3156 catch {unset cflist_top}
3157 }
3158 $cflist conf -state disabled
3159 set difffilestart {}
3160}
3161
3162proc highlight_tag {f} {
3163 global highlight_paths
3164
3165 foreach p $highlight_paths {
3166 if {[string match $p $f]} {
3167 return "bold"
3168 }
3169 }
3170 return {}
3171}
3172
3173proc highlight_filelist {} {
3174 global cmitmode cflist
3175
3176 $cflist conf -state normal
3177 if {$cmitmode ne "tree"} {
3178 set end [lindex [split [$cflist index end] .] 0]
3179 for {set l 2} {$l < $end} {incr l} {
3180 set line [$cflist get $l.0 "$l.0 lineend"]
3181 if {[highlight_tag $line] ne {}} {
3182 $cflist tag add bold $l.0 "$l.0 lineend"
3183 }
3184 }
3185 } else {
3186 highlight_tree 2 {}
3187 }
3188 $cflist conf -state disabled
3189}
3190
3191proc unhighlight_filelist {} {
3192 global cflist
3193
3194 $cflist conf -state normal
3195 $cflist tag remove bold 1.0 end
3196 $cflist conf -state disabled
3197}
3198
3199proc add_flist {fl} {
3200 global cflist
3201
3202 $cflist conf -state normal
3203 foreach f $fl {
3204 $cflist insert end "\n"
3205 $cflist insert end $f [highlight_tag $f]
3206 }
3207 $cflist conf -state disabled
3208}
3209
3210proc sel_flist {w x y} {
3211 global ctext difffilestart cflist cflist_top cmitmode
3212
3213 if {$cmitmode eq "tree"} return
3214 if {![info exists cflist_top]} return
3215 set l [lindex [split [$w index "@$x,$y"] "."] 0]
3216 $cflist tag remove highlight $cflist_top.0 "$cflist_top.0 lineend"
3217 $cflist tag add highlight $l.0 "$l.0 lineend"
3218 set cflist_top $l
3219 if {$l == 1} {
3220 $ctext yview 1.0
3221 } else {
3222 catch {$ctext yview [lindex $difffilestart [expr {$l - 2}]]}
3223 }
3224}
3225
3226proc pop_flist_menu {w X Y x y} {
3227 global ctext cflist cmitmode flist_menu flist_menu_file
3228 global treediffs diffids
3229
3230 stopfinding
3231 set l [lindex [split [$w index "@$x,$y"] "."] 0]
3232 if {$l <= 1} return
3233 if {$cmitmode eq "tree"} {
3234 set e [linetoelt $l]
3235 if {[string index $e end] eq "/"} return
3236 } else {
3237 set e [lindex $treediffs($diffids) [expr {$l-2}]]
3238 }
3239 set flist_menu_file $e
3240 set xdiffstate "normal"
3241 if {$cmitmode eq "tree"} {
3242 set xdiffstate "disabled"
3243 }
3244 # Disable "External diff" item in tree mode
3245 $flist_menu entryconf 2 -state $xdiffstate
3246 tk_popup $flist_menu $X $Y
3247}
3248
3249proc find_ctext_fileinfo {line} {
3250 global ctext_file_names ctext_file_lines
3251
3252 set ok [bsearch $ctext_file_lines $line]
3253 set tline [lindex $ctext_file_lines $ok]
3254
3255 if {$ok >= [llength $ctext_file_lines] || $line < $tline} {
3256 return {}
3257 } else {
3258 return [list [lindex $ctext_file_names $ok] $tline]
3259 }
3260}
3261
3262proc pop_diff_menu {w X Y x y} {
3263 global ctext diff_menu flist_menu_file
3264 global diff_menu_txtpos diff_menu_line
3265 global diff_menu_filebase
3266
3267 set diff_menu_txtpos [split [$w index "@$x,$y"] "."]
3268 set diff_menu_line [lindex $diff_menu_txtpos 0]
3269 # don't pop up the menu on hunk-separator or file-separator lines
3270 if {[lsearch -glob [$ctext tag names $diff_menu_line.0] "*sep"] >= 0} {
3271 return
3272 }
3273 stopfinding
3274 set f [find_ctext_fileinfo $diff_menu_line]
3275 if {$f eq {}} return
3276 set flist_menu_file [lindex $f 0]
3277 set diff_menu_filebase [lindex $f 1]
3278 tk_popup $diff_menu $X $Y
3279}
3280
3281proc flist_hl {only} {
3282 global flist_menu_file findstring gdttype
3283
3284 set x [shellquote $flist_menu_file]
3285 if {$only || $findstring eq {} || $gdttype ne [mc "touching paths:"]} {
3286 set findstring $x
3287 } else {
3288 append findstring " " $x
3289 }
3290 set gdttype [mc "touching paths:"]
3291}
3292
3293proc gitknewtmpdir {} {
3294 global diffnum gitktmpdir gitdir
3295
3296 if {![info exists gitktmpdir]} {
3297 set gitktmpdir [file join [file dirname $gitdir] \
3298 [format ".gitk-tmp.%s" [pid]]]
3299 if {[catch {file mkdir $gitktmpdir} err]} {
3300 error_popup "[mc "Error creating temporary directory %s:" $gitktmpdir] $err"
3301 unset gitktmpdir
3302 return {}
3303 }
3304 set diffnum 0
3305 }
3306 incr diffnum
3307 set diffdir [file join $gitktmpdir $diffnum]
3308 if {[catch {file mkdir $diffdir} err]} {
3309 error_popup "[mc "Error creating temporary directory %s:" $diffdir] $err"
3310 return {}
3311 }
3312 return $diffdir
3313}
3314
3315proc save_file_from_commit {filename output what} {
3316 global nullfile
3317
3318 if {[catch {exec git show $filename -- > $output} err]} {
3319 if {[string match "fatal: bad revision *" $err]} {
3320 return $nullfile
3321 }
3322 error_popup "[mc "Error getting \"%s\" from %s:" $filename $what] $err"
3323 return {}
3324 }
3325 return $output
3326}
3327
3328proc external_diff_get_one_file {diffid filename diffdir} {
3329 global nullid nullid2 nullfile
3330 global gitdir
3331
3332 if {$diffid == $nullid} {
3333 set difffile [file join [file dirname $gitdir] $filename]
3334 if {[file exists $difffile]} {
3335 return $difffile
3336 }
3337 return $nullfile
3338 }
3339 if {$diffid == $nullid2} {
3340 set difffile [file join $diffdir "\[index\] [file tail $filename]"]
3341 return [save_file_from_commit :$filename $difffile index]
3342 }
3343 set difffile [file join $diffdir "\[$diffid\] [file tail $filename]"]
3344 return [save_file_from_commit $diffid:$filename $difffile \
3345 "revision $diffid"]
3346}
3347
3348proc external_diff {} {
3349 global nullid nullid2
3350 global flist_menu_file
3351 global diffids
3352 global extdifftool
3353
3354 if {[llength $diffids] == 1} {
3355 # no reference commit given
3356 set diffidto [lindex $diffids 0]
3357 if {$diffidto eq $nullid} {
3358 # diffing working copy with index
3359 set diffidfrom $nullid2
3360 } elseif {$diffidto eq $nullid2} {
3361 # diffing index with HEAD
3362 set diffidfrom "HEAD"
3363 } else {
3364 # use first parent commit
3365 global parentlist selectedline
3366 set diffidfrom [lindex $parentlist $selectedline 0]
3367 }
3368 } else {
3369 set diffidfrom [lindex $diffids 0]
3370 set diffidto [lindex $diffids 1]
3371 }
3372
3373 # make sure that several diffs wont collide
3374 set diffdir [gitknewtmpdir]
3375 if {$diffdir eq {}} return
3376
3377 # gather files to diff
3378 set difffromfile [external_diff_get_one_file $diffidfrom $flist_menu_file $diffdir]
3379 set difftofile [external_diff_get_one_file $diffidto $flist_menu_file $diffdir]
3380
3381 if {$difffromfile ne {} && $difftofile ne {}} {
3382 set cmd [list [shellsplit $extdifftool] $difffromfile $difftofile]
3383 if {[catch {set fl [open |$cmd r]} err]} {
3384 file delete -force $diffdir
3385 error_popup "$extdifftool: [mc "command failed:"] $err"
3386 } else {
3387 fconfigure $fl -blocking 0
3388 filerun $fl [list delete_at_eof $fl $diffdir]
3389 }
3390 }
3391}
3392
3393proc find_hunk_blamespec {base line} {
3394 global ctext
3395
3396 # Find and parse the hunk header
3397 set s_lix [$ctext search -backwards -regexp ^@@ "$line.0 lineend" $base.0]
3398 if {$s_lix eq {}} return
3399
3400 set s_line [$ctext get $s_lix "$s_lix + 1 lines"]
3401 if {![regexp {^@@@*(( -\d+(,\d+)?)+) \+(\d+)(,\d+)? @@} $s_line \
3402 s_line old_specs osz osz1 new_line nsz]} {
3403 return
3404 }
3405
3406 # base lines for the parents
3407 set base_lines [list $new_line]
3408 foreach old_spec [lrange [split $old_specs " "] 1 end] {
3409 if {![regexp -- {-(\d+)(,\d+)?} $old_spec \
3410 old_spec old_line osz]} {
3411 return
3412 }
3413 lappend base_lines $old_line
3414 }
3415
3416 # Now scan the lines to determine offset within the hunk
3417 set max_parent [expr {[llength $base_lines]-2}]
3418 set dline 0
3419 set s_lno [lindex [split $s_lix "."] 0]
3420
3421 # Determine if the line is removed
3422 set chunk [$ctext get $line.0 "$line.1 + $max_parent chars"]
3423 if {[string match {[-+ ]*} $chunk]} {
3424 set removed_idx [string first "-" $chunk]
3425 # Choose a parent index
3426 if {$removed_idx >= 0} {
3427 set parent $removed_idx
3428 } else {
3429 set unchanged_idx [string first " " $chunk]
3430 if {$unchanged_idx >= 0} {
3431 set parent $unchanged_idx
3432 } else {
3433 # blame the current commit
3434 set parent -1
3435 }
3436 }
3437 # then count other lines that belong to it
3438 for {set i $line} {[incr i -1] > $s_lno} {} {
3439 set chunk [$ctext get $i.0 "$i.1 + $max_parent chars"]
3440 # Determine if the line is removed
3441 set removed_idx [string first "-" $chunk]
3442 if {$parent >= 0} {
3443 set code [string index $chunk $parent]
3444 if {$code eq "-" || ($removed_idx < 0 && $code ne "+")} {
3445 incr dline
3446 }
3447 } else {
3448 if {$removed_idx < 0} {
3449 incr dline
3450 }
3451 }
3452 }
3453 incr parent
3454 } else {
3455 set parent 0
3456 }
3457
3458 incr dline [lindex $base_lines $parent]
3459 return [list $parent $dline]
3460}
3461
3462proc external_blame_diff {} {
3463 global currentid cmitmode
3464 global diff_menu_txtpos diff_menu_line
3465 global diff_menu_filebase flist_menu_file
3466
3467 if {$cmitmode eq "tree"} {
3468 set parent_idx 0
3469 set line [expr {$diff_menu_line - $diff_menu_filebase}]
3470 } else {
3471 set hinfo [find_hunk_blamespec $diff_menu_filebase $diff_menu_line]
3472 if {$hinfo ne {}} {
3473 set parent_idx [lindex $hinfo 0]
3474 set line [lindex $hinfo 1]
3475 } else {
3476 set parent_idx 0
3477 set line 0
3478 }
3479 }
3480
3481 external_blame $parent_idx $line
3482}
3483
3484# Find the SHA1 ID of the blob for file $fname in the index
3485# at stage 0 or 2
3486proc index_sha1 {fname} {
3487 set f [open [list | git ls-files -s $fname] r]
3488 while {[gets $f line] >= 0} {
3489 set info [lindex [split $line "\t"] 0]
3490 set stage [lindex $info 2]
3491 if {$stage eq "0" || $stage eq "2"} {
3492 close $f
3493 return [lindex $info 1]
3494 }
3495 }
3496 close $f
3497 return {}
3498}
3499
3500# Turn an absolute path into one relative to the current directory
3501proc make_relative {f} {
3502 set elts [file split $f]
3503 set here [file split [pwd]]
3504 set ei 0
3505 set hi 0
3506 set res {}
3507 foreach d $here {
3508 if {$ei < $hi || $ei >= [llength $elts] || [lindex $elts $ei] ne $d} {
3509 lappend res ".."
3510 } else {
3511 incr ei
3512 }
3513 incr hi
3514 }
3515 set elts [concat $res [lrange $elts $ei end]]
3516 return [eval file join $elts]
3517}
3518
3519proc external_blame {parent_idx {line {}}} {
3520 global flist_menu_file gitdir
3521 global nullid nullid2
3522 global parentlist selectedline currentid
3523
3524 if {$parent_idx > 0} {
3525 set base_commit [lindex $parentlist $selectedline [expr {$parent_idx-1}]]
3526 } else {
3527 set base_commit $currentid
3528 }
3529
3530 if {$base_commit eq {} || $base_commit eq $nullid || $base_commit eq $nullid2} {
3531 error_popup [mc "No such commit"]
3532 return
3533 }
3534
3535 set cmdline [list git gui blame]
3536 if {$line ne {} && $line > 1} {
3537 lappend cmdline "--line=$line"
3538 }
3539 set f [file join [file dirname $gitdir] $flist_menu_file]
3540 # Unfortunately it seems git gui blame doesn't like
3541 # being given an absolute path...
3542 set f [make_relative $f]
3543 lappend cmdline $base_commit $f
3544 if {[catch {eval exec $cmdline &} err]} {
3545 error_popup "[mc "git gui blame: command failed:"] $err"
3546 }
3547}
3548
3549proc show_line_source {} {
3550 global cmitmode currentid parents curview blamestuff blameinst
3551 global diff_menu_line diff_menu_filebase flist_menu_file
3552 global nullid nullid2 gitdir
3553
3554 set from_index {}
3555 if {$cmitmode eq "tree"} {
3556 set id $currentid
3557 set line [expr {$diff_menu_line - $diff_menu_filebase}]
3558 } else {
3559 set h [find_hunk_blamespec $diff_menu_filebase $diff_menu_line]
3560 if {$h eq {}} return
3561 set pi [lindex $h 0]
3562 if {$pi == 0} {
3563 mark_ctext_line $diff_menu_line
3564 return
3565 }
3566 incr pi -1
3567 if {$currentid eq $nullid} {
3568 if {$pi > 0} {
3569 # must be a merge in progress...
3570 if {[catch {
3571 # get the last line from .git/MERGE_HEAD
3572 set f [open [file join $gitdir MERGE_HEAD] r]
3573 set id [lindex [split [read $f] "\n"] end-1]
3574 close $f
3575 } err]} {
3576 error_popup [mc "Couldn't read merge head: %s" $err]
3577 return
3578 }
3579 } elseif {$parents($curview,$currentid) eq $nullid2} {
3580 # need to do the blame from the index
3581 if {[catch {
3582 set from_index [index_sha1 $flist_menu_file]
3583 } err]} {
3584 error_popup [mc "Error reading index: %s" $err]
3585 return
3586 }
3587 } else {
3588 set id $parents($curview,$currentid)
3589 }
3590 } else {
3591 set id [lindex $parents($curview,$currentid) $pi]
3592 }
3593 set line [lindex $h 1]
3594 }
3595 set blameargs {}
3596 if {$from_index ne {}} {
3597 lappend blameargs | git cat-file blob $from_index
3598 }
3599 lappend blameargs | git blame -p -L$line,+1
3600 if {$from_index ne {}} {
3601 lappend blameargs --contents -
3602 } else {
3603 lappend blameargs $id
3604 }
3605 lappend blameargs -- [file join [file dirname $gitdir] $flist_menu_file]
3606 if {[catch {
3607 set f [open $blameargs r]
3608 } err]} {
3609 error_popup [mc "Couldn't start git blame: %s" $err]
3610 return
3611 }
3612 nowbusy blaming [mc "Searching"]
3613 fconfigure $f -blocking 0
3614 set i [reg_instance $f]
3615 set blamestuff($i) {}
3616 set blameinst $i
3617 filerun $f [list read_line_source $f $i]
3618}
3619
3620proc stopblaming {} {
3621 global blameinst
3622
3623 if {[info exists blameinst]} {
3624 stop_instance $blameinst
3625 unset blameinst
3626 notbusy blaming
3627 }
3628}
3629
3630proc read_line_source {fd inst} {
3631 global blamestuff curview commfd blameinst nullid nullid2
3632
3633 while {[gets $fd line] >= 0} {
3634 lappend blamestuff($inst) $line
3635 }
3636 if {![eof $fd]} {
3637 return 1
3638 }
3639 unset commfd($inst)
3640 unset blameinst
3641 notbusy blaming
3642 fconfigure $fd -blocking 1
3643 if {[catch {close $fd} err]} {
3644 error_popup [mc "Error running git blame: %s" $err]
3645 return 0
3646 }
3647
3648 set fname {}
3649 set line [split [lindex $blamestuff($inst) 0] " "]
3650 set id [lindex $line 0]
3651 set lnum [lindex $line 1]
3652 if {[string length $id] == 40 && [string is xdigit $id] &&
3653 [string is digit -strict $lnum]} {
3654 # look for "filename" line
3655 foreach l $blamestuff($inst) {
3656 if {[string match "filename *" $l]} {
3657 set fname [string range $l 9 end]
3658 break
3659 }
3660 }
3661 }
3662 if {$fname ne {}} {
3663 # all looks good, select it
3664 if {$id eq $nullid} {
3665 # blame uses all-zeroes to mean not committed,
3666 # which would mean a change in the index
3667 set id $nullid2
3668 }
3669 if {[commitinview $id $curview]} {
3670 selectline [rowofcommit $id] 1 [list $fname $lnum]
3671 } else {
3672 error_popup [mc "That line comes from commit %s, \
3673 which is not in this view" [shortids $id]]
3674 }
3675 } else {
3676 puts "oops couldn't parse git blame output"
3677 }
3678 return 0
3679}
3680
3681# delete $dir when we see eof on $f (presumably because the child has exited)
3682proc delete_at_eof {f dir} {
3683 while {[gets $f line] >= 0} {}
3684 if {[eof $f]} {
3685 if {[catch {close $f} err]} {
3686 error_popup "[mc "External diff viewer failed:"] $err"
3687 }
3688 file delete -force $dir
3689 return 0
3690 }
3691 return 1
3692}
3693
3694# Functions for adding and removing shell-type quoting
3695
3696proc shellquote {str} {
3697 if {![string match "*\['\"\\ \t]*" $str]} {
3698 return $str
3699 }
3700 if {![string match "*\['\"\\]*" $str]} {
3701 return "\"$str\""
3702 }
3703 if {![string match "*'*" $str]} {
3704 return "'$str'"
3705 }
3706 return "\"[string map {\" \\\" \\ \\\\} $str]\""
3707}
3708
3709proc shellarglist {l} {
3710 set str {}
3711 foreach a $l {
3712 if {$str ne {}} {
3713 append str " "
3714 }
3715 append str [shellquote $a]
3716 }
3717 return $str
3718}
3719
3720proc shelldequote {str} {
3721 set ret {}
3722 set used -1
3723 while {1} {
3724 incr used
3725 if {![regexp -start $used -indices "\['\"\\\\ \t]" $str first]} {
3726 append ret [string range $str $used end]
3727 set used [string length $str]
3728 break
3729 }
3730 set first [lindex $first 0]
3731 set ch [string index $str $first]
3732 if {$first > $used} {
3733 append ret [string range $str $used [expr {$first - 1}]]
3734 set used $first
3735 }
3736 if {$ch eq " " || $ch eq "\t"} break
3737 incr used
3738 if {$ch eq "'"} {
3739 set first [string first "'" $str $used]
3740 if {$first < 0} {
3741 error "unmatched single-quote"
3742 }
3743 append ret [string range $str $used [expr {$first - 1}]]
3744 set used $first
3745 continue
3746 }
3747 if {$ch eq "\\"} {
3748 if {$used >= [string length $str]} {
3749 error "trailing backslash"
3750 }
3751 append ret [string index $str $used]
3752 continue
3753 }
3754 # here ch == "\""
3755 while {1} {
3756 if {![regexp -start $used -indices "\[\"\\\\]" $str first]} {
3757 error "unmatched double-quote"
3758 }
3759 set first [lindex $first 0]
3760 set ch [string index $str $first]
3761 if {$first > $used} {
3762 append ret [string range $str $used [expr {$first - 1}]]
3763 set used $first
3764 }
3765 if {$ch eq "\""} break
3766 incr used
3767 append ret [string index $str $used]
3768 incr used
3769 }
3770 }
3771 return [list $used $ret]
3772}
3773
3774proc shellsplit {str} {
3775 set l {}
3776 while {1} {
3777 set str [string trimleft $str]
3778 if {$str eq {}} break
3779 set dq [shelldequote $str]
3780 set n [lindex $dq 0]
3781 set word [lindex $dq 1]
3782 set str [string range $str $n end]
3783 lappend l $word
3784 }
3785 return $l
3786}
3787
3788# Code to implement multiple views
3789
3790proc newview {ishighlight} {
3791 global nextviewnum newviewname newishighlight
3792 global revtreeargs viewargscmd newviewopts curview
3793
3794 set newishighlight $ishighlight
3795 set top .gitkview
3796 if {[winfo exists $top]} {
3797 raise $top
3798 return
3799 }
3800 set newviewname($nextviewnum) "[mc "View"] $nextviewnum"
3801 set newviewopts($nextviewnum,perm) 0
3802 set newviewopts($nextviewnum,cmd) $viewargscmd($curview)
3803 decode_view_opts $nextviewnum $revtreeargs
3804 vieweditor $top $nextviewnum [mc "Gitk view definition"]
3805}
3806
3807set known_view_options {
3808 {perm b . {} {mc "Remember this view"}}
3809 {reflabel l + {} {mc "References (space separated list):"}}
3810 {refs t15 .. {} {mc "Branches & tags:"}}
3811 {allrefs b *. "--all" {mc "All refs"}}
3812 {branches b . "--branches" {mc "All (local) branches"}}
3813 {tags b . "--tags" {mc "All tags"}}
3814 {remotes b . "--remotes" {mc "All remote-tracking branches"}}
3815 {commitlbl l + {} {mc "Commit Info (regular expressions):"}}
3816 {author t15 .. "--author=*" {mc "Author:"}}
3817 {committer t15 . "--committer=*" {mc "Committer:"}}
3818 {loginfo t15 .. "--grep=*" {mc "Commit Message:"}}
3819 {allmatch b .. "--all-match" {mc "Matches all Commit Info criteria"}}
3820 {changes_l l + {} {mc "Changes to Files:"}}
3821 {pickaxe_s r0 . {} {mc "Fixed String"}}
3822 {pickaxe_t r1 . "--pickaxe-regex" {mc "Regular Expression"}}
3823 {pickaxe t15 .. "-S*" {mc "Search string:"}}
3824 {datelabel l + {} {mc "Commit Dates (\"2 weeks ago\", \"2009-03-17 15:27:38\", \"March 17, 2009 15:27:38\"):"}}
3825 {since t15 .. {"--since=*" "--after=*"} {mc "Since:"}}
3826 {until t15 . {"--until=*" "--before=*"} {mc "Until:"}}
3827 {limit_lbl l + {} {mc "Limit and/or skip a number of revisions (positive integer):"}}
3828 {limit t10 *. "--max-count=*" {mc "Number to show:"}}
3829 {skip t10 . "--skip=*" {mc "Number to skip:"}}
3830 {misc_lbl l + {} {mc "Miscellaneous options:"}}
3831 {dorder b *. {"--date-order" "-d"} {mc "Strictly sort by date"}}
3832 {lright b . "--left-right" {mc "Mark branch sides"}}
3833 {first b . "--first-parent" {mc "Limit to first parent"}}
3834 {smplhst b . "--simplify-by-decoration" {mc "Simple history"}}
3835 {args t50 *. {} {mc "Additional arguments to git log:"}}
3836 {allpaths path + {} {mc "Enter files and directories to include, one per line:"}}
3837 {cmd t50= + {} {mc "Command to generate more commits to include:"}}
3838 }
3839
3840proc encode_view_opts {n} {
3841 global known_view_options newviewopts
3842
3843 set rargs [list]
3844 foreach opt $known_view_options {
3845 set patterns [lindex $opt 3]
3846 if {$patterns eq {}} continue
3847 set pattern [lindex $patterns 0]
3848
3849 if {[lindex $opt 1] eq "b"} {
3850 set val $newviewopts($n,[lindex $opt 0])
3851 if {$val} {
3852 lappend rargs $pattern
3853 }
3854 } elseif {[regexp {^r(\d+)$} [lindex $opt 1] type value]} {
3855 regexp {^(.*_)} [lindex $opt 0] uselessvar button_id
3856 set val $newviewopts($n,$button_id)
3857 if {$val eq $value} {
3858 lappend rargs $pattern
3859 }
3860 } else {
3861 set val $newviewopts($n,[lindex $opt 0])
3862 set val [string trim $val]
3863 if {$val ne {}} {
3864 set pfix [string range $pattern 0 end-1]
3865 lappend rargs $pfix$val
3866 }
3867 }
3868 }
3869 set rargs [concat $rargs [shellsplit $newviewopts($n,refs)]]
3870 return [concat $rargs [shellsplit $newviewopts($n,args)]]
3871}
3872
3873proc decode_view_opts {n view_args} {
3874 global known_view_options newviewopts
3875
3876 foreach opt $known_view_options {
3877 set id [lindex $opt 0]
3878 if {[lindex $opt 1] eq "b"} {
3879 # Checkboxes
3880 set val 0
3881 } elseif {[regexp {^r(\d+)$} [lindex $opt 1]]} {
3882 # Radiobuttons
3883 regexp {^(.*_)} $id uselessvar id
3884 set val 0
3885 } else {
3886 # Text fields
3887 set val {}
3888 }
3889 set newviewopts($n,$id) $val
3890 }
3891 set oargs [list]
3892 set refargs [list]
3893 foreach arg $view_args {
3894 if {[regexp -- {^-([0-9]+)$} $arg arg cnt]
3895 && ![info exists found(limit)]} {
3896 set newviewopts($n,limit) $cnt
3897 set found(limit) 1
3898 continue
3899 }
3900 catch { unset val }
3901 foreach opt $known_view_options {
3902 set id [lindex $opt 0]
3903 if {[info exists found($id)]} continue
3904 foreach pattern [lindex $opt 3] {
3905 if {![string match $pattern $arg]} continue
3906 if {[lindex $opt 1] eq "b"} {
3907 # Check buttons
3908 set val 1
3909 } elseif {[regexp {^r(\d+)$} [lindex $opt 1] match num]} {
3910 # Radio buttons
3911 regexp {^(.*_)} $id uselessvar id
3912 set val $num
3913 } else {
3914 # Text input fields
3915 set size [string length $pattern]
3916 set val [string range $arg [expr {$size-1}] end]
3917 }
3918 set newviewopts($n,$id) $val
3919 set found($id) 1
3920 break
3921 }
3922 if {[info exists val]} break
3923 }
3924 if {[info exists val]} continue
3925 if {[regexp {^-} $arg]} {
3926 lappend oargs $arg
3927 } else {
3928 lappend refargs $arg
3929 }
3930 }
3931 set newviewopts($n,refs) [shellarglist $refargs]
3932 set newviewopts($n,args) [shellarglist $oargs]
3933}
3934
3935proc edit_or_newview {} {
3936 global curview
3937
3938 if {$curview > 0} {
3939 editview
3940 } else {
3941 newview 0
3942 }
3943}
3944
3945proc editview {} {
3946 global curview
3947 global viewname viewperm newviewname newviewopts
3948 global viewargs viewargscmd
3949
3950 set top .gitkvedit-$curview
3951 if {[winfo exists $top]} {
3952 raise $top
3953 return
3954 }
3955 set newviewname($curview) $viewname($curview)
3956 set newviewopts($curview,perm) $viewperm($curview)
3957 set newviewopts($curview,cmd) $viewargscmd($curview)
3958 decode_view_opts $curview $viewargs($curview)
3959 vieweditor $top $curview "[mc "Gitk: edit view"] $viewname($curview)"
3960}
3961
3962proc vieweditor {top n title} {
3963 global newviewname newviewopts viewfiles bgcolor
3964 global known_view_options NS
3965
3966 ttk_toplevel $top
3967 wm title $top [concat $title [mc "-- criteria for selecting revisions"]]
3968 make_transient $top .
3969
3970 # View name
3971 ${NS}::frame $top.nfr
3972 ${NS}::label $top.nl -text [mc "View Name"]
3973 ${NS}::entry $top.name -width 20 -textvariable newviewname($n)
3974 pack $top.nfr -in $top -fill x -pady 5 -padx 3
3975 pack $top.nl -in $top.nfr -side left -padx {0 5}
3976 pack $top.name -in $top.nfr -side left -padx {0 25}
3977
3978 # View options
3979 set cframe $top.nfr
3980 set cexpand 0
3981 set cnt 0
3982 foreach opt $known_view_options {
3983 set id [lindex $opt 0]
3984 set type [lindex $opt 1]
3985 set flags [lindex $opt 2]
3986 set title [eval [lindex $opt 4]]
3987 set lxpad 0
3988
3989 if {$flags eq "+" || $flags eq "*"} {
3990 set cframe $top.fr$cnt
3991 incr cnt
3992 ${NS}::frame $cframe
3993 pack $cframe -in $top -fill x -pady 3 -padx 3
3994 set cexpand [expr {$flags eq "*"}]
3995 } elseif {$flags eq ".." || $flags eq "*."} {
3996 set cframe $top.fr$cnt
3997 incr cnt
3998 ${NS}::frame $cframe
3999 pack $cframe -in $top -fill x -pady 3 -padx [list 15 3]
4000 set cexpand [expr {$flags eq "*."}]
4001 } else {
4002 set lxpad 5
4003 }
4004
4005 if {$type eq "l"} {
4006 ${NS}::label $cframe.l_$id -text $title
4007 pack $cframe.l_$id -in $cframe -side left -pady [list 3 0] -anchor w
4008 } elseif {$type eq "b"} {
4009 ${NS}::checkbutton $cframe.c_$id -text $title -variable newviewopts($n,$id)
4010 pack $cframe.c_$id -in $cframe -side left \
4011 -padx [list $lxpad 0] -expand $cexpand -anchor w
4012 } elseif {[regexp {^r(\d+)$} $type type sz]} {
4013 regexp {^(.*_)} $id uselessvar button_id
4014 ${NS}::radiobutton $cframe.c_$id -text $title -variable newviewopts($n,$button_id) -value $sz
4015 pack $cframe.c_$id -in $cframe -side left \
4016 -padx [list $lxpad 0] -expand $cexpand -anchor w
4017 } elseif {[regexp {^t(\d+)$} $type type sz]} {
4018 ${NS}::label $cframe.l_$id -text $title
4019 ${NS}::entry $cframe.e_$id -width $sz -background $bgcolor \
4020 -textvariable newviewopts($n,$id)
4021 pack $cframe.l_$id -in $cframe -side left -padx [list $lxpad 0]
4022 pack $cframe.e_$id -in $cframe -side left -expand 1 -fill x
4023 } elseif {[regexp {^t(\d+)=$} $type type sz]} {
4024 ${NS}::label $cframe.l_$id -text $title
4025 ${NS}::entry $cframe.e_$id -width $sz -background $bgcolor \
4026 -textvariable newviewopts($n,$id)
4027 pack $cframe.l_$id -in $cframe -side top -pady [list 3 0] -anchor w
4028 pack $cframe.e_$id -in $cframe -side top -fill x
4029 } elseif {$type eq "path"} {
4030 ${NS}::label $top.l -text $title
4031 pack $top.l -in $top -side top -pady [list 3 0] -anchor w -padx 3
4032 text $top.t -width 40 -height 5 -background $bgcolor -font uifont
4033 if {[info exists viewfiles($n)]} {
4034 foreach f $viewfiles($n) {
4035 $top.t insert end $f
4036 $top.t insert end "\n"
4037 }
4038 $top.t delete {end - 1c} end
4039 $top.t mark set insert 0.0
4040 }
4041 pack $top.t -in $top -side top -pady [list 0 5] -fill both -expand 1 -padx 3
4042 }
4043 }
4044
4045 ${NS}::frame $top.buts
4046 ${NS}::button $top.buts.ok -text [mc "OK"] -command [list newviewok $top $n]
4047 ${NS}::button $top.buts.apply -text [mc "Apply (F5)"] -command [list newviewok $top $n 1]
4048 ${NS}::button $top.buts.can -text [mc "Cancel"] -command [list destroy $top]
4049 bind $top <Control-Return> [list newviewok $top $n]
4050 bind $top <F5> [list newviewok $top $n 1]
4051 bind $top <Escape> [list destroy $top]
4052 grid $top.buts.ok $top.buts.apply $top.buts.can
4053 grid columnconfigure $top.buts 0 -weight 1 -uniform a
4054 grid columnconfigure $top.buts 1 -weight 1 -uniform a
4055 grid columnconfigure $top.buts 2 -weight 1 -uniform a
4056 pack $top.buts -in $top -side top -fill x
4057 focus $top.t
4058}
4059
4060proc doviewmenu {m first cmd op argv} {
4061 set nmenu [$m index end]
4062 for {set i $first} {$i <= $nmenu} {incr i} {
4063 if {[$m entrycget $i -command] eq $cmd} {
4064 eval $m $op $i $argv
4065 break
4066 }
4067 }
4068}
4069
4070proc allviewmenus {n op args} {
4071 # global viewhlmenu
4072
4073 doviewmenu .bar.view 5 [list showview $n] $op $args
4074 # doviewmenu $viewhlmenu 1 [list addvhighlight $n] $op $args
4075}
4076
4077proc newviewok {top n {apply 0}} {
4078 global nextviewnum newviewperm newviewname newishighlight
4079 global viewname viewfiles viewperm selectedview curview
4080 global viewargs viewargscmd newviewopts viewhlmenu
4081
4082 if {[catch {
4083 set newargs [encode_view_opts $n]
4084 } err]} {
4085 error_popup "[mc "Error in commit selection arguments:"] $err" $top
4086 return
4087 }
4088 set files {}
4089 foreach f [split [$top.t get 0.0 end] "\n"] {
4090 set ft [string trim $f]
4091 if {$ft ne {}} {
4092 lappend files $ft
4093 }
4094 }
4095 if {![info exists viewfiles($n)]} {
4096 # creating a new view
4097 incr nextviewnum
4098 set viewname($n) $newviewname($n)
4099 set viewperm($n) $newviewopts($n,perm)
4100 set viewfiles($n) $files
4101 set viewargs($n) $newargs
4102 set viewargscmd($n) $newviewopts($n,cmd)
4103 addviewmenu $n
4104 if {!$newishighlight} {
4105 run showview $n
4106 } else {
4107 run addvhighlight $n
4108 }
4109 } else {
4110 # editing an existing view
4111 set viewperm($n) $newviewopts($n,perm)
4112 if {$newviewname($n) ne $viewname($n)} {
4113 set viewname($n) $newviewname($n)
4114 doviewmenu .bar.view 5 [list showview $n] \
4115 entryconf [list -label $viewname($n)]
4116 # doviewmenu $viewhlmenu 1 [list addvhighlight $n] \
4117 # entryconf [list -label $viewname($n) -value $viewname($n)]
4118 }
4119 if {$files ne $viewfiles($n) || $newargs ne $viewargs($n) || \
4120 $newviewopts($n,cmd) ne $viewargscmd($n)} {
4121 set viewfiles($n) $files
4122 set viewargs($n) $newargs
4123 set viewargscmd($n) $newviewopts($n,cmd)
4124 if {$curview == $n} {
4125 run reloadcommits
4126 }
4127 }
4128 }
4129 if {$apply} return
4130 catch {destroy $top}
4131}
4132
4133proc delview {} {
4134 global curview viewperm hlview selectedhlview
4135
4136 if {$curview == 0} return
4137 if {[info exists hlview] && $hlview == $curview} {
4138 set selectedhlview [mc "None"]
4139 unset hlview
4140 }
4141 allviewmenus $curview delete
4142 set viewperm($curview) 0
4143 showview 0
4144}
4145
4146proc addviewmenu {n} {
4147 global viewname viewhlmenu
4148
4149 .bar.view add radiobutton -label $viewname($n) \
4150 -command [list showview $n] -variable selectedview -value $n
4151 #$viewhlmenu add radiobutton -label $viewname($n) \
4152 # -command [list addvhighlight $n] -variable selectedhlview
4153}
4154
4155proc showview {n} {
4156 global curview cached_commitrow ordertok
4157 global displayorder parentlist rowidlist rowisopt rowfinal
4158 global colormap rowtextx nextcolor canvxmax
4159 global numcommits viewcomplete
4160 global selectedline currentid canv canvy0
4161 global treediffs
4162 global pending_select mainheadid
4163 global commitidx
4164 global selectedview
4165 global hlview selectedhlview commitinterest
4166
4167 if {$n == $curview} return
4168 set selid {}
4169 set ymax [lindex [$canv cget -scrollregion] 3]
4170 set span [$canv yview]
4171 set ytop [expr {[lindex $span 0] * $ymax}]
4172 set ybot [expr {[lindex $span 1] * $ymax}]
4173 set yscreen [expr {($ybot - $ytop) / 2}]
4174 if {$selectedline ne {}} {
4175 set selid $currentid
4176 set y [yc $selectedline]
4177 if {$ytop < $y && $y < $ybot} {
4178 set yscreen [expr {$y - $ytop}]
4179 }
4180 } elseif {[info exists pending_select]} {
4181 set selid $pending_select
4182 unset pending_select
4183 }
4184 unselectline
4185 normalline
4186 catch {unset treediffs}
4187 clear_display
4188 if {[info exists hlview] && $hlview == $n} {
4189 unset hlview
4190 set selectedhlview [mc "None"]
4191 }
4192 catch {unset commitinterest}
4193 catch {unset cached_commitrow}
4194 catch {unset ordertok}
4195
4196 set curview $n
4197 set selectedview $n
4198 .bar.view entryconf [mca "Edit view..."] -state [expr {$n == 0? "disabled": "normal"}]
4199 .bar.view entryconf [mca "Delete view"] -state [expr {$n == 0? "disabled": "normal"}]
4200
4201 run refill_reflist
4202 if {![info exists viewcomplete($n)]} {
4203 getcommits $selid
4204 return
4205 }
4206
4207 set displayorder {}
4208 set parentlist {}
4209 set rowidlist {}
4210 set rowisopt {}
4211 set rowfinal {}
4212 set numcommits $commitidx($n)
4213
4214 catch {unset colormap}
4215 catch {unset rowtextx}
4216 set nextcolor 0
4217 set canvxmax [$canv cget -width]
4218 set curview $n
4219 set row 0
4220 setcanvscroll
4221 set yf 0
4222 set row {}
4223 if {$selid ne {} && [commitinview $selid $n]} {
4224 set row [rowofcommit $selid]
4225 # try to get the selected row in the same position on the screen
4226 set ymax [lindex [$canv cget -scrollregion] 3]
4227 set ytop [expr {[yc $row] - $yscreen}]
4228 if {$ytop < 0} {
4229 set ytop 0
4230 }
4231 set yf [expr {$ytop * 1.0 / $ymax}]
4232 }
4233 allcanvs yview moveto $yf
4234 drawvisible
4235 if {$row ne {}} {
4236 selectline $row 0
4237 } elseif {!$viewcomplete($n)} {
4238 reset_pending_select $selid
4239 } else {
4240 reset_pending_select {}
4241
4242 if {[commitinview $pending_select $curview]} {
4243 selectline [rowofcommit $pending_select] 1
4244 } else {
4245 set row [first_real_row]
4246 if {$row < $numcommits} {
4247 selectline $row 0
4248 }
4249 }
4250 }
4251 if {!$viewcomplete($n)} {
4252 if {$numcommits == 0} {
4253 show_status [mc "Reading commits..."]
4254 }
4255 } elseif {$numcommits == 0} {
4256 show_status [mc "No commits selected"]
4257 }
4258}
4259
4260# Stuff relating to the highlighting facility
4261
4262proc ishighlighted {id} {
4263 global vhighlights fhighlights nhighlights rhighlights
4264
4265 if {[info exists nhighlights($id)] && $nhighlights($id) > 0} {
4266 return $nhighlights($id)
4267 }
4268 if {[info exists vhighlights($id)] && $vhighlights($id) > 0} {
4269 return $vhighlights($id)
4270 }
4271 if {[info exists fhighlights($id)] && $fhighlights($id) > 0} {
4272 return $fhighlights($id)
4273 }
4274 if {[info exists rhighlights($id)] && $rhighlights($id) > 0} {
4275 return $rhighlights($id)
4276 }
4277 return 0
4278}
4279
4280proc bolden {id font} {
4281 global canv linehtag currentid boldids need_redisplay markedid
4282
4283 # need_redisplay = 1 means the display is stale and about to be redrawn
4284 if {$need_redisplay} return
4285 lappend boldids $id
4286 $canv itemconf $linehtag($id) -font $font
4287 if {[info exists currentid] && $id eq $currentid} {
4288 $canv delete secsel
4289 set t [eval $canv create rect [$canv bbox $linehtag($id)] \
4290 -outline {{}} -tags secsel \
4291 -fill [$canv cget -selectbackground]]
4292 $canv lower $t
4293 }
4294 if {[info exists markedid] && $id eq $markedid} {
4295 make_idmark $id
4296 }
4297}
4298
4299proc bolden_name {id font} {
4300 global canv2 linentag currentid boldnameids need_redisplay
4301
4302 if {$need_redisplay} return
4303 lappend boldnameids $id
4304 $canv2 itemconf $linentag($id) -font $font
4305 if {[info exists currentid] && $id eq $currentid} {
4306 $canv2 delete secsel
4307 set t [eval $canv2 create rect [$canv2 bbox $linentag($id)] \
4308 -outline {{}} -tags secsel \
4309 -fill [$canv2 cget -selectbackground]]
4310 $canv2 lower $t
4311 }
4312}
4313
4314proc unbolden {} {
4315 global boldids
4316
4317 set stillbold {}
4318 foreach id $boldids {
4319 if {![ishighlighted $id]} {
4320 bolden $id mainfont
4321 } else {
4322 lappend stillbold $id
4323 }
4324 }
4325 set boldids $stillbold
4326}
4327
4328proc addvhighlight {n} {
4329 global hlview viewcomplete curview vhl_done commitidx
4330
4331 if {[info exists hlview]} {
4332 delvhighlight
4333 }
4334 set hlview $n
4335 if {$n != $curview && ![info exists viewcomplete($n)]} {
4336 start_rev_list $n
4337 }
4338 set vhl_done $commitidx($hlview)
4339 if {$vhl_done > 0} {
4340 drawvisible
4341 }
4342}
4343
4344proc delvhighlight {} {
4345 global hlview vhighlights
4346
4347 if {![info exists hlview]} return
4348 unset hlview
4349 catch {unset vhighlights}
4350 unbolden
4351}
4352
4353proc vhighlightmore {} {
4354 global hlview vhl_done commitidx vhighlights curview
4355
4356 set max $commitidx($hlview)
4357 set vr [visiblerows]
4358 set r0 [lindex $vr 0]
4359 set r1 [lindex $vr 1]
4360 for {set i $vhl_done} {$i < $max} {incr i} {
4361 set id [commitonrow $i $hlview]
4362 if {[commitinview $id $curview]} {
4363 set row [rowofcommit $id]
4364 if {$r0 <= $row && $row <= $r1} {
4365 if {![highlighted $row]} {
4366 bolden $id mainfontbold
4367 }
4368 set vhighlights($id) 1
4369 }
4370 }
4371 }
4372 set vhl_done $max
4373 return 0
4374}
4375
4376proc askvhighlight {row id} {
4377 global hlview vhighlights iddrawn
4378
4379 if {[commitinview $id $hlview]} {
4380 if {[info exists iddrawn($id)] && ![ishighlighted $id]} {
4381 bolden $id mainfontbold
4382 }
4383 set vhighlights($id) 1
4384 } else {
4385 set vhighlights($id) 0
4386 }
4387}
4388
4389proc hfiles_change {} {
4390 global highlight_files filehighlight fhighlights fh_serial
4391 global highlight_paths
4392
4393 if {[info exists filehighlight]} {
4394 # delete previous highlights
4395 catch {close $filehighlight}
4396 unset filehighlight
4397 catch {unset fhighlights}
4398 unbolden
4399 unhighlight_filelist
4400 }
4401 set highlight_paths {}
4402 after cancel do_file_hl $fh_serial
4403 incr fh_serial
4404 if {$highlight_files ne {}} {
4405 after 300 do_file_hl $fh_serial
4406 }
4407}
4408
4409proc gdttype_change {name ix op} {
4410 global gdttype highlight_files findstring findpattern
4411
4412 stopfinding
4413 if {$findstring ne {}} {
4414 if {$gdttype eq [mc "containing:"]} {
4415 if {$highlight_files ne {}} {
4416 set highlight_files {}
4417 hfiles_change
4418 }
4419 findcom_change
4420 } else {
4421 if {$findpattern ne {}} {
4422 set findpattern {}
4423 findcom_change
4424 }
4425 set highlight_files $findstring
4426 hfiles_change
4427 }
4428 drawvisible
4429 }
4430 # enable/disable findtype/findloc menus too
4431}
4432
4433proc find_change {name ix op} {
4434 global gdttype findstring highlight_files
4435
4436 stopfinding
4437 if {$gdttype eq [mc "containing:"]} {
4438 findcom_change
4439 } else {
4440 if {$highlight_files ne $findstring} {
4441 set highlight_files $findstring
4442 hfiles_change
4443 }
4444 }
4445 drawvisible
4446}
4447
4448proc findcom_change args {
4449 global nhighlights boldnameids
4450 global findpattern findtype findstring gdttype
4451
4452 stopfinding
4453 # delete previous highlights, if any
4454 foreach id $boldnameids {
4455 bolden_name $id mainfont
4456 }
4457 set boldnameids {}
4458 catch {unset nhighlights}
4459 unbolden
4460 unmarkmatches
4461 if {$gdttype ne [mc "containing:"] || $findstring eq {}} {
4462 set findpattern {}
4463 } elseif {$findtype eq [mc "Regexp"]} {
4464 set findpattern $findstring
4465 } else {
4466 set e [string map {"*" "\\*" "?" "\\?" "\[" "\\\[" "\\" "\\\\"} \
4467 $findstring]
4468 set findpattern "*$e*"
4469 }
4470}
4471
4472proc makepatterns {l} {
4473 set ret {}
4474 foreach e $l {
4475 set ee [string map {"*" "\\*" "?" "\\?" "\[" "\\\[" "\\" "\\\\"} $e]
4476 if {[string index $ee end] eq "/"} {
4477 lappend ret "$ee*"
4478 } else {
4479 lappend ret $ee
4480 lappend ret "$ee/*"
4481 }
4482 }
4483 return $ret
4484}
4485
4486proc do_file_hl {serial} {
4487 global highlight_files filehighlight highlight_paths gdttype fhl_list
4488
4489 if {$gdttype eq [mc "touching paths:"]} {
4490 if {[catch {set paths [shellsplit $highlight_files]}]} return
4491 set highlight_paths [makepatterns $paths]
4492 highlight_filelist
4493 set gdtargs [concat -- $paths]
4494 } elseif {$gdttype eq [mc "adding/removing string:"]} {
4495 set gdtargs [list "-S$highlight_files"]
4496 } else {
4497 # must be "containing:", i.e. we're searching commit info
4498 return
4499 }
4500 set cmd [concat | git diff-tree -r -s --stdin $gdtargs]
4501 set filehighlight [open $cmd r+]
4502 fconfigure $filehighlight -blocking 0
4503 filerun $filehighlight readfhighlight
4504 set fhl_list {}
4505 drawvisible
4506 flushhighlights
4507}
4508
4509proc flushhighlights {} {
4510 global filehighlight fhl_list
4511
4512 if {[info exists filehighlight]} {
4513 lappend fhl_list {}
4514 puts $filehighlight ""
4515 flush $filehighlight
4516 }
4517}
4518
4519proc askfilehighlight {row id} {
4520 global filehighlight fhighlights fhl_list
4521
4522 lappend fhl_list $id
4523 set fhighlights($id) -1
4524 puts $filehighlight $id
4525}
4526
4527proc readfhighlight {} {
4528 global filehighlight fhighlights curview iddrawn
4529 global fhl_list find_dirn
4530
4531 if {![info exists filehighlight]} {
4532 return 0
4533 }
4534 set nr 0
4535 while {[incr nr] <= 100 && [gets $filehighlight line] >= 0} {
4536 set line [string trim $line]
4537 set i [lsearch -exact $fhl_list $line]
4538 if {$i < 0} continue
4539 for {set j 0} {$j < $i} {incr j} {
4540 set id [lindex $fhl_list $j]
4541 set fhighlights($id) 0
4542 }
4543 set fhl_list [lrange $fhl_list [expr {$i+1}] end]
4544 if {$line eq {}} continue
4545 if {![commitinview $line $curview]} continue
4546 if {[info exists iddrawn($line)] && ![ishighlighted $line]} {
4547 bolden $line mainfontbold
4548 }
4549 set fhighlights($line) 1
4550 }
4551 if {[eof $filehighlight]} {
4552 # strange...
4553 puts "oops, git diff-tree died"
4554 catch {close $filehighlight}
4555 unset filehighlight
4556 return 0
4557 }
4558 if {[info exists find_dirn]} {
4559 run findmore
4560 }
4561 return 1
4562}
4563
4564proc doesmatch {f} {
4565 global findtype findpattern
4566
4567 if {$findtype eq [mc "Regexp"]} {
4568 return [regexp $findpattern $f]
4569 } elseif {$findtype eq [mc "IgnCase"]} {
4570 return [string match -nocase $findpattern $f]
4571 } else {
4572 return [string match $findpattern $f]
4573 }
4574}
4575
4576proc askfindhighlight {row id} {
4577 global nhighlights commitinfo iddrawn
4578 global findloc
4579 global markingmatches
4580
4581 if {![info exists commitinfo($id)]} {
4582 getcommit $id
4583 }
4584 set info $commitinfo($id)
4585 set isbold 0
4586 set fldtypes [list [mc Headline] [mc Author] [mc Date] [mc Committer] [mc CDate] [mc Comments]]
4587 foreach f $info ty $fldtypes {
4588 if {($findloc eq [mc "All fields"] || $findloc eq $ty) &&
4589 [doesmatch $f]} {
4590 if {$ty eq [mc "Author"]} {
4591 set isbold 2
4592 break
4593 }
4594 set isbold 1
4595 }
4596 }
4597 if {$isbold && [info exists iddrawn($id)]} {
4598 if {![ishighlighted $id]} {
4599 bolden $id mainfontbold
4600 if {$isbold > 1} {
4601 bolden_name $id mainfontbold
4602 }
4603 }
4604 if {$markingmatches} {
4605 markrowmatches $row $id
4606 }
4607 }
4608 set nhighlights($id) $isbold
4609}
4610
4611proc markrowmatches {row id} {
4612 global canv canv2 linehtag linentag commitinfo findloc
4613
4614 set headline [lindex $commitinfo($id) 0]
4615 set author [lindex $commitinfo($id) 1]
4616 $canv delete match$row
4617 $canv2 delete match$row
4618 if {$findloc eq [mc "All fields"] || $findloc eq [mc "Headline"]} {
4619 set m [findmatches $headline]
4620 if {$m ne {}} {
4621 markmatches $canv $row $headline $linehtag($id) $m \
4622 [$canv itemcget $linehtag($id) -font] $row
4623 }
4624 }
4625 if {$findloc eq [mc "All fields"] || $findloc eq [mc "Author"]} {
4626 set m [findmatches $author]
4627 if {$m ne {}} {
4628 markmatches $canv2 $row $author $linentag($id) $m \
4629 [$canv2 itemcget $linentag($id) -font] $row
4630 }
4631 }
4632}
4633
4634proc vrel_change {name ix op} {
4635 global highlight_related
4636
4637 rhighlight_none
4638 if {$highlight_related ne [mc "None"]} {
4639 run drawvisible
4640 }
4641}
4642
4643# prepare for testing whether commits are descendents or ancestors of a
4644proc rhighlight_sel {a} {
4645 global descendent desc_todo ancestor anc_todo
4646 global highlight_related
4647
4648 catch {unset descendent}
4649 set desc_todo [list $a]
4650 catch {unset ancestor}
4651 set anc_todo [list $a]
4652 if {$highlight_related ne [mc "None"]} {
4653 rhighlight_none
4654 run drawvisible
4655 }
4656}
4657
4658proc rhighlight_none {} {
4659 global rhighlights
4660
4661 catch {unset rhighlights}
4662 unbolden
4663}
4664
4665proc is_descendent {a} {
4666 global curview children descendent desc_todo
4667
4668 set v $curview
4669 set la [rowofcommit $a]
4670 set todo $desc_todo
4671 set leftover {}
4672 set done 0
4673 for {set i 0} {$i < [llength $todo]} {incr i} {
4674 set do [lindex $todo $i]
4675 if {[rowofcommit $do] < $la} {
4676 lappend leftover $do
4677 continue
4678 }
4679 foreach nk $children($v,$do) {
4680 if {![info exists descendent($nk)]} {
4681 set descendent($nk) 1
4682 lappend todo $nk
4683 if {$nk eq $a} {
4684 set done 1
4685 }
4686 }
4687 }
4688 if {$done} {
4689 set desc_todo [concat $leftover [lrange $todo [expr {$i+1}] end]]
4690 return
4691 }
4692 }
4693 set descendent($a) 0
4694 set desc_todo $leftover
4695}
4696
4697proc is_ancestor {a} {
4698 global curview parents ancestor anc_todo
4699
4700 set v $curview
4701 set la [rowofcommit $a]
4702 set todo $anc_todo
4703 set leftover {}
4704 set done 0
4705 for {set i 0} {$i < [llength $todo]} {incr i} {
4706 set do [lindex $todo $i]
4707 if {![commitinview $do $v] || [rowofcommit $do] > $la} {
4708 lappend leftover $do
4709 continue
4710 }
4711 foreach np $parents($v,$do) {
4712 if {![info exists ancestor($np)]} {
4713 set ancestor($np) 1
4714 lappend todo $np
4715 if {$np eq $a} {
4716 set done 1
4717 }
4718 }
4719 }
4720 if {$done} {
4721 set anc_todo [concat $leftover [lrange $todo [expr {$i+1}] end]]
4722 return
4723 }
4724 }
4725 set ancestor($a) 0
4726 set anc_todo $leftover
4727}
4728
4729proc askrelhighlight {row id} {
4730 global descendent highlight_related iddrawn rhighlights
4731 global selectedline ancestor
4732
4733 if {$selectedline eq {}} return
4734 set isbold 0
4735 if {$highlight_related eq [mc "Descendant"] ||
4736 $highlight_related eq [mc "Not descendant"]} {
4737 if {![info exists descendent($id)]} {
4738 is_descendent $id
4739 }
4740 if {$descendent($id) == ($highlight_related eq [mc "Descendant"])} {
4741 set isbold 1
4742 }
4743 } elseif {$highlight_related eq [mc "Ancestor"] ||
4744 $highlight_related eq [mc "Not ancestor"]} {
4745 if {![info exists ancestor($id)]} {
4746 is_ancestor $id
4747 }
4748 if {$ancestor($id) == ($highlight_related eq [mc "Ancestor"])} {
4749 set isbold 1
4750 }
4751 }
4752 if {[info exists iddrawn($id)]} {
4753 if {$isbold && ![ishighlighted $id]} {
4754 bolden $id mainfontbold
4755 }
4756 }
4757 set rhighlights($id) $isbold
4758}
4759
4760# Graph layout functions
4761
4762proc shortids {ids} {
4763 set res {}
4764 foreach id $ids {
4765 if {[llength $id] > 1} {
4766 lappend res [shortids $id]
4767 } elseif {[regexp {^[0-9a-f]{40}$} $id]} {
4768 lappend res [string range $id 0 7]
4769 } else {
4770 lappend res $id
4771 }
4772 }
4773 return $res
4774}
4775
4776proc ntimes {n o} {
4777 set ret {}
4778 set o [list $o]
4779 for {set mask 1} {$mask <= $n} {incr mask $mask} {
4780 if {($n & $mask) != 0} {
4781 set ret [concat $ret $o]
4782 }
4783 set o [concat $o $o]
4784 }
4785 return $ret
4786}
4787
4788proc ordertoken {id} {
4789 global ordertok curview varcid varcstart varctok curview parents children
4790 global nullid nullid2
4791
4792 if {[info exists ordertok($id)]} {
4793 return $ordertok($id)
4794 }
4795 set origid $id
4796 set todo {}
4797 while {1} {
4798 if {[info exists varcid($curview,$id)]} {
4799 set a $varcid($curview,$id)
4800 set p [lindex $varcstart($curview) $a]
4801 } else {
4802 set p [lindex $children($curview,$id) 0]
4803 }
4804 if {[info exists ordertok($p)]} {
4805 set tok $ordertok($p)
4806 break
4807 }
4808 set id [first_real_child $curview,$p]
4809 if {$id eq {}} {
4810 # it's a root
4811 set tok [lindex $varctok($curview) $varcid($curview,$p)]
4812 break
4813 }
4814 if {[llength $parents($curview,$id)] == 1} {
4815 lappend todo [list $p {}]
4816 } else {
4817 set j [lsearch -exact $parents($curview,$id) $p]
4818 if {$j < 0} {
4819 puts "oops didn't find [shortids $p] in parents of [shortids $id]"
4820 }
4821 lappend todo [list $p [strrep $j]]
4822 }
4823 }
4824 for {set i [llength $todo]} {[incr i -1] >= 0} {} {
4825 set p [lindex $todo $i 0]
4826 append tok [lindex $todo $i 1]
4827 set ordertok($p) $tok
4828 }
4829 set ordertok($origid) $tok
4830 return $tok
4831}
4832
4833# Work out where id should go in idlist so that order-token
4834# values increase from left to right
4835proc idcol {idlist id {i 0}} {
4836 set t [ordertoken $id]
4837 if {$i < 0} {
4838 set i 0
4839 }
4840 if {$i >= [llength $idlist] || $t < [ordertoken [lindex $idlist $i]]} {
4841 if {$i > [llength $idlist]} {
4842 set i [llength $idlist]
4843 }
4844 while {[incr i -1] >= 0 && $t < [ordertoken [lindex $idlist $i]]} {}
4845 incr i
4846 } else {
4847 if {$t > [ordertoken [lindex $idlist $i]]} {
4848 while {[incr i] < [llength $idlist] &&
4849 $t >= [ordertoken [lindex $idlist $i]]} {}
4850 }
4851 }
4852 return $i
4853}
4854
4855proc initlayout {} {
4856 global rowidlist rowisopt rowfinal displayorder parentlist
4857 global numcommits canvxmax canv
4858 global nextcolor
4859 global colormap rowtextx
4860
4861 set numcommits 0
4862 set displayorder {}
4863 set parentlist {}
4864 set nextcolor 0
4865 set rowidlist {}
4866 set rowisopt {}
4867 set rowfinal {}
4868 set canvxmax [$canv cget -width]
4869 catch {unset colormap}
4870 catch {unset rowtextx}
4871 setcanvscroll
4872}
4873
4874proc setcanvscroll {} {
4875 global canv canv2 canv3 numcommits linespc canvxmax canvy0
4876 global lastscrollset lastscrollrows
4877
4878 set ymax [expr {$canvy0 + ($numcommits - 0.5) * $linespc + 2}]
4879 $canv conf -scrollregion [list 0 0 $canvxmax $ymax]
4880 $canv2 conf -scrollregion [list 0 0 0 $ymax]
4881 $canv3 conf -scrollregion [list 0 0 0 $ymax]
4882 set lastscrollset [clock clicks -milliseconds]
4883 set lastscrollrows $numcommits
4884}
4885
4886proc visiblerows {} {
4887 global canv numcommits linespc
4888
4889 set ymax [lindex [$canv cget -scrollregion] 3]
4890 if {$ymax eq {} || $ymax == 0} return
4891 set f [$canv yview]
4892 set y0 [expr {int([lindex $f 0] * $ymax)}]
4893 set r0 [expr {int(($y0 - 3) / $linespc) - 1}]
4894 if {$r0 < 0} {
4895 set r0 0
4896 }
4897 set y1 [expr {int([lindex $f 1] * $ymax)}]
4898 set r1 [expr {int(($y1 - 3) / $linespc) + 1}]
4899 if {$r1 >= $numcommits} {
4900 set r1 [expr {$numcommits - 1}]
4901 }
4902 return [list $r0 $r1]
4903}
4904
4905proc layoutmore {} {
4906 global commitidx viewcomplete curview
4907 global numcommits pending_select curview
4908 global lastscrollset lastscrollrows
4909
4910 if {$lastscrollrows < 100 || $viewcomplete($curview) ||
4911 [clock clicks -milliseconds] - $lastscrollset > 500} {
4912 setcanvscroll
4913 }
4914 if {[info exists pending_select] &&
4915 [commitinview $pending_select $curview]} {
4916 update
4917 selectline [rowofcommit $pending_select] 1
4918 }
4919 drawvisible
4920}
4921
4922# With path limiting, we mightn't get the actual HEAD commit,
4923# so ask git rev-list what is the first ancestor of HEAD that
4924# touches a file in the path limit.
4925proc get_viewmainhead {view} {
4926 global viewmainheadid vfilelimit viewinstances mainheadid
4927
4928 catch {
4929 set rfd [open [concat | git rev-list -1 $mainheadid \
4930 -- $vfilelimit($view)] r]
4931 set j [reg_instance $rfd]
4932 lappend viewinstances($view) $j
4933 fconfigure $rfd -blocking 0
4934 filerun $rfd [list getviewhead $rfd $j $view]
4935 set viewmainheadid($curview) {}
4936 }
4937}
4938
4939# git rev-list should give us just 1 line to use as viewmainheadid($view)
4940proc getviewhead {fd inst view} {
4941 global viewmainheadid commfd curview viewinstances showlocalchanges
4942
4943 set id {}
4944 if {[gets $fd line] < 0} {
4945 if {![eof $fd]} {
4946 return 1
4947 }
4948 } elseif {[string length $line] == 40 && [string is xdigit $line]} {
4949 set id $line
4950 }
4951 set viewmainheadid($view) $id
4952 close $fd
4953 unset commfd($inst)
4954 set i [lsearch -exact $viewinstances($view) $inst]
4955 if {$i >= 0} {
4956 set viewinstances($view) [lreplace $viewinstances($view) $i $i]
4957 }
4958 if {$showlocalchanges && $id ne {} && $view == $curview} {
4959 doshowlocalchanges
4960 }
4961 return 0
4962}
4963
4964proc doshowlocalchanges {} {
4965 global curview viewmainheadid
4966
4967 if {$viewmainheadid($curview) eq {}} return
4968 if {[commitinview $viewmainheadid($curview) $curview]} {
4969 dodiffindex
4970 } else {
4971 interestedin $viewmainheadid($curview) dodiffindex
4972 }
4973}
4974
4975proc dohidelocalchanges {} {
4976 global nullid nullid2 lserial curview
4977
4978 if {[commitinview $nullid $curview]} {
4979 removefakerow $nullid
4980 }
4981 if {[commitinview $nullid2 $curview]} {
4982 removefakerow $nullid2
4983 }
4984 incr lserial
4985}
4986
4987# spawn off a process to do git diff-index --cached HEAD
4988proc dodiffindex {} {
4989 global lserial showlocalchanges vfilelimit curview
4990 global isworktree
4991
4992 if {!$showlocalchanges || !$isworktree} return
4993 incr lserial
4994 set cmd "|git diff-index --cached HEAD"
4995 if {$vfilelimit($curview) ne {}} {
4996 set cmd [concat $cmd -- $vfilelimit($curview)]
4997 }
4998 set fd [open $cmd r]
4999 fconfigure $fd -blocking 0
5000 set i [reg_instance $fd]
5001 filerun $fd [list readdiffindex $fd $lserial $i]
5002}
5003
5004proc readdiffindex {fd serial inst} {
5005 global viewmainheadid nullid nullid2 curview commitinfo commitdata lserial
5006 global vfilelimit
5007
5008 set isdiff 1
5009 if {[gets $fd line] < 0} {
5010 if {![eof $fd]} {
5011 return 1
5012 }
5013 set isdiff 0
5014 }
5015 # we only need to see one line and we don't really care what it says...
5016 stop_instance $inst
5017
5018 if {$serial != $lserial} {
5019 return 0
5020 }
5021
5022 # now see if there are any local changes not checked in to the index
5023 set cmd "|git diff-files"
5024 if {$vfilelimit($curview) ne {}} {
5025 set cmd [concat $cmd -- $vfilelimit($curview)]
5026 }
5027 set fd [open $cmd r]
5028 fconfigure $fd -blocking 0
5029 set i [reg_instance $fd]
5030 filerun $fd [list readdifffiles $fd $serial $i]
5031
5032 if {$isdiff && ![commitinview $nullid2 $curview]} {
5033 # add the line for the changes in the index to the graph
5034 set hl [mc "Local changes checked in to index but not committed"]
5035 set commitinfo($nullid2) [list $hl {} {} {} {} " $hl\n"]
5036 set commitdata($nullid2) "\n $hl\n"
5037 if {[commitinview $nullid $curview]} {
5038 removefakerow $nullid
5039 }
5040 insertfakerow $nullid2 $viewmainheadid($curview)
5041 } elseif {!$isdiff && [commitinview $nullid2 $curview]} {
5042 if {[commitinview $nullid $curview]} {
5043 removefakerow $nullid
5044 }
5045 removefakerow $nullid2
5046 }
5047 return 0
5048}
5049
5050proc readdifffiles {fd serial inst} {
5051 global viewmainheadid nullid nullid2 curview
5052 global commitinfo commitdata lserial
5053
5054 set isdiff 1
5055 if {[gets $fd line] < 0} {
5056 if {![eof $fd]} {
5057 return 1
5058 }
5059 set isdiff 0
5060 }
5061 # we only need to see one line and we don't really care what it says...
5062 stop_instance $inst
5063
5064 if {$serial != $lserial} {
5065 return 0
5066 }
5067
5068 if {$isdiff && ![commitinview $nullid $curview]} {
5069 # add the line for the local diff to the graph
5070 set hl [mc "Local uncommitted changes, not checked in to index"]
5071 set commitinfo($nullid) [list $hl {} {} {} {} " $hl\n"]
5072 set commitdata($nullid) "\n $hl\n"
5073 if {[commitinview $nullid2 $curview]} {
5074 set p $nullid2
5075 } else {
5076 set p $viewmainheadid($curview)
5077 }
5078 insertfakerow $nullid $p
5079 } elseif {!$isdiff && [commitinview $nullid $curview]} {
5080 removefakerow $nullid
5081 }
5082 return 0
5083}
5084
5085proc nextuse {id row} {
5086 global curview children
5087
5088 if {[info exists children($curview,$id)]} {
5089 foreach kid $children($curview,$id) {
5090 if {![commitinview $kid $curview]} {
5091 return -1
5092 }
5093 if {[rowofcommit $kid] > $row} {
5094 return [rowofcommit $kid]
5095 }
5096 }
5097 }
5098 if {[commitinview $id $curview]} {
5099 return [rowofcommit $id]
5100 }
5101 return -1
5102}
5103
5104proc prevuse {id row} {
5105 global curview children
5106
5107 set ret -1
5108 if {[info exists children($curview,$id)]} {
5109 foreach kid $children($curview,$id) {
5110 if {![commitinview $kid $curview]} break
5111 if {[rowofcommit $kid] < $row} {
5112 set ret [rowofcommit $kid]
5113 }
5114 }
5115 }
5116 return $ret
5117}
5118
5119proc make_idlist {row} {
5120 global displayorder parentlist uparrowlen downarrowlen mingaplen
5121 global commitidx curview children
5122
5123 set r [expr {$row - $mingaplen - $downarrowlen - 1}]
5124 if {$r < 0} {
5125 set r 0
5126 }
5127 set ra [expr {$row - $downarrowlen}]
5128 if {$ra < 0} {
5129 set ra 0
5130 }
5131 set rb [expr {$row + $uparrowlen}]
5132 if {$rb > $commitidx($curview)} {
5133 set rb $commitidx($curview)
5134 }
5135 make_disporder $r [expr {$rb + 1}]
5136 set ids {}
5137 for {} {$r < $ra} {incr r} {
5138 set nextid [lindex $displayorder [expr {$r + 1}]]
5139 foreach p [lindex $parentlist $r] {
5140 if {$p eq $nextid} continue
5141 set rn [nextuse $p $r]
5142 if {$rn >= $row &&
5143 $rn <= $r + $downarrowlen + $mingaplen + $uparrowlen} {
5144 lappend ids [list [ordertoken $p] $p]
5145 }
5146 }
5147 }
5148 for {} {$r < $row} {incr r} {
5149 set nextid [lindex $displayorder [expr {$r + 1}]]
5150 foreach p [lindex $parentlist $r] {
5151 if {$p eq $nextid} continue
5152 set rn [nextuse $p $r]
5153 if {$rn < 0 || $rn >= $row} {
5154 lappend ids [list [ordertoken $p] $p]
5155 }
5156 }
5157 }
5158 set id [lindex $displayorder $row]
5159 lappend ids [list [ordertoken $id] $id]
5160 while {$r < $rb} {
5161 foreach p [lindex $parentlist $r] {
5162 set firstkid [lindex $children($curview,$p) 0]
5163 if {[rowofcommit $firstkid] < $row} {
5164 lappend ids [list [ordertoken $p] $p]
5165 }
5166 }
5167 incr r
5168 set id [lindex $displayorder $r]
5169 if {$id ne {}} {
5170 set firstkid [lindex $children($curview,$id) 0]
5171 if {$firstkid ne {} && [rowofcommit $firstkid] < $row} {
5172 lappend ids [list [ordertoken $id] $id]
5173 }
5174 }
5175 }
5176 set idlist {}
5177 foreach idx [lsort -unique $ids] {
5178 lappend idlist [lindex $idx 1]
5179 }
5180 return $idlist
5181}
5182
5183proc rowsequal {a b} {
5184 while {[set i [lsearch -exact $a {}]] >= 0} {
5185 set a [lreplace $a $i $i]
5186 }
5187 while {[set i [lsearch -exact $b {}]] >= 0} {
5188 set b [lreplace $b $i $i]
5189 }
5190 return [expr {$a eq $b}]
5191}
5192
5193proc makeupline {id row rend col} {
5194 global rowidlist uparrowlen downarrowlen mingaplen
5195
5196 for {set r $rend} {1} {set r $rstart} {
5197 set rstart [prevuse $id $r]
5198 if {$rstart < 0} return
5199 if {$rstart < $row} break
5200 }
5201 if {$rstart + $uparrowlen + $mingaplen + $downarrowlen < $rend} {
5202 set rstart [expr {$rend - $uparrowlen - 1}]
5203 }
5204 for {set r $rstart} {[incr r] <= $row} {} {
5205 set idlist [lindex $rowidlist $r]
5206 if {$idlist ne {} && [lsearch -exact $idlist $id] < 0} {
5207 set col [idcol $idlist $id $col]
5208 lset rowidlist $r [linsert $idlist $col $id]
5209 changedrow $r
5210 }
5211 }
5212}
5213
5214proc layoutrows {row endrow} {
5215 global rowidlist rowisopt rowfinal displayorder
5216 global uparrowlen downarrowlen maxwidth mingaplen
5217 global children parentlist
5218 global commitidx viewcomplete curview
5219
5220 make_disporder [expr {$row - 1}] [expr {$endrow + $uparrowlen}]
5221 set idlist {}
5222 if {$row > 0} {
5223 set rm1 [expr {$row - 1}]
5224 foreach id [lindex $rowidlist $rm1] {
5225 if {$id ne {}} {
5226 lappend idlist $id
5227 }
5228 }
5229 set final [lindex $rowfinal $rm1]
5230 }
5231 for {} {$row < $endrow} {incr row} {
5232 set rm1 [expr {$row - 1}]
5233 if {$rm1 < 0 || $idlist eq {}} {
5234 set idlist [make_idlist $row]
5235 set final 1
5236 } else {
5237 set id [lindex $displayorder $rm1]
5238 set col [lsearch -exact $idlist $id]
5239 set idlist [lreplace $idlist $col $col]
5240 foreach p [lindex $parentlist $rm1] {
5241 if {[lsearch -exact $idlist $p] < 0} {
5242 set col [idcol $idlist $p $col]
5243 set idlist [linsert $idlist $col $p]
5244 # if not the first child, we have to insert a line going up
5245 if {$id ne [lindex $children($curview,$p) 0]} {
5246 makeupline $p $rm1 $row $col
5247 }
5248 }
5249 }
5250 set id [lindex $displayorder $row]
5251 if {$row > $downarrowlen} {
5252 set termrow [expr {$row - $downarrowlen - 1}]
5253 foreach p [lindex $parentlist $termrow] {
5254 set i [lsearch -exact $idlist $p]
5255 if {$i < 0} continue
5256 set nr [nextuse $p $termrow]
5257 if {$nr < 0 || $nr >= $row + $mingaplen + $uparrowlen} {
5258 set idlist [lreplace $idlist $i $i]
5259 }
5260 }
5261 }
5262 set col [lsearch -exact $idlist $id]
5263 if {$col < 0} {
5264 set col [idcol $idlist $id]
5265 set idlist [linsert $idlist $col $id]
5266 if {$children($curview,$id) ne {}} {
5267 makeupline $id $rm1 $row $col
5268 }
5269 }
5270 set r [expr {$row + $uparrowlen - 1}]
5271 if {$r < $commitidx($curview)} {
5272 set x $col
5273 foreach p [lindex $parentlist $r] {
5274 if {[lsearch -exact $idlist $p] >= 0} continue
5275 set fk [lindex $children($curview,$p) 0]
5276 if {[rowofcommit $fk] < $row} {
5277 set x [idcol $idlist $p $x]
5278 set idlist [linsert $idlist $x $p]
5279 }
5280 }
5281 if {[incr r] < $commitidx($curview)} {
5282 set p [lindex $displayorder $r]
5283 if {[lsearch -exact $idlist $p] < 0} {
5284 set fk [lindex $children($curview,$p) 0]
5285 if {$fk ne {} && [rowofcommit $fk] < $row} {
5286 set x [idcol $idlist $p $x]
5287 set idlist [linsert $idlist $x $p]
5288 }
5289 }
5290 }
5291 }
5292 }
5293 if {$final && !$viewcomplete($curview) &&
5294 $row + $uparrowlen + $mingaplen + $downarrowlen
5295 >= $commitidx($curview)} {
5296 set final 0
5297 }
5298 set l [llength $rowidlist]
5299 if {$row == $l} {
5300 lappend rowidlist $idlist
5301 lappend rowisopt 0
5302 lappend rowfinal $final
5303 } elseif {$row < $l} {
5304 if {![rowsequal $idlist [lindex $rowidlist $row]]} {
5305 lset rowidlist $row $idlist
5306 changedrow $row
5307 }
5308 lset rowfinal $row $final
5309 } else {
5310 set pad [ntimes [expr {$row - $l}] {}]
5311 set rowidlist [concat $rowidlist $pad]
5312 lappend rowidlist $idlist
5313 set rowfinal [concat $rowfinal $pad]
5314 lappend rowfinal $final
5315 set rowisopt [concat $rowisopt [ntimes [expr {$row - $l + 1}] 0]]
5316 }
5317 }
5318 return $row
5319}
5320
5321proc changedrow {row} {
5322 global displayorder iddrawn rowisopt need_redisplay
5323
5324 set l [llength $rowisopt]
5325 if {$row < $l} {
5326 lset rowisopt $row 0
5327 if {$row + 1 < $l} {
5328 lset rowisopt [expr {$row + 1}] 0
5329 if {$row + 2 < $l} {
5330 lset rowisopt [expr {$row + 2}] 0
5331 }
5332 }
5333 }
5334 set id [lindex $displayorder $row]
5335 if {[info exists iddrawn($id)]} {
5336 set need_redisplay 1
5337 }
5338}
5339
5340proc insert_pad {row col npad} {
5341 global rowidlist
5342
5343 set pad [ntimes $npad {}]
5344 set idlist [lindex $rowidlist $row]
5345 set bef [lrange $idlist 0 [expr {$col - 1}]]
5346 set aft [lrange $idlist $col end]
5347 set i [lsearch -exact $aft {}]
5348 if {$i > 0} {
5349 set aft [lreplace $aft $i $i]
5350 }
5351 lset rowidlist $row [concat $bef $pad $aft]
5352 changedrow $row
5353}
5354
5355proc optimize_rows {row col endrow} {
5356 global rowidlist rowisopt displayorder curview children
5357
5358 if {$row < 1} {
5359 set row 1
5360 }
5361 for {} {$row < $endrow} {incr row; set col 0} {
5362 if {[lindex $rowisopt $row]} continue
5363 set haspad 0
5364 set y0 [expr {$row - 1}]
5365 set ym [expr {$row - 2}]
5366 set idlist [lindex $rowidlist $row]
5367 set previdlist [lindex $rowidlist $y0]
5368 if {$idlist eq {} || $previdlist eq {}} continue
5369 if {$ym >= 0} {
5370 set pprevidlist [lindex $rowidlist $ym]
5371 if {$pprevidlist eq {}} continue
5372 } else {
5373 set pprevidlist {}
5374 }
5375 set x0 -1
5376 set xm -1
5377 for {} {$col < [llength $idlist]} {incr col} {
5378 set id [lindex $idlist $col]
5379 if {[lindex $previdlist $col] eq $id} continue
5380 if {$id eq {}} {
5381 set haspad 1
5382 continue
5383 }
5384 set x0 [lsearch -exact $previdlist $id]
5385 if {$x0 < 0} continue
5386 set z [expr {$x0 - $col}]
5387 set isarrow 0
5388 set z0 {}
5389 if {$ym >= 0} {
5390 set xm [lsearch -exact $pprevidlist $id]
5391 if {$xm >= 0} {
5392 set z0 [expr {$xm - $x0}]
5393 }
5394 }
5395 if {$z0 eq {}} {
5396 # if row y0 is the first child of $id then it's not an arrow
5397 if {[lindex $children($curview,$id) 0] ne
5398 [lindex $displayorder $y0]} {
5399 set isarrow 1
5400 }
5401 }
5402 if {!$isarrow && $id ne [lindex $displayorder $row] &&
5403 [lsearch -exact [lindex $rowidlist [expr {$row+1}]] $id] < 0} {
5404 set isarrow 1
5405 }
5406 # Looking at lines from this row to the previous row,
5407 # make them go straight up if they end in an arrow on
5408 # the previous row; otherwise make them go straight up
5409 # or at 45 degrees.
5410 if {$z < -1 || ($z < 0 && $isarrow)} {
5411 # Line currently goes left too much;
5412 # insert pads in the previous row, then optimize it
5413 set npad [expr {-1 - $z + $isarrow}]
5414 insert_pad $y0 $x0 $npad
5415 if {$y0 > 0} {
5416 optimize_rows $y0 $x0 $row
5417 }
5418 set previdlist [lindex $rowidlist $y0]
5419 set x0 [lsearch -exact $previdlist $id]
5420 set z [expr {$x0 - $col}]
5421 if {$z0 ne {}} {
5422 set pprevidlist [lindex $rowidlist $ym]
5423 set xm [lsearch -exact $pprevidlist $id]
5424 set z0 [expr {$xm - $x0}]
5425 }
5426 } elseif {$z > 1 || ($z > 0 && $isarrow)} {
5427 # Line currently goes right too much;
5428 # insert pads in this line
5429 set npad [expr {$z - 1 + $isarrow}]
5430 insert_pad $row $col $npad
5431 set idlist [lindex $rowidlist $row]
5432 incr col $npad
5433 set z [expr {$x0 - $col}]
5434 set haspad 1
5435 }
5436 if {$z0 eq {} && !$isarrow && $ym >= 0} {
5437 # this line links to its first child on row $row-2
5438 set id [lindex $displayorder $ym]
5439 set xc [lsearch -exact $pprevidlist $id]
5440 if {$xc >= 0} {
5441 set z0 [expr {$xc - $x0}]
5442 }
5443 }
5444 # avoid lines jigging left then immediately right
5445 if {$z0 ne {} && $z < 0 && $z0 > 0} {
5446 insert_pad $y0 $x0 1
5447 incr x0
5448 optimize_rows $y0 $x0 $row
5449 set previdlist [lindex $rowidlist $y0]
5450 }
5451 }
5452 if {!$haspad} {
5453 # Find the first column that doesn't have a line going right
5454 for {set col [llength $idlist]} {[incr col -1] >= 0} {} {
5455 set id [lindex $idlist $col]
5456 if {$id eq {}} break
5457 set x0 [lsearch -exact $previdlist $id]
5458 if {$x0 < 0} {
5459 # check if this is the link to the first child
5460 set kid [lindex $displayorder $y0]
5461 if {[lindex $children($curview,$id) 0] eq $kid} {
5462 # it is, work out offset to child
5463 set x0 [lsearch -exact $previdlist $kid]
5464 }
5465 }
5466 if {$x0 <= $col} break
5467 }
5468 # Insert a pad at that column as long as it has a line and
5469 # isn't the last column
5470 if {$x0 >= 0 && [incr col] < [llength $idlist]} {
5471 set idlist [linsert $idlist $col {}]
5472 lset rowidlist $row $idlist
5473 changedrow $row
5474 }
5475 }
5476 }
5477}
5478
5479proc xc {row col} {
5480 global canvx0 linespc
5481 return [expr {$canvx0 + $col * $linespc}]
5482}
5483
5484proc yc {row} {
5485 global canvy0 linespc
5486 return [expr {$canvy0 + $row * $linespc}]
5487}
5488
5489proc linewidth {id} {
5490 global thickerline lthickness
5491
5492 set wid $lthickness
5493 if {[info exists thickerline] && $id eq $thickerline} {
5494 set wid [expr {2 * $lthickness}]
5495 }
5496 return $wid
5497}
5498
5499proc rowranges {id} {
5500 global curview children uparrowlen downarrowlen
5501 global rowidlist
5502
5503 set kids $children($curview,$id)
5504 if {$kids eq {}} {
5505 return {}
5506 }
5507 set ret {}
5508 lappend kids $id
5509 foreach child $kids {
5510 if {![commitinview $child $curview]} break
5511 set row [rowofcommit $child]
5512 if {![info exists prev]} {
5513 lappend ret [expr {$row + 1}]
5514 } else {
5515 if {$row <= $prevrow} {
5516 puts "oops children of [shortids $id] out of order [shortids $child] $row <= [shortids $prev] $prevrow"
5517 }
5518 # see if the line extends the whole way from prevrow to row
5519 if {$row > $prevrow + $uparrowlen + $downarrowlen &&
5520 [lsearch -exact [lindex $rowidlist \
5521 [expr {int(($row + $prevrow) / 2)}]] $id] < 0} {
5522 # it doesn't, see where it ends
5523 set r [expr {$prevrow + $downarrowlen}]
5524 if {[lsearch -exact [lindex $rowidlist $r] $id] < 0} {
5525 while {[incr r -1] > $prevrow &&
5526 [lsearch -exact [lindex $rowidlist $r] $id] < 0} {}
5527 } else {
5528 while {[incr r] <= $row &&
5529 [lsearch -exact [lindex $rowidlist $r] $id] >= 0} {}
5530 incr r -1
5531 }
5532 lappend ret $r
5533 # see where it starts up again
5534 set r [expr {$row - $uparrowlen}]
5535 if {[lsearch -exact [lindex $rowidlist $r] $id] < 0} {
5536 while {[incr r] < $row &&
5537 [lsearch -exact [lindex $rowidlist $r] $id] < 0} {}
5538 } else {
5539 while {[incr r -1] >= $prevrow &&
5540 [lsearch -exact [lindex $rowidlist $r] $id] >= 0} {}
5541 incr r
5542 }
5543 lappend ret $r
5544 }
5545 }
5546 if {$child eq $id} {
5547 lappend ret $row
5548 }
5549 set prev $child
5550 set prevrow $row
5551 }
5552 return $ret
5553}
5554
5555proc drawlineseg {id row endrow arrowlow} {
5556 global rowidlist displayorder iddrawn linesegs
5557 global canv colormap linespc curview maxlinelen parentlist
5558
5559 set cols [list [lsearch -exact [lindex $rowidlist $row] $id]]
5560 set le [expr {$row + 1}]
5561 set arrowhigh 1
5562 while {1} {
5563 set c [lsearch -exact [lindex $rowidlist $le] $id]
5564 if {$c < 0} {
5565 incr le -1
5566 break
5567 }
5568 lappend cols $c
5569 set x [lindex $displayorder $le]
5570 if {$x eq $id} {
5571 set arrowhigh 0
5572 break
5573 }
5574 if {[info exists iddrawn($x)] || $le == $endrow} {
5575 set c [lsearch -exact [lindex $rowidlist [expr {$le+1}]] $id]
5576 if {$c >= 0} {
5577 lappend cols $c
5578 set arrowhigh 0
5579 }
5580 break
5581 }
5582 incr le
5583 }
5584 if {$le <= $row} {
5585 return $row
5586 }
5587
5588 set lines {}
5589 set i 0
5590 set joinhigh 0
5591 if {[info exists linesegs($id)]} {
5592 set lines $linesegs($id)
5593 foreach li $lines {
5594 set r0 [lindex $li 0]
5595 if {$r0 > $row} {
5596 if {$r0 == $le && [lindex $li 1] - $row <= $maxlinelen} {
5597 set joinhigh 1
5598 }
5599 break
5600 }
5601 incr i
5602 }
5603 }
5604 set joinlow 0
5605 if {$i > 0} {
5606 set li [lindex $lines [expr {$i-1}]]
5607 set r1 [lindex $li 1]
5608 if {$r1 == $row && $le - [lindex $li 0] <= $maxlinelen} {
5609 set joinlow 1
5610 }
5611 }
5612
5613 set x [lindex $cols [expr {$le - $row}]]
5614 set xp [lindex $cols [expr {$le - 1 - $row}]]
5615 set dir [expr {$xp - $x}]
5616 if {$joinhigh} {
5617 set ith [lindex $lines $i 2]
5618 set coords [$canv coords $ith]
5619 set ah [$canv itemcget $ith -arrow]
5620 set arrowhigh [expr {$ah eq "first" || $ah eq "both"}]
5621 set x2 [lindex $cols [expr {$le + 1 - $row}]]
5622 if {$x2 ne {} && $x - $x2 == $dir} {
5623 set coords [lrange $coords 0 end-2]
5624 }
5625 } else {
5626 set coords [list [xc $le $x] [yc $le]]
5627 }
5628 if {$joinlow} {
5629 set itl [lindex $lines [expr {$i-1}] 2]
5630 set al [$canv itemcget $itl -arrow]
5631 set arrowlow [expr {$al eq "last" || $al eq "both"}]
5632 } elseif {$arrowlow} {
5633 if {[lsearch -exact [lindex $rowidlist [expr {$row-1}]] $id] >= 0 ||
5634 [lsearch -exact [lindex $parentlist [expr {$row-1}]] $id] >= 0} {
5635 set arrowlow 0
5636 }
5637 }
5638 set arrow [lindex {none first last both} [expr {$arrowhigh + 2*$arrowlow}]]
5639 for {set y $le} {[incr y -1] > $row} {} {
5640 set x $xp
5641 set xp [lindex $cols [expr {$y - 1 - $row}]]
5642 set ndir [expr {$xp - $x}]
5643 if {$dir != $ndir || $xp < 0} {
5644 lappend coords [xc $y $x] [yc $y]
5645 }
5646 set dir $ndir
5647 }
5648 if {!$joinlow} {
5649 if {$xp < 0} {
5650 # join parent line to first child
5651 set ch [lindex $displayorder $row]
5652 set xc [lsearch -exact [lindex $rowidlist $row] $ch]
5653 if {$xc < 0} {
5654 puts "oops: drawlineseg: child $ch not on row $row"
5655 } elseif {$xc != $x} {
5656 if {($arrowhigh && $le == $row + 1) || $dir == 0} {
5657 set d [expr {int(0.5 * $linespc)}]
5658 set x1 [xc $row $x]
5659 if {$xc < $x} {
5660 set x2 [expr {$x1 - $d}]
5661 } else {
5662 set x2 [expr {$x1 + $d}]
5663 }
5664 set y2 [yc $row]
5665 set y1 [expr {$y2 + $d}]
5666 lappend coords $x1 $y1 $x2 $y2
5667 } elseif {$xc < $x - 1} {
5668 lappend coords [xc $row [expr {$x-1}]] [yc $row]
5669 } elseif {$xc > $x + 1} {
5670 lappend coords [xc $row [expr {$x+1}]] [yc $row]
5671 }
5672 set x $xc
5673 }
5674 lappend coords [xc $row $x] [yc $row]
5675 } else {
5676 set xn [xc $row $xp]
5677 set yn [yc $row]
5678 lappend coords $xn $yn
5679 }
5680 if {!$joinhigh} {
5681 assigncolor $id
5682 set t [$canv create line $coords -width [linewidth $id] \
5683 -fill $colormap($id) -tags lines.$id -arrow $arrow]
5684 $canv lower $t
5685 bindline $t $id
5686 set lines [linsert $lines $i [list $row $le $t]]
5687 } else {
5688 $canv coords $ith $coords
5689 if {$arrow ne $ah} {
5690 $canv itemconf $ith -arrow $arrow
5691 }
5692 lset lines $i 0 $row
5693 }
5694 } else {
5695 set xo [lsearch -exact [lindex $rowidlist [expr {$row - 1}]] $id]
5696 set ndir [expr {$xo - $xp}]
5697 set clow [$canv coords $itl]
5698 if {$dir == $ndir} {
5699 set clow [lrange $clow 2 end]
5700 }
5701 set coords [concat $coords $clow]
5702 if {!$joinhigh} {
5703 lset lines [expr {$i-1}] 1 $le
5704 } else {
5705 # coalesce two pieces
5706 $canv delete $ith
5707 set b [lindex $lines [expr {$i-1}] 0]
5708 set e [lindex $lines $i 1]
5709 set lines [lreplace $lines [expr {$i-1}] $i [list $b $e $itl]]
5710 }
5711 $canv coords $itl $coords
5712 if {$arrow ne $al} {
5713 $canv itemconf $itl -arrow $arrow
5714 }
5715 }
5716
5717 set linesegs($id) $lines
5718 return $le
5719}
5720
5721proc drawparentlinks {id row} {
5722 global rowidlist canv colormap curview parentlist
5723 global idpos linespc
5724
5725 set rowids [lindex $rowidlist $row]
5726 set col [lsearch -exact $rowids $id]
5727 if {$col < 0} return
5728 set olds [lindex $parentlist $row]
5729 set row2 [expr {$row + 1}]
5730 set x [xc $row $col]
5731 set y [yc $row]
5732 set y2 [yc $row2]
5733 set d [expr {int(0.5 * $linespc)}]
5734 set ymid [expr {$y + $d}]
5735 set ids [lindex $rowidlist $row2]
5736 # rmx = right-most X coord used
5737 set rmx 0
5738 foreach p $olds {
5739 set i [lsearch -exact $ids $p]
5740 if {$i < 0} {
5741 puts "oops, parent $p of $id not in list"
5742 continue
5743 }
5744 set x2 [xc $row2 $i]
5745 if {$x2 > $rmx} {
5746 set rmx $x2
5747 }
5748 set j [lsearch -exact $rowids $p]
5749 if {$j < 0} {
5750 # drawlineseg will do this one for us
5751 continue
5752 }
5753 assigncolor $p
5754 # should handle duplicated parents here...
5755 set coords [list $x $y]
5756 if {$i != $col} {
5757 # if attaching to a vertical segment, draw a smaller
5758 # slant for visual distinctness
5759 if {$i == $j} {
5760 if {$i < $col} {
5761 lappend coords [expr {$x2 + $d}] $y $x2 $ymid
5762 } else {
5763 lappend coords [expr {$x2 - $d}] $y $x2 $ymid
5764 }
5765 } elseif {$i < $col && $i < $j} {
5766 # segment slants towards us already
5767 lappend coords [xc $row $j] $y
5768 } else {
5769 if {$i < $col - 1} {
5770 lappend coords [expr {$x2 + $linespc}] $y
5771 } elseif {$i > $col + 1} {
5772 lappend coords [expr {$x2 - $linespc}] $y
5773 }
5774 lappend coords $x2 $y2
5775 }
5776 } else {
5777 lappend coords $x2 $y2
5778 }
5779 set t [$canv create line $coords -width [linewidth $p] \
5780 -fill $colormap($p) -tags lines.$p]
5781 $canv lower $t
5782 bindline $t $p
5783 }
5784 if {$rmx > [lindex $idpos($id) 1]} {
5785 lset idpos($id) 1 $rmx
5786 redrawtags $id
5787 }
5788}
5789
5790proc drawlines {id} {
5791 global canv
5792
5793 $canv itemconf lines.$id -width [linewidth $id]
5794}
5795
5796proc drawcmittext {id row col} {
5797 global linespc canv canv2 canv3 fgcolor curview
5798 global cmitlisted commitinfo rowidlist parentlist
5799 global rowtextx idpos idtags idheads idotherrefs
5800 global linehtag linentag linedtag selectedline
5801 global canvxmax boldids boldnameids fgcolor markedid
5802 global mainheadid nullid nullid2 circleitem circlecolors ctxbut
5803
5804 # listed is 0 for boundary, 1 for normal, 2 for negative, 3 for left, 4 for right
5805 set listed $cmitlisted($curview,$id)
5806 if {$id eq $nullid} {
5807 set ofill red
5808 } elseif {$id eq $nullid2} {
5809 set ofill green
5810 } elseif {$id eq $mainheadid} {
5811 set ofill yellow
5812 } else {
5813 set ofill [lindex $circlecolors $listed]
5814 }
5815 set x [xc $row $col]
5816 set y [yc $row]
5817 set orad [expr {$linespc / 3}]
5818 if {$listed <= 2} {
5819 set t [$canv create oval [expr {$x - $orad}] [expr {$y - $orad}] \
5820 [expr {$x + $orad - 1}] [expr {$y + $orad - 1}] \
5821 -fill $ofill -outline $fgcolor -width 1 -tags circle]
5822 } elseif {$listed == 3} {
5823 # triangle pointing left for left-side commits
5824 set t [$canv create polygon \
5825 [expr {$x - $orad}] $y \
5826 [expr {$x + $orad - 1}] [expr {$y - $orad}] \
5827 [expr {$x + $orad - 1}] [expr {$y + $orad - 1}] \
5828 -fill $ofill -outline $fgcolor -width 1 -tags circle]
5829 } else {
5830 # triangle pointing right for right-side commits
5831 set t [$canv create polygon \
5832 [expr {$x + $orad - 1}] $y \
5833 [expr {$x - $orad}] [expr {$y - $orad}] \
5834 [expr {$x - $orad}] [expr {$y + $orad - 1}] \
5835 -fill $ofill -outline $fgcolor -width 1 -tags circle]
5836 }
5837 set circleitem($row) $t
5838 $canv raise $t
5839 $canv bind $t <1> {selcanvline {} %x %y}
5840 set rmx [llength [lindex $rowidlist $row]]
5841 set olds [lindex $parentlist $row]
5842 if {$olds ne {}} {
5843 set nextids [lindex $rowidlist [expr {$row + 1}]]
5844 foreach p $olds {
5845 set i [lsearch -exact $nextids $p]
5846 if {$i > $rmx} {
5847 set rmx $i
5848 }
5849 }
5850 }
5851 set xt [xc $row $rmx]
5852 set rowtextx($row) $xt
5853 set idpos($id) [list $x $xt $y]
5854 if {[info exists idtags($id)] || [info exists idheads($id)]
5855 || [info exists idotherrefs($id)]} {
5856 set xt [drawtags $id $x $xt $y]
5857 }
5858 set headline [lindex $commitinfo($id) 0]
5859 set name [lindex $commitinfo($id) 1]
5860 set date [lindex $commitinfo($id) 2]
5861 set date [formatdate $date]
5862 set font mainfont
5863 set nfont mainfont
5864 set isbold [ishighlighted $id]
5865 if {$isbold > 0} {
5866 lappend boldids $id
5867 set font mainfontbold
5868 if {$isbold > 1} {
5869 lappend boldnameids $id
5870 set nfont mainfontbold
5871 }
5872 }
5873 set linehtag($id) [$canv create text $xt $y -anchor w -fill $fgcolor \
5874 -text $headline -font $font -tags text]
5875 $canv bind $linehtag($id) $ctxbut "rowmenu %X %Y $id"
5876 set linentag($id) [$canv2 create text 3 $y -anchor w -fill $fgcolor \
5877 -text $name -font $nfont -tags text]
5878 set linedtag($id) [$canv3 create text 3 $y -anchor w -fill $fgcolor \
5879 -text $date -font mainfont -tags text]
5880 if {$selectedline == $row} {
5881 make_secsel $id
5882 }
5883 if {[info exists markedid] && $markedid eq $id} {
5884 make_idmark $id
5885 }
5886 set xr [expr {$xt + [font measure $font $headline]}]
5887 if {$xr > $canvxmax} {
5888 set canvxmax $xr
5889 setcanvscroll
5890 }
5891}
5892
5893proc drawcmitrow {row} {
5894 global displayorder rowidlist nrows_drawn
5895 global iddrawn markingmatches
5896 global commitinfo numcommits
5897 global filehighlight fhighlights findpattern nhighlights
5898 global hlview vhighlights
5899 global highlight_related rhighlights
5900
5901 if {$row >= $numcommits} return
5902
5903 set id [lindex $displayorder $row]
5904 if {[info exists hlview] && ![info exists vhighlights($id)]} {
5905 askvhighlight $row $id
5906 }
5907 if {[info exists filehighlight] && ![info exists fhighlights($id)]} {
5908 askfilehighlight $row $id
5909 }
5910 if {$findpattern ne {} && ![info exists nhighlights($id)]} {
5911 askfindhighlight $row $id
5912 }
5913 if {$highlight_related ne [mc "None"] && ![info exists rhighlights($id)]} {
5914 askrelhighlight $row $id
5915 }
5916 if {![info exists iddrawn($id)]} {
5917 set col [lsearch -exact [lindex $rowidlist $row] $id]
5918 if {$col < 0} {
5919 puts "oops, row $row id $id not in list"
5920 return
5921 }
5922 if {![info exists commitinfo($id)]} {
5923 getcommit $id
5924 }
5925 assigncolor $id
5926 drawcmittext $id $row $col
5927 set iddrawn($id) 1
5928 incr nrows_drawn
5929 }
5930 if {$markingmatches} {
5931 markrowmatches $row $id
5932 }
5933}
5934
5935proc drawcommits {row {endrow {}}} {
5936 global numcommits iddrawn displayorder curview need_redisplay
5937 global parentlist rowidlist rowfinal uparrowlen downarrowlen nrows_drawn
5938
5939 if {$row < 0} {
5940 set row 0
5941 }
5942 if {$endrow eq {}} {
5943 set endrow $row
5944 }
5945 if {$endrow >= $numcommits} {
5946 set endrow [expr {$numcommits - 1}]
5947 }
5948
5949 set rl1 [expr {$row - $downarrowlen - 3}]
5950 if {$rl1 < 0} {
5951 set rl1 0
5952 }
5953 set ro1 [expr {$row - 3}]
5954 if {$ro1 < 0} {
5955 set ro1 0
5956 }
5957 set r2 [expr {$endrow + $uparrowlen + 3}]
5958 if {$r2 > $numcommits} {
5959 set r2 $numcommits
5960 }
5961 for {set r $rl1} {$r < $r2} {incr r} {
5962 if {[lindex $rowidlist $r] ne {} && [lindex $rowfinal $r]} {
5963 if {$rl1 < $r} {
5964 layoutrows $rl1 $r
5965 }
5966 set rl1 [expr {$r + 1}]
5967 }
5968 }
5969 if {$rl1 < $r} {
5970 layoutrows $rl1 $r
5971 }
5972 optimize_rows $ro1 0 $r2
5973 if {$need_redisplay || $nrows_drawn > 2000} {
5974 clear_display
5975 }
5976
5977 # make the lines join to already-drawn rows either side
5978 set r [expr {$row - 1}]
5979 if {$r < 0 || ![info exists iddrawn([lindex $displayorder $r])]} {
5980 set r $row
5981 }
5982 set er [expr {$endrow + 1}]
5983 if {$er >= $numcommits ||
5984 ![info exists iddrawn([lindex $displayorder $er])]} {
5985 set er $endrow
5986 }
5987 for {} {$r <= $er} {incr r} {
5988 set id [lindex $displayorder $r]
5989 set wasdrawn [info exists iddrawn($id)]
5990 drawcmitrow $r
5991 if {$r == $er} break
5992 set nextid [lindex $displayorder [expr {$r + 1}]]
5993 if {$wasdrawn && [info exists iddrawn($nextid)]} continue
5994 drawparentlinks $id $r
5995
5996 set rowids [lindex $rowidlist $r]
5997 foreach lid $rowids {
5998 if {$lid eq {}} continue
5999 if {[info exists lineend($lid)] && $lineend($lid) > $r} continue
6000 if {$lid eq $id} {
6001 # see if this is the first child of any of its parents
6002 foreach p [lindex $parentlist $r] {
6003 if {[lsearch -exact $rowids $p] < 0} {
6004 # make this line extend up to the child
6005 set lineend($p) [drawlineseg $p $r $er 0]
6006 }
6007 }
6008 } else {
6009 set lineend($lid) [drawlineseg $lid $r $er 1]
6010 }
6011 }
6012 }
6013}
6014
6015proc undolayout {row} {
6016 global uparrowlen mingaplen downarrowlen
6017 global rowidlist rowisopt rowfinal need_redisplay
6018
6019 set r [expr {$row - ($uparrowlen + $mingaplen + $downarrowlen)}]
6020 if {$r < 0} {
6021 set r 0
6022 }
6023 if {[llength $rowidlist] > $r} {
6024 incr r -1
6025 set rowidlist [lrange $rowidlist 0 $r]
6026 set rowfinal [lrange $rowfinal 0 $r]
6027 set rowisopt [lrange $rowisopt 0 $r]
6028 set need_redisplay 1
6029 run drawvisible
6030 }
6031}
6032
6033proc drawvisible {} {
6034 global canv linespc curview vrowmod selectedline targetrow targetid
6035 global need_redisplay cscroll numcommits
6036
6037 set fs [$canv yview]
6038 set ymax [lindex [$canv cget -scrollregion] 3]
6039 if {$ymax eq {} || $ymax == 0 || $numcommits == 0} return
6040 set f0 [lindex $fs 0]
6041 set f1 [lindex $fs 1]
6042 set y0 [expr {int($f0 * $ymax)}]
6043 set y1 [expr {int($f1 * $ymax)}]
6044
6045 if {[info exists targetid]} {
6046 if {[commitinview $targetid $curview]} {
6047 set r [rowofcommit $targetid]
6048 if {$r != $targetrow} {
6049 # Fix up the scrollregion and change the scrolling position
6050 # now that our target row has moved.
6051 set diff [expr {($r - $targetrow) * $linespc}]
6052 set targetrow $r
6053 setcanvscroll
6054 set ymax [lindex [$canv cget -scrollregion] 3]
6055 incr y0 $diff
6056 incr y1 $diff
6057 set f0 [expr {$y0 / $ymax}]
6058 set f1 [expr {$y1 / $ymax}]
6059 allcanvs yview moveto $f0
6060 $cscroll set $f0 $f1
6061 set need_redisplay 1
6062 }
6063 } else {
6064 unset targetid
6065 }
6066 }
6067
6068 set row [expr {int(($y0 - 3) / $linespc) - 1}]
6069 set endrow [expr {int(($y1 - 3) / $linespc) + 1}]
6070 if {$endrow >= $vrowmod($curview)} {
6071 update_arcrows $curview
6072 }
6073 if {$selectedline ne {} &&
6074 $row <= $selectedline && $selectedline <= $endrow} {
6075 set targetrow $selectedline
6076 } elseif {[info exists targetid]} {
6077 set targetrow [expr {int(($row + $endrow) / 2)}]
6078 }
6079 if {[info exists targetrow]} {
6080 if {$targetrow >= $numcommits} {
6081 set targetrow [expr {$numcommits - 1}]
6082 }
6083 set targetid [commitonrow $targetrow]
6084 }
6085 drawcommits $row $endrow
6086}
6087
6088proc clear_display {} {
6089 global iddrawn linesegs need_redisplay nrows_drawn
6090 global vhighlights fhighlights nhighlights rhighlights
6091 global linehtag linentag linedtag boldids boldnameids
6092
6093 allcanvs delete all
6094 catch {unset iddrawn}
6095 catch {unset linesegs}
6096 catch {unset linehtag}
6097 catch {unset linentag}
6098 catch {unset linedtag}
6099 set boldids {}
6100 set boldnameids {}
6101 catch {unset vhighlights}
6102 catch {unset fhighlights}
6103 catch {unset nhighlights}
6104 catch {unset rhighlights}
6105 set need_redisplay 0
6106 set nrows_drawn 0
6107}
6108
6109proc findcrossings {id} {
6110 global rowidlist parentlist numcommits displayorder
6111
6112 set cross {}
6113 set ccross {}
6114 foreach {s e} [rowranges $id] {
6115 if {$e >= $numcommits} {
6116 set e [expr {$numcommits - 1}]
6117 }
6118 if {$e <= $s} continue
6119 for {set row $e} {[incr row -1] >= $s} {} {
6120 set x [lsearch -exact [lindex $rowidlist $row] $id]
6121 if {$x < 0} break
6122 set olds [lindex $parentlist $row]
6123 set kid [lindex $displayorder $row]
6124 set kidx [lsearch -exact [lindex $rowidlist $row] $kid]
6125 if {$kidx < 0} continue
6126 set nextrow [lindex $rowidlist [expr {$row + 1}]]
6127 foreach p $olds {
6128 set px [lsearch -exact $nextrow $p]
6129 if {$px < 0} continue
6130 if {($kidx < $x && $x < $px) || ($px < $x && $x < $kidx)} {
6131 if {[lsearch -exact $ccross $p] >= 0} continue
6132 if {$x == $px + ($kidx < $px? -1: 1)} {
6133 lappend ccross $p
6134 } elseif {[lsearch -exact $cross $p] < 0} {
6135 lappend cross $p
6136 }
6137 }
6138 }
6139 }
6140 }
6141 return [concat $ccross {{}} $cross]
6142}
6143
6144proc assigncolor {id} {
6145 global colormap colors nextcolor
6146 global parents children children curview
6147
6148 if {[info exists colormap($id)]} return
6149 set ncolors [llength $colors]
6150 if {[info exists children($curview,$id)]} {
6151 set kids $children($curview,$id)
6152 } else {
6153 set kids {}
6154 }
6155 if {[llength $kids] == 1} {
6156 set child [lindex $kids 0]
6157 if {[info exists colormap($child)]
6158 && [llength $parents($curview,$child)] == 1} {
6159 set colormap($id) $colormap($child)
6160 return
6161 }
6162 }
6163 set badcolors {}
6164 set origbad {}
6165 foreach x [findcrossings $id] {
6166 if {$x eq {}} {
6167 # delimiter between corner crossings and other crossings
6168 if {[llength $badcolors] >= $ncolors - 1} break
6169 set origbad $badcolors
6170 }
6171 if {[info exists colormap($x)]
6172 && [lsearch -exact $badcolors $colormap($x)] < 0} {
6173 lappend badcolors $colormap($x)
6174 }
6175 }
6176 if {[llength $badcolors] >= $ncolors} {
6177 set badcolors $origbad
6178 }
6179 set origbad $badcolors
6180 if {[llength $badcolors] < $ncolors - 1} {
6181 foreach child $kids {
6182 if {[info exists colormap($child)]
6183 && [lsearch -exact $badcolors $colormap($child)] < 0} {
6184 lappend badcolors $colormap($child)
6185 }
6186 foreach p $parents($curview,$child) {
6187 if {[info exists colormap($p)]
6188 && [lsearch -exact $badcolors $colormap($p)] < 0} {
6189 lappend badcolors $colormap($p)
6190 }
6191 }
6192 }
6193 if {[llength $badcolors] >= $ncolors} {
6194 set badcolors $origbad
6195 }
6196 }
6197 for {set i 0} {$i <= $ncolors} {incr i} {
6198 set c [lindex $colors $nextcolor]
6199 if {[incr nextcolor] >= $ncolors} {
6200 set nextcolor 0
6201 }
6202 if {[lsearch -exact $badcolors $c]} break
6203 }
6204 set colormap($id) $c
6205}
6206
6207proc bindline {t id} {
6208 global canv
6209
6210 $canv bind $t <Enter> "lineenter %x %y $id"
6211 $canv bind $t <Motion> "linemotion %x %y $id"
6212 $canv bind $t <Leave> "lineleave $id"
6213 $canv bind $t <Button-1> "lineclick %x %y $id 1"
6214}
6215
6216proc drawtags {id x xt y1} {
6217 global idtags idheads idotherrefs mainhead
6218 global linespc lthickness
6219 global canv rowtextx curview fgcolor bgcolor ctxbut
6220
6221 set marks {}
6222 set ntags 0
6223 set nheads 0
6224 if {[info exists idtags($id)]} {
6225 set marks $idtags($id)
6226 set ntags [llength $marks]
6227 }
6228 if {[info exists idheads($id)]} {
6229 set marks [concat $marks $idheads($id)]
6230 set nheads [llength $idheads($id)]
6231 }
6232 if {[info exists idotherrefs($id)]} {
6233 set marks [concat $marks $idotherrefs($id)]
6234 }
6235 if {$marks eq {}} {
6236 return $xt
6237 }
6238
6239 set delta [expr {int(0.5 * ($linespc - $lthickness))}]
6240 set yt [expr {$y1 - 0.5 * $linespc}]
6241 set yb [expr {$yt + $linespc - 1}]
6242 set xvals {}
6243 set wvals {}
6244 set i -1
6245 foreach tag $marks {
6246 incr i
6247 if {$i >= $ntags && $i < $ntags + $nheads && $tag eq $mainhead} {
6248 set wid [font measure mainfontbold $tag]
6249 } else {
6250 set wid [font measure mainfont $tag]
6251 }
6252 lappend xvals $xt
6253 lappend wvals $wid
6254 set xt [expr {$xt + $delta + $wid + $lthickness + $linespc}]
6255 }
6256 set t [$canv create line $x $y1 [lindex $xvals end] $y1 \
6257 -width $lthickness -fill black -tags tag.$id]
6258 $canv lower $t
6259 foreach tag $marks x $xvals wid $wvals {
6260 set xl [expr {$x + $delta}]
6261 set xr [expr {$x + $delta + $wid + $lthickness}]
6262 set font mainfont
6263 if {[incr ntags -1] >= 0} {
6264 # draw a tag
6265 set t [$canv create polygon $x [expr {$yt + $delta}] $xl $yt \
6266 $xr $yt $xr $yb $xl $yb $x [expr {$yb - $delta}] \
6267 -width 1 -outline black -fill yellow -tags tag.$id]
6268 $canv bind $t <1> [list showtag $tag 1]
6269 set rowtextx([rowofcommit $id]) [expr {$xr + $linespc}]
6270 } else {
6271 # draw a head or other ref
6272 if {[incr nheads -1] >= 0} {
6273 set col green
6274 if {$tag eq $mainhead} {
6275 set font mainfontbold
6276 }
6277 } else {
6278 set col "#ddddff"
6279 }
6280 set xl [expr {$xl - $delta/2}]
6281 $canv create polygon $x $yt $xr $yt $xr $yb $x $yb \
6282 -width 1 -outline black -fill $col -tags tag.$id
6283 if {[regexp {^(remotes/.*/|remotes/)} $tag match remoteprefix]} {
6284 set rwid [font measure mainfont $remoteprefix]
6285 set xi [expr {$x + 1}]
6286 set yti [expr {$yt + 1}]
6287 set xri [expr {$x + $rwid}]
6288 $canv create polygon $xi $yti $xri $yti $xri $yb $xi $yb \
6289 -width 0 -fill "#ffddaa" -tags tag.$id
6290 }
6291 }
6292 set t [$canv create text $xl $y1 -anchor w -text $tag -fill $fgcolor \
6293 -font $font -tags [list tag.$id text]]
6294 if {$ntags >= 0} {
6295 $canv bind $t <1> [list showtag $tag 1]
6296 } elseif {$nheads >= 0} {
6297 $canv bind $t $ctxbut [list headmenu %X %Y $id $tag]
6298 }
6299 }
6300 return $xt
6301}
6302
6303proc xcoord {i level ln} {
6304 global canvx0 xspc1 xspc2
6305
6306 set x [expr {$canvx0 + $i * $xspc1($ln)}]
6307 if {$i > 0 && $i == $level} {
6308 set x [expr {$x + 0.5 * ($xspc2 - $xspc1($ln))}]
6309 } elseif {$i > $level} {
6310 set x [expr {$x + $xspc2 - $xspc1($ln)}]
6311 }
6312 return $x
6313}
6314
6315proc show_status {msg} {
6316 global canv fgcolor
6317
6318 clear_display
6319 $canv create text 3 3 -anchor nw -text $msg -font mainfont \
6320 -tags text -fill $fgcolor
6321}
6322
6323# Don't change the text pane cursor if it is currently the hand cursor,
6324# showing that we are over a sha1 ID link.
6325proc settextcursor {c} {
6326 global ctext curtextcursor
6327
6328 if {[$ctext cget -cursor] == $curtextcursor} {
6329 $ctext config -cursor $c
6330 }
6331 set curtextcursor $c
6332}
6333
6334proc nowbusy {what {name {}}} {
6335 global isbusy busyname statusw
6336
6337 if {[array names isbusy] eq {}} {
6338 . config -cursor watch
6339 settextcursor watch
6340 }
6341 set isbusy($what) 1
6342 set busyname($what) $name
6343 if {$name ne {}} {
6344 $statusw conf -text $name
6345 }
6346}
6347
6348proc notbusy {what} {
6349 global isbusy maincursor textcursor busyname statusw
6350
6351 catch {
6352 unset isbusy($what)
6353 if {$busyname($what) ne {} &&
6354 [$statusw cget -text] eq $busyname($what)} {
6355 $statusw conf -text {}
6356 }
6357 }
6358 if {[array names isbusy] eq {}} {
6359 . config -cursor $maincursor
6360 settextcursor $textcursor
6361 }
6362}
6363
6364proc findmatches {f} {
6365 global findtype findstring
6366 if {$findtype == [mc "Regexp"]} {
6367 set matches [regexp -indices -all -inline $findstring $f]
6368 } else {
6369 set fs $findstring
6370 if {$findtype == [mc "IgnCase"]} {
6371 set f [string tolower $f]
6372 set fs [string tolower $fs]
6373 }
6374 set matches {}
6375 set i 0
6376 set l [string length $fs]
6377 while {[set j [string first $fs $f $i]] >= 0} {
6378 lappend matches [list $j [expr {$j+$l-1}]]
6379 set i [expr {$j + $l}]
6380 }
6381 }
6382 return $matches
6383}
6384
6385proc dofind {{dirn 1} {wrap 1}} {
6386 global findstring findstartline findcurline selectedline numcommits
6387 global gdttype filehighlight fh_serial find_dirn findallowwrap
6388
6389 if {[info exists find_dirn]} {
6390 if {$find_dirn == $dirn} return
6391 stopfinding
6392 }
6393 focus .
6394 if {$findstring eq {} || $numcommits == 0} return
6395 if {$selectedline eq {}} {
6396 set findstartline [lindex [visiblerows] [expr {$dirn < 0}]]
6397 } else {
6398 set findstartline $selectedline
6399 }
6400 set findcurline $findstartline
6401 nowbusy finding [mc "Searching"]
6402 if {$gdttype ne [mc "containing:"] && ![info exists filehighlight]} {
6403 after cancel do_file_hl $fh_serial
6404 do_file_hl $fh_serial
6405 }
6406 set find_dirn $dirn
6407 set findallowwrap $wrap
6408 run findmore
6409}
6410
6411proc stopfinding {} {
6412 global find_dirn findcurline fprogcoord
6413
6414 if {[info exists find_dirn]} {
6415 unset find_dirn
6416 unset findcurline
6417 notbusy finding
6418 set fprogcoord 0
6419 adjustprogress
6420 }
6421 stopblaming
6422}
6423
6424proc findmore {} {
6425 global commitdata commitinfo numcommits findpattern findloc
6426 global findstartline findcurline findallowwrap
6427 global find_dirn gdttype fhighlights fprogcoord
6428 global curview varcorder vrownum varccommits vrowmod
6429
6430 if {![info exists find_dirn]} {
6431 return 0
6432 }
6433 set fldtypes [list [mc "Headline"] [mc "Author"] [mc "Date"] [mc "Committer"] [mc "CDate"] [mc "Comments"]]
6434 set l $findcurline
6435 set moretodo 0
6436 if {$find_dirn > 0} {
6437 incr l
6438 if {$l >= $numcommits} {
6439 set l 0
6440 }
6441 if {$l <= $findstartline} {
6442 set lim [expr {$findstartline + 1}]
6443 } else {
6444 set lim $numcommits
6445 set moretodo $findallowwrap
6446 }
6447 } else {
6448 if {$l == 0} {
6449 set l $numcommits
6450 }
6451 incr l -1
6452 if {$l >= $findstartline} {
6453 set lim [expr {$findstartline - 1}]
6454 } else {
6455 set lim -1
6456 set moretodo $findallowwrap
6457 }
6458 }
6459 set n [expr {($lim - $l) * $find_dirn}]
6460 if {$n > 500} {
6461 set n 500
6462 set moretodo 1
6463 }
6464 if {$l + ($find_dirn > 0? $n: 1) > $vrowmod($curview)} {
6465 update_arcrows $curview
6466 }
6467 set found 0
6468 set domore 1
6469 set ai [bsearch $vrownum($curview) $l]
6470 set a [lindex $varcorder($curview) $ai]
6471 set arow [lindex $vrownum($curview) $ai]
6472 set ids [lindex $varccommits($curview,$a)]
6473 set arowend [expr {$arow + [llength $ids]}]
6474 if {$gdttype eq [mc "containing:"]} {
6475 for {} {$n > 0} {incr n -1; incr l $find_dirn} {
6476 if {$l < $arow || $l >= $arowend} {
6477 incr ai $find_dirn
6478 set a [lindex $varcorder($curview) $ai]
6479 set arow [lindex $vrownum($curview) $ai]
6480 set ids [lindex $varccommits($curview,$a)]
6481 set arowend [expr {$arow + [llength $ids]}]
6482 }
6483 set id [lindex $ids [expr {$l - $arow}]]
6484 # shouldn't happen unless git log doesn't give all the commits...
6485 if {![info exists commitdata($id)] ||
6486 ![doesmatch $commitdata($id)]} {
6487 continue
6488 }
6489 if {![info exists commitinfo($id)]} {
6490 getcommit $id
6491 }
6492 set info $commitinfo($id)
6493 foreach f $info ty $fldtypes {
6494 if {($findloc eq [mc "All fields"] || $findloc eq $ty) &&
6495 [doesmatch $f]} {
6496 set found 1
6497 break
6498 }
6499 }
6500 if {$found} break
6501 }
6502 } else {
6503 for {} {$n > 0} {incr n -1; incr l $find_dirn} {
6504 if {$l < $arow || $l >= $arowend} {
6505 incr ai $find_dirn
6506 set a [lindex $varcorder($curview) $ai]
6507 set arow [lindex $vrownum($curview) $ai]
6508 set ids [lindex $varccommits($curview,$a)]
6509 set arowend [expr {$arow + [llength $ids]}]
6510 }
6511 set id [lindex $ids [expr {$l - $arow}]]
6512 if {![info exists fhighlights($id)]} {
6513 # this sets fhighlights($id) to -1
6514 askfilehighlight $l $id
6515 }
6516 if {$fhighlights($id) > 0} {
6517 set found $domore
6518 break
6519 }
6520 if {$fhighlights($id) < 0} {
6521 if {$domore} {
6522 set domore 0
6523 set findcurline [expr {$l - $find_dirn}]
6524 }
6525 }
6526 }
6527 }
6528 if {$found || ($domore && !$moretodo)} {
6529 unset findcurline
6530 unset find_dirn
6531 notbusy finding
6532 set fprogcoord 0
6533 adjustprogress
6534 if {$found} {
6535 findselectline $l
6536 } else {
6537 bell
6538 }
6539 return 0
6540 }
6541 if {!$domore} {
6542 flushhighlights
6543 } else {
6544 set findcurline [expr {$l - $find_dirn}]
6545 }
6546 set n [expr {($findcurline - $findstartline) * $find_dirn - 1}]
6547 if {$n < 0} {
6548 incr n $numcommits
6549 }
6550 set fprogcoord [expr {$n * 1.0 / $numcommits}]
6551 adjustprogress
6552 return $domore
6553}
6554
6555proc findselectline {l} {
6556 global findloc commentend ctext findcurline markingmatches gdttype
6557
6558 set markingmatches [expr {$gdttype eq [mc "containing:"]}]
6559 set findcurline $l
6560 selectline $l 1
6561 if {$markingmatches &&
6562 ($findloc eq [mc "All fields"] || $findloc eq [mc "Comments"])} {
6563 # highlight the matches in the comments
6564 set f [$ctext get 1.0 $commentend]
6565 set matches [findmatches $f]
6566 foreach match $matches {
6567 set start [lindex $match 0]
6568 set end [expr {[lindex $match 1] + 1}]
6569 $ctext tag add found "1.0 + $start c" "1.0 + $end c"
6570 }
6571 }
6572 drawvisible
6573}
6574
6575# mark the bits of a headline or author that match a find string
6576proc markmatches {canv l str tag matches font row} {
6577 global selectedline
6578
6579 set bbox [$canv bbox $tag]
6580 set x0 [lindex $bbox 0]
6581 set y0 [lindex $bbox 1]
6582 set y1 [lindex $bbox 3]
6583 foreach match $matches {
6584 set start [lindex $match 0]
6585 set end [lindex $match 1]
6586 if {$start > $end} continue
6587 set xoff [font measure $font [string range $str 0 [expr {$start-1}]]]
6588 set xlen [font measure $font [string range $str 0 [expr {$end}]]]
6589 set t [$canv create rect [expr {$x0+$xoff}] $y0 \
6590 [expr {$x0+$xlen+2}] $y1 \
6591 -outline {} -tags [list match$l matches] -fill yellow]
6592 $canv lower $t
6593 if {$row == $selectedline} {
6594 $canv raise $t secsel
6595 }
6596 }
6597}
6598
6599proc unmarkmatches {} {
6600 global markingmatches
6601
6602 allcanvs delete matches
6603 set markingmatches 0
6604 stopfinding
6605}
6606
6607proc selcanvline {w x y} {
6608 global canv canvy0 ctext linespc
6609 global rowtextx
6610 set ymax [lindex [$canv cget -scrollregion] 3]
6611 if {$ymax == {}} return
6612 set yfrac [lindex [$canv yview] 0]
6613 set y [expr {$y + $yfrac * $ymax}]
6614 set l [expr {int(($y - $canvy0) / $linespc + 0.5)}]
6615 if {$l < 0} {
6616 set l 0
6617 }
6618 if {$w eq $canv} {
6619 set xmax [lindex [$canv cget -scrollregion] 2]
6620 set xleft [expr {[lindex [$canv xview] 0] * $xmax}]
6621 if {![info exists rowtextx($l)] || $xleft + $x < $rowtextx($l)} return
6622 }
6623 unmarkmatches
6624 selectline $l 1
6625}
6626
6627proc commit_descriptor {p} {
6628 global commitinfo
6629 if {![info exists commitinfo($p)]} {
6630 getcommit $p
6631 }
6632 set l "..."
6633 if {[llength $commitinfo($p)] > 1} {
6634 set l [lindex $commitinfo($p) 0]
6635 }
6636 return "$p ($l)\n"
6637}
6638
6639# append some text to the ctext widget, and make any SHA1 ID
6640# that we know about be a clickable link.
6641proc appendwithlinks {text tags} {
6642 global ctext linknum curview
6643
6644 set start [$ctext index "end - 1c"]
6645 $ctext insert end $text $tags
6646 set links [regexp -indices -all -inline {\m[0-9a-f]{6,40}\M} $text]
6647 foreach l $links {
6648 set s [lindex $l 0]
6649 set e [lindex $l 1]
6650 set linkid [string range $text $s $e]
6651 incr e
6652 $ctext tag delete link$linknum
6653 $ctext tag add link$linknum "$start + $s c" "$start + $e c"
6654 setlink $linkid link$linknum
6655 incr linknum
6656 }
6657}
6658
6659proc setlink {id lk} {
6660 global curview ctext pendinglinks
6661
6662 set known 0
6663 if {[string length $id] < 40} {
6664 set matches [longid $id]
6665 if {[llength $matches] > 0} {
6666 if {[llength $matches] > 1} return
6667 set known 1
6668 set id [lindex $matches 0]
6669 }
6670 } else {
6671 set known [commitinview $id $curview]
6672 }
6673 if {$known} {
6674 $ctext tag conf $lk -foreground blue -underline 1
6675 $ctext tag bind $lk <1> [list selbyid $id]
6676 $ctext tag bind $lk <Enter> {linkcursor %W 1}
6677 $ctext tag bind $lk <Leave> {linkcursor %W -1}
6678 } else {
6679 lappend pendinglinks($id) $lk
6680 interestedin $id {makelink %P}
6681 }
6682}
6683
6684proc appendshortlink {id {pre {}} {post {}}} {
6685 global ctext linknum
6686
6687 $ctext insert end $pre
6688 $ctext tag delete link$linknum
6689 $ctext insert end [string range $id 0 7] link$linknum
6690 $ctext insert end $post
6691 setlink $id link$linknum
6692 incr linknum
6693}
6694
6695proc makelink {id} {
6696 global pendinglinks
6697
6698 if {![info exists pendinglinks($id)]} return
6699 foreach lk $pendinglinks($id) {
6700 setlink $id $lk
6701 }
6702 unset pendinglinks($id)
6703}
6704
6705proc linkcursor {w inc} {
6706 global linkentercount curtextcursor
6707
6708 if {[incr linkentercount $inc] > 0} {
6709 $w configure -cursor hand2
6710 } else {
6711 $w configure -cursor $curtextcursor
6712 if {$linkentercount < 0} {
6713 set linkentercount 0
6714 }
6715 }
6716}
6717
6718proc viewnextline {dir} {
6719 global canv linespc
6720
6721 $canv delete hover
6722 set ymax [lindex [$canv cget -scrollregion] 3]
6723 set wnow [$canv yview]
6724 set wtop [expr {[lindex $wnow 0] * $ymax}]
6725 set newtop [expr {$wtop + $dir * $linespc}]
6726 if {$newtop < 0} {
6727 set newtop 0
6728 } elseif {$newtop > $ymax} {
6729 set newtop $ymax
6730 }
6731 allcanvs yview moveto [expr {$newtop * 1.0 / $ymax}]
6732}
6733
6734# add a list of tag or branch names at position pos
6735# returns the number of names inserted
6736proc appendrefs {pos ids var} {
6737 global ctext linknum curview $var maxrefs
6738
6739 if {[catch {$ctext index $pos}]} {
6740 return 0
6741 }
6742 $ctext conf -state normal
6743 $ctext delete $pos "$pos lineend"
6744 set tags {}
6745 foreach id $ids {
6746 foreach tag [set $var\($id\)] {
6747 lappend tags [list $tag $id]
6748 }
6749 }
6750 if {[llength $tags] > $maxrefs} {
6751 $ctext insert $pos "[mc "many"] ([llength $tags])"
6752 } else {
6753 set tags [lsort -index 0 -decreasing $tags]
6754 set sep {}
6755 foreach ti $tags {
6756 set id [lindex $ti 1]
6757 set lk link$linknum
6758 incr linknum
6759 $ctext tag delete $lk
6760 $ctext insert $pos $sep
6761 $ctext insert $pos [lindex $ti 0] $lk
6762 setlink $id $lk
6763 set sep ", "
6764 }
6765 }
6766 $ctext conf -state disabled
6767 return [llength $tags]
6768}
6769
6770# called when we have finished computing the nearby tags
6771proc dispneartags {delay} {
6772 global selectedline currentid showneartags tagphase
6773
6774 if {$selectedline eq {} || !$showneartags} return
6775 after cancel dispnexttag
6776 if {$delay} {
6777 after 200 dispnexttag
6778 set tagphase -1
6779 } else {
6780 after idle dispnexttag
6781 set tagphase 0
6782 }
6783}
6784
6785proc dispnexttag {} {
6786 global selectedline currentid showneartags tagphase ctext
6787
6788 if {$selectedline eq {} || !$showneartags} return
6789 switch -- $tagphase {
6790 0 {
6791 set dtags [desctags $currentid]
6792 if {$dtags ne {}} {
6793 appendrefs precedes $dtags idtags
6794 }
6795 }
6796 1 {
6797 set atags [anctags $currentid]
6798 if {$atags ne {}} {
6799 appendrefs follows $atags idtags
6800 }
6801 }
6802 2 {
6803 set dheads [descheads $currentid]
6804 if {$dheads ne {}} {
6805 if {[appendrefs branch $dheads idheads] > 1
6806 && [$ctext get "branch -3c"] eq "h"} {
6807 # turn "Branch" into "Branches"
6808 $ctext conf -state normal
6809 $ctext insert "branch -2c" "es"
6810 $ctext conf -state disabled
6811 }
6812 }
6813 }
6814 }
6815 if {[incr tagphase] <= 2} {
6816 after idle dispnexttag
6817 }
6818}
6819
6820proc make_secsel {id} {
6821 global linehtag linentag linedtag canv canv2 canv3
6822
6823 if {![info exists linehtag($id)]} return
6824 $canv delete secsel
6825 set t [eval $canv create rect [$canv bbox $linehtag($id)] -outline {{}} \
6826 -tags secsel -fill [$canv cget -selectbackground]]
6827 $canv lower $t
6828 $canv2 delete secsel
6829 set t [eval $canv2 create rect [$canv2 bbox $linentag($id)] -outline {{}} \
6830 -tags secsel -fill [$canv2 cget -selectbackground]]
6831 $canv2 lower $t
6832 $canv3 delete secsel
6833 set t [eval $canv3 create rect [$canv3 bbox $linedtag($id)] -outline {{}} \
6834 -tags secsel -fill [$canv3 cget -selectbackground]]
6835 $canv3 lower $t
6836}
6837
6838proc make_idmark {id} {
6839 global linehtag canv fgcolor
6840
6841 if {![info exists linehtag($id)]} return
6842 $canv delete markid
6843 set t [eval $canv create rect [$canv bbox $linehtag($id)] \
6844 -tags markid -outline $fgcolor]
6845 $canv raise $t
6846}
6847
6848proc selectline {l isnew {desired_loc {}}} {
6849 global canv ctext commitinfo selectedline
6850 global canvy0 linespc parents children curview
6851 global currentid sha1entry
6852 global commentend idtags linknum
6853 global mergemax numcommits pending_select
6854 global cmitmode showneartags allcommits
6855 global targetrow targetid lastscrollrows
6856 global autoselect jump_to_here
6857
6858 catch {unset pending_select}
6859 $canv delete hover
6860 normalline
6861 unsel_reflist
6862 stopfinding
6863 if {$l < 0 || $l >= $numcommits} return
6864 set id [commitonrow $l]
6865 set targetid $id
6866 set targetrow $l
6867 set selectedline $l
6868 set currentid $id
6869 if {$lastscrollrows < $numcommits} {
6870 setcanvscroll
6871 }
6872
6873 set y [expr {$canvy0 + $l * $linespc}]
6874 set ymax [lindex [$canv cget -scrollregion] 3]
6875 set ytop [expr {$y - $linespc - 1}]
6876 set ybot [expr {$y + $linespc + 1}]
6877 set wnow [$canv yview]
6878 set wtop [expr {[lindex $wnow 0] * $ymax}]
6879 set wbot [expr {[lindex $wnow 1] * $ymax}]
6880 set wh [expr {$wbot - $wtop}]
6881 set newtop $wtop
6882 if {$ytop < $wtop} {
6883 if {$ybot < $wtop} {
6884 set newtop [expr {$y - $wh / 2.0}]
6885 } else {
6886 set newtop $ytop
6887 if {$newtop > $wtop - $linespc} {
6888 set newtop [expr {$wtop - $linespc}]
6889 }
6890 }
6891 } elseif {$ybot > $wbot} {
6892 if {$ytop > $wbot} {
6893 set newtop [expr {$y - $wh / 2.0}]
6894 } else {
6895 set newtop [expr {$ybot - $wh}]
6896 if {$newtop < $wtop + $linespc} {
6897 set newtop [expr {$wtop + $linespc}]
6898 }
6899 }
6900 }
6901 if {$newtop != $wtop} {
6902 if {$newtop < 0} {
6903 set newtop 0
6904 }
6905 allcanvs yview moveto [expr {$newtop * 1.0 / $ymax}]
6906 drawvisible
6907 }
6908
6909 make_secsel $id
6910
6911 if {$isnew} {
6912 addtohistory [list selbyid $id 0] savecmitpos
6913 }
6914
6915 $sha1entry delete 0 end
6916 $sha1entry insert 0 $id
6917 if {$autoselect} {
6918 $sha1entry selection range 0 end
6919 }
6920 rhighlight_sel $id
6921
6922 $ctext conf -state normal
6923 clear_ctext
6924 set linknum 0
6925 if {![info exists commitinfo($id)]} {
6926 getcommit $id
6927 }
6928 set info $commitinfo($id)
6929 set date [formatdate [lindex $info 2]]
6930 $ctext insert end "[mc "Author"]: [lindex $info 1] $date\n"
6931 set date [formatdate [lindex $info 4]]
6932 $ctext insert end "[mc "Committer"]: [lindex $info 3] $date\n"
6933 if {[info exists idtags($id)]} {
6934 $ctext insert end [mc "Tags:"]
6935 foreach tag $idtags($id) {
6936 $ctext insert end " $tag"
6937 }
6938 $ctext insert end "\n"
6939 }
6940
6941 set headers {}
6942 set olds $parents($curview,$id)
6943 if {[llength $olds] > 1} {
6944 set np 0
6945 foreach p $olds {
6946 if {$np >= $mergemax} {
6947 set tag mmax
6948 } else {
6949 set tag m$np
6950 }
6951 $ctext insert end "[mc "Parent"]: " $tag
6952 appendwithlinks [commit_descriptor $p] {}
6953 incr np
6954 }
6955 } else {
6956 foreach p $olds {
6957 append headers "[mc "Parent"]: [commit_descriptor $p]"
6958 }
6959 }
6960
6961 foreach c $children($curview,$id) {
6962 append headers "[mc "Child"]: [commit_descriptor $c]"
6963 }
6964
6965 # make anything that looks like a SHA1 ID be a clickable link
6966 appendwithlinks $headers {}
6967 if {$showneartags} {
6968 if {![info exists allcommits]} {
6969 getallcommits
6970 }
6971 $ctext insert end "[mc "Branch"]: "
6972 $ctext mark set branch "end -1c"
6973 $ctext mark gravity branch left
6974 $ctext insert end "\n[mc "Follows"]: "
6975 $ctext mark set follows "end -1c"
6976 $ctext mark gravity follows left
6977 $ctext insert end "\n[mc "Precedes"]: "
6978 $ctext mark set precedes "end -1c"
6979 $ctext mark gravity precedes left
6980 $ctext insert end "\n"
6981 dispneartags 1
6982 }
6983 $ctext insert end "\n"
6984 set comment [lindex $info 5]
6985 if {[string first "\r" $comment] >= 0} {
6986 set comment [string map {"\r" "\n "} $comment]
6987 }
6988 appendwithlinks $comment {comment}
6989
6990 $ctext tag remove found 1.0 end
6991 $ctext conf -state disabled
6992 set commentend [$ctext index "end - 1c"]
6993
6994 set jump_to_here $desired_loc
6995 init_flist [mc "Comments"]
6996 if {$cmitmode eq "tree"} {
6997 gettree $id
6998 } elseif {[llength $olds] <= 1} {
6999 startdiff $id
7000 } else {
7001 mergediff $id
7002 }
7003}
7004
7005proc selfirstline {} {
7006 unmarkmatches
7007 selectline 0 1
7008}
7009
7010proc sellastline {} {
7011 global numcommits
7012 unmarkmatches
7013 set l [expr {$numcommits - 1}]
7014 selectline $l 1
7015}
7016
7017proc selnextline {dir} {
7018 global selectedline
7019 focus .
7020 if {$selectedline eq {}} return
7021 set l [expr {$selectedline + $dir}]
7022 unmarkmatches
7023 selectline $l 1
7024}
7025
7026proc selnextpage {dir} {
7027 global canv linespc selectedline numcommits
7028
7029 set lpp [expr {([winfo height $canv] - 2) / $linespc}]
7030 if {$lpp < 1} {
7031 set lpp 1
7032 }
7033 allcanvs yview scroll [expr {$dir * $lpp}] units
7034 drawvisible
7035 if {$selectedline eq {}} return
7036 set l [expr {$selectedline + $dir * $lpp}]
7037 if {$l < 0} {
7038 set l 0
7039 } elseif {$l >= $numcommits} {
7040 set l [expr $numcommits - 1]
7041 }
7042 unmarkmatches
7043 selectline $l 1
7044}
7045
7046proc unselectline {} {
7047 global selectedline currentid
7048
7049 set selectedline {}
7050 catch {unset currentid}
7051 allcanvs delete secsel
7052 rhighlight_none
7053}
7054
7055proc reselectline {} {
7056 global selectedline
7057
7058 if {$selectedline ne {}} {
7059 selectline $selectedline 0
7060 }
7061}
7062
7063proc addtohistory {cmd {saveproc {}}} {
7064 global history historyindex curview
7065
7066 unset_posvars
7067 save_position
7068 set elt [list $curview $cmd $saveproc {}]
7069 if {$historyindex > 0
7070 && [lindex $history [expr {$historyindex - 1}]] == $elt} {
7071 return
7072 }
7073
7074 if {$historyindex < [llength $history]} {
7075 set history [lreplace $history $historyindex end $elt]
7076 } else {
7077 lappend history $elt
7078 }
7079 incr historyindex
7080 if {$historyindex > 1} {
7081 .tf.bar.leftbut conf -state normal
7082 } else {
7083 .tf.bar.leftbut conf -state disabled
7084 }
7085 .tf.bar.rightbut conf -state disabled
7086}
7087
7088# save the scrolling position of the diff display pane
7089proc save_position {} {
7090 global historyindex history
7091
7092 if {$historyindex < 1} return
7093 set hi [expr {$historyindex - 1}]
7094 set fn [lindex $history $hi 2]
7095 if {$fn ne {}} {
7096 lset history $hi 3 [eval $fn]
7097 }
7098}
7099
7100proc unset_posvars {} {
7101 global last_posvars
7102
7103 if {[info exists last_posvars]} {
7104 foreach {var val} $last_posvars {
7105 global $var
7106 catch {unset $var}
7107 }
7108 unset last_posvars
7109 }
7110}
7111
7112proc godo {elt} {
7113 global curview last_posvars
7114
7115 set view [lindex $elt 0]
7116 set cmd [lindex $elt 1]
7117 set pv [lindex $elt 3]
7118 if {$curview != $view} {
7119 showview $view
7120 }
7121 unset_posvars
7122 foreach {var val} $pv {
7123 global $var
7124 set $var $val
7125 }
7126 set last_posvars $pv
7127 eval $cmd
7128}
7129
7130proc goback {} {
7131 global history historyindex
7132 focus .
7133
7134 if {$historyindex > 1} {
7135 save_position
7136 incr historyindex -1
7137 godo [lindex $history [expr {$historyindex - 1}]]
7138 .tf.bar.rightbut conf -state normal
7139 }
7140 if {$historyindex <= 1} {
7141 .tf.bar.leftbut conf -state disabled
7142 }
7143}
7144
7145proc goforw {} {
7146 global history historyindex
7147 focus .
7148
7149 if {$historyindex < [llength $history]} {
7150 save_position
7151 set cmd [lindex $history $historyindex]
7152 incr historyindex
7153 godo $cmd
7154 .tf.bar.leftbut conf -state normal
7155 }
7156 if {$historyindex >= [llength $history]} {
7157 .tf.bar.rightbut conf -state disabled
7158 }
7159}
7160
7161proc gettree {id} {
7162 global treefilelist treeidlist diffids diffmergeid treepending
7163 global nullid nullid2
7164
7165 set diffids $id
7166 catch {unset diffmergeid}
7167 if {![info exists treefilelist($id)]} {
7168 if {![info exists treepending]} {
7169 if {$id eq $nullid} {
7170 set cmd [list | git ls-files]
7171 } elseif {$id eq $nullid2} {
7172 set cmd [list | git ls-files --stage -t]
7173 } else {
7174 set cmd [list | git ls-tree -r $id]
7175 }
7176 if {[catch {set gtf [open $cmd r]}]} {
7177 return
7178 }
7179 set treepending $id
7180 set treefilelist($id) {}
7181 set treeidlist($id) {}
7182 fconfigure $gtf -blocking 0 -encoding binary
7183 filerun $gtf [list gettreeline $gtf $id]
7184 }
7185 } else {
7186 setfilelist $id
7187 }
7188}
7189
7190proc gettreeline {gtf id} {
7191 global treefilelist treeidlist treepending cmitmode diffids nullid nullid2
7192
7193 set nl 0
7194 while {[incr nl] <= 1000 && [gets $gtf line] >= 0} {
7195 if {$diffids eq $nullid} {
7196 set fname $line
7197 } else {
7198 set i [string first "\t" $line]
7199 if {$i < 0} continue
7200 set fname [string range $line [expr {$i+1}] end]
7201 set line [string range $line 0 [expr {$i-1}]]
7202 if {$diffids ne $nullid2 && [lindex $line 1] ne "blob"} continue
7203 set sha1 [lindex $line 2]
7204 lappend treeidlist($id) $sha1
7205 }
7206 if {[string index $fname 0] eq "\""} {
7207 set fname [lindex $fname 0]
7208 }
7209 set fname [encoding convertfrom $fname]
7210 lappend treefilelist($id) $fname
7211 }
7212 if {![eof $gtf]} {
7213 return [expr {$nl >= 1000? 2: 1}]
7214 }
7215 close $gtf
7216 unset treepending
7217 if {$cmitmode ne "tree"} {
7218 if {![info exists diffmergeid]} {
7219 gettreediffs $diffids
7220 }
7221 } elseif {$id ne $diffids} {
7222 gettree $diffids
7223 } else {
7224 setfilelist $id
7225 }
7226 return 0
7227}
7228
7229proc showfile {f} {
7230 global treefilelist treeidlist diffids nullid nullid2
7231 global ctext_file_names ctext_file_lines
7232 global ctext commentend
7233
7234 set i [lsearch -exact $treefilelist($diffids) $f]
7235 if {$i < 0} {
7236 puts "oops, $f not in list for id $diffids"
7237 return
7238 }
7239 if {$diffids eq $nullid} {
7240 if {[catch {set bf [open $f r]} err]} {
7241 puts "oops, can't read $f: $err"
7242 return
7243 }
7244 } else {
7245 set blob [lindex $treeidlist($diffids) $i]
7246 if {[catch {set bf [open [concat | git cat-file blob $blob] r]} err]} {
7247 puts "oops, error reading blob $blob: $err"
7248 return
7249 }
7250 }
7251 fconfigure $bf -blocking 0 -encoding [get_path_encoding $f]
7252 filerun $bf [list getblobline $bf $diffids]
7253 $ctext config -state normal
7254 clear_ctext $commentend
7255 lappend ctext_file_names $f
7256 lappend ctext_file_lines [lindex [split $commentend "."] 0]
7257 $ctext insert end "\n"
7258 $ctext insert end "$f\n" filesep
7259 $ctext config -state disabled
7260 $ctext yview $commentend
7261 settabs 0
7262}
7263
7264proc getblobline {bf id} {
7265 global diffids cmitmode ctext
7266
7267 if {$id ne $diffids || $cmitmode ne "tree"} {
7268 catch {close $bf}
7269 return 0
7270 }
7271 $ctext config -state normal
7272 set nl 0
7273 while {[incr nl] <= 1000 && [gets $bf line] >= 0} {
7274 $ctext insert end "$line\n"
7275 }
7276 if {[eof $bf]} {
7277 global jump_to_here ctext_file_names commentend
7278
7279 # delete last newline
7280 $ctext delete "end - 2c" "end - 1c"
7281 close $bf
7282 if {$jump_to_here ne {} &&
7283 [lindex $jump_to_here 0] eq [lindex $ctext_file_names 0]} {
7284 set lnum [expr {[lindex $jump_to_here 1] +
7285 [lindex [split $commentend .] 0]}]
7286 mark_ctext_line $lnum
7287 }
7288 return 0
7289 }
7290 $ctext config -state disabled
7291 return [expr {$nl >= 1000? 2: 1}]
7292}
7293
7294proc mark_ctext_line {lnum} {
7295 global ctext markbgcolor
7296
7297 $ctext tag delete omark
7298 $ctext tag add omark $lnum.0 "$lnum.0 + 1 line"
7299 $ctext tag conf omark -background $markbgcolor
7300 $ctext see $lnum.0
7301}
7302
7303proc mergediff {id} {
7304 global diffmergeid
7305 global diffids treediffs
7306 global parents curview
7307
7308 set diffmergeid $id
7309 set diffids $id
7310 set treediffs($id) {}
7311 set np [llength $parents($curview,$id)]
7312 settabs $np
7313 getblobdiffs $id
7314}
7315
7316proc startdiff {ids} {
7317 global treediffs diffids treepending diffmergeid nullid nullid2
7318
7319 settabs 1
7320 set diffids $ids
7321 catch {unset diffmergeid}
7322 if {![info exists treediffs($ids)] ||
7323 [lsearch -exact $ids $nullid] >= 0 ||
7324 [lsearch -exact $ids $nullid2] >= 0} {
7325 if {![info exists treepending]} {
7326 gettreediffs $ids
7327 }
7328 } else {
7329 addtocflist $ids
7330 }
7331}
7332
7333proc path_filter {filter name} {
7334 foreach p $filter {
7335 set l [string length $p]
7336 if {[string index $p end] eq "/"} {
7337 if {[string compare -length $l $p $name] == 0} {
7338 return 1
7339 }
7340 } else {
7341 if {[string compare -length $l $p $name] == 0 &&
7342 ([string length $name] == $l ||
7343 [string index $name $l] eq "/")} {
7344 return 1
7345 }
7346 }
7347 }
7348 return 0
7349}
7350
7351proc addtocflist {ids} {
7352 global treediffs
7353
7354 add_flist $treediffs($ids)
7355 getblobdiffs $ids
7356}
7357
7358proc diffcmd {ids flags} {
7359 global nullid nullid2
7360
7361 set i [lsearch -exact $ids $nullid]
7362 set j [lsearch -exact $ids $nullid2]
7363 if {$i >= 0} {
7364 if {[llength $ids] > 1 && $j < 0} {
7365 # comparing working directory with some specific revision
7366 set cmd [concat | git diff-index $flags]
7367 if {$i == 0} {
7368 lappend cmd -R [lindex $ids 1]
7369 } else {
7370 lappend cmd [lindex $ids 0]
7371 }
7372 } else {
7373 # comparing working directory with index
7374 set cmd [concat | git diff-files $flags]
7375 if {$j == 1} {
7376 lappend cmd -R
7377 }
7378 }
7379 } elseif {$j >= 0} {
7380 set cmd [concat | git diff-index --cached $flags]
7381 if {[llength $ids] > 1} {
7382 # comparing index with specific revision
7383 if {$i == 0} {
7384 lappend cmd -R [lindex $ids 1]
7385 } else {
7386 lappend cmd [lindex $ids 0]
7387 }
7388 } else {
7389 # comparing index with HEAD
7390 lappend cmd HEAD
7391 }
7392 } else {
7393 set cmd [concat | git diff-tree -r $flags $ids]
7394 }
7395 return $cmd
7396}
7397
7398proc gettreediffs {ids} {
7399 global treediff treepending
7400
7401 if {[catch {set gdtf [open [diffcmd $ids {--no-commit-id}] r]}]} return
7402
7403 set treepending $ids
7404 set treediff {}
7405 fconfigure $gdtf -blocking 0 -encoding binary
7406 filerun $gdtf [list gettreediffline $gdtf $ids]
7407}
7408
7409proc gettreediffline {gdtf ids} {
7410 global treediff treediffs treepending diffids diffmergeid
7411 global cmitmode vfilelimit curview limitdiffs perfile_attrs
7412
7413 set nr 0
7414 set sublist {}
7415 set max 1000
7416 if {$perfile_attrs} {
7417 # cache_gitattr is slow, and even slower on win32 where we
7418 # have to invoke it for only about 30 paths at a time
7419 set max 500
7420 if {[tk windowingsystem] == "win32"} {
7421 set max 120
7422 }
7423 }
7424 while {[incr nr] <= $max && [gets $gdtf line] >= 0} {
7425 set i [string first "\t" $line]
7426 if {$i >= 0} {
7427 set file [string range $line [expr {$i+1}] end]
7428 if {[string index $file 0] eq "\""} {
7429 set file [lindex $file 0]
7430 }
7431 set file [encoding convertfrom $file]
7432 if {$file ne [lindex $treediff end]} {
7433 lappend treediff $file
7434 lappend sublist $file
7435 }
7436 }
7437 }
7438 if {$perfile_attrs} {
7439 cache_gitattr encoding $sublist
7440 }
7441 if {![eof $gdtf]} {
7442 return [expr {$nr >= $max? 2: 1}]
7443 }
7444 close $gdtf
7445 if {$limitdiffs && $vfilelimit($curview) ne {}} {
7446 set flist {}
7447 foreach f $treediff {
7448 if {[path_filter $vfilelimit($curview) $f]} {
7449 lappend flist $f
7450 }
7451 }
7452 set treediffs($ids) $flist
7453 } else {
7454 set treediffs($ids) $treediff
7455 }
7456 unset treepending
7457 if {$cmitmode eq "tree" && [llength $diffids] == 1} {
7458 gettree $diffids
7459 } elseif {$ids != $diffids} {
7460 if {![info exists diffmergeid]} {
7461 gettreediffs $diffids
7462 }
7463 } else {
7464 addtocflist $ids
7465 }
7466 return 0
7467}
7468
7469# empty string or positive integer
7470proc diffcontextvalidate {v} {
7471 return [regexp {^(|[1-9][0-9]*)$} $v]
7472}
7473
7474proc diffcontextchange {n1 n2 op} {
7475 global diffcontextstring diffcontext
7476
7477 if {[string is integer -strict $diffcontextstring]} {
7478 if {$diffcontextstring >= 0} {
7479 set diffcontext $diffcontextstring
7480 reselectline
7481 }
7482 }
7483}
7484
7485proc changeignorespace {} {
7486 reselectline
7487}
7488
7489proc getblobdiffs {ids} {
7490 global blobdifffd diffids env
7491 global diffinhdr treediffs
7492 global diffcontext
7493 global ignorespace
7494 global limitdiffs vfilelimit curview
7495 global diffencoding targetline diffnparents
7496 global git_version
7497
7498 set textconv {}
7499 if {[package vcompare $git_version "1.6.1"] >= 0} {
7500 set textconv "--textconv"
7501 }
7502 set cmd [diffcmd $ids "-p $textconv -C --cc --no-commit-id -U$diffcontext"]
7503 if {$ignorespace} {
7504 append cmd " -w"
7505 }
7506 if {$limitdiffs && $vfilelimit($curview) ne {}} {
7507 set cmd [concat $cmd -- $vfilelimit($curview)]
7508 }
7509 if {[catch {set bdf [open $cmd r]} err]} {
7510 error_popup [mc "Error getting diffs: %s" $err]
7511 return
7512 }
7513 set targetline {}
7514 set diffnparents 0
7515 set diffinhdr 0
7516 set diffencoding [get_path_encoding {}]
7517 fconfigure $bdf -blocking 0 -encoding binary -eofchar {}
7518 set blobdifffd($ids) $bdf
7519 filerun $bdf [list getblobdiffline $bdf $diffids]
7520}
7521
7522proc savecmitpos {} {
7523 global ctext cmitmode
7524
7525 if {$cmitmode eq "tree"} {
7526 return {}
7527 }
7528 return [list target_scrollpos [$ctext index @0,0]]
7529}
7530
7531proc savectextpos {} {
7532 global ctext
7533
7534 return [list target_scrollpos [$ctext index @0,0]]
7535}
7536
7537proc maybe_scroll_ctext {ateof} {
7538 global ctext target_scrollpos
7539
7540 if {![info exists target_scrollpos]} return
7541 if {!$ateof} {
7542 set nlines [expr {[winfo height $ctext]
7543 / [font metrics textfont -linespace]}]
7544 if {[$ctext compare "$target_scrollpos + $nlines lines" <= end]} return
7545 }
7546 $ctext yview $target_scrollpos
7547 unset target_scrollpos
7548}
7549
7550proc setinlist {var i val} {
7551 global $var
7552
7553 while {[llength [set $var]] < $i} {
7554 lappend $var {}
7555 }
7556 if {[llength [set $var]] == $i} {
7557 lappend $var $val
7558 } else {
7559 lset $var $i $val
7560 }
7561}
7562
7563proc makediffhdr {fname ids} {
7564 global ctext curdiffstart treediffs diffencoding
7565 global ctext_file_names jump_to_here targetline diffline
7566
7567 set fname [encoding convertfrom $fname]
7568 set diffencoding [get_path_encoding $fname]
7569 set i [lsearch -exact $treediffs($ids) $fname]
7570 if {$i >= 0} {
7571 setinlist difffilestart $i $curdiffstart
7572 }
7573 lset ctext_file_names end $fname
7574 set l [expr {(78 - [string length $fname]) / 2}]
7575 set pad [string range "----------------------------------------" 1 $l]
7576 $ctext insert $curdiffstart "$pad $fname $pad" filesep
7577 set targetline {}
7578 if {$jump_to_here ne {} && [lindex $jump_to_here 0] eq $fname} {
7579 set targetline [lindex $jump_to_here 1]
7580 }
7581 set diffline 0
7582}
7583
7584proc getblobdiffline {bdf ids} {
7585 global diffids blobdifffd ctext curdiffstart
7586 global diffnexthead diffnextnote difffilestart
7587 global ctext_file_names ctext_file_lines
7588 global diffinhdr treediffs mergemax diffnparents
7589 global diffencoding jump_to_here targetline diffline
7590
7591 set nr 0
7592 $ctext conf -state normal
7593 while {[incr nr] <= 1000 && [gets $bdf line] >= 0} {
7594 if {$ids != $diffids || $bdf != $blobdifffd($ids)} {
7595 catch {close $bdf}
7596 return 0
7597 }
7598 if {![string compare -length 5 "diff " $line]} {
7599 if {![regexp {^diff (--cc|--git) } $line m type]} {
7600 set line [encoding convertfrom $line]
7601 $ctext insert end "$line\n" hunksep
7602 continue
7603 }
7604 # start of a new file
7605 set diffinhdr 1
7606 $ctext insert end "\n"
7607 set curdiffstart [$ctext index "end - 1c"]
7608 lappend ctext_file_names ""
7609 lappend ctext_file_lines [lindex [split $curdiffstart "."] 0]
7610 $ctext insert end "\n" filesep
7611
7612 if {$type eq "--cc"} {
7613 # start of a new file in a merge diff
7614 set fname [string range $line 10 end]
7615 if {[lsearch -exact $treediffs($ids) $fname] < 0} {
7616 lappend treediffs($ids) $fname
7617 add_flist [list $fname]
7618 }
7619
7620 } else {
7621 set line [string range $line 11 end]
7622 # If the name hasn't changed the length will be odd,
7623 # the middle char will be a space, and the two bits either
7624 # side will be a/name and b/name, or "a/name" and "b/name".
7625 # If the name has changed we'll get "rename from" and
7626 # "rename to" or "copy from" and "copy to" lines following
7627 # this, and we'll use them to get the filenames.
7628 # This complexity is necessary because spaces in the
7629 # filename(s) don't get escaped.
7630 set l [string length $line]
7631 set i [expr {$l / 2}]
7632 if {!(($l & 1) && [string index $line $i] eq " " &&
7633 [string range $line 2 [expr {$i - 1}]] eq \
7634 [string range $line [expr {$i + 3}] end])} {
7635 continue
7636 }
7637 # unescape if quoted and chop off the a/ from the front
7638 if {[string index $line 0] eq "\""} {
7639 set fname [string range [lindex $line 0] 2 end]
7640 } else {
7641 set fname [string range $line 2 [expr {$i - 1}]]
7642 }
7643 }
7644 makediffhdr $fname $ids
7645
7646 } elseif {![string compare -length 16 "* Unmerged path " $line]} {
7647 set fname [encoding convertfrom [string range $line 16 end]]
7648 $ctext insert end "\n"
7649 set curdiffstart [$ctext index "end - 1c"]
7650 lappend ctext_file_names $fname
7651 lappend ctext_file_lines [lindex [split $curdiffstart "."] 0]
7652 $ctext insert end "$line\n" filesep
7653 set i [lsearch -exact $treediffs($ids) $fname]
7654 if {$i >= 0} {
7655 setinlist difffilestart $i $curdiffstart
7656 }
7657
7658 } elseif {![string compare -length 2 "@@" $line]} {
7659 regexp {^@@+} $line ats
7660 set line [encoding convertfrom $diffencoding $line]
7661 $ctext insert end "$line\n" hunksep
7662 if {[regexp { \+(\d+),\d+ @@} $line m nl]} {
7663 set diffline $nl
7664 }
7665 set diffnparents [expr {[string length $ats] - 1}]
7666 set diffinhdr 0
7667
7668 } elseif {$diffinhdr} {
7669 if {![string compare -length 12 "rename from " $line]} {
7670 set fname [string range $line [expr 6 + [string first " from " $line] ] end]
7671 if {[string index $fname 0] eq "\""} {
7672 set fname [lindex $fname 0]
7673 }
7674 set fname [encoding convertfrom $fname]
7675 set i [lsearch -exact $treediffs($ids) $fname]
7676 if {$i >= 0} {
7677 setinlist difffilestart $i $curdiffstart
7678 }
7679 } elseif {![string compare -length 10 $line "rename to "] ||
7680 ![string compare -length 8 $line "copy to "]} {
7681 set fname [string range $line [expr 4 + [string first " to " $line] ] end]
7682 if {[string index $fname 0] eq "\""} {
7683 set fname [lindex $fname 0]
7684 }
7685 makediffhdr $fname $ids
7686 } elseif {[string compare -length 3 $line "---"] == 0} {
7687 # do nothing
7688 continue
7689 } elseif {[string compare -length 3 $line "+++"] == 0} {
7690 set diffinhdr 0
7691 continue
7692 }
7693 $ctext insert end "$line\n" filesep
7694
7695 } else {
7696 set line [string map {\x1A ^Z} \
7697 [encoding convertfrom $diffencoding $line]]
7698 # parse the prefix - one ' ', '-' or '+' for each parent
7699 set prefix [string range $line 0 [expr {$diffnparents - 1}]]
7700 set tag [expr {$diffnparents > 1? "m": "d"}]
7701 if {[string trim $prefix " -+"] eq {}} {
7702 # prefix only has " ", "-" and "+" in it: normal diff line
7703 set num [string first "-" $prefix]
7704 if {$num >= 0} {
7705 # removed line, first parent with line is $num
7706 if {$num >= $mergemax} {
7707 set num "max"
7708 }
7709 $ctext insert end "$line\n" $tag$num
7710 } else {
7711 set tags {}
7712 if {[string first "+" $prefix] >= 0} {
7713 # added line
7714 lappend tags ${tag}result
7715 if {$diffnparents > 1} {
7716 set num [string first " " $prefix]
7717 if {$num >= 0} {
7718 if {$num >= $mergemax} {
7719 set num "max"
7720 }
7721 lappend tags m$num
7722 }
7723 }
7724 }
7725 if {$targetline ne {}} {
7726 if {$diffline == $targetline} {
7727 set seehere [$ctext index "end - 1 chars"]
7728 set targetline {}
7729 } else {
7730 incr diffline
7731 }
7732 }
7733 $ctext insert end "$line\n" $tags
7734 }
7735 } else {
7736 # "\ No newline at end of file",
7737 # or something else we don't recognize
7738 $ctext insert end "$line\n" hunksep
7739 }
7740 }
7741 }
7742 if {[info exists seehere]} {
7743 mark_ctext_line [lindex [split $seehere .] 0]
7744 }
7745 maybe_scroll_ctext [eof $bdf]
7746 $ctext conf -state disabled
7747 if {[eof $bdf]} {
7748 catch {close $bdf}
7749 return 0
7750 }
7751 return [expr {$nr >= 1000? 2: 1}]
7752}
7753
7754proc changediffdisp {} {
7755 global ctext diffelide
7756
7757 $ctext tag conf d0 -elide [lindex $diffelide 0]
7758 $ctext tag conf dresult -elide [lindex $diffelide 1]
7759}
7760
7761proc highlightfile {loc cline} {
7762 global ctext cflist cflist_top
7763
7764 $ctext yview $loc
7765 $cflist tag remove highlight $cflist_top.0 "$cflist_top.0 lineend"
7766 $cflist tag add highlight $cline.0 "$cline.0 lineend"
7767 $cflist see $cline.0
7768 set cflist_top $cline
7769}
7770
7771proc prevfile {} {
7772 global difffilestart ctext cmitmode
7773
7774 if {$cmitmode eq "tree"} return
7775 set prev 0.0
7776 set prevline 1
7777 set here [$ctext index @0,0]
7778 foreach loc $difffilestart {
7779 if {[$ctext compare $loc >= $here]} {
7780 highlightfile $prev $prevline
7781 return
7782 }
7783 set prev $loc
7784 incr prevline
7785 }
7786 highlightfile $prev $prevline
7787}
7788
7789proc nextfile {} {
7790 global difffilestart ctext cmitmode
7791
7792 if {$cmitmode eq "tree"} return
7793 set here [$ctext index @0,0]
7794 set line 1
7795 foreach loc $difffilestart {
7796 incr line
7797 if {[$ctext compare $loc > $here]} {
7798 highlightfile $loc $line
7799 return
7800 }
7801 }
7802}
7803
7804proc clear_ctext {{first 1.0}} {
7805 global ctext smarktop smarkbot
7806 global ctext_file_names ctext_file_lines
7807 global pendinglinks
7808
7809 set l [lindex [split $first .] 0]
7810 if {![info exists smarktop] || [$ctext compare $first < $smarktop.0]} {
7811 set smarktop $l
7812 }
7813 if {![info exists smarkbot] || [$ctext compare $first < $smarkbot.0]} {
7814 set smarkbot $l
7815 }
7816 $ctext delete $first end
7817 if {$first eq "1.0"} {
7818 catch {unset pendinglinks}
7819 }
7820 set ctext_file_names {}
7821 set ctext_file_lines {}
7822}
7823
7824proc settabs {{firstab {}}} {
7825 global firsttabstop tabstop ctext have_tk85
7826
7827 if {$firstab ne {} && $have_tk85} {
7828 set firsttabstop $firstab
7829 }
7830 set w [font measure textfont "0"]
7831 if {$firsttabstop != 0} {
7832 $ctext conf -tabs [list [expr {($firsttabstop + $tabstop) * $w}] \
7833 [expr {($firsttabstop + 2 * $tabstop) * $w}]]
7834 } elseif {$have_tk85 || $tabstop != 8} {
7835 $ctext conf -tabs [expr {$tabstop * $w}]
7836 } else {
7837 $ctext conf -tabs {}
7838 }
7839}
7840
7841proc incrsearch {name ix op} {
7842 global ctext searchstring searchdirn
7843
7844 $ctext tag remove found 1.0 end
7845 if {[catch {$ctext index anchor}]} {
7846 # no anchor set, use start of selection, or of visible area
7847 set sel [$ctext tag ranges sel]
7848 if {$sel ne {}} {
7849 $ctext mark set anchor [lindex $sel 0]
7850 } elseif {$searchdirn eq "-forwards"} {
7851 $ctext mark set anchor @0,0
7852 } else {
7853 $ctext mark set anchor @0,[winfo height $ctext]
7854 }
7855 }
7856 if {$searchstring ne {}} {
7857 set here [$ctext search $searchdirn -- $searchstring anchor]
7858 if {$here ne {}} {
7859 $ctext see $here
7860 }
7861 searchmarkvisible 1
7862 }
7863}
7864
7865proc dosearch {} {
7866 global sstring ctext searchstring searchdirn
7867
7868 focus $sstring
7869 $sstring icursor end
7870 set searchdirn -forwards
7871 if {$searchstring ne {}} {
7872 set sel [$ctext tag ranges sel]
7873 if {$sel ne {}} {
7874 set start "[lindex $sel 0] + 1c"
7875 } elseif {[catch {set start [$ctext index anchor]}]} {
7876 set start "@0,0"
7877 }
7878 set match [$ctext search -count mlen -- $searchstring $start]
7879 $ctext tag remove sel 1.0 end
7880 if {$match eq {}} {
7881 bell
7882 return
7883 }
7884 $ctext see $match
7885 set mend "$match + $mlen c"
7886 $ctext tag add sel $match $mend
7887 $ctext mark unset anchor
7888 }
7889}
7890
7891proc dosearchback {} {
7892 global sstring ctext searchstring searchdirn
7893
7894 focus $sstring
7895 $sstring icursor end
7896 set searchdirn -backwards
7897 if {$searchstring ne {}} {
7898 set sel [$ctext tag ranges sel]
7899 if {$sel ne {}} {
7900 set start [lindex $sel 0]
7901 } elseif {[catch {set start [$ctext index anchor]}]} {
7902 set start @0,[winfo height $ctext]
7903 }
7904 set match [$ctext search -backwards -count ml -- $searchstring $start]
7905 $ctext tag remove sel 1.0 end
7906 if {$match eq {}} {
7907 bell
7908 return
7909 }
7910 $ctext see $match
7911 set mend "$match + $ml c"
7912 $ctext tag add sel $match $mend
7913 $ctext mark unset anchor
7914 }
7915}
7916
7917proc searchmark {first last} {
7918 global ctext searchstring
7919
7920 set mend $first.0
7921 while {1} {
7922 set match [$ctext search -count mlen -- $searchstring $mend $last.end]
7923 if {$match eq {}} break
7924 set mend "$match + $mlen c"
7925 $ctext tag add found $match $mend
7926 }
7927}
7928
7929proc searchmarkvisible {doall} {
7930 global ctext smarktop smarkbot
7931
7932 set topline [lindex [split [$ctext index @0,0] .] 0]
7933 set botline [lindex [split [$ctext index @0,[winfo height $ctext]] .] 0]
7934 if {$doall || $botline < $smarktop || $topline > $smarkbot} {
7935 # no overlap with previous
7936 searchmark $topline $botline
7937 set smarktop $topline
7938 set smarkbot $botline
7939 } else {
7940 if {$topline < $smarktop} {
7941 searchmark $topline [expr {$smarktop-1}]
7942 set smarktop $topline
7943 }
7944 if {$botline > $smarkbot} {
7945 searchmark [expr {$smarkbot+1}] $botline
7946 set smarkbot $botline
7947 }
7948 }
7949}
7950
7951proc scrolltext {f0 f1} {
7952 global searchstring
7953
7954 .bleft.bottom.sb set $f0 $f1
7955 if {$searchstring ne {}} {
7956 searchmarkvisible 0
7957 }
7958}
7959
7960proc setcoords {} {
7961 global linespc charspc canvx0 canvy0
7962 global xspc1 xspc2 lthickness
7963
7964 set linespc [font metrics mainfont -linespace]
7965 set charspc [font measure mainfont "m"]
7966 set canvy0 [expr {int(3 + 0.5 * $linespc)}]
7967 set canvx0 [expr {int(3 + 0.5 * $linespc)}]
7968 set lthickness [expr {int($linespc / 9) + 1}]
7969 set xspc1(0) $linespc
7970 set xspc2 $linespc
7971}
7972
7973proc redisplay {} {
7974 global canv
7975 global selectedline
7976
7977 set ymax [lindex [$canv cget -scrollregion] 3]
7978 if {$ymax eq {} || $ymax == 0} return
7979 set span [$canv yview]
7980 clear_display
7981 setcanvscroll
7982 allcanvs yview moveto [lindex $span 0]
7983 drawvisible
7984 if {$selectedline ne {}} {
7985 selectline $selectedline 0
7986 allcanvs yview moveto [lindex $span 0]
7987 }
7988}
7989
7990proc parsefont {f n} {
7991 global fontattr
7992
7993 set fontattr($f,family) [lindex $n 0]
7994 set s [lindex $n 1]
7995 if {$s eq {} || $s == 0} {
7996 set s 10
7997 } elseif {$s < 0} {
7998 set s [expr {int(-$s / [winfo fpixels . 1p] + 0.5)}]
7999 }
8000 set fontattr($f,size) $s
8001 set fontattr($f,weight) normal
8002 set fontattr($f,slant) roman
8003 foreach style [lrange $n 2 end] {
8004 switch -- $style {
8005 "normal" -
8006 "bold" {set fontattr($f,weight) $style}
8007 "roman" -
8008 "italic" {set fontattr($f,slant) $style}
8009 }
8010 }
8011}
8012
8013proc fontflags {f {isbold 0}} {
8014 global fontattr
8015
8016 return [list -family $fontattr($f,family) -size $fontattr($f,size) \
8017 -weight [expr {$isbold? "bold": $fontattr($f,weight)}] \
8018 -slant $fontattr($f,slant)]
8019}
8020
8021proc fontname {f} {
8022 global fontattr
8023
8024 set n [list $fontattr($f,family) $fontattr($f,size)]
8025 if {$fontattr($f,weight) eq "bold"} {
8026 lappend n "bold"
8027 }
8028 if {$fontattr($f,slant) eq "italic"} {
8029 lappend n "italic"
8030 }
8031 return $n
8032}
8033
8034proc incrfont {inc} {
8035 global mainfont textfont ctext canv cflist showrefstop
8036 global stopped entries fontattr
8037
8038 unmarkmatches
8039 set s $fontattr(mainfont,size)
8040 incr s $inc
8041 if {$s < 1} {
8042 set s 1
8043 }
8044 set fontattr(mainfont,size) $s
8045 font config mainfont -size $s
8046 font config mainfontbold -size $s
8047 set mainfont [fontname mainfont]
8048 set s $fontattr(textfont,size)
8049 incr s $inc
8050 if {$s < 1} {
8051 set s 1
8052 }
8053 set fontattr(textfont,size) $s
8054 font config textfont -size $s
8055 font config textfontbold -size $s
8056 set textfont [fontname textfont]
8057 setcoords
8058 settabs
8059 redisplay
8060}
8061
8062proc clearsha1 {} {
8063 global sha1entry sha1string
8064 if {[string length $sha1string] == 40} {
8065 $sha1entry delete 0 end
8066 }
8067}
8068
8069proc sha1change {n1 n2 op} {
8070 global sha1string currentid sha1but
8071 if {$sha1string == {}
8072 || ([info exists currentid] && $sha1string == $currentid)} {
8073 set state disabled
8074 } else {
8075 set state normal
8076 }
8077 if {[$sha1but cget -state] == $state} return
8078 if {$state == "normal"} {
8079 $sha1but conf -state normal -relief raised -text "[mc "Goto:"] "
8080 } else {
8081 $sha1but conf -state disabled -relief flat -text "[mc "SHA1 ID:"] "
8082 }
8083}
8084
8085proc gotocommit {} {
8086 global sha1string tagids headids curview varcid
8087
8088 if {$sha1string == {}
8089 || ([info exists currentid] && $sha1string == $currentid)} return
8090 if {[info exists tagids($sha1string)]} {
8091 set id $tagids($sha1string)
8092 } elseif {[info exists headids($sha1string)]} {
8093 set id $headids($sha1string)
8094 } else {
8095 set id [string tolower $sha1string]
8096 if {[regexp {^[0-9a-f]{4,39}$} $id]} {
8097 set matches [longid $id]
8098 if {$matches ne {}} {
8099 if {[llength $matches] > 1} {
8100 error_popup [mc "Short SHA1 id %s is ambiguous" $id]
8101 return
8102 }
8103 set id [lindex $matches 0]
8104 }
8105 } else {
8106 if {[catch {set id [exec git rev-parse --verify $sha1string]}]} {
8107 error_popup [mc "Revision %s is not known" $sha1string]
8108 return
8109 }
8110 }
8111 }
8112 if {[commitinview $id $curview]} {
8113 selectline [rowofcommit $id] 1
8114 return
8115 }
8116 if {[regexp {^[0-9a-fA-F]{4,}$} $sha1string]} {
8117 set msg [mc "SHA1 id %s is not known" $sha1string]
8118 } else {
8119 set msg [mc "Revision %s is not in the current view" $sha1string]
8120 }
8121 error_popup $msg
8122}
8123
8124proc lineenter {x y id} {
8125 global hoverx hovery hoverid hovertimer
8126 global commitinfo canv
8127
8128 if {![info exists commitinfo($id)] && ![getcommit $id]} return
8129 set hoverx $x
8130 set hovery $y
8131 set hoverid $id
8132 if {[info exists hovertimer]} {
8133 after cancel $hovertimer
8134 }
8135 set hovertimer [after 500 linehover]
8136 $canv delete hover
8137}
8138
8139proc linemotion {x y id} {
8140 global hoverx hovery hoverid hovertimer
8141
8142 if {[info exists hoverid] && $id == $hoverid} {
8143 set hoverx $x
8144 set hovery $y
8145 if {[info exists hovertimer]} {
8146 after cancel $hovertimer
8147 }
8148 set hovertimer [after 500 linehover]
8149 }
8150}
8151
8152proc lineleave {id} {
8153 global hoverid hovertimer canv
8154
8155 if {[info exists hoverid] && $id == $hoverid} {
8156 $canv delete hover
8157 if {[info exists hovertimer]} {
8158 after cancel $hovertimer
8159 unset hovertimer
8160 }
8161 unset hoverid
8162 }
8163}
8164
8165proc linehover {} {
8166 global hoverx hovery hoverid hovertimer
8167 global canv linespc lthickness
8168 global commitinfo
8169
8170 set text [lindex $commitinfo($hoverid) 0]
8171 set ymax [lindex [$canv cget -scrollregion] 3]
8172 if {$ymax == {}} return
8173 set yfrac [lindex [$canv yview] 0]
8174 set x [expr {$hoverx + 2 * $linespc}]
8175 set y [expr {$hovery + $yfrac * $ymax - $linespc / 2}]
8176 set x0 [expr {$x - 2 * $lthickness}]
8177 set y0 [expr {$y - 2 * $lthickness}]
8178 set x1 [expr {$x + [font measure mainfont $text] + 2 * $lthickness}]
8179 set y1 [expr {$y + $linespc + 2 * $lthickness}]
8180 set t [$canv create rectangle $x0 $y0 $x1 $y1 \
8181 -fill \#ffff80 -outline black -width 1 -tags hover]
8182 $canv raise $t
8183 set t [$canv create text $x $y -anchor nw -text $text -tags hover \
8184 -font mainfont]
8185 $canv raise $t
8186}
8187
8188proc clickisonarrow {id y} {
8189 global lthickness
8190
8191 set ranges [rowranges $id]
8192 set thresh [expr {2 * $lthickness + 6}]
8193 set n [expr {[llength $ranges] - 1}]
8194 for {set i 1} {$i < $n} {incr i} {
8195 set row [lindex $ranges $i]
8196 if {abs([yc $row] - $y) < $thresh} {
8197 return $i
8198 }
8199 }
8200 return {}
8201}
8202
8203proc arrowjump {id n y} {
8204 global canv
8205
8206 # 1 <-> 2, 3 <-> 4, etc...
8207 set n [expr {(($n - 1) ^ 1) + 1}]
8208 set row [lindex [rowranges $id] $n]
8209 set yt [yc $row]
8210 set ymax [lindex [$canv cget -scrollregion] 3]
8211 if {$ymax eq {} || $ymax <= 0} return
8212 set view [$canv yview]
8213 set yspan [expr {[lindex $view 1] - [lindex $view 0]}]
8214 set yfrac [expr {$yt / $ymax - $yspan / 2}]
8215 if {$yfrac < 0} {
8216 set yfrac 0
8217 }
8218 allcanvs yview moveto $yfrac
8219}
8220
8221proc lineclick {x y id isnew} {
8222 global ctext commitinfo children canv thickerline curview
8223
8224 if {![info exists commitinfo($id)] && ![getcommit $id]} return
8225 unmarkmatches
8226 unselectline
8227 normalline
8228 $canv delete hover
8229 # draw this line thicker than normal
8230 set thickerline $id
8231 drawlines $id
8232 if {$isnew} {
8233 set ymax [lindex [$canv cget -scrollregion] 3]
8234 if {$ymax eq {}} return
8235 set yfrac [lindex [$canv yview] 0]
8236 set y [expr {$y + $yfrac * $ymax}]
8237 }
8238 set dirn [clickisonarrow $id $y]
8239 if {$dirn ne {}} {
8240 arrowjump $id $dirn $y
8241 return
8242 }
8243
8244 if {$isnew} {
8245 addtohistory [list lineclick $x $y $id 0] savectextpos
8246 }
8247 # fill the details pane with info about this line
8248 $ctext conf -state normal
8249 clear_ctext
8250 settabs 0
8251 $ctext insert end "[mc "Parent"]:\t"
8252 $ctext insert end $id link0
8253 setlink $id link0
8254 set info $commitinfo($id)
8255 $ctext insert end "\n\t[lindex $info 0]\n"
8256 $ctext insert end "\t[mc "Author"]:\t[lindex $info 1]\n"
8257 set date [formatdate [lindex $info 2]]
8258 $ctext insert end "\t[mc "Date"]:\t$date\n"
8259 set kids $children($curview,$id)
8260 if {$kids ne {}} {
8261 $ctext insert end "\n[mc "Children"]:"
8262 set i 0
8263 foreach child $kids {
8264 incr i
8265 if {![info exists commitinfo($child)] && ![getcommit $child]} continue
8266 set info $commitinfo($child)
8267 $ctext insert end "\n\t"
8268 $ctext insert end $child link$i
8269 setlink $child link$i
8270 $ctext insert end "\n\t[lindex $info 0]"
8271 $ctext insert end "\n\t[mc "Author"]:\t[lindex $info 1]"
8272 set date [formatdate [lindex $info 2]]
8273 $ctext insert end "\n\t[mc "Date"]:\t$date\n"
8274 }
8275 }
8276 maybe_scroll_ctext 1
8277 $ctext conf -state disabled
8278 init_flist {}
8279}
8280
8281proc normalline {} {
8282 global thickerline
8283 if {[info exists thickerline]} {
8284 set id $thickerline
8285 unset thickerline
8286 drawlines $id
8287 }
8288}
8289
8290proc selbyid {id {isnew 1}} {
8291 global curview
8292 if {[commitinview $id $curview]} {
8293 selectline [rowofcommit $id] $isnew
8294 }
8295}
8296
8297proc mstime {} {
8298 global startmstime
8299 if {![info exists startmstime]} {
8300 set startmstime [clock clicks -milliseconds]
8301 }
8302 return [format "%.3f" [expr {([clock click -milliseconds] - $startmstime) / 1000.0}]]
8303}
8304
8305proc rowmenu {x y id} {
8306 global rowctxmenu selectedline rowmenuid curview
8307 global nullid nullid2 fakerowmenu mainhead markedid
8308
8309 stopfinding
8310 set rowmenuid $id
8311 if {$selectedline eq {} || [rowofcommit $id] eq $selectedline} {
8312 set state disabled
8313 } else {
8314 set state normal
8315 }
8316 if {$id ne $nullid && $id ne $nullid2} {
8317 set menu $rowctxmenu
8318 if {$mainhead ne {}} {
8319 $menu entryconfigure 7 -label [mc "Reset %s branch to here" $mainhead] -state normal
8320 } else {
8321 $menu entryconfigure 7 -label [mc "Detached head: can't reset" $mainhead] -state disabled
8322 }
8323 if {[info exists markedid] && $markedid ne $id} {
8324 $menu entryconfigure 9 -state normal
8325 $menu entryconfigure 10 -state normal
8326 $menu entryconfigure 11 -state normal
8327 } else {
8328 $menu entryconfigure 9 -state disabled
8329 $menu entryconfigure 10 -state disabled
8330 $menu entryconfigure 11 -state disabled
8331 }
8332 } else {
8333 set menu $fakerowmenu
8334 }
8335 $menu entryconfigure [mca "Diff this -> selected"] -state $state
8336 $menu entryconfigure [mca "Diff selected -> this"] -state $state
8337 $menu entryconfigure [mca "Make patch"] -state $state
8338 tk_popup $menu $x $y
8339}
8340
8341proc markhere {} {
8342 global rowmenuid markedid canv
8343
8344 set markedid $rowmenuid
8345 make_idmark $markedid
8346}
8347
8348proc gotomark {} {
8349 global markedid
8350
8351 if {[info exists markedid]} {
8352 selbyid $markedid
8353 }
8354}
8355
8356proc replace_by_kids {l r} {
8357 global curview children
8358
8359 set id [commitonrow $r]
8360 set l [lreplace $l 0 0]
8361 foreach kid $children($curview,$id) {
8362 lappend l [rowofcommit $kid]
8363 }
8364 return [lsort -integer -decreasing -unique $l]
8365}
8366
8367proc find_common_desc {} {
8368 global markedid rowmenuid curview children
8369
8370 if {![info exists markedid]} return
8371 if {![commitinview $markedid $curview] ||
8372 ![commitinview $rowmenuid $curview]} return
8373 #set t1 [clock clicks -milliseconds]
8374 set l1 [list [rowofcommit $markedid]]
8375 set l2 [list [rowofcommit $rowmenuid]]
8376 while 1 {
8377 set r1 [lindex $l1 0]
8378 set r2 [lindex $l2 0]
8379 if {$r1 eq {} || $r2 eq {}} break
8380 if {$r1 == $r2} {
8381 selectline $r1 1
8382 break
8383 }
8384 if {$r1 > $r2} {
8385 set l1 [replace_by_kids $l1 $r1]
8386 } else {
8387 set l2 [replace_by_kids $l2 $r2]
8388 }
8389 }
8390 #set t2 [clock clicks -milliseconds]
8391 #puts "took [expr {$t2-$t1}]ms"
8392}
8393
8394proc compare_commits {} {
8395 global markedid rowmenuid curview children
8396
8397 if {![info exists markedid]} return
8398 if {![commitinview $markedid $curview]} return
8399 addtohistory [list do_cmp_commits $markedid $rowmenuid]
8400 do_cmp_commits $markedid $rowmenuid
8401}
8402
8403proc getpatchid {id} {
8404 global patchids
8405
8406 if {![info exists patchids($id)]} {
8407 set cmd [diffcmd [list $id] {-p --root}]
8408 # trim off the initial "|"
8409 set cmd [lrange $cmd 1 end]
8410 if {[catch {
8411 set x [eval exec $cmd | git patch-id]
8412 set patchids($id) [lindex $x 0]
8413 }]} {
8414 set patchids($id) "error"
8415 }
8416 }
8417 return $patchids($id)
8418}
8419
8420proc do_cmp_commits {a b} {
8421 global ctext curview parents children patchids commitinfo
8422
8423 $ctext conf -state normal
8424 clear_ctext
8425 init_flist {}
8426 for {set i 0} {$i < 100} {incr i} {
8427 set skipa 0
8428 set skipb 0
8429 if {[llength $parents($curview,$a)] > 1} {
8430 appendshortlink $a [mc "Skipping merge commit "] "\n"
8431 set skipa 1
8432 } else {
8433 set patcha [getpatchid $a]
8434 }
8435 if {[llength $parents($curview,$b)] > 1} {
8436 appendshortlink $b [mc "Skipping merge commit "] "\n"
8437 set skipb 1
8438 } else {
8439 set patchb [getpatchid $b]
8440 }
8441 if {!$skipa && !$skipb} {
8442 set heada [lindex $commitinfo($a) 0]
8443 set headb [lindex $commitinfo($b) 0]
8444 if {$patcha eq "error"} {
8445 appendshortlink $a [mc "Error getting patch ID for "] \
8446 [mc " - stopping\n"]
8447 break
8448 }
8449 if {$patchb eq "error"} {
8450 appendshortlink $b [mc "Error getting patch ID for "] \
8451 [mc " - stopping\n"]
8452 break
8453 }
8454 if {$patcha eq $patchb} {
8455 if {$heada eq $headb} {
8456 appendshortlink $a [mc "Commit "]
8457 appendshortlink $b " == " " $heada\n"
8458 } else {
8459 appendshortlink $a [mc "Commit "] " $heada\n"
8460 appendshortlink $b [mc " is the same patch as\n "] \
8461 " $headb\n"
8462 }
8463 set skipa 1
8464 set skipb 1
8465 } else {
8466 $ctext insert end "\n"
8467 appendshortlink $a [mc "Commit "] " $heada\n"
8468 appendshortlink $b [mc " differs from\n "] \
8469 " $headb\n"
8470 $ctext insert end [mc "Diff of commits:\n\n"]
8471 $ctext conf -state disabled
8472 update
8473 diffcommits $a $b
8474 return
8475 }
8476 }
8477 if {$skipa} {
8478 set kids [real_children $curview,$a]
8479 if {[llength $kids] != 1} {
8480 $ctext insert end "\n"
8481 appendshortlink $a [mc "Commit "] \
8482 [mc " has %s children - stopping\n" [llength $kids]]
8483 break
8484 }
8485 set a [lindex $kids 0]
8486 }
8487 if {$skipb} {
8488 set kids [real_children $curview,$b]
8489 if {[llength $kids] != 1} {
8490 appendshortlink $b [mc "Commit "] \
8491 [mc " has %s children - stopping\n" [llength $kids]]
8492 break
8493 }
8494 set b [lindex $kids 0]
8495 }
8496 }
8497 $ctext conf -state disabled
8498}
8499
8500proc diffcommits {a b} {
8501 global diffcontext diffids blobdifffd diffinhdr
8502
8503 set tmpdir [gitknewtmpdir]
8504 set fna [file join $tmpdir "commit-[string range $a 0 7]"]
8505 set fnb [file join $tmpdir "commit-[string range $b 0 7]"]
8506 if {[catch {
8507 exec git diff-tree -p --pretty $a >$fna
8508 exec git diff-tree -p --pretty $b >$fnb
8509 } err]} {
8510 error_popup [mc "Error writing commit to file: %s" $err]
8511 return
8512 }
8513 if {[catch {
8514 set fd [open "| diff -U$diffcontext $fna $fnb" r]
8515 } err]} {
8516 error_popup [mc "Error diffing commits: %s" $err]
8517 return
8518 }
8519 set diffids [list commits $a $b]
8520 set blobdifffd($diffids) $fd
8521 set diffinhdr 0
8522 filerun $fd [list getblobdiffline $fd $diffids]
8523}
8524
8525proc diffvssel {dirn} {
8526 global rowmenuid selectedline
8527
8528 if {$selectedline eq {}} return
8529 if {$dirn} {
8530 set oldid [commitonrow $selectedline]
8531 set newid $rowmenuid
8532 } else {
8533 set oldid $rowmenuid
8534 set newid [commitonrow $selectedline]
8535 }
8536 addtohistory [list doseldiff $oldid $newid] savectextpos
8537 doseldiff $oldid $newid
8538}
8539
8540proc doseldiff {oldid newid} {
8541 global ctext
8542 global commitinfo
8543
8544 $ctext conf -state normal
8545 clear_ctext
8546 init_flist [mc "Top"]
8547 $ctext insert end "[mc "From"] "
8548 $ctext insert end $oldid link0
8549 setlink $oldid link0
8550 $ctext insert end "\n "
8551 $ctext insert end [lindex $commitinfo($oldid) 0]
8552 $ctext insert end "\n\n[mc "To"] "
8553 $ctext insert end $newid link1
8554 setlink $newid link1
8555 $ctext insert end "\n "
8556 $ctext insert end [lindex $commitinfo($newid) 0]
8557 $ctext insert end "\n"
8558 $ctext conf -state disabled
8559 $ctext tag remove found 1.0 end
8560 startdiff [list $oldid $newid]
8561}
8562
8563proc mkpatch {} {
8564 global rowmenuid currentid commitinfo patchtop patchnum NS
8565
8566 if {![info exists currentid]} return
8567 set oldid $currentid
8568 set oldhead [lindex $commitinfo($oldid) 0]
8569 set newid $rowmenuid
8570 set newhead [lindex $commitinfo($newid) 0]
8571 set top .patch
8572 set patchtop $top
8573 catch {destroy $top}
8574 ttk_toplevel $top
8575 make_transient $top .
8576 ${NS}::label $top.title -text [mc "Generate patch"]
8577 grid $top.title - -pady 10
8578 ${NS}::label $top.from -text [mc "From:"]
8579 ${NS}::entry $top.fromsha1 -width 40
8580 $top.fromsha1 insert 0 $oldid
8581 $top.fromsha1 conf -state readonly
8582 grid $top.from $top.fromsha1 -sticky w
8583 ${NS}::entry $top.fromhead -width 60
8584 $top.fromhead insert 0 $oldhead
8585 $top.fromhead conf -state readonly
8586 grid x $top.fromhead -sticky w
8587 ${NS}::label $top.to -text [mc "To:"]
8588 ${NS}::entry $top.tosha1 -width 40
8589 $top.tosha1 insert 0 $newid
8590 $top.tosha1 conf -state readonly
8591 grid $top.to $top.tosha1 -sticky w
8592 ${NS}::entry $top.tohead -width 60
8593 $top.tohead insert 0 $newhead
8594 $top.tohead conf -state readonly
8595 grid x $top.tohead -sticky w
8596 ${NS}::button $top.rev -text [mc "Reverse"] -command mkpatchrev
8597 grid $top.rev x -pady 10 -padx 5
8598 ${NS}::label $top.flab -text [mc "Output file:"]
8599 ${NS}::entry $top.fname -width 60
8600 $top.fname insert 0 [file normalize "patch$patchnum.patch"]
8601 incr patchnum
8602 grid $top.flab $top.fname -sticky w
8603 ${NS}::frame $top.buts
8604 ${NS}::button $top.buts.gen -text [mc "Generate"] -command mkpatchgo
8605 ${NS}::button $top.buts.can -text [mc "Cancel"] -command mkpatchcan
8606 bind $top <Key-Return> mkpatchgo
8607 bind $top <Key-Escape> mkpatchcan
8608 grid $top.buts.gen $top.buts.can
8609 grid columnconfigure $top.buts 0 -weight 1 -uniform a
8610 grid columnconfigure $top.buts 1 -weight 1 -uniform a
8611 grid $top.buts - -pady 10 -sticky ew
8612 focus $top.fname
8613}
8614
8615proc mkpatchrev {} {
8616 global patchtop
8617
8618 set oldid [$patchtop.fromsha1 get]
8619 set oldhead [$patchtop.fromhead get]
8620 set newid [$patchtop.tosha1 get]
8621 set newhead [$patchtop.tohead get]
8622 foreach e [list fromsha1 fromhead tosha1 tohead] \
8623 v [list $newid $newhead $oldid $oldhead] {
8624 $patchtop.$e conf -state normal
8625 $patchtop.$e delete 0 end
8626 $patchtop.$e insert 0 $v
8627 $patchtop.$e conf -state readonly
8628 }
8629}
8630
8631proc mkpatchgo {} {
8632 global patchtop nullid nullid2
8633
8634 set oldid [$patchtop.fromsha1 get]
8635 set newid [$patchtop.tosha1 get]
8636 set fname [$patchtop.fname get]
8637 set cmd [diffcmd [list $oldid $newid] -p]
8638 # trim off the initial "|"
8639 set cmd [lrange $cmd 1 end]
8640 lappend cmd >$fname &
8641 if {[catch {eval exec $cmd} err]} {
8642 error_popup "[mc "Error creating patch:"] $err" $patchtop
8643 }
8644 catch {destroy $patchtop}
8645 unset patchtop
8646}
8647
8648proc mkpatchcan {} {
8649 global patchtop
8650
8651 catch {destroy $patchtop}
8652 unset patchtop
8653}
8654
8655proc mktag {} {
8656 global rowmenuid mktagtop commitinfo NS
8657
8658 set top .maketag
8659 set mktagtop $top
8660 catch {destroy $top}
8661 ttk_toplevel $top
8662 make_transient $top .
8663 ${NS}::label $top.title -text [mc "Create tag"]
8664 grid $top.title - -pady 10
8665 ${NS}::label $top.id -text [mc "ID:"]
8666 ${NS}::entry $top.sha1 -width 40
8667 $top.sha1 insert 0 $rowmenuid
8668 $top.sha1 conf -state readonly
8669 grid $top.id $top.sha1 -sticky w
8670 ${NS}::entry $top.head -width 60
8671 $top.head insert 0 [lindex $commitinfo($rowmenuid) 0]
8672 $top.head conf -state readonly
8673 grid x $top.head -sticky w
8674 ${NS}::label $top.tlab -text [mc "Tag name:"]
8675 ${NS}::entry $top.tag -width 60
8676 grid $top.tlab $top.tag -sticky w
8677 ${NS}::frame $top.buts
8678 ${NS}::button $top.buts.gen -text [mc "Create"] -command mktaggo
8679 ${NS}::button $top.buts.can -text [mc "Cancel"] -command mktagcan
8680 bind $top <Key-Return> mktaggo
8681 bind $top <Key-Escape> mktagcan
8682 grid $top.buts.gen $top.buts.can
8683 grid columnconfigure $top.buts 0 -weight 1 -uniform a
8684 grid columnconfigure $top.buts 1 -weight 1 -uniform a
8685 grid $top.buts - -pady 10 -sticky ew
8686 focus $top.tag
8687}
8688
8689proc domktag {} {
8690 global mktagtop env tagids idtags
8691
8692 set id [$mktagtop.sha1 get]
8693 set tag [$mktagtop.tag get]
8694 if {$tag == {}} {
8695 error_popup [mc "No tag name specified"] $mktagtop
8696 return 0
8697 }
8698 if {[info exists tagids($tag)]} {
8699 error_popup [mc "Tag \"%s\" already exists" $tag] $mktagtop
8700 return 0
8701 }
8702 if {[catch {
8703 exec git tag $tag $id
8704 } err]} {
8705 error_popup "[mc "Error creating tag:"] $err" $mktagtop
8706 return 0
8707 }
8708
8709 set tagids($tag) $id
8710 lappend idtags($id) $tag
8711 redrawtags $id
8712 addedtag $id
8713 dispneartags 0
8714 run refill_reflist
8715 return 1
8716}
8717
8718proc redrawtags {id} {
8719 global canv linehtag idpos currentid curview cmitlisted markedid
8720 global canvxmax iddrawn circleitem mainheadid circlecolors
8721
8722 if {![commitinview $id $curview]} return
8723 if {![info exists iddrawn($id)]} return
8724 set row [rowofcommit $id]
8725 if {$id eq $mainheadid} {
8726 set ofill yellow
8727 } else {
8728 set ofill [lindex $circlecolors $cmitlisted($curview,$id)]
8729 }
8730 $canv itemconf $circleitem($row) -fill $ofill
8731 $canv delete tag.$id
8732 set xt [eval drawtags $id $idpos($id)]
8733 $canv coords $linehtag($id) $xt [lindex $idpos($id) 2]
8734 set text [$canv itemcget $linehtag($id) -text]
8735 set font [$canv itemcget $linehtag($id) -font]
8736 set xr [expr {$xt + [font measure $font $text]}]
8737 if {$xr > $canvxmax} {
8738 set canvxmax $xr
8739 setcanvscroll
8740 }
8741 if {[info exists currentid] && $currentid == $id} {
8742 make_secsel $id
8743 }
8744 if {[info exists markedid] && $markedid eq $id} {
8745 make_idmark $id
8746 }
8747}
8748
8749proc mktagcan {} {
8750 global mktagtop
8751
8752 catch {destroy $mktagtop}
8753 unset mktagtop
8754}
8755
8756proc mktaggo {} {
8757 if {![domktag]} return
8758 mktagcan
8759}
8760
8761proc writecommit {} {
8762 global rowmenuid wrcomtop commitinfo wrcomcmd NS
8763
8764 set top .writecommit
8765 set wrcomtop $top
8766 catch {destroy $top}
8767 ttk_toplevel $top
8768 make_transient $top .
8769 ${NS}::label $top.title -text [mc "Write commit to file"]
8770 grid $top.title - -pady 10
8771 ${NS}::label $top.id -text [mc "ID:"]
8772 ${NS}::entry $top.sha1 -width 40
8773 $top.sha1 insert 0 $rowmenuid
8774 $top.sha1 conf -state readonly
8775 grid $top.id $top.sha1 -sticky w
8776 ${NS}::entry $top.head -width 60
8777 $top.head insert 0 [lindex $commitinfo($rowmenuid) 0]
8778 $top.head conf -state readonly
8779 grid x $top.head -sticky w
8780 ${NS}::label $top.clab -text [mc "Command:"]
8781 ${NS}::entry $top.cmd -width 60 -textvariable wrcomcmd
8782 grid $top.clab $top.cmd -sticky w -pady 10
8783 ${NS}::label $top.flab -text [mc "Output file:"]
8784 ${NS}::entry $top.fname -width 60
8785 $top.fname insert 0 [file normalize "commit-[string range $rowmenuid 0 6]"]
8786 grid $top.flab $top.fname -sticky w
8787 ${NS}::frame $top.buts
8788 ${NS}::button $top.buts.gen -text [mc "Write"] -command wrcomgo
8789 ${NS}::button $top.buts.can -text [mc "Cancel"] -command wrcomcan
8790 bind $top <Key-Return> wrcomgo
8791 bind $top <Key-Escape> wrcomcan
8792 grid $top.buts.gen $top.buts.can
8793 grid columnconfigure $top.buts 0 -weight 1 -uniform a
8794 grid columnconfigure $top.buts 1 -weight 1 -uniform a
8795 grid $top.buts - -pady 10 -sticky ew
8796 focus $top.fname
8797}
8798
8799proc wrcomgo {} {
8800 global wrcomtop
8801
8802 set id [$wrcomtop.sha1 get]
8803 set cmd "echo $id | [$wrcomtop.cmd get]"
8804 set fname [$wrcomtop.fname get]
8805 if {[catch {exec sh -c $cmd >$fname &} err]} {
8806 error_popup "[mc "Error writing commit:"] $err" $wrcomtop
8807 }
8808 catch {destroy $wrcomtop}
8809 unset wrcomtop
8810}
8811
8812proc wrcomcan {} {
8813 global wrcomtop
8814
8815 catch {destroy $wrcomtop}
8816 unset wrcomtop
8817}
8818
8819proc mkbranch {} {
8820 global rowmenuid mkbrtop NS
8821
8822 set top .makebranch
8823 catch {destroy $top}
8824 ttk_toplevel $top
8825 make_transient $top .
8826 ${NS}::label $top.title -text [mc "Create new branch"]
8827 grid $top.title - -pady 10
8828 ${NS}::label $top.id -text [mc "ID:"]
8829 ${NS}::entry $top.sha1 -width 40
8830 $top.sha1 insert 0 $rowmenuid
8831 $top.sha1 conf -state readonly
8832 grid $top.id $top.sha1 -sticky w
8833 ${NS}::label $top.nlab -text [mc "Name:"]
8834 ${NS}::entry $top.name -width 40
8835 grid $top.nlab $top.name -sticky w
8836 ${NS}::frame $top.buts
8837 ${NS}::button $top.buts.go -text [mc "Create"] -command [list mkbrgo $top]
8838 ${NS}::button $top.buts.can -text [mc "Cancel"] -command "catch {destroy $top}"
8839 bind $top <Key-Return> [list mkbrgo $top]
8840 bind $top <Key-Escape> "catch {destroy $top}"
8841 grid $top.buts.go $top.buts.can
8842 grid columnconfigure $top.buts 0 -weight 1 -uniform a
8843 grid columnconfigure $top.buts 1 -weight 1 -uniform a
8844 grid $top.buts - -pady 10 -sticky ew
8845 focus $top.name
8846}
8847
8848proc mkbrgo {top} {
8849 global headids idheads
8850
8851 set name [$top.name get]
8852 set id [$top.sha1 get]
8853 set cmdargs {}
8854 set old_id {}
8855 if {$name eq {}} {
8856 error_popup [mc "Please specify a name for the new branch"] $top
8857 return
8858 }
8859 if {[info exists headids($name)]} {
8860 if {![confirm_popup [mc \
8861 "Branch '%s' already exists. Overwrite?" $name] $top]} {
8862 return
8863 }
8864 set old_id $headids($name)
8865 lappend cmdargs -f
8866 }
8867 catch {destroy $top}
8868 lappend cmdargs $name $id
8869 nowbusy newbranch
8870 update
8871 if {[catch {
8872 eval exec git branch $cmdargs
8873 } err]} {
8874 notbusy newbranch
8875 error_popup $err
8876 } else {
8877 notbusy newbranch
8878 if {$old_id ne {}} {
8879 movehead $id $name
8880 movedhead $id $name
8881 redrawtags $old_id
8882 redrawtags $id
8883 } else {
8884 set headids($name) $id
8885 lappend idheads($id) $name
8886 addedhead $id $name
8887 redrawtags $id
8888 }
8889 dispneartags 0
8890 run refill_reflist
8891 }
8892}
8893
8894proc exec_citool {tool_args {baseid {}}} {
8895 global commitinfo env
8896
8897 set save_env [array get env GIT_AUTHOR_*]
8898
8899 if {$baseid ne {}} {
8900 if {![info exists commitinfo($baseid)]} {
8901 getcommit $baseid
8902 }
8903 set author [lindex $commitinfo($baseid) 1]
8904 set date [lindex $commitinfo($baseid) 2]
8905 if {[regexp {^\s*(\S.*\S|\S)\s*<(.*)>\s*$} \
8906 $author author name email]
8907 && $date ne {}} {
8908 set env(GIT_AUTHOR_NAME) $name
8909 set env(GIT_AUTHOR_EMAIL) $email
8910 set env(GIT_AUTHOR_DATE) $date
8911 }
8912 }
8913
8914 eval exec git citool $tool_args &
8915
8916 array unset env GIT_AUTHOR_*
8917 array set env $save_env
8918}
8919
8920proc cherrypick {} {
8921 global rowmenuid curview
8922 global mainhead mainheadid
8923
8924 set oldhead [exec git rev-parse HEAD]
8925 set dheads [descheads $rowmenuid]
8926 if {$dheads ne {} && [lsearch -exact $dheads $oldhead] >= 0} {
8927 set ok [confirm_popup [mc "Commit %s is already\
8928 included in branch %s -- really re-apply it?" \
8929 [string range $rowmenuid 0 7] $mainhead]]
8930 if {!$ok} return
8931 }
8932 nowbusy cherrypick [mc "Cherry-picking"]
8933 update
8934 # Unfortunately git-cherry-pick writes stuff to stderr even when
8935 # no error occurs, and exec takes that as an indication of error...
8936 if {[catch {exec sh -c "git cherry-pick -r $rowmenuid 2>&1"} err]} {
8937 notbusy cherrypick
8938 if {[regexp -line \
8939 {Entry '(.*)' (would be overwritten by merge|not uptodate)} \
8940 $err msg fname]} {
8941 error_popup [mc "Cherry-pick failed because of local changes\
8942 to file '%s'.\nPlease commit, reset or stash\
8943 your changes and try again." $fname]
8944 } elseif {[regexp -line \
8945 {^(CONFLICT \(.*\):|Automatic cherry-pick failed)} \
8946 $err]} {
8947 if {[confirm_popup [mc "Cherry-pick failed because of merge\
8948 conflict.\nDo you wish to run git citool to\
8949 resolve it?"]]} {
8950 # Force citool to read MERGE_MSG
8951 file delete [file join [gitdir] "GITGUI_MSG"]
8952 exec_citool {} $rowmenuid
8953 }
8954 } else {
8955 error_popup $err
8956 }
8957 run updatecommits
8958 return
8959 }
8960 set newhead [exec git rev-parse HEAD]
8961 if {$newhead eq $oldhead} {
8962 notbusy cherrypick
8963 error_popup [mc "No changes committed"]
8964 return
8965 }
8966 addnewchild $newhead $oldhead
8967 if {[commitinview $oldhead $curview]} {
8968 # XXX this isn't right if we have a path limit...
8969 insertrow $newhead $oldhead $curview
8970 if {$mainhead ne {}} {
8971 movehead $newhead $mainhead
8972 movedhead $newhead $mainhead
8973 }
8974 set mainheadid $newhead
8975 redrawtags $oldhead
8976 redrawtags $newhead
8977 selbyid $newhead
8978 }
8979 notbusy cherrypick
8980}
8981
8982proc resethead {} {
8983 global mainhead rowmenuid confirm_ok resettype NS
8984
8985 set confirm_ok 0
8986 set w ".confirmreset"
8987 ttk_toplevel $w
8988 make_transient $w .
8989 wm title $w [mc "Confirm reset"]
8990 ${NS}::label $w.m -text \
8991 [mc "Reset branch %s to %s?" $mainhead [string range $rowmenuid 0 7]]
8992 pack $w.m -side top -fill x -padx 20 -pady 20
8993 ${NS}::labelframe $w.f -text [mc "Reset type:"]
8994 set resettype mixed
8995 ${NS}::radiobutton $w.f.soft -value soft -variable resettype \
8996 -text [mc "Soft: Leave working tree and index untouched"]
8997 grid $w.f.soft -sticky w
8998 ${NS}::radiobutton $w.f.mixed -value mixed -variable resettype \
8999 -text [mc "Mixed: Leave working tree untouched, reset index"]
9000 grid $w.f.mixed -sticky w
9001 ${NS}::radiobutton $w.f.hard -value hard -variable resettype \
9002 -text [mc "Hard: Reset working tree and index\n(discard ALL local changes)"]
9003 grid $w.f.hard -sticky w
9004 pack $w.f -side top -fill x -padx 4
9005 ${NS}::button $w.ok -text [mc OK] -command "set confirm_ok 1; destroy $w"
9006 pack $w.ok -side left -fill x -padx 20 -pady 20
9007 ${NS}::button $w.cancel -text [mc Cancel] -command "destroy $w"
9008 bind $w <Key-Escape> [list destroy $w]
9009 pack $w.cancel -side right -fill x -padx 20 -pady 20
9010 bind $w <Visibility> "grab $w; focus $w"
9011 tkwait window $w
9012 if {!$confirm_ok} return
9013 if {[catch {set fd [open \
9014 [list | git reset --$resettype $rowmenuid 2>@1] r]} err]} {
9015 error_popup $err
9016 } else {
9017 dohidelocalchanges
9018 filerun $fd [list readresetstat $fd]
9019 nowbusy reset [mc "Resetting"]
9020 selbyid $rowmenuid
9021 }
9022}
9023
9024proc readresetstat {fd} {
9025 global mainhead mainheadid showlocalchanges rprogcoord
9026
9027 if {[gets $fd line] >= 0} {
9028 if {[regexp {([0-9]+)% \(([0-9]+)/([0-9]+)\)} $line match p m n]} {
9029 set rprogcoord [expr {1.0 * $m / $n}]
9030 adjustprogress
9031 }
9032 return 1
9033 }
9034 set rprogcoord 0
9035 adjustprogress
9036 notbusy reset
9037 if {[catch {close $fd} err]} {
9038 error_popup $err
9039 }
9040 set oldhead $mainheadid
9041 set newhead [exec git rev-parse HEAD]
9042 if {$newhead ne $oldhead} {
9043 movehead $newhead $mainhead
9044 movedhead $newhead $mainhead
9045 set mainheadid $newhead
9046 redrawtags $oldhead
9047 redrawtags $newhead
9048 }
9049 if {$showlocalchanges} {
9050 doshowlocalchanges
9051 }
9052 return 0
9053}
9054
9055# context menu for a head
9056proc headmenu {x y id head} {
9057 global headmenuid headmenuhead headctxmenu mainhead
9058
9059 stopfinding
9060 set headmenuid $id
9061 set headmenuhead $head
9062 set state normal
9063 if {$head eq $mainhead} {
9064 set state disabled
9065 }
9066 $headctxmenu entryconfigure 0 -state $state
9067 $headctxmenu entryconfigure 1 -state $state
9068 tk_popup $headctxmenu $x $y
9069}
9070
9071proc cobranch {} {
9072 global headmenuid headmenuhead headids
9073 global showlocalchanges
9074
9075 # check the tree is clean first??
9076 nowbusy checkout [mc "Checking out"]
9077 update
9078 dohidelocalchanges
9079 if {[catch {
9080 set fd [open [list | git checkout $headmenuhead 2>@1] r]
9081 } err]} {
9082 notbusy checkout
9083 error_popup $err
9084 if {$showlocalchanges} {
9085 dodiffindex
9086 }
9087 } else {
9088 filerun $fd [list readcheckoutstat $fd $headmenuhead $headmenuid]
9089 }
9090}
9091
9092proc readcheckoutstat {fd newhead newheadid} {
9093 global mainhead mainheadid headids showlocalchanges progresscoords
9094 global viewmainheadid curview
9095
9096 if {[gets $fd line] >= 0} {
9097 if {[regexp {([0-9]+)% \(([0-9]+)/([0-9]+)\)} $line match p m n]} {
9098 set progresscoords [list 0 [expr {1.0 * $m / $n}]]
9099 adjustprogress
9100 }
9101 return 1
9102 }
9103 set progresscoords {0 0}
9104 adjustprogress
9105 notbusy checkout
9106 if {[catch {close $fd} err]} {
9107 error_popup $err
9108 }
9109 set oldmainid $mainheadid
9110 set mainhead $newhead
9111 set mainheadid $newheadid
9112 set viewmainheadid($curview) $newheadid
9113 redrawtags $oldmainid
9114 redrawtags $newheadid
9115 selbyid $newheadid
9116 if {$showlocalchanges} {
9117 dodiffindex
9118 }
9119}
9120
9121proc rmbranch {} {
9122 global headmenuid headmenuhead mainhead
9123 global idheads
9124
9125 set head $headmenuhead
9126 set id $headmenuid
9127 # this check shouldn't be needed any more...
9128 if {$head eq $mainhead} {
9129 error_popup [mc "Cannot delete the currently checked-out branch"]
9130 return
9131 }
9132 set dheads [descheads $id]
9133 if {[llength $dheads] == 1 && $idheads($dheads) eq $head} {
9134 # the stuff on this branch isn't on any other branch
9135 if {![confirm_popup [mc "The commits on branch %s aren't on any other\
9136 branch.\nReally delete branch %s?" $head $head]]} return
9137 }
9138 nowbusy rmbranch
9139 update
9140 if {[catch {exec git branch -D $head} err]} {
9141 notbusy rmbranch
9142 error_popup $err
9143 return
9144 }
9145 removehead $id $head
9146 removedhead $id $head
9147 redrawtags $id
9148 notbusy rmbranch
9149 dispneartags 0
9150 run refill_reflist
9151}
9152
9153# Display a list of tags and heads
9154proc showrefs {} {
9155 global showrefstop bgcolor fgcolor selectbgcolor NS
9156 global bglist fglist reflistfilter reflist maincursor
9157
9158 set top .showrefs
9159 set showrefstop $top
9160 if {[winfo exists $top]} {
9161 raise $top
9162 refill_reflist
9163 return
9164 }
9165 ttk_toplevel $top
9166 wm title $top [mc "Tags and heads: %s" [file tail [pwd]]]
9167 make_transient $top .
9168 text $top.list -background $bgcolor -foreground $fgcolor \
9169 -selectbackground $selectbgcolor -font mainfont \
9170 -xscrollcommand "$top.xsb set" -yscrollcommand "$top.ysb set" \
9171 -width 30 -height 20 -cursor $maincursor \
9172 -spacing1 1 -spacing3 1 -state disabled
9173 $top.list tag configure highlight -background $selectbgcolor
9174 lappend bglist $top.list
9175 lappend fglist $top.list
9176 ${NS}::scrollbar $top.ysb -command "$top.list yview" -orient vertical
9177 ${NS}::scrollbar $top.xsb -command "$top.list xview" -orient horizontal
9178 grid $top.list $top.ysb -sticky nsew
9179 grid $top.xsb x -sticky ew
9180 ${NS}::frame $top.f
9181 ${NS}::label $top.f.l -text "[mc "Filter"]: "
9182 ${NS}::entry $top.f.e -width 20 -textvariable reflistfilter
9183 set reflistfilter "*"
9184 trace add variable reflistfilter write reflistfilter_change
9185 pack $top.f.e -side right -fill x -expand 1
9186 pack $top.f.l -side left
9187 grid $top.f - -sticky ew -pady 2
9188 ${NS}::button $top.close -command [list destroy $top] -text [mc "Close"]
9189 bind $top <Key-Escape> [list destroy $top]
9190 grid $top.close -
9191 grid columnconfigure $top 0 -weight 1
9192 grid rowconfigure $top 0 -weight 1
9193 bind $top.list <1> {break}
9194 bind $top.list <B1-Motion> {break}
9195 bind $top.list <ButtonRelease-1> {sel_reflist %W %x %y; break}
9196 set reflist {}
9197 refill_reflist
9198}
9199
9200proc sel_reflist {w x y} {
9201 global showrefstop reflist headids tagids otherrefids
9202
9203 if {![winfo exists $showrefstop]} return
9204 set l [lindex [split [$w index "@$x,$y"] "."] 0]
9205 set ref [lindex $reflist [expr {$l-1}]]
9206 set n [lindex $ref 0]
9207 switch -- [lindex $ref 1] {
9208 "H" {selbyid $headids($n)}
9209 "T" {selbyid $tagids($n)}
9210 "o" {selbyid $otherrefids($n)}
9211 }
9212 $showrefstop.list tag add highlight $l.0 "$l.0 lineend"
9213}
9214
9215proc unsel_reflist {} {
9216 global showrefstop
9217
9218 if {![info exists showrefstop] || ![winfo exists $showrefstop]} return
9219 $showrefstop.list tag remove highlight 0.0 end
9220}
9221
9222proc reflistfilter_change {n1 n2 op} {
9223 global reflistfilter
9224
9225 after cancel refill_reflist
9226 after 200 refill_reflist
9227}
9228
9229proc refill_reflist {} {
9230 global reflist reflistfilter showrefstop headids tagids otherrefids
9231 global curview
9232
9233 if {![info exists showrefstop] || ![winfo exists $showrefstop]} return
9234 set refs {}
9235 foreach n [array names headids] {
9236 if {[string match $reflistfilter $n]} {
9237 if {[commitinview $headids($n) $curview]} {
9238 lappend refs [list $n H]
9239 } else {
9240 interestedin $headids($n) {run refill_reflist}
9241 }
9242 }
9243 }
9244 foreach n [array names tagids] {
9245 if {[string match $reflistfilter $n]} {
9246 if {[commitinview $tagids($n) $curview]} {
9247 lappend refs [list $n T]
9248 } else {
9249 interestedin $tagids($n) {run refill_reflist}
9250 }
9251 }
9252 }
9253 foreach n [array names otherrefids] {
9254 if {[string match $reflistfilter $n]} {
9255 if {[commitinview $otherrefids($n) $curview]} {
9256 lappend refs [list $n o]
9257 } else {
9258 interestedin $otherrefids($n) {run refill_reflist}
9259 }
9260 }
9261 }
9262 set refs [lsort -index 0 $refs]
9263 if {$refs eq $reflist} return
9264
9265 # Update the contents of $showrefstop.list according to the
9266 # differences between $reflist (old) and $refs (new)
9267 $showrefstop.list conf -state normal
9268 $showrefstop.list insert end "\n"
9269 set i 0
9270 set j 0
9271 while {$i < [llength $reflist] || $j < [llength $refs]} {
9272 if {$i < [llength $reflist]} {
9273 if {$j < [llength $refs]} {
9274 set cmp [string compare [lindex $reflist $i 0] \
9275 [lindex $refs $j 0]]
9276 if {$cmp == 0} {
9277 set cmp [string compare [lindex $reflist $i 1] \
9278 [lindex $refs $j 1]]
9279 }
9280 } else {
9281 set cmp -1
9282 }
9283 } else {
9284 set cmp 1
9285 }
9286 switch -- $cmp {
9287 -1 {
9288 $showrefstop.list delete "[expr {$j+1}].0" "[expr {$j+2}].0"
9289 incr i
9290 }
9291 0 {
9292 incr i
9293 incr j
9294 }
9295 1 {
9296 set l [expr {$j + 1}]
9297 $showrefstop.list image create $l.0 -align baseline \
9298 -image reficon-[lindex $refs $j 1] -padx 2
9299 $showrefstop.list insert $l.1 "[lindex $refs $j 0]\n"
9300 incr j
9301 }
9302 }
9303 }
9304 set reflist $refs
9305 # delete last newline
9306 $showrefstop.list delete end-2c end-1c
9307 $showrefstop.list conf -state disabled
9308}
9309
9310# Stuff for finding nearby tags
9311proc getallcommits {} {
9312 global allcommits nextarc seeds allccache allcwait cachedarcs allcupdate
9313 global idheads idtags idotherrefs allparents tagobjid
9314
9315 if {![info exists allcommits]} {
9316 set nextarc 0
9317 set allcommits 0
9318 set seeds {}
9319 set allcwait 0
9320 set cachedarcs 0
9321 set allccache [file join [gitdir] "gitk.cache"]
9322 if {![catch {
9323 set f [open $allccache r]
9324 set allcwait 1
9325 getcache $f
9326 }]} return
9327 }
9328
9329 if {$allcwait} {
9330 return
9331 }
9332 set cmd [list | git rev-list --parents]
9333 set allcupdate [expr {$seeds ne {}}]
9334 if {!$allcupdate} {
9335 set ids "--all"
9336 } else {
9337 set refs [concat [array names idheads] [array names idtags] \
9338 [array names idotherrefs]]
9339 set ids {}
9340 set tagobjs {}
9341 foreach name [array names tagobjid] {
9342 lappend tagobjs $tagobjid($name)
9343 }
9344 foreach id [lsort -unique $refs] {
9345 if {![info exists allparents($id)] &&
9346 [lsearch -exact $tagobjs $id] < 0} {
9347 lappend ids $id
9348 }
9349 }
9350 if {$ids ne {}} {
9351 foreach id $seeds {
9352 lappend ids "^$id"
9353 }
9354 }
9355 }
9356 if {$ids ne {}} {
9357 set fd [open [concat $cmd $ids] r]
9358 fconfigure $fd -blocking 0
9359 incr allcommits
9360 nowbusy allcommits
9361 filerun $fd [list getallclines $fd]
9362 } else {
9363 dispneartags 0
9364 }
9365}
9366
9367# Since most commits have 1 parent and 1 child, we group strings of
9368# such commits into "arcs" joining branch/merge points (BMPs), which
9369# are commits that either don't have 1 parent or don't have 1 child.
9370#
9371# arcnos(id) - incoming arcs for BMP, arc we're on for other nodes
9372# arcout(id) - outgoing arcs for BMP
9373# arcids(a) - list of IDs on arc including end but not start
9374# arcstart(a) - BMP ID at start of arc
9375# arcend(a) - BMP ID at end of arc
9376# growing(a) - arc a is still growing
9377# arctags(a) - IDs out of arcids (excluding end) that have tags
9378# archeads(a) - IDs out of arcids (excluding end) that have heads
9379# The start of an arc is at the descendent end, so "incoming" means
9380# coming from descendents, and "outgoing" means going towards ancestors.
9381
9382proc getallclines {fd} {
9383 global allparents allchildren idtags idheads nextarc
9384 global arcnos arcids arctags arcout arcend arcstart archeads growing
9385 global seeds allcommits cachedarcs allcupdate
9386
9387 set nid 0
9388 while {[incr nid] <= 1000 && [gets $fd line] >= 0} {
9389 set id [lindex $line 0]
9390 if {[info exists allparents($id)]} {
9391 # seen it already
9392 continue
9393 }
9394 set cachedarcs 0
9395 set olds [lrange $line 1 end]
9396 set allparents($id) $olds
9397 if {![info exists allchildren($id)]} {
9398 set allchildren($id) {}
9399 set arcnos($id) {}
9400 lappend seeds $id
9401 } else {
9402 set a $arcnos($id)
9403 if {[llength $olds] == 1 && [llength $a] == 1} {
9404 lappend arcids($a) $id
9405 if {[info exists idtags($id)]} {
9406 lappend arctags($a) $id
9407 }
9408 if {[info exists idheads($id)]} {
9409 lappend archeads($a) $id
9410 }
9411 if {[info exists allparents($olds)]} {
9412 # seen parent already
9413 if {![info exists arcout($olds)]} {
9414 splitarc $olds
9415 }
9416 lappend arcids($a) $olds
9417 set arcend($a) $olds
9418 unset growing($a)
9419 }
9420 lappend allchildren($olds) $id
9421 lappend arcnos($olds) $a
9422 continue
9423 }
9424 }
9425 foreach a $arcnos($id) {
9426 lappend arcids($a) $id
9427 set arcend($a) $id
9428 unset growing($a)
9429 }
9430
9431 set ao {}
9432 foreach p $olds {
9433 lappend allchildren($p) $id
9434 set a [incr nextarc]
9435 set arcstart($a) $id
9436 set archeads($a) {}
9437 set arctags($a) {}
9438 set archeads($a) {}
9439 set arcids($a) {}
9440 lappend ao $a
9441 set growing($a) 1
9442 if {[info exists allparents($p)]} {
9443 # seen it already, may need to make a new branch
9444 if {![info exists arcout($p)]} {
9445 splitarc $p
9446 }
9447 lappend arcids($a) $p
9448 set arcend($a) $p
9449 unset growing($a)
9450 }
9451 lappend arcnos($p) $a
9452 }
9453 set arcout($id) $ao
9454 }
9455 if {$nid > 0} {
9456 global cached_dheads cached_dtags cached_atags
9457 catch {unset cached_dheads}
9458 catch {unset cached_dtags}
9459 catch {unset cached_atags}
9460 }
9461 if {![eof $fd]} {
9462 return [expr {$nid >= 1000? 2: 1}]
9463 }
9464 set cacheok 1
9465 if {[catch {
9466 fconfigure $fd -blocking 1
9467 close $fd
9468 } err]} {
9469 # got an error reading the list of commits
9470 # if we were updating, try rereading the whole thing again
9471 if {$allcupdate} {
9472 incr allcommits -1
9473 dropcache $err
9474 return
9475 }
9476 error_popup "[mc "Error reading commit topology information;\
9477 branch and preceding/following tag information\
9478 will be incomplete."]\n($err)"
9479 set cacheok 0
9480 }
9481 if {[incr allcommits -1] == 0} {
9482 notbusy allcommits
9483 if {$cacheok} {
9484 run savecache
9485 }
9486 }
9487 dispneartags 0
9488 return 0
9489}
9490
9491proc recalcarc {a} {
9492 global arctags archeads arcids idtags idheads
9493
9494 set at {}
9495 set ah {}
9496 foreach id [lrange $arcids($a) 0 end-1] {
9497 if {[info exists idtags($id)]} {
9498 lappend at $id
9499 }
9500 if {[info exists idheads($id)]} {
9501 lappend ah $id
9502 }
9503 }
9504 set arctags($a) $at
9505 set archeads($a) $ah
9506}
9507
9508proc splitarc {p} {
9509 global arcnos arcids nextarc arctags archeads idtags idheads
9510 global arcstart arcend arcout allparents growing
9511
9512 set a $arcnos($p)
9513 if {[llength $a] != 1} {
9514 puts "oops splitarc called but [llength $a] arcs already"
9515 return
9516 }
9517 set a [lindex $a 0]
9518 set i [lsearch -exact $arcids($a) $p]
9519 if {$i < 0} {
9520 puts "oops splitarc $p not in arc $a"
9521 return
9522 }
9523 set na [incr nextarc]
9524 if {[info exists arcend($a)]} {
9525 set arcend($na) $arcend($a)
9526 } else {
9527 set l [lindex $allparents([lindex $arcids($a) end]) 0]
9528 set j [lsearch -exact $arcnos($l) $a]
9529 set arcnos($l) [lreplace $arcnos($l) $j $j $na]
9530 }
9531 set tail [lrange $arcids($a) [expr {$i+1}] end]
9532 set arcids($a) [lrange $arcids($a) 0 $i]
9533 set arcend($a) $p
9534 set arcstart($na) $p
9535 set arcout($p) $na
9536 set arcids($na) $tail
9537 if {[info exists growing($a)]} {
9538 set growing($na) 1
9539 unset growing($a)
9540 }
9541
9542 foreach id $tail {
9543 if {[llength $arcnos($id)] == 1} {
9544 set arcnos($id) $na
9545 } else {
9546 set j [lsearch -exact $arcnos($id) $a]
9547 set arcnos($id) [lreplace $arcnos($id) $j $j $na]
9548 }
9549 }
9550
9551 # reconstruct tags and heads lists
9552 if {$arctags($a) ne {} || $archeads($a) ne {}} {
9553 recalcarc $a
9554 recalcarc $na
9555 } else {
9556 set arctags($na) {}
9557 set archeads($na) {}
9558 }
9559}
9560
9561# Update things for a new commit added that is a child of one
9562# existing commit. Used when cherry-picking.
9563proc addnewchild {id p} {
9564 global allparents allchildren idtags nextarc
9565 global arcnos arcids arctags arcout arcend arcstart archeads growing
9566 global seeds allcommits
9567
9568 if {![info exists allcommits] || ![info exists arcnos($p)]} return
9569 set allparents($id) [list $p]
9570 set allchildren($id) {}
9571 set arcnos($id) {}
9572 lappend seeds $id
9573 lappend allchildren($p) $id
9574 set a [incr nextarc]
9575 set arcstart($a) $id
9576 set archeads($a) {}
9577 set arctags($a) {}
9578 set arcids($a) [list $p]
9579 set arcend($a) $p
9580 if {![info exists arcout($p)]} {
9581 splitarc $p
9582 }
9583 lappend arcnos($p) $a
9584 set arcout($id) [list $a]
9585}
9586
9587# This implements a cache for the topology information.
9588# The cache saves, for each arc, the start and end of the arc,
9589# the ids on the arc, and the outgoing arcs from the end.
9590proc readcache {f} {
9591 global arcnos arcids arcout arcstart arcend arctags archeads nextarc
9592 global idtags idheads allparents cachedarcs possible_seeds seeds growing
9593 global allcwait
9594
9595 set a $nextarc
9596 set lim $cachedarcs
9597 if {$lim - $a > 500} {
9598 set lim [expr {$a + 500}]
9599 }
9600 if {[catch {
9601 if {$a == $lim} {
9602 # finish reading the cache and setting up arctags, etc.
9603 set line [gets $f]
9604 if {$line ne "1"} {error "bad final version"}
9605 close $f
9606 foreach id [array names idtags] {
9607 if {[info exists arcnos($id)] && [llength $arcnos($id)] == 1 &&
9608 [llength $allparents($id)] == 1} {
9609 set a [lindex $arcnos($id) 0]
9610 if {$arctags($a) eq {}} {
9611 recalcarc $a
9612 }
9613 }
9614 }
9615 foreach id [array names idheads] {
9616 if {[info exists arcnos($id)] && [llength $arcnos($id)] == 1 &&
9617 [llength $allparents($id)] == 1} {
9618 set a [lindex $arcnos($id) 0]
9619 if {$archeads($a) eq {}} {
9620 recalcarc $a
9621 }
9622 }
9623 }
9624 foreach id [lsort -unique $possible_seeds] {
9625 if {$arcnos($id) eq {}} {
9626 lappend seeds $id
9627 }
9628 }
9629 set allcwait 0
9630 } else {
9631 while {[incr a] <= $lim} {
9632 set line [gets $f]
9633 if {[llength $line] != 3} {error "bad line"}
9634 set s [lindex $line 0]
9635 set arcstart($a) $s
9636 lappend arcout($s) $a
9637 if {![info exists arcnos($s)]} {
9638 lappend possible_seeds $s
9639 set arcnos($s) {}
9640 }
9641 set e [lindex $line 1]
9642 if {$e eq {}} {
9643 set growing($a) 1
9644 } else {
9645 set arcend($a) $e
9646 if {![info exists arcout($e)]} {
9647 set arcout($e) {}
9648 }
9649 }
9650 set arcids($a) [lindex $line 2]
9651 foreach id $arcids($a) {
9652 lappend allparents($s) $id
9653 set s $id
9654 lappend arcnos($id) $a
9655 }
9656 if {![info exists allparents($s)]} {
9657 set allparents($s) {}
9658 }
9659 set arctags($a) {}
9660 set archeads($a) {}
9661 }
9662 set nextarc [expr {$a - 1}]
9663 }
9664 } err]} {
9665 dropcache $err
9666 return 0
9667 }
9668 if {!$allcwait} {
9669 getallcommits
9670 }
9671 return $allcwait
9672}
9673
9674proc getcache {f} {
9675 global nextarc cachedarcs possible_seeds
9676
9677 if {[catch {
9678 set line [gets $f]
9679 if {[llength $line] != 2 || [lindex $line 0] ne "1"} {error "bad version"}
9680 # make sure it's an integer
9681 set cachedarcs [expr {int([lindex $line 1])}]
9682 if {$cachedarcs < 0} {error "bad number of arcs"}
9683 set nextarc 0
9684 set possible_seeds {}
9685 run readcache $f
9686 } err]} {
9687 dropcache $err
9688 }
9689 return 0
9690}
9691
9692proc dropcache {err} {
9693 global allcwait nextarc cachedarcs seeds
9694
9695 #puts "dropping cache ($err)"
9696 foreach v {arcnos arcout arcids arcstart arcend growing \
9697 arctags archeads allparents allchildren} {
9698 global $v
9699 catch {unset $v}
9700 }
9701 set allcwait 0
9702 set nextarc 0
9703 set cachedarcs 0
9704 set seeds {}
9705 getallcommits
9706}
9707
9708proc writecache {f} {
9709 global cachearc cachedarcs allccache
9710 global arcstart arcend arcnos arcids arcout
9711
9712 set a $cachearc
9713 set lim $cachedarcs
9714 if {$lim - $a > 1000} {
9715 set lim [expr {$a + 1000}]
9716 }
9717 if {[catch {
9718 while {[incr a] <= $lim} {
9719 if {[info exists arcend($a)]} {
9720 puts $f [list $arcstart($a) $arcend($a) $arcids($a)]
9721 } else {
9722 puts $f [list $arcstart($a) {} $arcids($a)]
9723 }
9724 }
9725 } err]} {
9726 catch {close $f}
9727 catch {file delete $allccache}
9728 #puts "writing cache failed ($err)"
9729 return 0
9730 }
9731 set cachearc [expr {$a - 1}]
9732 if {$a > $cachedarcs} {
9733 puts $f "1"
9734 close $f
9735 return 0
9736 }
9737 return 1
9738}
9739
9740proc savecache {} {
9741 global nextarc cachedarcs cachearc allccache
9742
9743 if {$nextarc == $cachedarcs} return
9744 set cachearc 0
9745 set cachedarcs $nextarc
9746 catch {
9747 set f [open $allccache w]
9748 puts $f [list 1 $cachedarcs]
9749 run writecache $f
9750 }
9751}
9752
9753# Returns 1 if a is an ancestor of b, -1 if b is an ancestor of a,
9754# or 0 if neither is true.
9755proc anc_or_desc {a b} {
9756 global arcout arcstart arcend arcnos cached_isanc
9757
9758 if {$arcnos($a) eq $arcnos($b)} {
9759 # Both are on the same arc(s); either both are the same BMP,
9760 # or if one is not a BMP, the other is also not a BMP or is
9761 # the BMP at end of the arc (and it only has 1 incoming arc).
9762 # Or both can be BMPs with no incoming arcs.
9763 if {$a eq $b || $arcnos($a) eq {}} {
9764 return 0
9765 }
9766 # assert {[llength $arcnos($a)] == 1}
9767 set arc [lindex $arcnos($a) 0]
9768 set i [lsearch -exact $arcids($arc) $a]
9769 set j [lsearch -exact $arcids($arc) $b]
9770 if {$i < 0 || $i > $j} {
9771 return 1
9772 } else {
9773 return -1
9774 }
9775 }
9776
9777 if {![info exists arcout($a)]} {
9778 set arc [lindex $arcnos($a) 0]
9779 if {[info exists arcend($arc)]} {
9780 set aend $arcend($arc)
9781 } else {
9782 set aend {}
9783 }
9784 set a $arcstart($arc)
9785 } else {
9786 set aend $a
9787 }
9788 if {![info exists arcout($b)]} {
9789 set arc [lindex $arcnos($b) 0]
9790 if {[info exists arcend($arc)]} {
9791 set bend $arcend($arc)
9792 } else {
9793 set bend {}
9794 }
9795 set b $arcstart($arc)
9796 } else {
9797 set bend $b
9798 }
9799 if {$a eq $bend} {
9800 return 1
9801 }
9802 if {$b eq $aend} {
9803 return -1
9804 }
9805 if {[info exists cached_isanc($a,$bend)]} {
9806 if {$cached_isanc($a,$bend)} {
9807 return 1
9808 }
9809 }
9810 if {[info exists cached_isanc($b,$aend)]} {
9811 if {$cached_isanc($b,$aend)} {
9812 return -1
9813 }
9814 if {[info exists cached_isanc($a,$bend)]} {
9815 return 0
9816 }
9817 }
9818
9819 set todo [list $a $b]
9820 set anc($a) a
9821 set anc($b) b
9822 for {set i 0} {$i < [llength $todo]} {incr i} {
9823 set x [lindex $todo $i]
9824 if {$anc($x) eq {}} {
9825 continue
9826 }
9827 foreach arc $arcnos($x) {
9828 set xd $arcstart($arc)
9829 if {$xd eq $bend} {
9830 set cached_isanc($a,$bend) 1
9831 set cached_isanc($b,$aend) 0
9832 return 1
9833 } elseif {$xd eq $aend} {
9834 set cached_isanc($b,$aend) 1
9835 set cached_isanc($a,$bend) 0
9836 return -1
9837 }
9838 if {![info exists anc($xd)]} {
9839 set anc($xd) $anc($x)
9840 lappend todo $xd
9841 } elseif {$anc($xd) ne $anc($x)} {
9842 set anc($xd) {}
9843 }
9844 }
9845 }
9846 set cached_isanc($a,$bend) 0
9847 set cached_isanc($b,$aend) 0
9848 return 0
9849}
9850
9851# This identifies whether $desc has an ancestor that is
9852# a growing tip of the graph and which is not an ancestor of $anc
9853# and returns 0 if so and 1 if not.
9854# If we subsequently discover a tag on such a growing tip, and that
9855# turns out to be a descendent of $anc (which it could, since we
9856# don't necessarily see children before parents), then $desc
9857# isn't a good choice to display as a descendent tag of
9858# $anc (since it is the descendent of another tag which is
9859# a descendent of $anc). Similarly, $anc isn't a good choice to
9860# display as a ancestor tag of $desc.
9861#
9862proc is_certain {desc anc} {
9863 global arcnos arcout arcstart arcend growing problems
9864
9865 set certain {}
9866 if {[llength $arcnos($anc)] == 1} {
9867 # tags on the same arc are certain
9868 if {$arcnos($desc) eq $arcnos($anc)} {
9869 return 1
9870 }
9871 if {![info exists arcout($anc)]} {
9872 # if $anc is partway along an arc, use the start of the arc instead
9873 set a [lindex $arcnos($anc) 0]
9874 set anc $arcstart($a)
9875 }
9876 }
9877 if {[llength $arcnos($desc)] > 1 || [info exists arcout($desc)]} {
9878 set x $desc
9879 } else {
9880 set a [lindex $arcnos($desc) 0]
9881 set x $arcend($a)
9882 }
9883 if {$x == $anc} {
9884 return 1
9885 }
9886 set anclist [list $x]
9887 set dl($x) 1
9888 set nnh 1
9889 set ngrowanc 0
9890 for {set i 0} {$i < [llength $anclist] && ($nnh > 0 || $ngrowanc > 0)} {incr i} {
9891 set x [lindex $anclist $i]
9892 if {$dl($x)} {
9893 incr nnh -1
9894 }
9895 set done($x) 1
9896 foreach a $arcout($x) {
9897 if {[info exists growing($a)]} {
9898 if {![info exists growanc($x)] && $dl($x)} {
9899 set growanc($x) 1
9900 incr ngrowanc
9901 }
9902 } else {
9903 set y $arcend($a)
9904 if {[info exists dl($y)]} {
9905 if {$dl($y)} {
9906 if {!$dl($x)} {
9907 set dl($y) 0
9908 if {![info exists done($y)]} {
9909 incr nnh -1
9910 }
9911 if {[info exists growanc($x)]} {
9912 incr ngrowanc -1
9913 }
9914 set xl [list $y]
9915 for {set k 0} {$k < [llength $xl]} {incr k} {
9916 set z [lindex $xl $k]
9917 foreach c $arcout($z) {
9918 if {[info exists arcend($c)]} {
9919 set v $arcend($c)
9920 if {[info exists dl($v)] && $dl($v)} {
9921 set dl($v) 0
9922 if {![info exists done($v)]} {
9923 incr nnh -1
9924 }
9925 if {[info exists growanc($v)]} {
9926 incr ngrowanc -1
9927 }
9928 lappend xl $v
9929 }
9930 }
9931 }
9932 }
9933 }
9934 }
9935 } elseif {$y eq $anc || !$dl($x)} {
9936 set dl($y) 0
9937 lappend anclist $y
9938 } else {
9939 set dl($y) 1
9940 lappend anclist $y
9941 incr nnh
9942 }
9943 }
9944 }
9945 }
9946 foreach x [array names growanc] {
9947 if {$dl($x)} {
9948 return 0
9949 }
9950 return 0
9951 }
9952 return 1
9953}
9954
9955proc validate_arctags {a} {
9956 global arctags idtags
9957
9958 set i -1
9959 set na $arctags($a)
9960 foreach id $arctags($a) {
9961 incr i
9962 if {![info exists idtags($id)]} {
9963 set na [lreplace $na $i $i]
9964 incr i -1
9965 }
9966 }
9967 set arctags($a) $na
9968}
9969
9970proc validate_archeads {a} {
9971 global archeads idheads
9972
9973 set i -1
9974 set na $archeads($a)
9975 foreach id $archeads($a) {
9976 incr i
9977 if {![info exists idheads($id)]} {
9978 set na [lreplace $na $i $i]
9979 incr i -1
9980 }
9981 }
9982 set archeads($a) $na
9983}
9984
9985# Return the list of IDs that have tags that are descendents of id,
9986# ignoring IDs that are descendents of IDs already reported.
9987proc desctags {id} {
9988 global arcnos arcstart arcids arctags idtags allparents
9989 global growing cached_dtags
9990
9991 if {![info exists allparents($id)]} {
9992 return {}
9993 }
9994 set t1 [clock clicks -milliseconds]
9995 set argid $id
9996 if {[llength $arcnos($id)] == 1 && [llength $allparents($id)] == 1} {
9997 # part-way along an arc; check that arc first
9998 set a [lindex $arcnos($id) 0]
9999 if {$arctags($a) ne {}} {
10000 validate_arctags $a
10001 set i [lsearch -exact $arcids($a) $id]
10002 set tid {}
10003 foreach t $arctags($a) {
10004 set j [lsearch -exact $arcids($a) $t]
10005 if {$j >= $i} break
10006 set tid $t
10007 }
10008 if {$tid ne {}} {
10009 return $tid
10010 }
10011 }
10012 set id $arcstart($a)
10013 if {[info exists idtags($id)]} {
10014 return $id
10015 }
10016 }
10017 if {[info exists cached_dtags($id)]} {
10018 return $cached_dtags($id)
10019 }
10020
10021 set origid $id
10022 set todo [list $id]
10023 set queued($id) 1
10024 set nc 1
10025 for {set i 0} {$i < [llength $todo] && $nc > 0} {incr i} {
10026 set id [lindex $todo $i]
10027 set done($id) 1
10028 set ta [info exists hastaggedancestor($id)]
10029 if {!$ta} {
10030 incr nc -1
10031 }
10032 # ignore tags on starting node
10033 if {!$ta && $i > 0} {
10034 if {[info exists idtags($id)]} {
10035 set tagloc($id) $id
10036 set ta 1
10037 } elseif {[info exists cached_dtags($id)]} {
10038 set tagloc($id) $cached_dtags($id)
10039 set ta 1
10040 }
10041 }
10042 foreach a $arcnos($id) {
10043 set d $arcstart($a)
10044 if {!$ta && $arctags($a) ne {}} {
10045 validate_arctags $a
10046 if {$arctags($a) ne {}} {
10047 lappend tagloc($id) [lindex $arctags($a) end]
10048 }
10049 }
10050 if {$ta || $arctags($a) ne {}} {
10051 set tomark [list $d]
10052 for {set j 0} {$j < [llength $tomark]} {incr j} {
10053 set dd [lindex $tomark $j]
10054 if {![info exists hastaggedancestor($dd)]} {
10055 if {[info exists done($dd)]} {
10056 foreach b $arcnos($dd) {
10057 lappend tomark $arcstart($b)
10058 }
10059 if {[info exists tagloc($dd)]} {
10060 unset tagloc($dd)
10061 }
10062 } elseif {[info exists queued($dd)]} {
10063 incr nc -1
10064 }
10065 set hastaggedancestor($dd) 1
10066 }
10067 }
10068 }
10069 if {![info exists queued($d)]} {
10070 lappend todo $d
10071 set queued($d) 1
10072 if {![info exists hastaggedancestor($d)]} {
10073 incr nc
10074 }
10075 }
10076 }
10077 }
10078 set tags {}
10079 foreach id [array names tagloc] {
10080 if {![info exists hastaggedancestor($id)]} {
10081 foreach t $tagloc($id) {
10082 if {[lsearch -exact $tags $t] < 0} {
10083 lappend tags $t
10084 }
10085 }
10086 }
10087 }
10088 set t2 [clock clicks -milliseconds]
10089 set loopix $i
10090
10091 # remove tags that are descendents of other tags
10092 for {set i 0} {$i < [llength $tags]} {incr i} {
10093 set a [lindex $tags $i]
10094 for {set j 0} {$j < $i} {incr j} {
10095 set b [lindex $tags $j]
10096 set r [anc_or_desc $a $b]
10097 if {$r == 1} {
10098 set tags [lreplace $tags $j $j]
10099 incr j -1
10100 incr i -1
10101 } elseif {$r == -1} {
10102 set tags [lreplace $tags $i $i]
10103 incr i -1
10104 break
10105 }
10106 }
10107 }
10108
10109 if {[array names growing] ne {}} {
10110 # graph isn't finished, need to check if any tag could get
10111 # eclipsed by another tag coming later. Simply ignore any
10112 # tags that could later get eclipsed.
10113 set ctags {}
10114 foreach t $tags {
10115 if {[is_certain $t $origid]} {
10116 lappend ctags $t
10117 }
10118 }
10119 if {$tags eq $ctags} {
10120 set cached_dtags($origid) $tags
10121 } else {
10122 set tags $ctags
10123 }
10124 } else {
10125 set cached_dtags($origid) $tags
10126 }
10127 set t3 [clock clicks -milliseconds]
10128 if {0 && $t3 - $t1 >= 100} {
10129 puts "iterating descendents ($loopix/[llength $todo] nodes) took\
10130 [expr {$t2-$t1}]+[expr {$t3-$t2}]ms, $nc candidates left"
10131 }
10132 return $tags
10133}
10134
10135proc anctags {id} {
10136 global arcnos arcids arcout arcend arctags idtags allparents
10137 global growing cached_atags
10138
10139 if {![info exists allparents($id)]} {
10140 return {}
10141 }
10142 set t1 [clock clicks -milliseconds]
10143 set argid $id
10144 if {[llength $arcnos($id)] == 1 && [llength $allparents($id)] == 1} {
10145 # part-way along an arc; check that arc first
10146 set a [lindex $arcnos($id) 0]
10147 if {$arctags($a) ne {}} {
10148 validate_arctags $a
10149 set i [lsearch -exact $arcids($a) $id]
10150 foreach t $arctags($a) {
10151 set j [lsearch -exact $arcids($a) $t]
10152 if {$j > $i} {
10153 return $t
10154 }
10155 }
10156 }
10157 if {![info exists arcend($a)]} {
10158 return {}
10159 }
10160 set id $arcend($a)
10161 if {[info exists idtags($id)]} {
10162 return $id
10163 }
10164 }
10165 if {[info exists cached_atags($id)]} {
10166 return $cached_atags($id)
10167 }
10168
10169 set origid $id
10170 set todo [list $id]
10171 set queued($id) 1
10172 set taglist {}
10173 set nc 1
10174 for {set i 0} {$i < [llength $todo] && $nc > 0} {incr i} {
10175 set id [lindex $todo $i]
10176 set done($id) 1
10177 set td [info exists hastaggeddescendent($id)]
10178 if {!$td} {
10179 incr nc -1
10180 }
10181 # ignore tags on starting node
10182 if {!$td && $i > 0} {
10183 if {[info exists idtags($id)]} {
10184 set tagloc($id) $id
10185 set td 1
10186 } elseif {[info exists cached_atags($id)]} {
10187 set tagloc($id) $cached_atags($id)
10188 set td 1
10189 }
10190 }
10191 foreach a $arcout($id) {
10192 if {!$td && $arctags($a) ne {}} {
10193 validate_arctags $a
10194 if {$arctags($a) ne {}} {
10195 lappend tagloc($id) [lindex $arctags($a) 0]
10196 }
10197 }
10198 if {![info exists arcend($a)]} continue
10199 set d $arcend($a)
10200 if {$td || $arctags($a) ne {}} {
10201 set tomark [list $d]
10202 for {set j 0} {$j < [llength $tomark]} {incr j} {
10203 set dd [lindex $tomark $j]
10204 if {![info exists hastaggeddescendent($dd)]} {
10205 if {[info exists done($dd)]} {
10206 foreach b $arcout($dd) {
10207 if {[info exists arcend($b)]} {
10208 lappend tomark $arcend($b)
10209 }
10210 }
10211 if {[info exists tagloc($dd)]} {
10212 unset tagloc($dd)
10213 }
10214 } elseif {[info exists queued($dd)]} {
10215 incr nc -1
10216 }
10217 set hastaggeddescendent($dd) 1
10218 }
10219 }
10220 }
10221 if {![info exists queued($d)]} {
10222 lappend todo $d
10223 set queued($d) 1
10224 if {![info exists hastaggeddescendent($d)]} {
10225 incr nc
10226 }
10227 }
10228 }
10229 }
10230 set t2 [clock clicks -milliseconds]
10231 set loopix $i
10232 set tags {}
10233 foreach id [array names tagloc] {
10234 if {![info exists hastaggeddescendent($id)]} {
10235 foreach t $tagloc($id) {
10236 if {[lsearch -exact $tags $t] < 0} {
10237 lappend tags $t
10238 }
10239 }
10240 }
10241 }
10242
10243 # remove tags that are ancestors of other tags
10244 for {set i 0} {$i < [llength $tags]} {incr i} {
10245 set a [lindex $tags $i]
10246 for {set j 0} {$j < $i} {incr j} {
10247 set b [lindex $tags $j]
10248 set r [anc_or_desc $a $b]
10249 if {$r == -1} {
10250 set tags [lreplace $tags $j $j]
10251 incr j -1
10252 incr i -1
10253 } elseif {$r == 1} {
10254 set tags [lreplace $tags $i $i]
10255 incr i -1
10256 break
10257 }
10258 }
10259 }
10260
10261 if {[array names growing] ne {}} {
10262 # graph isn't finished, need to check if any tag could get
10263 # eclipsed by another tag coming later. Simply ignore any
10264 # tags that could later get eclipsed.
10265 set ctags {}
10266 foreach t $tags {
10267 if {[is_certain $origid $t]} {
10268 lappend ctags $t
10269 }
10270 }
10271 if {$tags eq $ctags} {
10272 set cached_atags($origid) $tags
10273 } else {
10274 set tags $ctags
10275 }
10276 } else {
10277 set cached_atags($origid) $tags
10278 }
10279 set t3 [clock clicks -milliseconds]
10280 if {0 && $t3 - $t1 >= 100} {
10281 puts "iterating ancestors ($loopix/[llength $todo] nodes) took\
10282 [expr {$t2-$t1}]+[expr {$t3-$t2}]ms, $nc candidates left"
10283 }
10284 return $tags
10285}
10286
10287# Return the list of IDs that have heads that are descendents of id,
10288# including id itself if it has a head.
10289proc descheads {id} {
10290 global arcnos arcstart arcids archeads idheads cached_dheads
10291 global allparents
10292
10293 if {![info exists allparents($id)]} {
10294 return {}
10295 }
10296 set aret {}
10297 if {[llength $arcnos($id)] == 1 && [llength $allparents($id)] == 1} {
10298 # part-way along an arc; check it first
10299 set a [lindex $arcnos($id) 0]
10300 if {$archeads($a) ne {}} {
10301 validate_archeads $a
10302 set i [lsearch -exact $arcids($a) $id]
10303 foreach t $archeads($a) {
10304 set j [lsearch -exact $arcids($a) $t]
10305 if {$j > $i} break
10306 lappend aret $t
10307 }
10308 }
10309 set id $arcstart($a)
10310 }
10311 set origid $id
10312 set todo [list $id]
10313 set seen($id) 1
10314 set ret {}
10315 for {set i 0} {$i < [llength $todo]} {incr i} {
10316 set id [lindex $todo $i]
10317 if {[info exists cached_dheads($id)]} {
10318 set ret [concat $ret $cached_dheads($id)]
10319 } else {
10320 if {[info exists idheads($id)]} {
10321 lappend ret $id
10322 }
10323 foreach a $arcnos($id) {
10324 if {$archeads($a) ne {}} {
10325 validate_archeads $a
10326 if {$archeads($a) ne {}} {
10327 set ret [concat $ret $archeads($a)]
10328 }
10329 }
10330 set d $arcstart($a)
10331 if {![info exists seen($d)]} {
10332 lappend todo $d
10333 set seen($d) 1
10334 }
10335 }
10336 }
10337 }
10338 set ret [lsort -unique $ret]
10339 set cached_dheads($origid) $ret
10340 return [concat $ret $aret]
10341}
10342
10343proc addedtag {id} {
10344 global arcnos arcout cached_dtags cached_atags
10345
10346 if {![info exists arcnos($id)]} return
10347 if {![info exists arcout($id)]} {
10348 recalcarc [lindex $arcnos($id) 0]
10349 }
10350 catch {unset cached_dtags}
10351 catch {unset cached_atags}
10352}
10353
10354proc addedhead {hid head} {
10355 global arcnos arcout cached_dheads
10356
10357 if {![info exists arcnos($hid)]} return
10358 if {![info exists arcout($hid)]} {
10359 recalcarc [lindex $arcnos($hid) 0]
10360 }
10361 catch {unset cached_dheads}
10362}
10363
10364proc removedhead {hid head} {
10365 global cached_dheads
10366
10367 catch {unset cached_dheads}
10368}
10369
10370proc movedhead {hid head} {
10371 global arcnos arcout cached_dheads
10372
10373 if {![info exists arcnos($hid)]} return
10374 if {![info exists arcout($hid)]} {
10375 recalcarc [lindex $arcnos($hid) 0]
10376 }
10377 catch {unset cached_dheads}
10378}
10379
10380proc changedrefs {} {
10381 global cached_dheads cached_dtags cached_atags
10382 global arctags archeads arcnos arcout idheads idtags
10383
10384 foreach id [concat [array names idheads] [array names idtags]] {
10385 if {[info exists arcnos($id)] && ![info exists arcout($id)]} {
10386 set a [lindex $arcnos($id) 0]
10387 if {![info exists donearc($a)]} {
10388 recalcarc $a
10389 set donearc($a) 1
10390 }
10391 }
10392 }
10393 catch {unset cached_dtags}
10394 catch {unset cached_atags}
10395 catch {unset cached_dheads}
10396}
10397
10398proc rereadrefs {} {
10399 global idtags idheads idotherrefs mainheadid
10400
10401 set refids [concat [array names idtags] \
10402 [array names idheads] [array names idotherrefs]]
10403 foreach id $refids {
10404 if {![info exists ref($id)]} {
10405 set ref($id) [listrefs $id]
10406 }
10407 }
10408 set oldmainhead $mainheadid
10409 readrefs
10410 changedrefs
10411 set refids [lsort -unique [concat $refids [array names idtags] \
10412 [array names idheads] [array names idotherrefs]]]
10413 foreach id $refids {
10414 set v [listrefs $id]
10415 if {![info exists ref($id)] || $ref($id) != $v} {
10416 redrawtags $id
10417 }
10418 }
10419 if {$oldmainhead ne $mainheadid} {
10420 redrawtags $oldmainhead
10421 redrawtags $mainheadid
10422 }
10423 run refill_reflist
10424}
10425
10426proc listrefs {id} {
10427 global idtags idheads idotherrefs
10428
10429 set x {}
10430 if {[info exists idtags($id)]} {
10431 set x $idtags($id)
10432 }
10433 set y {}
10434 if {[info exists idheads($id)]} {
10435 set y $idheads($id)
10436 }
10437 set z {}
10438 if {[info exists idotherrefs($id)]} {
10439 set z $idotherrefs($id)
10440 }
10441 return [list $x $y $z]
10442}
10443
10444proc showtag {tag isnew} {
10445 global ctext tagcontents tagids linknum tagobjid
10446
10447 if {$isnew} {
10448 addtohistory [list showtag $tag 0] savectextpos
10449 }
10450 $ctext conf -state normal
10451 clear_ctext
10452 settabs 0
10453 set linknum 0
10454 if {![info exists tagcontents($tag)]} {
10455 catch {
10456 set tagcontents($tag) [exec git cat-file tag $tagobjid($tag)]
10457 }
10458 }
10459 if {[info exists tagcontents($tag)]} {
10460 set text $tagcontents($tag)
10461 } else {
10462 set text "[mc "Tag"]: $tag\n[mc "Id"]: $tagids($tag)"
10463 }
10464 appendwithlinks $text {}
10465 maybe_scroll_ctext
10466 $ctext conf -state disabled
10467 init_flist {}
10468}
10469
10470proc doquit {} {
10471 global stopped
10472 global gitktmpdir
10473
10474 set stopped 100
10475 savestuff .
10476 destroy .
10477
10478 if {[info exists gitktmpdir]} {
10479 catch {file delete -force $gitktmpdir}
10480 }
10481}
10482
10483proc mkfontdisp {font top which} {
10484 global fontattr fontpref $font NS use_ttk
10485
10486 set fontpref($font) [set $font]
10487 ${NS}::button $top.${font}but -text $which \
10488 -command [list choosefont $font $which]
10489 if {!$use_ttk} {$top.${font}but configure -font optionfont}
10490 ${NS}::label $top.$font -relief flat -font $font \
10491 -text $fontattr($font,family) -justify left
10492 grid x $top.${font}but $top.$font -sticky w
10493}
10494
10495proc choosefont {font which} {
10496 global fontparam fontlist fonttop fontattr
10497 global prefstop NS
10498
10499 set fontparam(which) $which
10500 set fontparam(font) $font
10501 set fontparam(family) [font actual $font -family]
10502 set fontparam(size) $fontattr($font,size)
10503 set fontparam(weight) $fontattr($font,weight)
10504 set fontparam(slant) $fontattr($font,slant)
10505 set top .gitkfont
10506 set fonttop $top
10507 if {![winfo exists $top]} {
10508 font create sample
10509 eval font config sample [font actual $font]
10510 ttk_toplevel $top
10511 make_transient $top $prefstop
10512 wm title $top [mc "Gitk font chooser"]
10513 ${NS}::label $top.l -textvariable fontparam(which)
10514 pack $top.l -side top
10515 set fontlist [lsort [font families]]
10516 ${NS}::frame $top.f
10517 listbox $top.f.fam -listvariable fontlist \
10518 -yscrollcommand [list $top.f.sb set]
10519 bind $top.f.fam <<ListboxSelect>> selfontfam
10520 ${NS}::scrollbar $top.f.sb -command [list $top.f.fam yview]
10521 pack $top.f.sb -side right -fill y
10522 pack $top.f.fam -side left -fill both -expand 1
10523 pack $top.f -side top -fill both -expand 1
10524 ${NS}::frame $top.g
10525 spinbox $top.g.size -from 4 -to 40 -width 4 \
10526 -textvariable fontparam(size) \
10527 -validatecommand {string is integer -strict %s}
10528 checkbutton $top.g.bold -padx 5 \
10529 -font {{Times New Roman} 12 bold} -text [mc "B"] -indicatoron 0 \
10530 -variable fontparam(weight) -onvalue bold -offvalue normal
10531 checkbutton $top.g.ital -padx 5 \
10532 -font {{Times New Roman} 12 italic} -text [mc "I"] -indicatoron 0 \
10533 -variable fontparam(slant) -onvalue italic -offvalue roman
10534 pack $top.g.size $top.g.bold $top.g.ital -side left
10535 pack $top.g -side top
10536 canvas $top.c -width 150 -height 50 -border 2 -relief sunk \
10537 -background white
10538 $top.c create text 100 25 -anchor center -text $which -font sample \
10539 -fill black -tags text
10540 bind $top.c <Configure> [list centertext $top.c]
10541 pack $top.c -side top -fill x
10542 ${NS}::frame $top.buts
10543 ${NS}::button $top.buts.ok -text [mc "OK"] -command fontok -default active
10544 ${NS}::button $top.buts.can -text [mc "Cancel"] -command fontcan -default normal
10545 bind $top <Key-Return> fontok
10546 bind $top <Key-Escape> fontcan
10547 grid $top.buts.ok $top.buts.can
10548 grid columnconfigure $top.buts 0 -weight 1 -uniform a
10549 grid columnconfigure $top.buts 1 -weight 1 -uniform a
10550 pack $top.buts -side bottom -fill x
10551 trace add variable fontparam write chg_fontparam
10552 } else {
10553 raise $top
10554 $top.c itemconf text -text $which
10555 }
10556 set i [lsearch -exact $fontlist $fontparam(family)]
10557 if {$i >= 0} {
10558 $top.f.fam selection set $i
10559 $top.f.fam see $i
10560 }
10561}
10562
10563proc centertext {w} {
10564 $w coords text [expr {[winfo width $w] / 2}] [expr {[winfo height $w] / 2}]
10565}
10566
10567proc fontok {} {
10568 global fontparam fontpref prefstop
10569
10570 set f $fontparam(font)
10571 set fontpref($f) [list $fontparam(family) $fontparam(size)]
10572 if {$fontparam(weight) eq "bold"} {
10573 lappend fontpref($f) "bold"
10574 }
10575 if {$fontparam(slant) eq "italic"} {
10576 lappend fontpref($f) "italic"
10577 }
10578 set w $prefstop.$f
10579 $w conf -text $fontparam(family) -font $fontpref($f)
10580
10581 fontcan
10582}
10583
10584proc fontcan {} {
10585 global fonttop fontparam
10586
10587 if {[info exists fonttop]} {
10588 catch {destroy $fonttop}
10589 catch {font delete sample}
10590 unset fonttop
10591 unset fontparam
10592 }
10593}
10594
10595if {[package vsatisfies [package provide Tk] 8.6]} {
10596 # In Tk 8.6 we have a native font chooser dialog. Overwrite the above
10597 # function to make use of it.
10598 proc choosefont {font which} {
10599 tk fontchooser configure -title $which -font $font \
10600 -command [list on_choosefont $font $which]
10601 tk fontchooser show
10602 }
10603 proc on_choosefont {font which newfont} {
10604 global fontparam
10605 puts stderr "$font $newfont"
10606 array set f [font actual $newfont]
10607 set fontparam(which) $which
10608 set fontparam(font) $font
10609 set fontparam(family) $f(-family)
10610 set fontparam(size) $f(-size)
10611 set fontparam(weight) $f(-weight)
10612 set fontparam(slant) $f(-slant)
10613 fontok
10614 }
10615}
10616
10617proc selfontfam {} {
10618 global fonttop fontparam
10619
10620 set i [$fonttop.f.fam curselection]
10621 if {$i ne {}} {
10622 set fontparam(family) [$fonttop.f.fam get $i]
10623 }
10624}
10625
10626proc chg_fontparam {v sub op} {
10627 global fontparam
10628
10629 font config sample -$sub $fontparam($sub)
10630}
10631
10632proc doprefs {} {
10633 global maxwidth maxgraphpct use_ttk NS
10634 global oldprefs prefstop showneartags showlocalchanges
10635 global bgcolor fgcolor ctext diffcolors selectbgcolor markbgcolor
10636 global tabstop limitdiffs autoselect extdifftool perfile_attrs
10637 global hideremotes want_ttk have_ttk
10638
10639 set top .gitkprefs
10640 set prefstop $top
10641 if {[winfo exists $top]} {
10642 raise $top
10643 return
10644 }
10645 foreach v {maxwidth maxgraphpct showneartags showlocalchanges \
10646 limitdiffs tabstop perfile_attrs hideremotes want_ttk} {
10647 set oldprefs($v) [set $v]
10648 }
10649 ttk_toplevel $top
10650 wm title $top [mc "Gitk preferences"]
10651 make_transient $top .
10652 ${NS}::label $top.ldisp -text [mc "Commit list display options"]
10653 grid $top.ldisp - -sticky w -pady 10
10654 ${NS}::label $top.spacer -text " "
10655 ${NS}::label $top.maxwidthl -text [mc "Maximum graph width (lines)"]
10656 spinbox $top.maxwidth -from 0 -to 100 -width 4 -textvariable maxwidth
10657 grid $top.spacer $top.maxwidthl $top.maxwidth -sticky w
10658 ${NS}::label $top.maxpctl -text [mc "Maximum graph width (% of pane)"]
10659 spinbox $top.maxpct -from 1 -to 100 -width 4 -textvariable maxgraphpct
10660 grid x $top.maxpctl $top.maxpct -sticky w
10661 ${NS}::checkbutton $top.showlocal -text [mc "Show local changes"] \
10662 -variable showlocalchanges
10663 grid x $top.showlocal -sticky w
10664 ${NS}::checkbutton $top.autoselect -text [mc "Auto-select SHA1"] \
10665 -variable autoselect
10666 grid x $top.autoselect -sticky w
10667 ${NS}::checkbutton $top.hideremotes -text [mc "Hide remote refs"] \
10668 -variable hideremotes
10669 grid x $top.hideremotes -sticky w
10670
10671 ${NS}::label $top.ddisp -text [mc "Diff display options"]
10672 grid $top.ddisp - -sticky w -pady 10
10673 ${NS}::label $top.tabstopl -text [mc "Tab spacing"]
10674 spinbox $top.tabstop -from 1 -to 20 -width 4 -textvariable tabstop
10675 grid x $top.tabstopl $top.tabstop -sticky w
10676 ${NS}::checkbutton $top.ntag -text [mc "Display nearby tags"] \
10677 -variable showneartags
10678 grid x $top.ntag -sticky w
10679 ${NS}::checkbutton $top.ldiff -text [mc "Limit diffs to listed paths"] \
10680 -variable limitdiffs
10681 grid x $top.ldiff -sticky w
10682 ${NS}::checkbutton $top.lattr -text [mc "Support per-file encodings"] \
10683 -variable perfile_attrs
10684 grid x $top.lattr -sticky w
10685
10686 ${NS}::entry $top.extdifft -textvariable extdifftool
10687 ${NS}::frame $top.extdifff
10688 ${NS}::label $top.extdifff.l -text [mc "External diff tool" ]
10689 ${NS}::button $top.extdifff.b -text [mc "Choose..."] -command choose_extdiff
10690 pack $top.extdifff.l $top.extdifff.b -side left
10691 pack configure $top.extdifff.l -padx 10
10692 grid x $top.extdifff $top.extdifft -sticky ew
10693
10694 ${NS}::label $top.lgen -text [mc "General options"]
10695 grid $top.lgen - -sticky w -pady 10
10696 ${NS}::checkbutton $top.want_ttk -variable want_ttk \
10697 -text [mc "Use themed widgets"]
10698 if {$have_ttk} {
10699 ${NS}::label $top.ttk_note -text [mc "(change requires restart)"]
10700 } else {
10701 ${NS}::label $top.ttk_note -text [mc "(currently unavailable)"]
10702 }
10703 grid x $top.want_ttk $top.ttk_note -sticky w
10704
10705 ${NS}::label $top.cdisp -text [mc "Colors: press to choose"]
10706 grid $top.cdisp - -sticky w -pady 10
10707 label $top.bg -padx 40 -relief sunk -background $bgcolor
10708 ${NS}::button $top.bgbut -text [mc "Background"] \
10709 -command [list choosecolor bgcolor {} $top.bg [mc "background"] setbg]
10710 grid x $top.bgbut $top.bg -sticky w
10711 label $top.fg -padx 40 -relief sunk -background $fgcolor
10712 ${NS}::button $top.fgbut -text [mc "Foreground"] \
10713 -command [list choosecolor fgcolor {} $top.fg [mc "foreground"] setfg]
10714 grid x $top.fgbut $top.fg -sticky w
10715 label $top.diffold -padx 40 -relief sunk -background [lindex $diffcolors 0]
10716 ${NS}::button $top.diffoldbut -text [mc "Diff: old lines"] \
10717 -command [list choosecolor diffcolors 0 $top.diffold [mc "diff old lines"] \
10718 [list $ctext tag conf d0 -foreground]]
10719 grid x $top.diffoldbut $top.diffold -sticky w
10720 label $top.diffnew -padx 40 -relief sunk -background [lindex $diffcolors 1]
10721 ${NS}::button $top.diffnewbut -text [mc "Diff: new lines"] \
10722 -command [list choosecolor diffcolors 1 $top.diffnew [mc "diff new lines"] \
10723 [list $ctext tag conf dresult -foreground]]
10724 grid x $top.diffnewbut $top.diffnew -sticky w
10725 label $top.hunksep -padx 40 -relief sunk -background [lindex $diffcolors 2]
10726 ${NS}::button $top.hunksepbut -text [mc "Diff: hunk header"] \
10727 -command [list choosecolor diffcolors 2 $top.hunksep \
10728 [mc "diff hunk header"] \
10729 [list $ctext tag conf hunksep -foreground]]
10730 grid x $top.hunksepbut $top.hunksep -sticky w
10731 label $top.markbgsep -padx 40 -relief sunk -background $markbgcolor
10732 ${NS}::button $top.markbgbut -text [mc "Marked line bg"] \
10733 -command [list choosecolor markbgcolor {} $top.markbgsep \
10734 [mc "marked line background"] \
10735 [list $ctext tag conf omark -background]]
10736 grid x $top.markbgbut $top.markbgsep -sticky w
10737 label $top.selbgsep -padx 40 -relief sunk -background $selectbgcolor
10738 ${NS}::button $top.selbgbut -text [mc "Select bg"] \
10739 -command [list choosecolor selectbgcolor {} $top.selbgsep [mc "background"] setselbg]
10740 grid x $top.selbgbut $top.selbgsep -sticky w
10741
10742 ${NS}::label $top.cfont -text [mc "Fonts: press to choose"]
10743 grid $top.cfont - -sticky w -pady 10
10744 mkfontdisp mainfont $top [mc "Main font"]
10745 mkfontdisp textfont $top [mc "Diff display font"]
10746 mkfontdisp uifont $top [mc "User interface font"]
10747
10748 if {!$use_ttk} {
10749 foreach w {maxpctl maxwidthl showlocal autoselect tabstopl ntag
10750 ldiff lattr extdifff.l extdifff.b bgbut fgbut
10751 diffoldbut diffnewbut hunksepbut markbgbut selbgbut
10752 want_ttk ttk_note} {
10753 $top.$w configure -font optionfont
10754 }
10755 }
10756
10757 ${NS}::frame $top.buts
10758 ${NS}::button $top.buts.ok -text [mc "OK"] -command prefsok -default active
10759 ${NS}::button $top.buts.can -text [mc "Cancel"] -command prefscan -default normal
10760 bind $top <Key-Return> prefsok
10761 bind $top <Key-Escape> prefscan
10762 grid $top.buts.ok $top.buts.can
10763 grid columnconfigure $top.buts 0 -weight 1 -uniform a
10764 grid columnconfigure $top.buts 1 -weight 1 -uniform a
10765 grid $top.buts - - -pady 10 -sticky ew
10766 grid columnconfigure $top 2 -weight 1
10767 bind $top <Visibility> "focus $top.buts.ok"
10768}
10769
10770proc choose_extdiff {} {
10771 global extdifftool
10772
10773 set prog [tk_getOpenFile -title [mc "External diff tool"] -multiple false]
10774 if {$prog ne {}} {
10775 set extdifftool $prog
10776 }
10777}
10778
10779proc choosecolor {v vi w x cmd} {
10780 global $v
10781
10782 set c [tk_chooseColor -initialcolor [lindex [set $v] $vi] \
10783 -title [mc "Gitk: choose color for %s" $x]]
10784 if {$c eq {}} return
10785 $w conf -background $c
10786 lset $v $vi $c
10787 eval $cmd $c
10788}
10789
10790proc setselbg {c} {
10791 global bglist cflist
10792 foreach w $bglist {
10793 $w configure -selectbackground $c
10794 }
10795 $cflist tag configure highlight \
10796 -background [$cflist cget -selectbackground]
10797 allcanvs itemconf secsel -fill $c
10798}
10799
10800proc setbg {c} {
10801 global bglist
10802
10803 foreach w $bglist {
10804 $w conf -background $c
10805 }
10806}
10807
10808proc setfg {c} {
10809 global fglist canv
10810
10811 foreach w $fglist {
10812 $w conf -foreground $c
10813 }
10814 allcanvs itemconf text -fill $c
10815 $canv itemconf circle -outline $c
10816 $canv itemconf markid -outline $c
10817}
10818
10819proc prefscan {} {
10820 global oldprefs prefstop
10821
10822 foreach v {maxwidth maxgraphpct showneartags showlocalchanges \
10823 limitdiffs tabstop perfile_attrs hideremotes want_ttk} {
10824 global $v
10825 set $v $oldprefs($v)
10826 }
10827 catch {destroy $prefstop}
10828 unset prefstop
10829 fontcan
10830}
10831
10832proc prefsok {} {
10833 global maxwidth maxgraphpct
10834 global oldprefs prefstop showneartags showlocalchanges
10835 global fontpref mainfont textfont uifont
10836 global limitdiffs treediffs perfile_attrs
10837 global hideremotes
10838
10839 catch {destroy $prefstop}
10840 unset prefstop
10841 fontcan
10842 set fontchanged 0
10843 if {$mainfont ne $fontpref(mainfont)} {
10844 set mainfont $fontpref(mainfont)
10845 parsefont mainfont $mainfont
10846 eval font configure mainfont [fontflags mainfont]
10847 eval font configure mainfontbold [fontflags mainfont 1]
10848 setcoords
10849 set fontchanged 1
10850 }
10851 if {$textfont ne $fontpref(textfont)} {
10852 set textfont $fontpref(textfont)
10853 parsefont textfont $textfont
10854 eval font configure textfont [fontflags textfont]
10855 eval font configure textfontbold [fontflags textfont 1]
10856 }
10857 if {$uifont ne $fontpref(uifont)} {
10858 set uifont $fontpref(uifont)
10859 parsefont uifont $uifont
10860 eval font configure uifont [fontflags uifont]
10861 }
10862 settabs
10863 if {$showlocalchanges != $oldprefs(showlocalchanges)} {
10864 if {$showlocalchanges} {
10865 doshowlocalchanges
10866 } else {
10867 dohidelocalchanges
10868 }
10869 }
10870 if {$limitdiffs != $oldprefs(limitdiffs) ||
10871 ($perfile_attrs && !$oldprefs(perfile_attrs))} {
10872 # treediffs elements are limited by path;
10873 # won't have encodings cached if perfile_attrs was just turned on
10874 catch {unset treediffs}
10875 }
10876 if {$fontchanged || $maxwidth != $oldprefs(maxwidth)
10877 || $maxgraphpct != $oldprefs(maxgraphpct)} {
10878 redisplay
10879 } elseif {$showneartags != $oldprefs(showneartags) ||
10880 $limitdiffs != $oldprefs(limitdiffs)} {
10881 reselectline
10882 }
10883 if {$hideremotes != $oldprefs(hideremotes)} {
10884 rereadrefs
10885 }
10886}
10887
10888proc formatdate {d} {
10889 global datetimeformat
10890 if {$d ne {}} {
10891 set d [clock format $d -format $datetimeformat]
10892 }
10893 return $d
10894}
10895
10896# This list of encoding names and aliases is distilled from
10897# http://www.iana.org/assignments/character-sets.
10898# Not all of them are supported by Tcl.
10899set encoding_aliases {
10900 { ANSI_X3.4-1968 iso-ir-6 ANSI_X3.4-1986 ISO_646.irv:1991 ASCII
10901 ISO646-US US-ASCII us IBM367 cp367 csASCII }
10902 { ISO-10646-UTF-1 csISO10646UTF1 }
10903 { ISO_646.basic:1983 ref csISO646basic1983 }
10904 { INVARIANT csINVARIANT }
10905 { ISO_646.irv:1983 iso-ir-2 irv csISO2IntlRefVersion }
10906 { BS_4730 iso-ir-4 ISO646-GB gb uk csISO4UnitedKingdom }
10907 { NATS-SEFI iso-ir-8-1 csNATSSEFI }
10908 { NATS-SEFI-ADD iso-ir-8-2 csNATSSEFIADD }
10909 { NATS-DANO iso-ir-9-1 csNATSDANO }
10910 { NATS-DANO-ADD iso-ir-9-2 csNATSDANOADD }
10911 { SEN_850200_B iso-ir-10 FI ISO646-FI ISO646-SE se csISO10Swedish }
10912 { SEN_850200_C iso-ir-11 ISO646-SE2 se2 csISO11SwedishForNames }
10913 { KS_C_5601-1987 iso-ir-149 KS_C_5601-1989 KSC_5601 korean csKSC56011987 }
10914 { ISO-2022-KR csISO2022KR }
10915 { EUC-KR csEUCKR }
10916 { ISO-2022-JP csISO2022JP }
10917 { ISO-2022-JP-2 csISO2022JP2 }
10918 { JIS_C6220-1969-jp JIS_C6220-1969 iso-ir-13 katakana x0201-7
10919 csISO13JISC6220jp }
10920 { JIS_C6220-1969-ro iso-ir-14 jp ISO646-JP csISO14JISC6220ro }
10921 { IT iso-ir-15 ISO646-IT csISO15Italian }
10922 { PT iso-ir-16 ISO646-PT csISO16Portuguese }
10923 { ES iso-ir-17 ISO646-ES csISO17Spanish }
10924 { greek7-old iso-ir-18 csISO18Greek7Old }
10925 { latin-greek iso-ir-19 csISO19LatinGreek }
10926 { DIN_66003 iso-ir-21 de ISO646-DE csISO21German }
10927 { NF_Z_62-010_(1973) iso-ir-25 ISO646-FR1 csISO25French }
10928 { Latin-greek-1 iso-ir-27 csISO27LatinGreek1 }
10929 { ISO_5427 iso-ir-37 csISO5427Cyrillic }
10930 { JIS_C6226-1978 iso-ir-42 csISO42JISC62261978 }
10931 { BS_viewdata iso-ir-47 csISO47BSViewdata }
10932 { INIS iso-ir-49 csISO49INIS }
10933 { INIS-8 iso-ir-50 csISO50INIS8 }
10934 { INIS-cyrillic iso-ir-51 csISO51INISCyrillic }
10935 { ISO_5427:1981 iso-ir-54 ISO5427Cyrillic1981 }
10936 { ISO_5428:1980 iso-ir-55 csISO5428Greek }
10937 { GB_1988-80 iso-ir-57 cn ISO646-CN csISO57GB1988 }
10938 { GB_2312-80 iso-ir-58 chinese csISO58GB231280 }
10939 { NS_4551-1 iso-ir-60 ISO646-NO no csISO60DanishNorwegian
10940 csISO60Norwegian1 }
10941 { NS_4551-2 ISO646-NO2 iso-ir-61 no2 csISO61Norwegian2 }
10942 { NF_Z_62-010 iso-ir-69 ISO646-FR fr csISO69French }
10943 { videotex-suppl iso-ir-70 csISO70VideotexSupp1 }
10944 { PT2 iso-ir-84 ISO646-PT2 csISO84Portuguese2 }
10945 { ES2 iso-ir-85 ISO646-ES2 csISO85Spanish2 }
10946 { MSZ_7795.3 iso-ir-86 ISO646-HU hu csISO86Hungarian }
10947 { JIS_C6226-1983 iso-ir-87 x0208 JIS_X0208-1983 csISO87JISX0208 }
10948 { greek7 iso-ir-88 csISO88Greek7 }
10949 { ASMO_449 ISO_9036 arabic7 iso-ir-89 csISO89ASMO449 }
10950 { iso-ir-90 csISO90 }
10951 { JIS_C6229-1984-a iso-ir-91 jp-ocr-a csISO91JISC62291984a }
10952 { JIS_C6229-1984-b iso-ir-92 ISO646-JP-OCR-B jp-ocr-b
10953 csISO92JISC62991984b }
10954 { JIS_C6229-1984-b-add iso-ir-93 jp-ocr-b-add csISO93JIS62291984badd }
10955 { JIS_C6229-1984-hand iso-ir-94 jp-ocr-hand csISO94JIS62291984hand }
10956 { JIS_C6229-1984-hand-add iso-ir-95 jp-ocr-hand-add
10957 csISO95JIS62291984handadd }
10958 { JIS_C6229-1984-kana iso-ir-96 csISO96JISC62291984kana }
10959 { ISO_2033-1983 iso-ir-98 e13b csISO2033 }
10960 { ANSI_X3.110-1983 iso-ir-99 CSA_T500-1983 NAPLPS csISO99NAPLPS }
10961 { ISO_8859-1:1987 iso-ir-100 ISO_8859-1 ISO-8859-1 latin1 l1 IBM819
10962 CP819 csISOLatin1 }
10963 { ISO_8859-2:1987 iso-ir-101 ISO_8859-2 ISO-8859-2 latin2 l2 csISOLatin2 }
10964 { T.61-7bit iso-ir-102 csISO102T617bit }
10965 { T.61-8bit T.61 iso-ir-103 csISO103T618bit }
10966 { ISO_8859-3:1988 iso-ir-109 ISO_8859-3 ISO-8859-3 latin3 l3 csISOLatin3 }
10967 { ISO_8859-4:1988 iso-ir-110 ISO_8859-4 ISO-8859-4 latin4 l4 csISOLatin4 }
10968 { ECMA-cyrillic iso-ir-111 KOI8-E csISO111ECMACyrillic }
10969 { CSA_Z243.4-1985-1 iso-ir-121 ISO646-CA csa7-1 ca csISO121Canadian1 }
10970 { CSA_Z243.4-1985-2 iso-ir-122 ISO646-CA2 csa7-2 csISO122Canadian2 }
10971 { CSA_Z243.4-1985-gr iso-ir-123 csISO123CSAZ24341985gr }
10972 { ISO_8859-6:1987 iso-ir-127 ISO_8859-6 ISO-8859-6 ECMA-114 ASMO-708
10973 arabic csISOLatinArabic }
10974 { ISO_8859-6-E csISO88596E ISO-8859-6-E }
10975 { ISO_8859-6-I csISO88596I ISO-8859-6-I }
10976 { ISO_8859-7:1987 iso-ir-126 ISO_8859-7 ISO-8859-7 ELOT_928 ECMA-118
10977 greek greek8 csISOLatinGreek }
10978 { T.101-G2 iso-ir-128 csISO128T101G2 }
10979 { ISO_8859-8:1988 iso-ir-138 ISO_8859-8 ISO-8859-8 hebrew
10980 csISOLatinHebrew }
10981 { ISO_8859-8-E csISO88598E ISO-8859-8-E }
10982 { ISO_8859-8-I csISO88598I ISO-8859-8-I }
10983 { CSN_369103 iso-ir-139 csISO139CSN369103 }
10984 { JUS_I.B1.002 iso-ir-141 ISO646-YU js yu csISO141JUSIB1002 }
10985 { ISO_6937-2-add iso-ir-142 csISOTextComm }
10986 { IEC_P27-1 iso-ir-143 csISO143IECP271 }
10987 { ISO_8859-5:1988 iso-ir-144 ISO_8859-5 ISO-8859-5 cyrillic
10988 csISOLatinCyrillic }
10989 { JUS_I.B1.003-serb iso-ir-146 serbian csISO146Serbian }
10990 { JUS_I.B1.003-mac macedonian iso-ir-147 csISO147Macedonian }
10991 { ISO_8859-9:1989 iso-ir-148 ISO_8859-9 ISO-8859-9 latin5 l5 csISOLatin5 }
10992 { greek-ccitt iso-ir-150 csISO150 csISO150GreekCCITT }
10993 { NC_NC00-10:81 cuba iso-ir-151 ISO646-CU csISO151Cuba }
10994 { ISO_6937-2-25 iso-ir-152 csISO6937Add }
10995 { GOST_19768-74 ST_SEV_358-88 iso-ir-153 csISO153GOST1976874 }
10996 { ISO_8859-supp iso-ir-154 latin1-2-5 csISO8859Supp }
10997 { ISO_10367-box iso-ir-155 csISO10367Box }
10998 { ISO-8859-10 iso-ir-157 l6 ISO_8859-10:1992 csISOLatin6 latin6 }
10999 { latin-lap lap iso-ir-158 csISO158Lap }
11000 { JIS_X0212-1990 x0212 iso-ir-159 csISO159JISX02121990 }
11001 { DS_2089 DS2089 ISO646-DK dk csISO646Danish }
11002 { us-dk csUSDK }
11003 { dk-us csDKUS }
11004 { JIS_X0201 X0201 csHalfWidthKatakana }
11005 { KSC5636 ISO646-KR csKSC5636 }
11006 { ISO-10646-UCS-2 csUnicode }
11007 { ISO-10646-UCS-4 csUCS4 }
11008 { DEC-MCS dec csDECMCS }
11009 { hp-roman8 roman8 r8 csHPRoman8 }
11010 { macintosh mac csMacintosh }
11011 { IBM037 cp037 ebcdic-cp-us ebcdic-cp-ca ebcdic-cp-wt ebcdic-cp-nl
11012 csIBM037 }
11013 { IBM038 EBCDIC-INT cp038 csIBM038 }
11014 { IBM273 CP273 csIBM273 }
11015 { IBM274 EBCDIC-BE CP274 csIBM274 }
11016 { IBM275 EBCDIC-BR cp275 csIBM275 }
11017 { IBM277 EBCDIC-CP-DK EBCDIC-CP-NO csIBM277 }
11018 { IBM278 CP278 ebcdic-cp-fi ebcdic-cp-se csIBM278 }
11019 { IBM280 CP280 ebcdic-cp-it csIBM280 }
11020 { IBM281 EBCDIC-JP-E cp281 csIBM281 }
11021 { IBM284 CP284 ebcdic-cp-es csIBM284 }
11022 { IBM285 CP285 ebcdic-cp-gb csIBM285 }
11023 { IBM290 cp290 EBCDIC-JP-kana csIBM290 }
11024 { IBM297 cp297 ebcdic-cp-fr csIBM297 }
11025 { IBM420 cp420 ebcdic-cp-ar1 csIBM420 }
11026 { IBM423 cp423 ebcdic-cp-gr csIBM423 }
11027 { IBM424 cp424 ebcdic-cp-he csIBM424 }
11028 { IBM437 cp437 437 csPC8CodePage437 }
11029 { IBM500 CP500 ebcdic-cp-be ebcdic-cp-ch csIBM500 }
11030 { IBM775 cp775 csPC775Baltic }
11031 { IBM850 cp850 850 csPC850Multilingual }
11032 { IBM851 cp851 851 csIBM851 }
11033 { IBM852 cp852 852 csPCp852 }
11034 { IBM855 cp855 855 csIBM855 }
11035 { IBM857 cp857 857 csIBM857 }
11036 { IBM860 cp860 860 csIBM860 }
11037 { IBM861 cp861 861 cp-is csIBM861 }
11038 { IBM862 cp862 862 csPC862LatinHebrew }
11039 { IBM863 cp863 863 csIBM863 }
11040 { IBM864 cp864 csIBM864 }
11041 { IBM865 cp865 865 csIBM865 }
11042 { IBM866 cp866 866 csIBM866 }
11043 { IBM868 CP868 cp-ar csIBM868 }
11044 { IBM869 cp869 869 cp-gr csIBM869 }
11045 { IBM870 CP870 ebcdic-cp-roece ebcdic-cp-yu csIBM870 }
11046 { IBM871 CP871 ebcdic-cp-is csIBM871 }
11047 { IBM880 cp880 EBCDIC-Cyrillic csIBM880 }
11048 { IBM891 cp891 csIBM891 }
11049 { IBM903 cp903 csIBM903 }
11050 { IBM904 cp904 904 csIBBM904 }
11051 { IBM905 CP905 ebcdic-cp-tr csIBM905 }
11052 { IBM918 CP918 ebcdic-cp-ar2 csIBM918 }
11053 { IBM1026 CP1026 csIBM1026 }
11054 { EBCDIC-AT-DE csIBMEBCDICATDE }
11055 { EBCDIC-AT-DE-A csEBCDICATDEA }
11056 { EBCDIC-CA-FR csEBCDICCAFR }
11057 { EBCDIC-DK-NO csEBCDICDKNO }
11058 { EBCDIC-DK-NO-A csEBCDICDKNOA }
11059 { EBCDIC-FI-SE csEBCDICFISE }
11060 { EBCDIC-FI-SE-A csEBCDICFISEA }
11061 { EBCDIC-FR csEBCDICFR }
11062 { EBCDIC-IT csEBCDICIT }
11063 { EBCDIC-PT csEBCDICPT }
11064 { EBCDIC-ES csEBCDICES }
11065 { EBCDIC-ES-A csEBCDICESA }
11066 { EBCDIC-ES-S csEBCDICESS }
11067 { EBCDIC-UK csEBCDICUK }
11068 { EBCDIC-US csEBCDICUS }
11069 { UNKNOWN-8BIT csUnknown8BiT }
11070 { MNEMONIC csMnemonic }
11071 { MNEM csMnem }
11072 { VISCII csVISCII }
11073 { VIQR csVIQR }
11074 { KOI8-R csKOI8R }
11075 { IBM00858 CCSID00858 CP00858 PC-Multilingual-850+euro }
11076 { IBM00924 CCSID00924 CP00924 ebcdic-Latin9--euro }
11077 { IBM01140 CCSID01140 CP01140 ebcdic-us-37+euro }
11078 { IBM01141 CCSID01141 CP01141 ebcdic-de-273+euro }
11079 { IBM01142 CCSID01142 CP01142 ebcdic-dk-277+euro ebcdic-no-277+euro }
11080 { IBM01143 CCSID01143 CP01143 ebcdic-fi-278+euro ebcdic-se-278+euro }
11081 { IBM01144 CCSID01144 CP01144 ebcdic-it-280+euro }
11082 { IBM01145 CCSID01145 CP01145 ebcdic-es-284+euro }
11083 { IBM01146 CCSID01146 CP01146 ebcdic-gb-285+euro }
11084 { IBM01147 CCSID01147 CP01147 ebcdic-fr-297+euro }
11085 { IBM01148 CCSID01148 CP01148 ebcdic-international-500+euro }
11086 { IBM01149 CCSID01149 CP01149 ebcdic-is-871+euro }
11087 { IBM1047 IBM-1047 }
11088 { PTCP154 csPTCP154 PT154 CP154 Cyrillic-Asian }
11089 { Amiga-1251 Ami1251 Amiga1251 Ami-1251 }
11090 { UNICODE-1-1 csUnicode11 }
11091 { CESU-8 csCESU-8 }
11092 { BOCU-1 csBOCU-1 }
11093 { UNICODE-1-1-UTF-7 csUnicode11UTF7 }
11094 { ISO-8859-14 iso-ir-199 ISO_8859-14:1998 ISO_8859-14 latin8 iso-celtic
11095 l8 }
11096 { ISO-8859-15 ISO_8859-15 Latin-9 }
11097 { ISO-8859-16 iso-ir-226 ISO_8859-16:2001 ISO_8859-16 latin10 l10 }
11098 { GBK CP936 MS936 windows-936 }
11099 { JIS_Encoding csJISEncoding }
11100 { Shift_JIS MS_Kanji csShiftJIS ShiftJIS Shift-JIS }
11101 { Extended_UNIX_Code_Packed_Format_for_Japanese csEUCPkdFmtJapanese
11102 EUC-JP }
11103 { Extended_UNIX_Code_Fixed_Width_for_Japanese csEUCFixWidJapanese }
11104 { ISO-10646-UCS-Basic csUnicodeASCII }
11105 { ISO-10646-Unicode-Latin1 csUnicodeLatin1 ISO-10646 }
11106 { ISO-Unicode-IBM-1261 csUnicodeIBM1261 }
11107 { ISO-Unicode-IBM-1268 csUnicodeIBM1268 }
11108 { ISO-Unicode-IBM-1276 csUnicodeIBM1276 }
11109 { ISO-Unicode-IBM-1264 csUnicodeIBM1264 }
11110 { ISO-Unicode-IBM-1265 csUnicodeIBM1265 }
11111 { ISO-8859-1-Windows-3.0-Latin-1 csWindows30Latin1 }
11112 { ISO-8859-1-Windows-3.1-Latin-1 csWindows31Latin1 }
11113 { ISO-8859-2-Windows-Latin-2 csWindows31Latin2 }
11114 { ISO-8859-9-Windows-Latin-5 csWindows31Latin5 }
11115 { Adobe-Standard-Encoding csAdobeStandardEncoding }
11116 { Ventura-US csVenturaUS }
11117 { Ventura-International csVenturaInternational }
11118 { PC8-Danish-Norwegian csPC8DanishNorwegian }
11119 { PC8-Turkish csPC8Turkish }
11120 { IBM-Symbols csIBMSymbols }
11121 { IBM-Thai csIBMThai }
11122 { HP-Legal csHPLegal }
11123 { HP-Pi-font csHPPiFont }
11124 { HP-Math8 csHPMath8 }
11125 { Adobe-Symbol-Encoding csHPPSMath }
11126 { HP-DeskTop csHPDesktop }
11127 { Ventura-Math csVenturaMath }
11128 { Microsoft-Publishing csMicrosoftPublishing }
11129 { Windows-31J csWindows31J }
11130 { GB2312 csGB2312 }
11131 { Big5 csBig5 }
11132}
11133
11134proc tcl_encoding {enc} {
11135 global encoding_aliases tcl_encoding_cache
11136 if {[info exists tcl_encoding_cache($enc)]} {
11137 return $tcl_encoding_cache($enc)
11138 }
11139 set names [encoding names]
11140 set lcnames [string tolower $names]
11141 set enc [string tolower $enc]
11142 set i [lsearch -exact $lcnames $enc]
11143 if {$i < 0} {
11144 # look for "isonnn" instead of "iso-nnn" or "iso_nnn"
11145 if {[regsub {^(iso|cp|ibm|jis)[-_]} $enc {\1} encx]} {
11146 set i [lsearch -exact $lcnames $encx]
11147 }
11148 }
11149 if {$i < 0} {
11150 foreach l $encoding_aliases {
11151 set ll [string tolower $l]
11152 if {[lsearch -exact $ll $enc] < 0} continue
11153 # look through the aliases for one that tcl knows about
11154 foreach e $ll {
11155 set i [lsearch -exact $lcnames $e]
11156 if {$i < 0} {
11157 if {[regsub {^(iso|cp|ibm|jis)[-_]} $e {\1} ex]} {
11158 set i [lsearch -exact $lcnames $ex]
11159 }
11160 }
11161 if {$i >= 0} break
11162 }
11163 break
11164 }
11165 }
11166 set tclenc {}
11167 if {$i >= 0} {
11168 set tclenc [lindex $names $i]
11169 }
11170 set tcl_encoding_cache($enc) $tclenc
11171 return $tclenc
11172}
11173
11174proc gitattr {path attr default} {
11175 global path_attr_cache
11176 if {[info exists path_attr_cache($attr,$path)]} {
11177 set r $path_attr_cache($attr,$path)
11178 } else {
11179 set r "unspecified"
11180 if {![catch {set line [exec git check-attr $attr -- $path]}]} {
11181 regexp "(.*): $attr: (.*)" $line m f r
11182 }
11183 set path_attr_cache($attr,$path) $r
11184 }
11185 if {$r eq "unspecified"} {
11186 return $default
11187 }
11188 return $r
11189}
11190
11191proc cache_gitattr {attr pathlist} {
11192 global path_attr_cache
11193 set newlist {}
11194 foreach path $pathlist {
11195 if {![info exists path_attr_cache($attr,$path)]} {
11196 lappend newlist $path
11197 }
11198 }
11199 set lim 1000
11200 if {[tk windowingsystem] == "win32"} {
11201 # windows has a 32k limit on the arguments to a command...
11202 set lim 30
11203 }
11204 while {$newlist ne {}} {
11205 set head [lrange $newlist 0 [expr {$lim - 1}]]
11206 set newlist [lrange $newlist $lim end]
11207 if {![catch {set rlist [eval exec git check-attr $attr -- $head]}]} {
11208 foreach row [split $rlist "\n"] {
11209 if {[regexp "(.*): $attr: (.*)" $row m path value]} {
11210 if {[string index $path 0] eq "\""} {
11211 set path [encoding convertfrom [lindex $path 0]]
11212 }
11213 set path_attr_cache($attr,$path) $value
11214 }
11215 }
11216 }
11217 }
11218}
11219
11220proc get_path_encoding {path} {
11221 global gui_encoding perfile_attrs
11222 set tcl_enc $gui_encoding
11223 if {$path ne {} && $perfile_attrs} {
11224 set enc2 [tcl_encoding [gitattr $path encoding $tcl_enc]]
11225 if {$enc2 ne {}} {
11226 set tcl_enc $enc2
11227 }
11228 }
11229 return $tcl_enc
11230}
11231
11232# First check that Tcl/Tk is recent enough
11233if {[catch {package require Tk 8.4} err]} {
11234 show_error {} . [mc "Sorry, gitk cannot run with this version of Tcl/Tk.\n\
11235 Gitk requires at least Tcl/Tk 8.4."]
11236 exit 1
11237}
11238
11239# defaults...
11240set wrcomcmd "git diff-tree --stdin -p --pretty"
11241
11242set gitencoding {}
11243catch {
11244 set gitencoding [exec git config --get i18n.commitencoding]
11245}
11246catch {
11247 set gitencoding [exec git config --get i18n.logoutputencoding]
11248}
11249if {$gitencoding == ""} {
11250 set gitencoding "utf-8"
11251}
11252set tclencoding [tcl_encoding $gitencoding]
11253if {$tclencoding == {}} {
11254 puts stderr "Warning: encoding $gitencoding is not supported by Tcl/Tk"
11255}
11256
11257set gui_encoding [encoding system]
11258catch {
11259 set enc [exec git config --get gui.encoding]
11260 if {$enc ne {}} {
11261 set tclenc [tcl_encoding $enc]
11262 if {$tclenc ne {}} {
11263 set gui_encoding $tclenc
11264 } else {
11265 puts stderr "Warning: encoding $enc is not supported by Tcl/Tk"
11266 }
11267 }
11268}
11269
11270if {[tk windowingsystem] eq "aqua"} {
11271 set mainfont {{Lucida Grande} 9}
11272 set textfont {Monaco 9}
11273 set uifont {{Lucida Grande} 9 bold}
11274} else {
11275 set mainfont {Helvetica 9}
11276 set textfont {Courier 9}
11277 set uifont {Helvetica 9 bold}
11278}
11279set tabstop 8
11280set findmergefiles 0
11281set maxgraphpct 50
11282set maxwidth 16
11283set revlistorder 0
11284set fastdate 0
11285set uparrowlen 5
11286set downarrowlen 5
11287set mingaplen 100
11288set cmitmode "patch"
11289set wrapcomment "none"
11290set showneartags 1
11291set hideremotes 0
11292set maxrefs 20
11293set maxlinelen 200
11294set showlocalchanges 1
11295set limitdiffs 1
11296set datetimeformat "%Y-%m-%d %H:%M:%S"
11297set autoselect 1
11298set perfile_attrs 0
11299set want_ttk 1
11300
11301if {[tk windowingsystem] eq "aqua"} {
11302 set extdifftool "opendiff"
11303} else {
11304 set extdifftool "meld"
11305}
11306
11307set colors {green red blue magenta darkgrey brown orange}
11308set bgcolor white
11309set fgcolor black
11310set diffcolors {red "#00a000" blue}
11311set diffcontext 3
11312set ignorespace 0
11313set selectbgcolor gray85
11314set markbgcolor "#e0e0ff"
11315
11316set circlecolors {white blue gray blue blue}
11317
11318# button for popping up context menus
11319if {[tk windowingsystem] eq "aqua"} {
11320 set ctxbut <Button-2>
11321} else {
11322 set ctxbut <Button-3>
11323}
11324
11325## For msgcat loading, first locate the installation location.
11326if { [info exists ::env(GITK_MSGSDIR)] } {
11327 ## Msgsdir was manually set in the environment.
11328 set gitk_msgsdir $::env(GITK_MSGSDIR)
11329} else {
11330 ## Let's guess the prefix from argv0.
11331 set gitk_prefix [file dirname [file dirname [file normalize $argv0]]]
11332 set gitk_libdir [file join $gitk_prefix share gitk lib]
11333 set gitk_msgsdir [file join $gitk_libdir msgs]
11334 unset gitk_prefix
11335}
11336
11337## Internationalization (i18n) through msgcat and gettext. See
11338## http://www.gnu.org/software/gettext/manual/html_node/Tcl.html
11339package require msgcat
11340namespace import ::msgcat::mc
11341## And eventually load the actual message catalog
11342::msgcat::mcload $gitk_msgsdir
11343
11344catch {source ~/.gitk}
11345
11346font create optionfont -family sans-serif -size -12
11347
11348parsefont mainfont $mainfont
11349eval font create mainfont [fontflags mainfont]
11350eval font create mainfontbold [fontflags mainfont 1]
11351
11352parsefont textfont $textfont
11353eval font create textfont [fontflags textfont]
11354eval font create textfontbold [fontflags textfont 1]
11355
11356parsefont uifont $uifont
11357eval font create uifont [fontflags uifont]
11358
11359setoptions
11360
11361# check that we can find a .git directory somewhere...
11362if {[catch {set gitdir [gitdir]}]} {
11363 show_error {} . [mc "Cannot find a git repository here."]
11364 exit 1
11365}
11366if {![file isdirectory $gitdir]} {
11367 show_error {} . [mc "Cannot find the git directory \"%s\"." $gitdir]
11368 exit 1
11369}
11370
11371set selecthead {}
11372set selectheadid {}
11373
11374set revtreeargs {}
11375set cmdline_files {}
11376set i 0
11377set revtreeargscmd {}
11378foreach arg $argv {
11379 switch -glob -- $arg {
11380 "" { }
11381 "--" {
11382 set cmdline_files [lrange $argv [expr {$i + 1}] end]
11383 break
11384 }
11385 "--select-commit=*" {
11386 set selecthead [string range $arg 16 end]
11387 }
11388 "--argscmd=*" {
11389 set revtreeargscmd [string range $arg 10 end]
11390 }
11391 default {
11392 lappend revtreeargs $arg
11393 }
11394 }
11395 incr i
11396}
11397
11398if {$selecthead eq "HEAD"} {
11399 set selecthead {}
11400}
11401
11402if {$i >= [llength $argv] && $revtreeargs ne {}} {
11403 # no -- on command line, but some arguments (other than --argscmd)
11404 if {[catch {
11405 set f [eval exec git rev-parse --no-revs --no-flags $revtreeargs]
11406 set cmdline_files [split $f "\n"]
11407 set n [llength $cmdline_files]
11408 set revtreeargs [lrange $revtreeargs 0 end-$n]
11409 # Unfortunately git rev-parse doesn't produce an error when
11410 # something is both a revision and a filename. To be consistent
11411 # with git log and git rev-list, check revtreeargs for filenames.
11412 foreach arg $revtreeargs {
11413 if {[file exists $arg]} {
11414 show_error {} . [mc "Ambiguous argument '%s': both revision\
11415 and filename" $arg]
11416 exit 1
11417 }
11418 }
11419 } err]} {
11420 # unfortunately we get both stdout and stderr in $err,
11421 # so look for "fatal:".
11422 set i [string first "fatal:" $err]
11423 if {$i > 0} {
11424 set err [string range $err [expr {$i + 6}] end]
11425 }
11426 show_error {} . "[mc "Bad arguments to gitk:"]\n$err"
11427 exit 1
11428 }
11429}
11430
11431set nullid "0000000000000000000000000000000000000000"
11432set nullid2 "0000000000000000000000000000000000000001"
11433set nullfile "/dev/null"
11434
11435set have_tk85 [expr {[package vcompare $tk_version "8.5"] >= 0}]
11436if {![info exists have_ttk]} {
11437 set have_ttk [llength [info commands ::ttk::style]]
11438}
11439set use_ttk [expr {$have_ttk && $want_ttk}]
11440set NS [expr {$use_ttk ? "ttk" : ""}]
11441
11442set git_version [join [lrange [split [lindex [exec git version] end] .] 0 2] .]
11443
11444set runq {}
11445set history {}
11446set historyindex 0
11447set fh_serial 0
11448set nhl_names {}
11449set highlight_paths {}
11450set findpattern {}
11451set searchdirn -forwards
11452set boldids {}
11453set boldnameids {}
11454set diffelide {0 0}
11455set markingmatches 0
11456set linkentercount 0
11457set need_redisplay 0
11458set nrows_drawn 0
11459set firsttabstop 0
11460
11461set nextviewnum 1
11462set curview 0
11463set selectedview 0
11464set selectedhlview [mc "None"]
11465set highlight_related [mc "None"]
11466set highlight_files {}
11467set viewfiles(0) {}
11468set viewperm(0) 0
11469set viewargs(0) {}
11470set viewargscmd(0) {}
11471
11472set selectedline {}
11473set numcommits 0
11474set loginstance 0
11475set cmdlineok 0
11476set stopped 0
11477set stuffsaved 0
11478set patchnum 0
11479set lserial 0
11480set isworktree [expr {[exec git rev-parse --is-inside-work-tree] == "true"}]
11481setcoords
11482makewindow
11483catch {
11484 image create photo gitlogo -width 16 -height 16
11485
11486 image create photo gitlogominus -width 4 -height 2
11487 gitlogominus put #C00000 -to 0 0 4 2
11488 gitlogo copy gitlogominus -to 1 5
11489 gitlogo copy gitlogominus -to 6 5
11490 gitlogo copy gitlogominus -to 11 5
11491 image delete gitlogominus
11492
11493 image create photo gitlogoplus -width 4 -height 4
11494 gitlogoplus put #008000 -to 1 0 3 4
11495 gitlogoplus put #008000 -to 0 1 4 3
11496 gitlogo copy gitlogoplus -to 1 9
11497 gitlogo copy gitlogoplus -to 6 9
11498 gitlogo copy gitlogoplus -to 11 9
11499 image delete gitlogoplus
11500
11501 image create photo gitlogo32 -width 32 -height 32
11502 gitlogo32 copy gitlogo -zoom 2 2
11503
11504 wm iconphoto . -default gitlogo gitlogo32
11505}
11506# wait for the window to become visible
11507tkwait visibility .
11508wm title . "[file tail $argv0]: [file tail [pwd]]"
11509update
11510readrefs
11511
11512if {$cmdline_files ne {} || $revtreeargs ne {} || $revtreeargscmd ne {}} {
11513 # create a view for the files/dirs specified on the command line
11514 set curview 1
11515 set selectedview 1
11516 set nextviewnum 2
11517 set viewname(1) [mc "Command line"]
11518 set viewfiles(1) $cmdline_files
11519 set viewargs(1) $revtreeargs
11520 set viewargscmd(1) $revtreeargscmd
11521 set viewperm(1) 0
11522 set vdatemode(1) 0
11523 addviewmenu 1
11524 .bar.view entryconf [mca "Edit view..."] -state normal
11525 .bar.view entryconf [mca "Delete view"] -state normal
11526}
11527
11528if {[info exists permviews]} {
11529 foreach v $permviews {
11530 set n $nextviewnum
11531 incr nextviewnum
11532 set viewname($n) [lindex $v 0]
11533 set viewfiles($n) [lindex $v 1]
11534 set viewargs($n) [lindex $v 2]
11535 set viewargscmd($n) [lindex $v 3]
11536 set viewperm($n) 1
11537 addviewmenu $n
11538 }
11539}
11540
11541if {[tk windowingsystem] eq "win32"} {
11542 focus -force .
11543}
11544
11545getcommits {}