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