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