Raw
1 #!/bin/sh
2 # Tcl ignores the next line -*- tcl -*- \
3 exec wish "$0" -- "$@"
4
5 # Copyright © 2005-2016 Paul Mackerras. All rights reserved.
6 # This program is free software; it may be used, copied, modified
7 # and distributed under the terms of the GNU General Public Licence,
8 # either version 2, or (at your option) any later version.
9
10 if {[catch {package require Tcl 8.6-} err]} {
11 catch {wm withdraw .}
12 tk_messageBox \
13 -icon error \
14 -type ok \
15 -title "gitk: fatal error" \
16 -message $err
17 exit 1
18 }
19
20 set MIN_GIT_VERSION 2.20
21 regexp {^git version ([\d.]*\d)} [exec git version] _ git_version
22 if {[package vcompare $git_version $MIN_GIT_VERSION] < 0} {
23 set message "The git executable found is too old.
24 The minimum required version is $MIN_GIT_VERSION.0.
25 The version of git found is $git_version."
26
27 catch {wm withdraw .}
28 tk_messageBox \
29 -icon error \
30 -type ok \
31 -title "gitk: fatal error" \
32 -message $message
33 exit 1
34 }
35
36 ######################################################################
37 ## Enable Tcl8 profile in Tcl9, allowing consumption of data that has
38 ## bytes not conforming to the assumed encoding profile.
39
40 if {[package vcompare $::tcl_version 9.0] >= 0} {
41 rename open _strict_open
42 proc open args {
43 set f [_strict_open {*}$args]
44 chan configure $f -profile tcl8
45 return $f
46 }
47 proc convertfrom args {
48 return [encoding convertfrom -profile tcl8 {*}$args]
49 }
50 } else {
51 proc convertfrom args {
52 return [encoding convertfrom {*}$args]
53 }
54 }
55
56 ######################################################################
57 ##
58 ## Enabling platform-specific code paths
59
60 proc is_Windows {} {
61 if {$::tcl_platform(platform) eq {windows}} {
62 return 1
63 }
64 return 0
65 }
66
67 ######################################################################
68 ##
69 ## PATH lookup
70
71 if {[is_Windows]} {
72 set _search_path {}
73 proc _which {what args} {
74 global env _search_path
75
76 if {$_search_path eq {}} {
77 set gitguidir [file dirname [info script]]
78 regsub -all ";" $gitguidir "\\;" gitguidir
79 set env(PATH) "$gitguidir;$env(PATH)"
80 set _search_path [split $env(PATH) {;}]
81 # Skip empty `PATH` elements
82 set _search_path [lsearch -all -inline -not -exact \
83 $_search_path ""]
84 }
85
86 if {[lsearch -exact $args -script] >= 0} {
87 set suffix {}
88 } else {
89 set suffix .exe
90 }
91
92 foreach p $_search_path {
93 set p [file join $p $what$suffix]
94 if {[file exists $p]} {
95 return [file normalize $p]
96 }
97 }
98 return {}
99 }
100
101 proc sanitize_command_line {command_line from_index} {
102 set i $from_index
103 while {$i < [llength $command_line]} {
104 set cmd [lindex $command_line $i]
105 if {[llength [file split $cmd]] < 2} {
106 set fullpath [_which $cmd]
107 if {$fullpath eq ""} {
108 throw {NOT-FOUND} "$cmd not found in PATH"
109 }
110 lset command_line $i $fullpath
111 }
112
113 # handle piped commands, e.g. `exec A | B`
114 for {incr i} {$i < [llength $command_line]} {incr i} {
115 if {[lindex $command_line $i] eq "|"} {
116 incr i
117 break
118 }
119 }
120 }
121 return $command_line
122 }
123
124 # Override `exec` to avoid unsafe PATH lookup
125
126 rename exec real_exec
127
128 proc exec {args} {
129 # skip options
130 for {set i 0} {$i < [llength $args]} {incr i} {
131 set arg [lindex $args $i]
132 if {$arg eq "--"} {
133 incr i
134 break
135 }
136 if {[string range $arg 0 0] ne "-"} {
137 break
138 }
139 }
140 set args [sanitize_command_line $args $i]
141 uplevel 1 real_exec $args
142 }
143
144 # Override `open` to avoid unsafe PATH lookup
145
146 rename open real_open
147
148 proc open {args} {
149 set arg0 [lindex $args 0]
150 if {[string range $arg0 0 0] eq "|"} {
151 set command_line [string trim [string range $arg0 1 end]]
152 lset args 0 "| [sanitize_command_line $command_line 0]"
153 }
154 uplevel 1 real_open $args
155 }
156 }
157
158 # End of safe PATH lookup stuff
159
160 # Wrap exec/open to sanitize arguments
161
162 # unsafe arguments begin with redirections or the pipe or background operators
163 proc is_arg_unsafe {arg} {
164 regexp {^([<|>&]|2>)} $arg
165 }
166
167 proc make_arg_safe {arg} {
168 if {[is_arg_unsafe $arg]} {
169 set arg [file join . $arg]
170 }
171 return $arg
172 }
173
174 proc make_arglist_safe {arglist} {
175 set res {}
176 foreach arg $arglist {
177 lappend res [make_arg_safe $arg]
178 }
179 return $res
180 }
181
182 # executes one command
183 # no redirections or pipelines are possible
184 # cmd is a list that specifies the command and its arguments
185 # calls `exec` and returns its value
186 proc safe_exec {cmd} {
187 eval exec [make_arglist_safe $cmd]
188 }
189
190 # executes one command with redirections
191 # no pipelines are possible
192 # cmd is a list that specifies the command and its arguments
193 # redir is a list that specifies redirections (output, background, constant(!) commands)
194 # calls `exec` and returns its value
195 proc safe_exec_redirect {cmd redir} {
196 eval exec [make_arglist_safe $cmd] $redir
197 }
198
199 proc safe_open_file {filename flags} {
200 # a file name starting with "|" would attempt to run a process
201 # but such a file name must be treated as a relative path
202 # hide the "|" behind "./"
203 if {[string index $filename 0] eq "|"} {
204 set filename [file join . $filename]
205 }
206 open $filename $flags
207 }
208
209 # opens a command pipeline for reading
210 # cmd is a list that specifies the command and its arguments
211 # calls `open` and returns the file id
212 proc safe_open_command {cmd} {
213 open |[make_arglist_safe $cmd] r
214 }
215
216 # opens a command pipeline for reading and writing
217 # cmd is a list that specifies the command and its arguments
218 # calls `open` and returns the file id
219 proc safe_open_command_rw {cmd} {
220 open |[make_arglist_safe $cmd] r+
221 }
222
223 # opens a command pipeline for reading with redirections
224 # cmd is a list that specifies the command and its arguments
225 # redir is a list that specifies redirections
226 # calls `open` and returns the file id
227 proc safe_open_command_redirect {cmd redir} {
228 set cmd [make_arglist_safe $cmd]
229 open |[concat $cmd $redir] r
230 }
231
232 # opens a pipeline with several commands for reading
233 # cmds is a list of lists, each of which specifies a command and its arguments
234 # calls `open` and returns the file id
235 proc safe_open_pipeline {cmds} {
236 set cmd {}
237 foreach subcmd $cmds {
238 set cmd [concat $cmd | [make_arglist_safe $subcmd]]
239 }
240 open $cmd r
241 }
242
243 # End exec/open wrappers
244
245 proc hasworktree {} {
246 return [expr {[exec git rev-parse --is-bare-repository] == "false" &&
247 [exec git rev-parse --is-inside-git-dir] == "false"}]
248 }
249
250 proc reponame {} {
251 global gitdir
252 set n [file normalize $gitdir]
253 if {[string match "*/.git" $n]} {
254 set n [string range $n 0 end-5]
255 }
256 return [file tail $n]
257 }
258
259 proc gitworktree {} {
260 variable _gitworktree
261 if {[info exists _gitworktree]} {
262 return $_gitworktree
263 }
264 # v1.7.0 introduced --show-toplevel to return the canonical work-tree
265 if {[catch {set _gitworktree [exec git rev-parse --show-toplevel]}]} {
266 # try to set work tree from environment, core.worktree or use
267 # cdup to obtain a relative path to the top of the worktree. If
268 # run from the top, the ./ prefix ensures normalize expands pwd.
269 if {[catch { set _gitworktree $env(GIT_WORK_TREE) }]} {
270 if {[catch {set _gitworktree [exec git config --get core.worktree]}]} {
271 set _gitworktree [file normalize ./[exec git rev-parse --show-cdup]]
272 }
273 }
274 }
275 return $_gitworktree
276 }
277
278 # A simple scheduler for compute-intensive stuff.
279 # The aim is to make sure that event handlers for GUI actions can
280 # run at least every 50-100 ms. Unfortunately fileevent handlers are
281 # run before X event handlers, so reading from a fast source can
282 # make the GUI completely unresponsive.
283 proc run args {
284 global isonrunq runq currunq
285
286 set script $args
287 if {[info exists isonrunq($script)]} return
288 if {$runq eq {} && ![info exists currunq]} {
289 after idle dorunq
290 }
291 lappend runq [list {} $script]
292 set isonrunq($script) 1
293 }
294
295 proc filerun {fd script} {
296 fileevent $fd readable [list filereadable $fd $script]
297 }
298
299 proc filereadable {fd script} {
300 global runq currunq
301
302 fileevent $fd readable {}
303 if {$runq eq {} && ![info exists currunq]} {
304 after idle dorunq
305 }
306 lappend runq [list $fd $script]
307 }
308
309 proc nukefile {fd} {
310 global runq
311
312 for {set i 0} {$i < [llength $runq]} {} {
313 if {[lindex $runq $i 0] eq $fd} {
314 set runq [lreplace $runq $i $i]
315 } else {
316 incr i
317 }
318 }
319 }
320
321 proc dorunq {} {
322 global isonrunq runq currunq
323
324 set tstart [clock clicks -milliseconds]
325 set t0 $tstart
326 while {[llength $runq] > 0} {
327 set fd [lindex $runq 0 0]
328 set script [lindex $runq 0 1]
329 set currunq [lindex $runq 0]
330 set runq [lrange $runq 1 end]
331 set repeat [eval $script]
332 unset currunq
333 set t1 [clock clicks -milliseconds]
334 set t [expr {$t1 - $t0}]
335 if {$repeat ne {} && $repeat} {
336 if {$fd eq {} || $repeat == 2} {
337 # script returns 1 if it wants to be readded
338 # file readers return 2 if they could do more straight away
339 lappend runq [list $fd $script]
340 } else {
341 fileevent $fd readable [list filereadable $fd $script]
342 }
343 } elseif {$fd eq {}} {
344 unset isonrunq($script)
345 }
346 set t0 $t1
347 if {$t1 - $tstart >= 80} break
348 }
349 if {$runq ne {}} {
350 after idle dorunq
351 }
352 }
353
354 proc reg_instance {fd} {
355 global commfd leftover loginstance
356
357 set i [incr loginstance]
358 set commfd($i) $fd
359 set leftover($i) {}
360 return $i
361 }
362
363 proc unmerged_files {files} {
364 global nr_unmerged
365
366 # find the list of unmerged files
367 set mlist {}
368 set nr_unmerged 0
369 if {[catch {
370 set fd [safe_open_command {git ls-files -u}]
371 } err]} {
372 show_error {} . "[mc "Couldn't get list of unmerged files:"] $err"
373 exit 1
374 }
375 while {[gets $fd line] >= 0} {
376 set i [string first "\t" $line]
377 if {$i < 0} continue
378 set fname [string range $line [expr {$i+1}] end]
379 if {[lsearch -exact $mlist $fname] >= 0} continue
380 incr nr_unmerged
381 if {$files eq {} || [path_filter $files $fname]} {
382 lappend mlist $fname
383 }
384 }
385 catch {close $fd}
386 return $mlist
387 }
388
389 proc parseviewargs {n arglist} {
390 global vdatemode vmergeonly vflags vdflags vrevs vfiltered vorigargs env
391 global vinlinediff
392 global worddiff
393
394 set vdatemode($n) 0
395 set vmergeonly($n) 0
396 set vinlinediff($n) 0
397 set glflags {}
398 set diffargs {}
399 set nextisval 0
400 set revargs {}
401 set origargs $arglist
402 set allknown 1
403 set filtered 0
404 set i -1
405 foreach arg $arglist {
406 incr i
407 if {$nextisval} {
408 lappend glflags $arg
409 set nextisval 0
410 continue
411 }
412 switch -glob -- $arg {
413 "-d" -
414 "--date-order" {
415 set vdatemode($n) 1
416 # remove from origargs in case we hit an unknown option
417 set origargs [lreplace $origargs $i $i]
418 incr i -1
419 }
420 "-[puabwcrRBMC]" -
421 "--no-renames" - "--full-index" - "--binary" - "--abbrev=*" -
422 "--find-copies-harder" - "-l*" - "--ext-diff" - "--no-ext-diff" -
423 "--src-prefix=*" - "--dst-prefix=*" - "--no-prefix" -
424 "-O*" - "--text" - "--full-diff" - "--ignore-space-at-eol" -
425 "--ignore-space-change" - "-U*" - "--unified=*" {
426 # These request or affect diff output, which we don't want.
427 # Some could be used to set our defaults for diff display.
428 lappend diffargs $arg
429 }
430 "--raw" - "--patch-with-raw" - "--patch-with-stat" -
431 "--name-only" - "--name-status" - "--color" -
432 "--log-size" - "--pretty=*" - "--decorate" - "--abbrev-commit" -
433 "--cc" - "-z" - "--header" - "--parents" - "--boundary" -
434 "--no-color" - "-g" - "--walk-reflogs" - "--no-walk" -
435 "--timestamp" - "relative-date" - "--date=*" - "--stdin" -
436 "--objects" - "--objects-edge" - "--reverse" {
437 # These cause our parsing of git log's output to fail, or else
438 # they're options we want to set ourselves, so ignore them.
439 }
440 "--color-words*" - "--word-diff=color" {
441 # These trigger a word diff in the console interface,
442 # so help the user by enabling our own support
443 set worddiff [mc "Color words"]
444 }
445 "--word-diff*" {
446 set worddiff [mc "Markup words"]
447 }
448 "--stat=*" - "--numstat" - "--shortstat" - "--summary" -
449 "--check" - "--exit-code" - "--quiet" - "--topo-order" -
450 "--full-history" - "--dense" - "--sparse" -
451 "--follow" - "--left-right" - "--encoding=*" {
452 # These are harmless, and some are even useful
453 lappend glflags $arg
454 }
455 "--diff-filter=*" - "--no-merges" - "--unpacked" -
456 "--max-count=*" - "--skip=*" - "--since=*" - "--after=*" -
457 "--until=*" - "--before=*" - "--max-age=*" - "--min-age=*" -
458 "--author=*" - "--committer=*" - "--grep=*" - "-[iE]" -
459 "--remove-empty" - "--first-parent" - "--cherry-pick" -
460 "-S*" - "-G*" - "--pickaxe-all" - "--pickaxe-regex" -
461 "--simplify-by-decoration" {
462 # These mean that we get a subset of the commits
463 set filtered 1
464 lappend glflags $arg
465 }
466 "-L*" {
467 # Line-log with 'stuck' argument (unstuck form is
468 # not supported)
469 set filtered 1
470 set vinlinediff($n) 1
471 set allknown 0
472 lappend glflags $arg
473 }
474 "-n" {
475 # This appears to be the only one that has a value as a
476 # separate word following it
477 set filtered 1
478 set nextisval 1
479 lappend glflags $arg
480 }
481 "--not" - "--all" {
482 lappend revargs $arg
483 }
484 "--merge" {
485 set vmergeonly($n) 1
486 # git rev-parse doesn't understand --merge
487 lappend revargs --gitk-symmetric-diff-marker MERGE_HEAD...HEAD
488 }
489 "--no-replace-objects" {
490 set env(GIT_NO_REPLACE_OBJECTS) "1"
491 }
492 "-*" {
493 # Other flag arguments including -<n>
494 if {[string is digit -strict [string range $arg 1 end]]} {
495 set filtered 1
496 } else {
497 # a flag argument that we don't recognize;
498 # that means we can't optimize
499 set allknown 0
500 }
501 lappend glflags $arg
502 }
503 default {
504 # Non-flag arguments specify commits or ranges of commits
505 if {[string match "*...*" $arg]} {
506 lappend revargs --gitk-symmetric-diff-marker
507 }
508 lappend revargs $arg
509 }
510 }
511 }
512 set vdflags($n) $diffargs
513 set vflags($n) $glflags
514 set vrevs($n) $revargs
515 set vfiltered($n) $filtered
516 set vorigargs($n) $origargs
517 return $allknown
518 }
519
520 proc parseviewrevs {view revs} {
521 global vposids vnegids
522 global hashlength
523
524 if {$revs eq {}} {
525 set revs HEAD
526 } elseif {[lsearch -exact $revs --all] >= 0} {
527 lappend revs HEAD
528 }
529 if {[catch {set ids [safe_exec [concat git rev-parse $revs]]} err]} {
530 # we get stdout followed by stderr in $err
531 # for an unknown rev, git rev-parse echoes it and then errors out
532 set errlines [split $err "\n"]
533 set badrev {}
534 for {set l 0} {$l < [llength $errlines]} {incr l} {
535 set line [lindex $errlines $l]
536 if {!([string length $line] == $hashlength && [string is xdigit $line])} {
537 if {[string match "fatal:*" $line]} {
538 if {[string match "fatal: ambiguous argument*" $line]
539 && $badrev ne {}} {
540 if {[llength $badrev] == 1} {
541 set err "unknown revision $badrev"
542 } else {
543 set err "unknown revisions: [join $badrev ", "]"
544 }
545 } else {
546 set err [join [lrange $errlines $l end] "\n"]
547 }
548 break
549 }
550 lappend badrev $line
551 }
552 }
553 error_popup "[mc "Error parsing revisions:"] $err"
554 return {}
555 }
556 set ret {}
557 set pos {}
558 set neg {}
559 set sdm 0
560 foreach id [split $ids "\n"] {
561 if {$id eq "--gitk-symmetric-diff-marker"} {
562 set sdm 4
563 } elseif {[string match "^*" $id]} {
564 if {$sdm != 1} {
565 lappend ret $id
566 if {$sdm == 3} {
567 set sdm 0
568 }
569 }
570 lappend neg [string range $id 1 end]
571 } else {
572 if {$sdm != 2} {
573 lappend ret $id
574 } else {
575 lset ret end $id...[lindex $ret end]
576 }
577 lappend pos $id
578 }
579 incr sdm -1
580 }
581 set vposids($view) $pos
582 set vnegids($view) $neg
583 return $ret
584 }
585
586 # Start off a git log process and arrange to read its output
587 proc start_rev_list {view} {
588 global startmsecs commitidx viewcomplete curview
589 global tclencoding
590 global viewargs viewargscmd viewfiles vfilelimit
591 global showlocalchanges
592 global viewactive viewinstances vmergeonly
593 global mainheadid viewmainheadid viewmainheadid_orig
594 global vcanopt vflags vrevs vorigargs
595
596 set startmsecs [clock clicks -milliseconds]
597 set commitidx($view) 0
598 # these are set this way for the error exits
599 set viewcomplete($view) 1
600 set viewactive($view) 0
601 varcinit $view
602
603 set args $viewargs($view)
604 if {$viewargscmd($view) ne {}} {
605 if {[catch {
606 set str [safe_exec [list sh -c $viewargscmd($view)]]
607 } err]} {
608 error_popup "[mc "Error executing --argscmd command:"] $err"
609 return 0
610 }
611 set args [concat $args [split $str "\n"]]
612 }
613 set vcanopt($view) [parseviewargs $view $args]
614
615 set files $viewfiles($view)
616 if {$vmergeonly($view)} {
617 set files [unmerged_files $files]
618 if {$files eq {}} {
619 global nr_unmerged
620 if {$nr_unmerged == 0} {
621 error_popup [mc "No files selected: --merge specified but\
622 no files are unmerged."]
623 } else {
624 error_popup [mc "No files selected: --merge specified but\
625 no unmerged files are within file limit."]
626 }
627 return 0
628 }
629 }
630 set vfilelimit($view) $files
631
632 if {$vcanopt($view)} {
633 set revs [parseviewrevs $view $vrevs($view)]
634 if {$revs eq {}} {
635 return 0
636 }
637 set args $vflags($view)
638 } else {
639 set revs {}
640 set args $vorigargs($view)
641 }
642
643 if {[catch {
644 set fd [safe_open_command_redirect [concat git log --no-color -z --pretty=raw --show-notes \
645 --parents --boundary $args --stdin] \
646 [list "<<[join [concat $revs "--" $files] "\n"]"]]
647 } err]} {
648 error_popup "[mc "Error executing git log:"] $err"
649 return 0
650 }
651 set i [reg_instance $fd]
652 set viewinstances($view) [list $i]
653 set viewmainheadid($view) $mainheadid
654 set viewmainheadid_orig($view) $mainheadid
655 if {$files ne {} && $mainheadid ne {}} {
656 get_viewmainhead $view
657 }
658 if {$showlocalchanges && $viewmainheadid($view) ne {}} {
659 interestedin $viewmainheadid($view) dodiffindex
660 }
661 fconfigure $fd -blocking 0 -translation lf -eofchar {}
662 if {$tclencoding != {}} {
663 fconfigure $fd -encoding $tclencoding
664 }
665 filerun $fd [list getcommitlines $fd $i $view 0]
666 nowbusy $view [mc "Reading"]
667 set viewcomplete($view) 0
668 set viewactive($view) 1
669 return 1
670 }
671
672 proc stop_instance {inst} {
673 global commfd leftover
674
675 set fd $commfd($inst)
676 catch {
677 set pid [pid $fd]
678
679 if {$::tcl_platform(platform) eq {windows}} {
680 safe_exec [list taskkill /pid $pid]
681 } else {
682 safe_exec [list kill $pid]
683 }
684 }
685 catch {close $fd}
686 nukefile $fd
687 unset commfd($inst)
688 unset leftover($inst)
689 }
690
691 proc stop_backends {} {
692 global commfd
693
694 foreach inst [array names commfd] {
695 stop_instance $inst
696 }
697 }
698
699 proc stop_rev_list {view} {
700 global viewinstances
701
702 foreach inst $viewinstances($view) {
703 stop_instance $inst
704 }
705 set viewinstances($view) {}
706 }
707
708 proc reset_pending_select {selid} {
709 global pending_select mainheadid selectheadid
710
711 if {$selid ne {}} {
712 set pending_select $selid
713 } elseif {$selectheadid ne {}} {
714 set pending_select $selectheadid
715 } else {
716 set pending_select $mainheadid
717 }
718 }
719
720 proc getcommits {selid} {
721 global canv curview need_redisplay viewactive
722
723 initlayout
724 if {[start_rev_list $curview]} {
725 reset_pending_select $selid
726 show_status [mc "Reading commits..."]
727 set need_redisplay 1
728 } else {
729 show_status [mc "No commits selected"]
730 }
731 }
732
733 proc updatecommits {} {
734 global curview vcanopt vorigargs vfilelimit viewinstances
735 global viewactive viewcomplete tclencoding
736 global startmsecs showneartags showlocalchanges
737 global mainheadid viewmainheadid viewmainheadid_orig pending_select
738 global hasworktree
739 global varcid vposids vnegids vflags vrevs
740 global hashlength
741
742 set hasworktree [hasworktree]
743 rereadrefs
744 set view $curview
745 if {$mainheadid ne $viewmainheadid_orig($view)} {
746 if {$showlocalchanges} {
747 dohidelocalchanges
748 }
749 set viewmainheadid($view) $mainheadid
750 set viewmainheadid_orig($view) $mainheadid
751 if {$vfilelimit($view) ne {}} {
752 get_viewmainhead $view
753 }
754 }
755 if {$showlocalchanges} {
756 doshowlocalchanges
757 }
758 if {$vcanopt($view)} {
759 set oldpos $vposids($view)
760 set oldneg $vnegids($view)
761 set revs [parseviewrevs $view $vrevs($view)]
762 if {$revs eq {}} {
763 return
764 }
765 # note: getting the delta when negative refs change is hard,
766 # and could require multiple git log invocations, so in that
767 # case we ask git log for all the commits (not just the delta)
768 if {$oldneg eq $vnegids($view)} {
769 set newrevs {}
770 set npos 0
771 # take out positive refs that we asked for before or
772 # that we have already seen
773 foreach rev $revs {
774 if {[string length $rev] == $hashlength} {
775 if {[lsearch -exact $oldpos $rev] < 0
776 && ![info exists varcid($view,$rev)]} {
777 lappend newrevs $rev
778 incr npos
779 }
780 } else {
781 lappend $newrevs $rev
782 }
783 }
784 if {$npos == 0} return
785 set revs $newrevs
786 set vposids($view) [lsort -unique [concat $oldpos $vposids($view)]]
787 }
788 set args $vflags($view)
789 foreach r $oldpos {
790 lappend revs "^$r"
791 }
792 } else {
793 set revs {}
794 set args $vorigargs($view)
795 }
796 if {[catch {
797 set fd [safe_open_command_redirect [concat git log --no-color -z --pretty=raw --show-notes \
798 --parents --boundary $args --stdin] \
799 [list "<<[join [concat $revs "--" $vfilelimit($view)] "\n"]"]]
800 } err]} {
801 error_popup "[mc "Error executing git log:"] $err"
802 return
803 }
804 if {$viewactive($view) == 0} {
805 set startmsecs [clock clicks -milliseconds]
806 }
807 set i [reg_instance $fd]
808 lappend viewinstances($view) $i
809 fconfigure $fd -blocking 0 -translation lf -eofchar {}
810 if {$tclencoding != {}} {
811 fconfigure $fd -encoding $tclencoding
812 }
813 filerun $fd [list getcommitlines $fd $i $view 1]
814 incr viewactive($view)
815 set viewcomplete($view) 0
816 reset_pending_select {}
817 nowbusy $view [mc "Reading"]
818 if {$showneartags} {
819 getallcommits
820 }
821 }
822
823 proc reloadcommits {} {
824 global curview viewcomplete selectedline currentid thickerline
825 global showneartags treediffs commitinterest cached_commitrow
826 global targetid commitinfo
827
828 set selid {}
829 if {$selectedline ne {}} {
830 set selid $currentid
831 }
832
833 if {!$viewcomplete($curview)} {
834 stop_rev_list $curview
835 }
836 resetvarcs $curview
837 set selectedline {}
838 unset -nocomplain currentid
839 unset -nocomplain thickerline
840 unset -nocomplain treediffs
841 readrefs
842 changedrefs
843 if {$showneartags} {
844 getallcommits
845 }
846 clear_display
847 unset -nocomplain commitinfo
848 unset -nocomplain commitinterest
849 unset -nocomplain cached_commitrow
850 unset -nocomplain targetid
851 setcanvscroll
852 getcommits $selid
853 return 0
854 }
855
856 # This makes a string representation of a positive integer which
857 # sorts as a string in numerical order
858 proc strrep {n} {
859 if {$n < 16} {
860 return [format "%x" $n]
861 } elseif {$n < 256} {
862 return [format "x%.2x" $n]
863 } elseif {$n < 65536} {
864 return [format "y%.4x" $n]
865 }
866 return [format "z%.8x" $n]
867 }
868
869 # Procedures used in reordering commits from git log (without
870 # --topo-order) into the order for display.
871
872 proc varcinit {view} {
873 global varcstart vupptr vdownptr vleftptr vbackptr varctok varcrow
874 global vtokmod varcmod vrowmod varcix vlastins
875
876 set varcstart($view) {{}}
877 set vupptr($view) {0}
878 set vdownptr($view) {0}
879 set vleftptr($view) {0}
880 set vbackptr($view) {0}
881 set varctok($view) {{}}
882 set varcrow($view) {{}}
883 set vtokmod($view) {}
884 set varcmod($view) 0
885 set vrowmod($view) 0
886 set varcix($view) {{}}
887 set vlastins($view) {0}
888 }
889
890 proc resetvarcs {view} {
891 global varcid varccommits parents children vseedcount ordertok
892 global vshortids
893
894 foreach vid [array names varcid $view,*] {
895 unset varcid($vid)
896 unset children($vid)
897 unset parents($vid)
898 }
899 foreach vid [array names vshortids $view,*] {
900 unset vshortids($vid)
901 }
902 # some commits might have children but haven't been seen yet
903 foreach vid [array names children $view,*] {
904 unset children($vid)
905 }
906 foreach va [array names varccommits $view,*] {
907 unset varccommits($va)
908 }
909 foreach vd [array names vseedcount $view,*] {
910 unset vseedcount($vd)
911 }
912 unset -nocomplain ordertok
913 }
914
915 # returns a list of the commits with no children
916 proc seeds {v} {
917 global vdownptr vleftptr varcstart
918
919 set ret {}
920 set a [lindex $vdownptr($v) 0]
921 while {$a != 0} {
922 lappend ret [lindex $varcstart($v) $a]
923 set a [lindex $vleftptr($v) $a]
924 }
925 return $ret
926 }
927
928 proc newvarc {view id} {
929 global varcid varctok parents children vdatemode
930 global vupptr vdownptr vleftptr vbackptr varcrow varcix varcstart
931 global commitdata commitinfo vseedcount varccommits vlastins
932
933 set a [llength $varctok($view)]
934 set vid $view,$id
935 if {[llength $children($vid)] == 0 || $vdatemode($view)} {
936 if {![info exists commitinfo($id)]} {
937 parsecommit $id $commitdata($id) 1
938 }
939 set cdate [lindex [lindex $commitinfo($id) 4] 0]
940 if {![string is integer -strict $cdate]} {
941 set cdate 0
942 }
943 if {![info exists vseedcount($view,$cdate)]} {
944 set vseedcount($view,$cdate) -1
945 }
946 set c [incr vseedcount($view,$cdate)]
947 set cdate [expr {$cdate ^ 0xffffffff}]
948 set tok "s[strrep $cdate][strrep $c]"
949 } else {
950 set tok {}
951 }
952 set ka 0
953 if {[llength $children($vid)] > 0} {
954 set kid [lindex $children($vid) end]
955 set k $varcid($view,$kid)
956 if {[string compare [lindex $varctok($view) $k] $tok] > 0} {
957 set ki $kid
958 set ka $k
959 set tok [lindex $varctok($view) $k]
960 }
961 }
962 if {$ka != 0} {
963 set i [lsearch -exact $parents($view,$ki) $id]
964 set j [expr {[llength $parents($view,$ki)] - 1 - $i}]
965 append tok [strrep $j]
966 }
967 set c [lindex $vlastins($view) $ka]
968 if {$c == 0 || [string compare $tok [lindex $varctok($view) $c]] < 0} {
969 set c $ka
970 set b [lindex $vdownptr($view) $ka]
971 } else {
972 set b [lindex $vleftptr($view) $c]
973 }
974 while {$b != 0 && [string compare $tok [lindex $varctok($view) $b]] >= 0} {
975 set c $b
976 set b [lindex $vleftptr($view) $c]
977 }
978 if {$c == $ka} {
979 lset vdownptr($view) $ka $a
980 lappend vbackptr($view) 0
981 } else {
982 lset vleftptr($view) $c $a
983 lappend vbackptr($view) $c
984 }
985 lset vlastins($view) $ka $a
986 lappend vupptr($view) $ka
987 lappend vleftptr($view) $b
988 if {$b != 0} {
989 lset vbackptr($view) $b $a
990 }
991 lappend varctok($view) $tok
992 lappend varcstart($view) $id
993 lappend vdownptr($view) 0
994 lappend varcrow($view) {}
995 lappend varcix($view) {}
996 set varccommits($view,$a) {}
997 lappend vlastins($view) 0
998 return $a
999 }
1000
1001 proc splitvarc {p v} {
1002 global varcid varcstart varccommits varctok vtokmod
1003 global vupptr vdownptr vleftptr vbackptr varcix varcrow vlastins
1004
1005 set oa $varcid($v,$p)
1006 set otok [lindex $varctok($v) $oa]
1007 set ac $varccommits($v,$oa)
1008 set i [lsearch -exact $varccommits($v,$oa) $p]
1009 if {$i <= 0} return
1010 set na [llength $varctok($v)]
1011 # "%" sorts before "0"...
1012 set tok "$otok%[strrep $i]"
1013 lappend varctok($v) $tok
1014 lappend varcrow($v) {}
1015 lappend varcix($v) {}
1016 set varccommits($v,$oa) [lrange $ac 0 [expr {$i - 1}]]
1017 set varccommits($v,$na) [lrange $ac $i end]
1018 lappend varcstart($v) $p
1019 foreach id $varccommits($v,$na) {
1020 set varcid($v,$id) $na
1021 }
1022 lappend vdownptr($v) [lindex $vdownptr($v) $oa]
1023 lappend vlastins($v) [lindex $vlastins($v) $oa]
1024 lset vdownptr($v) $oa $na
1025 lset vlastins($v) $oa 0
1026 lappend vupptr($v) $oa
1027 lappend vleftptr($v) 0
1028 lappend vbackptr($v) 0
1029 for {set b [lindex $vdownptr($v) $na]} {$b != 0} {set b [lindex $vleftptr($v) $b]} {
1030 lset vupptr($v) $b $na
1031 }
1032 if {[string compare $otok $vtokmod($v)] <= 0} {
1033 modify_arc $v $oa
1034 }
1035 }
1036
1037 proc renumbervarc {a v} {
1038 global parents children varctok varcstart varccommits
1039 global vupptr vdownptr vleftptr vbackptr vlastins varcid vtokmod vdatemode
1040
1041 set t1 [clock clicks -milliseconds]
1042 set todo {}
1043 set isrelated($a) 1
1044 set kidchanged($a) 1
1045 set ntot 0
1046 while {$a != 0} {
1047 if {[info exists isrelated($a)]} {
1048 lappend todo $a
1049 set id [lindex $varccommits($v,$a) end]
1050 foreach p $parents($v,$id) {
1051 if {[info exists varcid($v,$p)]} {
1052 set isrelated($varcid($v,$p)) 1
1053 }
1054 }
1055 }
1056 incr ntot
1057 set b [lindex $vdownptr($v) $a]
1058 if {$b == 0} {
1059 while {$a != 0} {
1060 set b [lindex $vleftptr($v) $a]
1061 if {$b != 0} break
1062 set a [lindex $vupptr($v) $a]
1063 }
1064 }
1065 set a $b
1066 }
1067 foreach a $todo {
1068 if {![info exists kidchanged($a)]} continue
1069 set id [lindex $varcstart($v) $a]
1070 if {[llength $children($v,$id)] > 1} {
1071 set children($v,$id) [lsort -command [list vtokcmp $v] \
1072 $children($v,$id)]
1073 }
1074 set oldtok [lindex $varctok($v) $a]
1075 if {!$vdatemode($v)} {
1076 set tok {}
1077 } else {
1078 set tok $oldtok
1079 }
1080 set ka 0
1081 set kid [last_real_child $v,$id]
1082 if {$kid ne {}} {
1083 set k $varcid($v,$kid)
1084 if {[string compare [lindex $varctok($v) $k] $tok] > 0} {
1085 set ki $kid
1086 set ka $k
1087 set tok [lindex $varctok($v) $k]
1088 }
1089 }
1090 if {$ka != 0} {
1091 set i [lsearch -exact $parents($v,$ki) $id]
1092 set j [expr {[llength $parents($v,$ki)] - 1 - $i}]
1093 append tok [strrep $j]
1094 }
1095 if {$tok eq $oldtok} {
1096 continue
1097 }
1098 set id [lindex $varccommits($v,$a) end]
1099 foreach p $parents($v,$id) {
1100 if {[info exists varcid($v,$p)]} {
1101 set kidchanged($varcid($v,$p)) 1
1102 } else {
1103 set sortkids($p) 1
1104 }
1105 }
1106 lset varctok($v) $a $tok
1107 set b [lindex $vupptr($v) $a]
1108 if {$b != $ka} {
1109 if {[string compare [lindex $varctok($v) $ka] $vtokmod($v)] < 0} {
1110 modify_arc $v $ka
1111 }
1112 if {[string compare [lindex $varctok($v) $b] $vtokmod($v)] < 0} {
1113 modify_arc $v $b
1114 }
1115 set c [lindex $vbackptr($v) $a]
1116 set d [lindex $vleftptr($v) $a]
1117 if {$c == 0} {
1118 lset vdownptr($v) $b $d
1119 } else {
1120 lset vleftptr($v) $c $d
1121 }
1122 if {$d != 0} {
1123 lset vbackptr($v) $d $c
1124 }
1125 if {[lindex $vlastins($v) $b] == $a} {
1126 lset vlastins($v) $b $c
1127 }
1128 lset vupptr($v) $a $ka
1129 set c [lindex $vlastins($v) $ka]
1130 if {$c == 0 || \
1131 [string compare $tok [lindex $varctok($v) $c]] < 0} {
1132 set c $ka
1133 set b [lindex $vdownptr($v) $ka]
1134 } else {
1135 set b [lindex $vleftptr($v) $c]
1136 }
1137 while {$b != 0 && \
1138 [string compare $tok [lindex $varctok($v) $b]] >= 0} {
1139 set c $b
1140 set b [lindex $vleftptr($v) $c]
1141 }
1142 if {$c == $ka} {
1143 lset vdownptr($v) $ka $a
1144 lset vbackptr($v) $a 0
1145 } else {
1146 lset vleftptr($v) $c $a
1147 lset vbackptr($v) $a $c
1148 }
1149 lset vleftptr($v) $a $b
1150 if {$b != 0} {
1151 lset vbackptr($v) $b $a
1152 }
1153 lset vlastins($v) $ka $a
1154 }
1155 }
1156 foreach id [array names sortkids] {
1157 if {[llength $children($v,$id)] > 1} {
1158 set children($v,$id) [lsort -command [list vtokcmp $v] \
1159 $children($v,$id)]
1160 }
1161 }
1162 set t2 [clock clicks -milliseconds]
1163 #puts "renumbervarc did [llength $todo] of $ntot arcs in [expr {$t2-$t1}]ms"
1164 }
1165
1166 # Fix up the graph after we have found out that in view $v,
1167 # $p (a commit that we have already seen) is actually the parent
1168 # of the last commit in arc $a.
1169 proc fix_reversal {p a v} {
1170 global varcid varcstart varctok vupptr
1171
1172 set pa $varcid($v,$p)
1173 if {$p ne [lindex $varcstart($v) $pa]} {
1174 splitvarc $p $v
1175 set pa $varcid($v,$p)
1176 }
1177 # seeds always need to be renumbered
1178 if {[lindex $vupptr($v) $pa] == 0 ||
1179 [string compare [lindex $varctok($v) $a] \
1180 [lindex $varctok($v) $pa]] > 0} {
1181 renumbervarc $pa $v
1182 }
1183 }
1184
1185 proc insertrow {id p v} {
1186 global cmitlisted children parents varcid varctok vtokmod
1187 global varccommits ordertok commitidx numcommits curview
1188 global targetid targetrow vshortids
1189
1190 readcommit $id
1191 set vid $v,$id
1192 set cmitlisted($vid) 1
1193 set children($vid) {}
1194 set parents($vid) [list $p]
1195 set a [newvarc $v $id]
1196 set varcid($vid) $a
1197 lappend vshortids($v,[string range $id 0 3]) $id
1198 if {[string compare [lindex $varctok($v) $a] $vtokmod($v)] < 0} {
1199 modify_arc $v $a
1200 }
1201 lappend varccommits($v,$a) $id
1202 set vp $v,$p
1203 if {[llength [lappend children($vp) $id]] > 1} {
1204 set children($vp) [lsort -command [list vtokcmp $v] $children($vp)]
1205 unset -nocomplain ordertok
1206 }
1207 fix_reversal $p $a $v
1208 incr commitidx($v)
1209 if {$v == $curview} {
1210 set numcommits $commitidx($v)
1211 setcanvscroll
1212 if {[info exists targetid]} {
1213 if {![comes_before $targetid $p]} {
1214 incr targetrow
1215 }
1216 }
1217 }
1218 }
1219
1220 proc insertfakerow {id p} {
1221 global varcid varccommits parents children cmitlisted
1222 global commitidx varctok vtokmod targetid targetrow curview numcommits
1223
1224 set v $curview
1225 set a $varcid($v,$p)
1226 set i [lsearch -exact $varccommits($v,$a) $p]
1227 if {$i < 0} {
1228 puts "oops: insertfakerow can't find [shortids $p] on arc $a"
1229 return
1230 }
1231 set children($v,$id) {}
1232 set parents($v,$id) [list $p]
1233 set varcid($v,$id) $a
1234 lappend children($v,$p) $id
1235 set cmitlisted($v,$id) 1
1236 set numcommits [incr commitidx($v)]
1237 # note we deliberately don't update varcstart($v) even if $i == 0
1238 set varccommits($v,$a) [linsert $varccommits($v,$a) $i $id]
1239 modify_arc $v $a $i
1240 if {[info exists targetid]} {
1241 if {![comes_before $targetid $p]} {
1242 incr targetrow
1243 }
1244 }
1245 setcanvscroll
1246 drawvisible
1247 }
1248
1249 proc removefakerow {id} {
1250 global varcid varccommits parents children commitidx
1251 global varctok vtokmod cmitlisted currentid selectedline
1252 global targetid curview numcommits
1253
1254 set v $curview
1255 if {[llength $parents($v,$id)] != 1} {
1256 puts "oops: removefakerow [shortids $id] has [llength $parents($v,$id)] parents"
1257 return
1258 }
1259 set p [lindex $parents($v,$id) 0]
1260 set a $varcid($v,$id)
1261 set i [lsearch -exact $varccommits($v,$a) $id]
1262 if {$i < 0} {
1263 puts "oops: removefakerow can't find [shortids $id] on arc $a"
1264 return
1265 }
1266 unset varcid($v,$id)
1267 set varccommits($v,$a) [lreplace $varccommits($v,$a) $i $i]
1268 unset parents($v,$id)
1269 unset children($v,$id)
1270 unset cmitlisted($v,$id)
1271 set numcommits [incr commitidx($v) -1]
1272 set j [lsearch -exact $children($v,$p) $id]
1273 if {$j >= 0} {
1274 set children($v,$p) [lreplace $children($v,$p) $j $j]
1275 }
1276 modify_arc $v $a $i
1277 if {[info exist currentid] && $id eq $currentid} {
1278 unset currentid
1279 set selectedline {}
1280 }
1281 if {[info exists targetid] && $targetid eq $id} {
1282 set targetid $p
1283 }
1284 setcanvscroll
1285 drawvisible
1286 }
1287
1288 proc real_children {vp} {
1289 global children nullid nullid2
1290
1291 set kids {}
1292 foreach id $children($vp) {
1293 if {$id ne $nullid && $id ne $nullid2} {
1294 lappend kids $id
1295 }
1296 }
1297 return $kids
1298 }
1299
1300 proc first_real_child {vp} {
1301 global children nullid nullid2
1302
1303 foreach id $children($vp) {
1304 if {$id ne $nullid && $id ne $nullid2} {
1305 return $id
1306 }
1307 }
1308 return {}
1309 }
1310
1311 proc last_real_child {vp} {
1312 global children nullid nullid2
1313
1314 set kids $children($vp)
1315 for {set i [llength $kids]} {[incr i -1] >= 0} {} {
1316 set id [lindex $kids $i]
1317 if {$id ne $nullid && $id ne $nullid2} {
1318 return $id
1319 }
1320 }
1321 return {}
1322 }
1323
1324 proc vtokcmp {v a b} {
1325 global varctok varcid
1326
1327 return [string compare [lindex $varctok($v) $varcid($v,$a)] \
1328 [lindex $varctok($v) $varcid($v,$b)]]
1329 }
1330
1331 # This assumes that if lim is not given, the caller has checked that
1332 # arc a's token is less than $vtokmod($v)
1333 proc modify_arc {v a {lim {}}} {
1334 global varctok vtokmod varcmod varcrow vupptr curview vrowmod varccommits
1335
1336 if {$lim ne {}} {
1337 set c [string compare [lindex $varctok($v) $a] $vtokmod($v)]
1338 if {$c > 0} return
1339 if {$c == 0} {
1340 set r [lindex $varcrow($v) $a]
1341 if {$r ne {} && $vrowmod($v) <= $r + $lim} return
1342 }
1343 }
1344 set vtokmod($v) [lindex $varctok($v) $a]
1345 set varcmod($v) $a
1346 if {$v == $curview} {
1347 while {$a != 0 && [lindex $varcrow($v) $a] eq {}} {
1348 set a [lindex $vupptr($v) $a]
1349 set lim {}
1350 }
1351 set r 0
1352 if {$a != 0} {
1353 if {$lim eq {}} {
1354 set lim [llength $varccommits($v,$a)]
1355 }
1356 set r [expr {[lindex $varcrow($v) $a] + $lim}]
1357 }
1358 set vrowmod($v) $r
1359 undolayout $r
1360 }
1361 }
1362
1363 proc update_arcrows {v} {
1364 global vtokmod varcmod vrowmod varcrow commitidx currentid selectedline
1365 global varcid vrownum varcorder varcix varccommits
1366 global vupptr vdownptr vleftptr varctok
1367 global displayorder parentlist curview cached_commitrow
1368
1369 if {$vrowmod($v) == $commitidx($v)} return
1370 if {$v == $curview} {
1371 if {[llength $displayorder] > $vrowmod($v)} {
1372 set displayorder [lrange $displayorder 0 [expr {$vrowmod($v) - 1}]]
1373 set parentlist [lrange $parentlist 0 [expr {$vrowmod($v) - 1}]]
1374 }
1375 unset -nocomplain cached_commitrow
1376 }
1377 set narctot [expr {[llength $varctok($v)] - 1}]
1378 set a $varcmod($v)
1379 while {$a != 0 && [lindex $varcix($v) $a] eq {}} {
1380 # go up the tree until we find something that has a row number,
1381 # or we get to a seed
1382 set a [lindex $vupptr($v) $a]
1383 }
1384 if {$a == 0} {
1385 set a [lindex $vdownptr($v) 0]
1386 if {$a == 0} return
1387 set vrownum($v) {0}
1388 set varcorder($v) [list $a]
1389 lset varcix($v) $a 0
1390 lset varcrow($v) $a 0
1391 set arcn 0
1392 set row 0
1393 } else {
1394 set arcn [lindex $varcix($v) $a]
1395 if {[llength $vrownum($v)] > $arcn + 1} {
1396 set vrownum($v) [lrange $vrownum($v) 0 $arcn]
1397 set varcorder($v) [lrange $varcorder($v) 0 $arcn]
1398 }
1399 set row [lindex $varcrow($v) $a]
1400 }
1401 while {1} {
1402 set p $a
1403 incr row [llength $varccommits($v,$a)]
1404 # go down if possible
1405 set b [lindex $vdownptr($v) $a]
1406 if {$b == 0} {
1407 # if not, go left, or go up until we can go left
1408 while {$a != 0} {
1409 set b [lindex $vleftptr($v) $a]
1410 if {$b != 0} break
1411 set a [lindex $vupptr($v) $a]
1412 }
1413 if {$a == 0} break
1414 }
1415 set a $b
1416 incr arcn
1417 lappend vrownum($v) $row
1418 lappend varcorder($v) $a
1419 lset varcix($v) $a $arcn
1420 lset varcrow($v) $a $row
1421 }
1422 set vtokmod($v) [lindex $varctok($v) $p]
1423 set varcmod($v) $p
1424 set vrowmod($v) $row
1425 if {[info exists currentid]} {
1426 set selectedline [rowofcommit $currentid]
1427 }
1428 }
1429
1430 # Test whether view $v contains commit $id
1431 proc commitinview {id v} {
1432 global varcid
1433
1434 return [info exists varcid($v,$id)]
1435 }
1436
1437 # Return the row number for commit $id in the current view
1438 proc rowofcommit {id} {
1439 global varcid varccommits varcrow curview cached_commitrow
1440 global varctok vtokmod
1441
1442 set v $curview
1443 if {![info exists varcid($v,$id)]} {
1444 puts "oops rowofcommit no arc for [shortids $id]"
1445 return {}
1446 }
1447 set a $varcid($v,$id)
1448 if {[string compare [lindex $varctok($v) $a] $vtokmod($v)] >= 0} {
1449 update_arcrows $v
1450 }
1451 if {[info exists cached_commitrow($id)]} {
1452 return $cached_commitrow($id)
1453 }
1454 set i [lsearch -exact $varccommits($v,$a) $id]
1455 if {$i < 0} {
1456 puts "oops didn't find commit [shortids $id] in arc $a"
1457 return {}
1458 }
1459 incr i [lindex $varcrow($v) $a]
1460 set cached_commitrow($id) $i
1461 return $i
1462 }
1463
1464 # Returns 1 if a is on an earlier row than b, otherwise 0
1465 proc comes_before {a b} {
1466 global varcid varctok curview
1467
1468 set v $curview
1469 if {$a eq $b || ![info exists varcid($v,$a)] || \
1470 ![info exists varcid($v,$b)]} {
1471 return 0
1472 }
1473 if {$varcid($v,$a) != $varcid($v,$b)} {
1474 return [expr {[string compare [lindex $varctok($v) $varcid($v,$a)] \
1475 [lindex $varctok($v) $varcid($v,$b)]] < 0}]
1476 }
1477 return [expr {[rowofcommit $a] < [rowofcommit $b]}]
1478 }
1479
1480 proc bsearch {l elt} {
1481 if {[llength $l] == 0 || $elt <= [lindex $l 0]} {
1482 return 0
1483 }
1484 set lo 0
1485 set hi [llength $l]
1486 while {$hi - $lo > 1} {
1487 set mid [expr {int(($lo + $hi) / 2)}]
1488 set t [lindex $l $mid]
1489 if {$elt < $t} {
1490 set hi $mid
1491 } elseif {$elt > $t} {
1492 set lo $mid
1493 } else {
1494 return $mid
1495 }
1496 }
1497 return $lo
1498 }
1499
1500 # Make sure rows $start..$end-1 are valid in displayorder and parentlist
1501 proc make_disporder {start end} {
1502 global vrownum curview commitidx displayorder parentlist
1503 global varccommits varcorder parents vrowmod varcrow
1504 global d_valid_start d_valid_end
1505
1506 if {$end > $vrowmod($curview)} {
1507 update_arcrows $curview
1508 }
1509 set ai [bsearch $vrownum($curview) $start]
1510 set start [lindex $vrownum($curview) $ai]
1511 set narc [llength $vrownum($curview)]
1512 for {set r $start} {$ai < $narc && $r < $end} {incr ai} {
1513 set a [lindex $varcorder($curview) $ai]
1514 set l [llength $displayorder]
1515 set al [llength $varccommits($curview,$a)]
1516 if {$l < $r + $al} {
1517 if {$l < $r} {
1518 set pad [ntimes [expr {$r - $l}] {}]
1519 set displayorder [concat $displayorder $pad]
1520 set parentlist [concat $parentlist $pad]
1521 } elseif {$l > $r} {
1522 set displayorder [lrange $displayorder 0 [expr {$r - 1}]]
1523 set parentlist [lrange $parentlist 0 [expr {$r - 1}]]
1524 }
1525 foreach id $varccommits($curview,$a) {
1526 lappend displayorder $id
1527 lappend parentlist $parents($curview,$id)
1528 }
1529 } elseif {[lindex $displayorder [expr {$r + $al - 1}]] eq {}} {
1530 set i $r
1531 foreach id $varccommits($curview,$a) {
1532 lset displayorder $i $id
1533 lset parentlist $i $parents($curview,$id)
1534 incr i
1535 }
1536 }
1537 incr r $al
1538 }
1539 }
1540
1541 proc commitonrow {row} {
1542 global displayorder
1543
1544 set id [lindex $displayorder $row]
1545 if {$id eq {}} {
1546 make_disporder $row [expr {$row + 1}]
1547 set id [lindex $displayorder $row]
1548 }
1549 return $id
1550 }
1551
1552 proc closevarcs {v} {
1553 global varctok varccommits varcid parents children
1554 global cmitlisted commitidx vtokmod curview numcommits
1555
1556 set missing_parents 0
1557 set scripts {}
1558 set narcs [llength $varctok($v)]
1559 for {set a 1} {$a < $narcs} {incr a} {
1560 set id [lindex $varccommits($v,$a) end]
1561 foreach p $parents($v,$id) {
1562 if {[info exists varcid($v,$p)]} continue
1563 # add p as a new commit
1564 incr missing_parents
1565 set cmitlisted($v,$p) 0
1566 set parents($v,$p) {}
1567 if {[llength $children($v,$p)] == 1 &&
1568 [llength $parents($v,$id)] == 1} {
1569 set b $a
1570 } else {
1571 set b [newvarc $v $p]
1572 }
1573 set varcid($v,$p) $b
1574 if {[string compare [lindex $varctok($v) $b] $vtokmod($v)] < 0} {
1575 modify_arc $v $b
1576 }
1577 lappend varccommits($v,$b) $p
1578 incr commitidx($v)
1579 if {$v == $curview} {
1580 set numcommits $commitidx($v)
1581 }
1582 set scripts [check_interest $p $scripts]
1583 }
1584 }
1585 if {$missing_parents > 0} {
1586 foreach s $scripts {
1587 eval $s
1588 }
1589 }
1590 }
1591
1592 # Use $rwid as a substitute for $id, i.e. reparent $id's children to $rwid
1593 # Assumes we already have an arc for $rwid.
1594 proc rewrite_commit {v id rwid} {
1595 global children parents varcid varctok vtokmod varccommits
1596
1597 foreach ch $children($v,$id) {
1598 # make $rwid be $ch's parent in place of $id
1599 set i [lsearch -exact $parents($v,$ch) $id]
1600 if {$i < 0} {
1601 puts "oops rewrite_commit didn't find $id in parent list for $ch"
1602 }
1603 set parents($v,$ch) [lreplace $parents($v,$ch) $i $i $rwid]
1604 # add $ch to $rwid's children and sort the list if necessary
1605 if {[llength [lappend children($v,$rwid) $ch]] > 1} {
1606 set children($v,$rwid) [lsort -command [list vtokcmp $v] \
1607 $children($v,$rwid)]
1608 }
1609 # fix the graph after joining $id to $rwid
1610 set a $varcid($v,$ch)
1611 fix_reversal $rwid $a $v
1612 # parentlist is wrong for the last element of arc $a
1613 # even if displayorder is right, hence the 3rd arg here
1614 modify_arc $v $a [expr {[llength $varccommits($v,$a)] - 1}]
1615 }
1616 }
1617
1618 # Mechanism for registering a command to be executed when we come
1619 # across a particular commit. To handle the case when only the
1620 # prefix of the commit is known, the commitinterest array is now
1621 # indexed by the first 4 characters of the ID. Each element is a
1622 # list of id, cmd pairs.
1623 proc interestedin {id cmd} {
1624 global commitinterest
1625
1626 lappend commitinterest([string range $id 0 3]) $id $cmd
1627 }
1628
1629 proc check_interest {id scripts} {
1630 global commitinterest
1631
1632 set prefix [string range $id 0 3]
1633 if {[info exists commitinterest($prefix)]} {
1634 set newlist {}
1635 foreach {i script} $commitinterest($prefix) {
1636 if {[string match "$i*" $id]} {
1637 lappend scripts [string map [list "%I" $id "%P" $i] $script]
1638 } else {
1639 lappend newlist $i $script
1640 }
1641 }
1642 if {$newlist ne {}} {
1643 set commitinterest($prefix) $newlist
1644 } else {
1645 unset commitinterest($prefix)
1646 }
1647 }
1648 return $scripts
1649 }
1650
1651 proc getcommitlines {fd inst view updating} {
1652 global cmitlisted leftover
1653 global commitidx commitdata vdatemode
1654 global parents children curview hlview
1655 global idpending ordertok
1656 global varccommits varcid varctok vtokmod vfilelimit vshortids
1657 global hashlength
1658
1659 set stuff [read $fd 500000]
1660 # git log doesn't terminate the last commit with a null...
1661 if {$stuff == {} && $leftover($inst) ne {} && [eof $fd]} {
1662 set stuff "\0"
1663 }
1664 if {$stuff == {}} {
1665 if {![eof $fd]} {
1666 return 1
1667 }
1668 global commfd viewcomplete viewactive viewname
1669 global viewinstances
1670 unset commfd($inst)
1671 set i [lsearch -exact $viewinstances($view) $inst]
1672 if {$i >= 0} {
1673 set viewinstances($view) [lreplace $viewinstances($view) $i $i]
1674 }
1675 # set it blocking so we wait for the process to terminate
1676 fconfigure $fd -blocking 1
1677 if {[catch {close $fd} err]} {
1678 set fv {}
1679 if {$view != $curview} {
1680 set fv " for the \"$viewname($view)\" view"
1681 }
1682 if {[string range $err 0 4] == "usage"} {
1683 set err "Gitk: error reading commits$fv:\
1684 bad arguments to git log."
1685 if {$viewname($view) eq [mc "Command line"]} {
1686 append err \
1687 " (Note: arguments to gitk are passed to git log\
1688 to allow selection of commits to be displayed.)"
1689 }
1690 } else {
1691 set err "Error reading commits$fv: $err"
1692 }
1693 error_popup $err
1694 }
1695 if {[incr viewactive($view) -1] <= 0} {
1696 set viewcomplete($view) 1
1697 # Check if we have seen any ids listed as parents that haven't
1698 # appeared in the list
1699 closevarcs $view
1700 notbusy $view
1701 }
1702 if {$view == $curview} {
1703 run chewcommits
1704 }
1705 return 0
1706 }
1707 set start 0
1708 set gotsome 0
1709 set scripts {}
1710 while 1 {
1711 set i [string first "\0" $stuff $start]
1712 if {$i < 0} {
1713 append leftover($inst) [string range $stuff $start end]
1714 break
1715 }
1716 if {$start == 0} {
1717 set cmit $leftover($inst)
1718 append cmit [string range $stuff 0 [expr {$i - 1}]]
1719 set leftover($inst) {}
1720 } else {
1721 set cmit [string range $stuff $start [expr {$i - 1}]]
1722 }
1723 set start [expr {$i + 1}]
1724 set j [string first "\n" $cmit]
1725 set ok 0
1726 set listed 1
1727 if {$j >= 0 && [string match "commit *" $cmit]} {
1728 set ids [string range $cmit 7 [expr {$j - 1}]]
1729 if {[string match {[-^<>]*} $ids]} {
1730 switch -- [string index $ids 0] {
1731 "-" {set listed 0}
1732 "^" {set listed 2}
1733 "<" {set listed 3}
1734 ">" {set listed 4}
1735 }
1736 set ids [string range $ids 1 end]
1737 }
1738 set ok 1
1739 foreach id $ids {
1740 if {[string length $id] != $hashlength} {
1741 set ok 0
1742 break
1743 }
1744 }
1745 }
1746 if {!$ok} {
1747 set shortcmit $cmit
1748 if {[string length $shortcmit] > 80} {
1749 set shortcmit "[string range $shortcmit 0 80]..."
1750 }
1751 error_popup "[mc "Can't parse git log output:"] {$shortcmit}"
1752 exit 1
1753 }
1754 set id [lindex $ids 0]
1755 set vid $view,$id
1756
1757 lappend vshortids($view,[string range $id 0 3]) $id
1758
1759 if {!$listed && $updating && ![info exists varcid($vid)] &&
1760 $vfilelimit($view) ne {}} {
1761 # git log doesn't rewrite parents for unlisted commits
1762 # when doing path limiting, so work around that here
1763 # by working out the rewritten parent with git rev-list
1764 # and if we already know about it, using the rewritten
1765 # parent as a substitute parent for $id's children.
1766 if {![catch {
1767 set rwid [safe_exec [list git rev-list --first-parent --max-count=1 \
1768 $id -- $vfilelimit($view)]]
1769 }]} {
1770 if {$rwid ne {} && [info exists varcid($view,$rwid)]} {
1771 # use $rwid in place of $id
1772 rewrite_commit $view $id $rwid
1773 continue
1774 }
1775 }
1776 }
1777
1778 set a 0
1779 if {[info exists varcid($vid)]} {
1780 if {$cmitlisted($vid) || !$listed} continue
1781 set a $varcid($vid)
1782 }
1783 if {$listed} {
1784 set olds [lrange $ids 1 end]
1785 } else {
1786 set olds {}
1787 }
1788 set commitdata($id) [string range $cmit [expr {$j + 1}] end]
1789 set cmitlisted($vid) $listed
1790 set parents($vid) $olds
1791 if {![info exists children($vid)]} {
1792 set children($vid) {}
1793 } elseif {$a == 0 && [llength $children($vid)] == 1} {
1794 set k [lindex $children($vid) 0]
1795 if {[llength $parents($view,$k)] == 1 &&
1796 (!$vdatemode($view) ||
1797 $varcid($view,$k) == [llength $varctok($view)] - 1)} {
1798 set a $varcid($view,$k)
1799 }
1800 }
1801 if {$a == 0} {
1802 # new arc
1803 set a [newvarc $view $id]
1804 }
1805 if {[string compare [lindex $varctok($view) $a] $vtokmod($view)] < 0} {
1806 modify_arc $view $a
1807 }
1808 if {![info exists varcid($vid)]} {
1809 set varcid($vid) $a
1810 lappend varccommits($view,$a) $id
1811 incr commitidx($view)
1812 }
1813
1814 set i 0
1815 foreach p $olds {
1816 if {$i == 0 || [lsearch -exact $olds $p] >= $i} {
1817 set vp $view,$p
1818 if {[llength [lappend children($vp) $id]] > 1 &&
1819 [vtokcmp $view [lindex $children($vp) end-1] $id] > 0} {
1820 set children($vp) [lsort -command [list vtokcmp $view] \
1821 $children($vp)]
1822 unset -nocomplain ordertok
1823 }
1824 if {[info exists varcid($view,$p)]} {
1825 fix_reversal $p $a $view
1826 }
1827 }
1828 incr i
1829 }
1830
1831 set scripts [check_interest $id $scripts]
1832 set gotsome 1
1833 }
1834 if {$gotsome} {
1835 global numcommits hlview
1836
1837 if {$view == $curview} {
1838 set numcommits $commitidx($view)
1839 run chewcommits
1840 }
1841 if {[info exists hlview] && $view == $hlview} {
1842 # we never actually get here...
1843 run vhighlightmore
1844 }
1845 foreach s $scripts {
1846 eval $s
1847 }
1848 }
1849 return 2
1850 }
1851
1852 proc chewcommits {} {
1853 global curview hlview viewcomplete
1854 global pending_select
1855
1856 layoutmore
1857 if {$viewcomplete($curview)} {
1858 global commitidx varctok
1859 global numcommits startmsecs
1860
1861 if {[info exists pending_select]} {
1862 update
1863 reset_pending_select {}
1864
1865 if {[commitinview $pending_select $curview]} {
1866 selectline [rowofcommit $pending_select] 1
1867 } else {
1868 set row [first_real_row]
1869 selectline $row 1
1870 }
1871 }
1872 if {$commitidx($curview) > 0} {
1873 #set ms [expr {[clock clicks -milliseconds] - $startmsecs}]
1874 #puts "overall $ms ms for $numcommits commits"
1875 #puts "[llength $varctok($view)] arcs, $commitidx($view) commits"
1876 } else {
1877 show_status [mc "No commits selected"]
1878 }
1879 notbusy layout
1880 }
1881 return 0
1882 }
1883
1884 proc do_readcommit {id} {
1885 global tclencoding
1886
1887 # Invoke git-log to handle automatic encoding conversion
1888 set fd [safe_open_command [concat git log --no-color --pretty=raw -1 $id]]
1889 # Read the results using i18n.logoutputencoding
1890 fconfigure $fd -translation lf -eofchar {}
1891 if {$tclencoding != {}} {
1892 fconfigure $fd -encoding $tclencoding
1893 }
1894 set contents [read $fd]
1895 close $fd
1896 # Remove the heading line
1897 regsub {^commit [0-9a-f]+\n} $contents {} contents
1898
1899 return $contents
1900 }
1901
1902 proc readcommit {id} {
1903 if {[catch {set contents [do_readcommit $id]}]} return
1904 parsecommit $id $contents 1
1905 }
1906
1907 proc parsecommit {id contents listed} {
1908 global commitinfo
1909
1910 set inhdr 1
1911 set comment {}
1912 set headline {}
1913 set auname {}
1914 set audate {}
1915 set comname {}
1916 set comdate {}
1917 set hdrend [string first "\n\n" $contents]
1918 if {$hdrend < 0} {
1919 # should never happen...
1920 set hdrend [string length $contents]
1921 }
1922 set header [string range $contents 0 [expr {$hdrend - 1}]]
1923 set comment [string range $contents [expr {$hdrend + 2}] end]
1924 foreach line [split $header "\n"] {
1925 set line [split $line " "]
1926 set tag [lindex $line 0]
1927 if {$tag == "author"} {
1928 set audate [lrange $line end-1 end]
1929 set auname [join [lrange $line 1 end-2] " "]
1930 } elseif {$tag == "committer"} {
1931 set comdate [lrange $line end-1 end]
1932 set comname [join [lrange $line 1 end-2] " "]
1933 }
1934 }
1935 set headline {}
1936 # take the first non-blank line of the comment as the headline
1937 set headline [string trimleft $comment]
1938 set i [string first "\n" $headline]
1939 if {$i >= 0} {
1940 set headline [string range $headline 0 $i]
1941 }
1942 set headline [string trimright $headline]
1943 set i [string first "\r" $headline]
1944 if {$i >= 0} {
1945 set headline [string trimright [string range $headline 0 $i]]
1946 }
1947 if {!$listed} {
1948 # git log indents the comment by 4 spaces;
1949 # if we got this via git cat-file, add the indentation
1950 set newcomment {}
1951 foreach line [split $comment "\n"] {
1952 append newcomment " "
1953 append newcomment $line
1954 append newcomment "\n"
1955 }
1956 set comment $newcomment
1957 }
1958 set hasnote [string first "\nNotes:\n" $contents]
1959 set diff ""
1960 # If there is diff output shown in the git-log stream, split it
1961 # out. But get rid of the empty line that always precedes the
1962 # diff.
1963 set i [string first "\n\ndiff" $comment]
1964 if {$i >= 0} {
1965 set diff [string range $comment $i+1 end]
1966 set comment [string range $comment 0 $i-1]
1967 }
1968 set commitinfo($id) [list $headline $auname $audate \
1969 $comname $comdate $comment $hasnote $diff]
1970 }
1971
1972 proc getcommit {id} {
1973 global commitdata commitinfo
1974
1975 if {[info exists commitdata($id)]} {
1976 parsecommit $id $commitdata($id) 1
1977 } else {
1978 readcommit $id
1979 if {![info exists commitinfo($id)]} {
1980 set commitinfo($id) [list [mc "No commit information available"]]
1981 }
1982 }
1983 return 1
1984 }
1985
1986 # Expand an abbreviated commit ID to a list of full 40-char (or 64-char
1987 # for SHA256 repo) IDs that match and are present in the current view.
1988 # This is fairly slow...
1989 proc longid {prefix} {
1990 global varcid curview vshortids
1991
1992 set ids {}
1993 if {[string length $prefix] >= 4} {
1994 set vshortid $curview,[string range $prefix 0 3]
1995 if {[info exists vshortids($vshortid)]} {
1996 foreach id $vshortids($vshortid) {
1997 if {[string match "$prefix*" $id]} {
1998 if {[lsearch -exact $ids $id] < 0} {
1999 lappend ids $id
2000 if {[llength $ids] >= 2} break
2001 }
2002 }
2003 }
2004 }
2005 } else {
2006 foreach match [array names varcid "$curview,$prefix*"] {
2007 lappend ids [lindex [split $match ","] 1]
2008 if {[llength $ids] >= 2} break
2009 }
2010 }
2011 return $ids
2012 }
2013
2014 proc readrefs {} {
2015 global tagids idtags headids idheads tagobjid upstreamofref
2016 global otherrefids idotherrefs mainhead mainheadid
2017 global selecthead selectheadid
2018 global hideremotes
2019 global tclencoding
2020 global hashlength
2021
2022 foreach v {tagids idtags headids idheads otherrefids idotherrefs upstreamofref} {
2023 unset -nocomplain $v
2024 }
2025 set refd [safe_open_command [list git show-ref -d]]
2026 if {$tclencoding != {}} {
2027 fconfigure $refd -encoding $tclencoding
2028 }
2029 while {[gets $refd line] >= 0} {
2030 if {[string index $line $hashlength] ne " "} continue
2031 set id [string range $line 0 [expr {$hashlength - 1}]]
2032 set ref [string range $line [expr {$hashlength + 1}] end]
2033 if {![string match "refs/*" $ref]} continue
2034 set name [string range $ref 5 end]
2035 if {[string match "remotes/*" $name]} {
2036 if {![string match "*/HEAD" $name] && !$hideremotes} {
2037 set headids($name) $id
2038 lappend idheads($id) $name
2039 }
2040 } elseif {[string match "heads/*" $name]} {
2041 set name [string range $name 6 end]
2042 set headids($name) $id
2043 lappend idheads($id) $name
2044 } elseif {[string match "tags/*" $name]} {
2045 # this lets refs/tags/foo^{} overwrite refs/tags/foo,
2046 # which is what we want since the former is the commit ID
2047 set name [string range $name 5 end]
2048 if {[string match "*^{}" $name]} {
2049 set name [string range $name 0 end-3]
2050 } else {
2051 set tagobjid($name) $id
2052 }
2053 set tagids($name) $id
2054 lappend idtags($id) $name
2055 } else {
2056 if [is_other_ref_visible $name] {
2057 set otherrefids($name) $id
2058 lappend idotherrefs($id) $name
2059 }
2060 }
2061 }
2062 catch {close $refd}
2063 set mainhead {}
2064 set mainheadid {}
2065 catch {
2066 set mainheadid [exec git rev-parse HEAD]
2067 set thehead [exec git symbolic-ref HEAD]
2068 if {[string match "refs/heads/*" $thehead]} {
2069 set mainhead [string range $thehead 11 end]
2070 }
2071 }
2072 set selectheadid {}
2073 if {$selecthead ne {}} {
2074 catch {
2075 set selectheadid [safe_exec [list git rev-parse --verify $selecthead]]
2076 }
2077 }
2078 #load the local_branch->upstream mapping
2079 # the result of the for-each-ref command produces: local_branch NUL upstream
2080 set refd [safe_open_command [list git for-each-ref {--format=%(refname:short)%00%(upstream)} refs/heads/]]
2081 while {[gets $refd local_tracking] >= 0} {
2082 set line [split $local_tracking \0]
2083 if {[lindex $line 1] ne {}} {
2084 set upstream_ref [string map {"refs/" ""} [lindex $line 1]]
2085 set upstreamofref([lindex $line 0]) $upstream_ref
2086 }
2087 }
2088 catch {close $refd}
2089 }
2090
2091 # skip over fake commits
2092 proc first_real_row {} {
2093 global nullid nullid2 numcommits
2094
2095 for {set row 0} {$row < $numcommits} {incr row} {
2096 set id [commitonrow $row]
2097 if {$id ne $nullid && $id ne $nullid2} {
2098 break
2099 }
2100 }
2101 return $row
2102 }
2103
2104 # update things for a head moved to a child of its previous location
2105 proc movehead {id name} {
2106 global headids idheads
2107
2108 removehead $headids($name) $name
2109 set headids($name) $id
2110 lappend idheads($id) $name
2111 }
2112
2113 # update things when a head has been removed
2114 proc removehead {id name} {
2115 global headids idheads
2116
2117 if {$idheads($id) eq $name} {
2118 unset idheads($id)
2119 } else {
2120 set i [lsearch -exact $idheads($id) $name]
2121 if {$i >= 0} {
2122 set idheads($id) [lreplace $idheads($id) $i $i]
2123 }
2124 }
2125 unset headids($name)
2126 }
2127
2128 proc ttk_toplevel {w args} {
2129 eval [linsert $args 0 ::toplevel $w]
2130 place [ttk::frame $w._toplevel_background] -x 0 -y 0 -relwidth 1 -relheight 1
2131 return $w
2132 }
2133
2134 proc make_transient {window origin {geometry ""}} {
2135 wm transient $window $origin
2136
2137 if {$geometry ne ""} {
2138 after idle [list wm geometry $window $geometry]
2139 } elseif {[tk windowingsystem] eq {win32}} {
2140 # Windows fails to place transient windows normally, so
2141 # schedule a callback to center them on the parent.
2142 after idle [list tk::PlaceWindow $window widget $origin]
2143 }
2144 }
2145
2146 proc show_error {w top msg} {
2147 if {[wm state $top] eq "withdrawn"} { wm deiconify $top }
2148 message $w.m -text $msg -justify center -aspect 400
2149 pack $w.m -side top -fill x -padx 20 -pady 20
2150 ttk::button $w.ok -default active -text [mc OK] -command "destroy $top"
2151 pack $w.ok -side bottom -fill x
2152 bind $top <Visibility> "grab $top; focus $top"
2153 bind $top <Key-Return> "destroy $top"
2154 bind $top <Key-space> "destroy $top"
2155 bind $top <Key-Escape> "destroy $top"
2156 tkwait window $top
2157 }
2158
2159 proc error_popup {msg {owner .}} {
2160 if {[tk windowingsystem] eq "win32"} {
2161 tk_messageBox -icon error -type ok -title [wm title .] \
2162 -parent $owner -message $msg
2163 } else {
2164 set w .error
2165 ttk_toplevel $w
2166 make_transient $w $owner
2167 show_error $w $w $msg
2168 }
2169 }
2170
2171 proc confirm_popup {msg {owner .}} {
2172 global confirm_ok
2173 set confirm_ok 0
2174 set w .confirm
2175 ttk_toplevel $w
2176 make_transient $w $owner
2177 message $w.m -text $msg -justify center -aspect 400
2178 pack $w.m -side top -fill x -padx 20 -pady 20
2179 ttk::button $w.ok -text [mc OK] -command "set confirm_ok 1; destroy $w"
2180 pack $w.ok -side left -fill x
2181 ttk::button $w.cancel -text [mc Cancel] -command "destroy $w"
2182 pack $w.cancel -side right -fill x
2183 bind $w <Visibility> "grab $w; focus $w"
2184 bind $w <Key-Return> "set confirm_ok 1; destroy $w"
2185 bind $w <Key-space> "set confirm_ok 1; destroy $w"
2186 bind $w <Key-Escape> "destroy $w"
2187 tk::PlaceWindow $w widget $owner
2188 tkwait window $w
2189 return $confirm_ok
2190 }
2191
2192 proc haveselectionclipboard {} {
2193 return [expr {[tk windowingsystem] eq "x11"}]
2194 }
2195
2196 proc setoptions {} {
2197 if {[tk windowingsystem] ne "win32"} {
2198 option add *Panedwindow.showHandle 1 startupFile
2199 option add *Panedwindow.sashRelief raised startupFile
2200 if {[tk windowingsystem] ne "aqua"} {
2201 option add *Menu.font uifont startupFile
2202 }
2203 } else {
2204 option add *Menu.TearOff 0 startupFile
2205 }
2206 option add *Button.font uifont startupFile
2207 option add *Checkbutton.font uifont startupFile
2208 option add *Radiobutton.font uifont startupFile
2209 option add *Menubutton.font uifont startupFile
2210 option add *Label.font uifont startupFile
2211 option add *Message.font uifont startupFile
2212 option add *Entry.font textfont startupFile
2213 option add *Text.font textfont startupFile
2214 option add *Labelframe.font uifont startupFile
2215 option add *Spinbox.font textfont startupFile
2216 option add *Listbox.font mainfont startupFile
2217 }
2218
2219 proc setttkstyle {} {
2220 global theme
2221 eval font configure TkDefaultFont [fontflags mainfont]
2222 eval font configure TkTextFont [fontflags textfont]
2223 eval font configure TkHeadingFont [fontflags mainfont]
2224 eval font configure TkCaptionFont [fontflags mainfont] -weight bold
2225 eval font configure TkTooltipFont [fontflags uifont]
2226 eval font configure TkFixedFont [fontflags textfont]
2227 eval font configure TkIconFont [fontflags uifont]
2228 eval font configure TkMenuFont [fontflags uifont]
2229 eval font configure TkSmallCaptionFont [fontflags uifont]
2230
2231 if {[catch {ttk::style theme use $theme} err]} {
2232 set theme [ttk::style theme use]
2233 }
2234 }
2235
2236 # Make a menu and submenus.
2237 # m is the window name for the menu, items is the list of menu items to add.
2238 # Each item is a list {mc label type description options...}
2239 # mc is ignored; it's so we can put mc there to alert xgettext
2240 # label is the string that appears in the menu
2241 # type is cascade, command or radiobutton (should add checkbutton)
2242 # description depends on type; it's the sublist for cascade, the
2243 # command to invoke for command, or {variable value} for radiobutton
2244 proc makemenu {m items} {
2245 menu $m
2246 if {[tk windowingsystem] eq {aqua}} {
2247 set Meta1 Cmd
2248 } else {
2249 set Meta1 Ctrl
2250 }
2251 foreach i $items {
2252 set name [mc [lindex $i 1]]
2253 set type [lindex $i 2]
2254 set thing [lindex $i 3]
2255 set params [list $type]
2256 if {$name ne {}} {
2257 set u [string first "&" [string map {&& x} $name]]
2258 lappend params -label [string map {&& & & {}} $name]
2259 if {$u >= 0} {
2260 lappend params -underline $u
2261 }
2262 }
2263 switch -- $type {
2264 "cascade" {
2265 set submenu [string tolower [string map {& ""} [lindex $i 1]]]
2266 lappend params -menu $m.$submenu
2267 }
2268 "command" {
2269 lappend params -command $thing
2270 }
2271 "radiobutton" {
2272 lappend params -variable [lindex $thing 0] \
2273 -value [lindex $thing 1]
2274 }
2275 }
2276 set tail [lrange $i 4 end]
2277 regsub -all {\yMeta1\y} $tail $Meta1 tail
2278 eval $m add $params $tail
2279 if {$type eq "cascade"} {
2280 makemenu $m.$submenu $thing
2281 }
2282 }
2283 }
2284
2285 # translate string and remove ampersands
2286 proc mca {str} {
2287 return [string map {&& & & {}} [mc $str]]
2288 }
2289
2290 proc cleardropsel {w} {
2291 $w selection clear
2292 }
2293 proc makedroplist {w varname args} {
2294 set width 0
2295 foreach label $args {
2296 set cx [string length $label]
2297 if {$cx > $width} {set width $cx}
2298 }
2299 set gm [ttk::combobox $w -width $width -state readonly\
2300 -textvariable $varname -values $args \
2301 -exportselection false]
2302 bind $gm <<ComboboxSelected>> [list $gm selection clear]
2303 return $gm
2304 }
2305
2306 proc scrollval {D {koff 0}} {
2307 global kscroll scroll_D0
2308 return [expr int(-($D / $scroll_D0) * max(1, $kscroll-$koff))]
2309 }
2310
2311 proc precisescrollval {D {koff 0}} {
2312 global kscroll
2313 return [expr (-($D / 10.0) * max(1, $kscroll-$koff))]
2314 }
2315
2316 proc bind_mousewheel {} {
2317 global canv cflist ctext
2318 bindall <MouseWheel> {allcanvs yview scroll [scrollval %D] units}
2319 bindall <Shift-MouseWheel> break
2320 bind $ctext <MouseWheel> {$ctext yview scroll [scrollval %D 2] units}
2321 bind $ctext <Shift-MouseWheel> {$ctext xview scroll [scrollval %D 2] units}
2322 bind $cflist <MouseWheel> {$cflist yview scroll [scrollval %D 2] units}
2323 bind $cflist <Shift-MouseWheel> break
2324 bind $canv <Shift-MouseWheel> {$canv xview scroll [scrollval %D] units}
2325
2326 if {[package vcompare $::tcl_version 8.7] >= 0} {
2327 bindall <Alt-MouseWheel> {allcanvs yview scroll [scrollval 5*%D] units}
2328 bindall <Alt-Shift-MouseWheel> break
2329 bind $ctext <Alt-MouseWheel> {$ctext yview scroll [scrollval 5*%D 2] units}
2330 bind $ctext <Alt-Shift-MouseWheel> {$ctext xview scroll [scrollval 5*%D 2] units}
2331 bind $cflist <Alt-MouseWheel> {$cflist yview scroll [scrollval 5*%D 2] units}
2332 bind $cflist <Alt-Shift-MouseWheel> break
2333 bind $canv <Alt-Shift-MouseWheel> {$canv xview scroll [scrollval 5*%D] units}
2334
2335 bindall <TouchpadScroll> {
2336 lassign [tk::PreciseScrollDeltas %D] deltaX deltaY
2337 allcanvs yview scroll [precisescrollval $deltaY] units
2338 }
2339 bind $ctext <TouchpadScroll> {
2340 lassign [tk::PreciseScrollDeltas %D] deltaX deltaY
2341 $ctext yview scroll [precisescrollval $deltaY 2] units
2342 $ctext xview scroll [precisescrollval $deltaX 2] units
2343 }
2344 bind $cflist <TouchpadScroll> {
2345 lassign [tk::PreciseScrollDeltas %D] deltaX deltaY
2346 $cflist yview scroll [precisescrollval $deltaY 2] units
2347 }
2348 bind $canv <TouchpadScroll> {
2349 lassign [tk::PreciseScrollDeltas %D] deltaX deltaY
2350 $canv xview scroll [precisescrollval $deltaX] units
2351 allcanvs yview scroll [precisescrollval $deltaY] units
2352 }
2353 }
2354 }
2355
2356 proc bind_mousewheel_buttons {} {
2357 global canv cflist ctext
2358 bindall <ButtonRelease-4> {allcanvs yview scroll [scrollval 1] units}
2359 bindall <ButtonRelease-5> {allcanvs yview scroll [scrollval -1] units}
2360 bindall <Shift-ButtonRelease-4> break
2361 bindall <Shift-ButtonRelease-5> break
2362 bind $ctext <ButtonRelease-4> {$ctext yview scroll [scrollval 1 2] units}
2363 bind $ctext <ButtonRelease-5> {$ctext yview scroll [scrollval -1 2] units}
2364 bind $ctext <Shift-ButtonRelease-4> {$ctext xview scroll [scrollval 1 2] units}
2365 bind $ctext <Shift-ButtonRelease-5> {$ctext xview scroll [scrollval -1 2] units}
2366 bind $cflist <ButtonRelease-4> {$cflist yview scroll [scrollval 1 2] units}
2367 bind $cflist <ButtonRelease-5> {$cflist yview scroll [scrollval -1 2] units}
2368 bind $cflist <Shift-ButtonRelease-4> break
2369 bind $cflist <Shift-ButtonRelease-5> break
2370 bind $canv <Shift-ButtonRelease-4> {$canv xview scroll [scrollval 1] units}
2371 bind $canv <Shift-ButtonRelease-5> {$canv xview scroll [scrollval -1] units}
2372 }
2373
2374 proc makewindow {} {
2375 global canv canv2 canv3 linespc charspc ctext cflist cscroll
2376 global tabstop
2377 global findtype findtypemenu findloc findstring fstring geometry
2378 global entries sha1entry sha1string sha1but
2379 global diffcontextstring diffcontext
2380 global ignorespace
2381 global maincursor textcursor curtextcursor
2382 global rowctxmenu fakerowmenu mergemax wrapcomment wrapdefault
2383 global highlight_files gdttype
2384 global searchstring sstring
2385 global bgcolor fgcolor bglist fglist diffcolors diffbgcolors selectbgcolor
2386 global filesepbgcolor filesepfgcolor
2387 global mergecolors foundbgcolor currentsearchhitbgcolor
2388 global headctxmenu progresscanv progressitem progresscoords statusw
2389 global fprogitem fprogcoord lastprogupdate progupdatepending
2390 global rprogitem rprogcoord rownumsel numcommits
2391 global worddiff
2392 global hashlength scroll_D0
2393
2394 # The "mc" arguments here are purely so that xgettext
2395 # sees the following string as needing to be translated
2396 set file {
2397 mc "&File" cascade {
2398 {mc "&Update" command updatecommits -accelerator F5}
2399 {mc "&Reload" command reloadcommits -accelerator Shift-F5}
2400 {mc "Reread re&ferences" command rereadrefs}
2401 {mc "&List references" command showrefs -accelerator F2}
2402 {xx "" separator}
2403 {mc "Start git &gui" command {safe_exec_redirect [list git gui] [list &]}}
2404 {xx "" separator}
2405 {mc "&Quit" command doquit -accelerator Meta1-Q}
2406 }}
2407 set edit {
2408 mc "&Edit" cascade {
2409 {mc "&Preferences" command doprefs}
2410 }}
2411 set view {
2412 mc "&View" cascade {
2413 {mc "&New view..." command {newview 0} -accelerator Shift-F4}
2414 {mc "&Edit view..." command editview -state disabled -accelerator F4}
2415 {mc "&Delete view" command delview -state disabled}
2416 {xx "" separator}
2417 {mc "&All files" radiobutton {selectedview 0} -command {showview 0}}
2418 }}
2419 if {[tk windowingsystem] ne "aqua"} {
2420 set help {
2421 mc "&Help" cascade {
2422 {mc "&About gitk" command about}
2423 {mc "&Key bindings" command keys}
2424 }}
2425 set bar [list $file $edit $view $help]
2426 } else {
2427 proc ::tk::mac::ShowPreferences {} {doprefs}
2428 proc ::tk::mac::Quit {} {doquit}
2429 lset file end [lreplace [lindex $file end] end-1 end]
2430 set apple {
2431 xx "&Apple" cascade {
2432 {mc "&About gitk" command about}
2433 {xx "" separator}
2434 }}
2435 set help {
2436 mc "&Help" cascade {
2437 {mc "&Key bindings" command keys}
2438 }}
2439 set bar [list $apple $file $view $help]
2440 }
2441 makemenu .bar $bar
2442 . configure -menu .bar
2443
2444 # cover the non-themed toplevel with a themed frame.
2445 place [ttk::frame ._main_background] -x 0 -y 0 -relwidth 1 -relheight 1
2446
2447 # the gui has upper and lower half, parts of a paned window.
2448 ttk::panedwindow .ctop -orient vertical
2449
2450 # possibly use assumed geometry
2451 if {![info exists geometry(pwsash0)]} {
2452 set geometry(topheight) [expr {15 * $linespc}]
2453 set geometry(topwidth) [expr {80 * $charspc}]
2454 set geometry(botheight) [expr {15 * $linespc}]
2455 set geometry(botwidth) [expr {50 * $charspc}]
2456 set geometry(pwsash0) [list [expr {40 * $charspc}] 2]
2457 set geometry(pwsash1) [list [expr {60 * $charspc}] 2]
2458 }
2459
2460 # the upper half will have a paned window, a scroll bar to the right, and some stuff below
2461 ttk::frame .tf -height $geometry(topheight) -width $geometry(topwidth)
2462 ttk::frame .tf.histframe
2463 ttk::panedwindow .tf.histframe.pwclist -orient horizontal
2464
2465 # create three canvases
2466 set cscroll .tf.histframe.csb
2467 set canv .tf.histframe.pwclist.canv
2468 canvas $canv \
2469 -selectbackground $selectbgcolor \
2470 -background $bgcolor -bd 0 \
2471 -xscrollincr $linespc \
2472 -yscrollincr $linespc -yscrollcommand "scrollcanv $cscroll" \
2473 -xscrollcommand ".tf.histframe.cxsb set"
2474 .tf.histframe.pwclist add $canv
2475 set canv2 .tf.histframe.pwclist.canv2
2476 canvas $canv2 \
2477 -selectbackground $selectbgcolor \
2478 -background $bgcolor -bd 0 -yscrollincr $linespc
2479 .tf.histframe.pwclist add $canv2
2480 set canv3 .tf.histframe.pwclist.canv3
2481 canvas $canv3 \
2482 -selectbackground $selectbgcolor \
2483 -background $bgcolor -bd 0 -yscrollincr $linespc
2484 .tf.histframe.pwclist add $canv3
2485 bind .tf.histframe.pwclist <Map> {
2486 bind %W <Map> {}
2487 .tf.histframe.pwclist sashpos 1 [lindex $::geometry(pwsash1) 0]
2488 .tf.histframe.pwclist sashpos 0 [lindex $::geometry(pwsash0) 0]
2489 }
2490
2491 # a scroll bar to rule them (vertical), and one for horizontal scroll of left pane
2492 ttk::scrollbar $cscroll -command {allcanvs yview}
2493 pack $cscroll -side right -fill y
2494 ttk::scrollbar .tf.histframe.cxsb -orient horizontal -command "$canv xview"
2495 pack .tf.histframe.cxsb -side bottom -fill x
2496 bind .tf.histframe.pwclist <Configure> {resizeclistpanes %W %w}
2497 lappend bglist $canv $canv2 $canv3
2498 pack .tf.histframe.pwclist -fill both -expand 1 -side left
2499
2500 # we have two button bars at bottom of top frame. Bar 1
2501 ttk::frame .tf.bar
2502 ttk::frame .tf.lbar -height 15
2503
2504 set sha1entry .tf.bar.sha1
2505 set entries $sha1entry
2506 set sha1but .tf.bar.sha1label
2507 ttk::button $sha1but -text "[mc "Commit ID:"] " -state disabled \
2508 -command gotocommit -width 8
2509 pack .tf.bar.sha1label -side left
2510 ttk::entry $sha1entry -width $hashlength -font textfont -textvariable sha1string
2511 trace add variable sha1string write sha1change
2512 pack $sha1entry -side left -pady 2
2513
2514 ttk::button .tf.bar.leftbut -command goback -state disabled
2515 .tf.bar.leftbut configure -text \u2190 -width 3
2516 pack .tf.bar.leftbut -side left -fill y
2517 ttk::button .tf.bar.rightbut -command goforw -state disabled
2518 .tf.bar.rightbut configure -text \u2192 -width 3
2519 pack .tf.bar.rightbut -side left -fill y
2520
2521 ttk::label .tf.bar.rowlabel -text [mc "Row"]
2522 set rownumsel {}
2523 ttk::label .tf.bar.rownum -width 7 -textvariable rownumsel \
2524 -relief sunken -anchor e
2525 ttk::label .tf.bar.rowlabel2 -text "/"
2526 ttk::label .tf.bar.numcommits -width 7 -textvariable numcommits \
2527 -relief sunken -anchor e
2528 pack .tf.bar.rowlabel .tf.bar.rownum .tf.bar.rowlabel2 .tf.bar.numcommits \
2529 -side left
2530 global selectedline
2531 trace add variable selectedline write selectedline_change
2532
2533 # Status label and progress bar
2534 set statusw .tf.bar.status
2535 ttk::label $statusw -width 15 -relief sunken
2536 pack $statusw -side left -padx 5
2537 set progresscanv [ttk::progressbar .tf.bar.progress]
2538 pack $progresscanv -side right -expand 1 -fill x -padx {0 2}
2539 set progresscoords {0 0}
2540 set fprogcoord 0
2541 set rprogcoord 0
2542 bind $progresscanv <Configure> adjustprogress
2543 set lastprogupdate [clock clicks -milliseconds]
2544 set progupdatepending 0
2545
2546 # build up the bottom bar of upper window
2547 ttk::label .tf.lbar.flabel -text "[mc "Find"] "
2548
2549 ttk::button .tf.lbar.fnext -command {dofind 1 1} -text \u2193 -width 3
2550 ttk::button .tf.lbar.fprev -command {dofind -1 1} -text \u2191 -width 3
2551
2552 ttk::label .tf.lbar.flab2 -text " [mc "commit"] "
2553
2554 pack .tf.lbar.flabel .tf.lbar.fnext .tf.lbar.fprev .tf.lbar.flab2 \
2555 -side left -fill y
2556 set gdttype [mc "containing:"]
2557 set gm [makedroplist .tf.lbar.gdttype gdttype \
2558 [mc "containing:"] \
2559 [mc "touching paths:"] \
2560 [mc "adding/removing string:"] \
2561 [mc "changing lines matching:"]]
2562 trace add variable gdttype write gdttype_change
2563 pack .tf.lbar.gdttype -side left -fill y
2564
2565 set findstring {}
2566 set fstring .tf.lbar.findstring
2567 lappend entries $fstring
2568 ttk::entry $fstring -width 30 -textvariable findstring
2569 trace add variable findstring write find_change
2570 set findtype [mc "Exact"]
2571 set findtypemenu [makedroplist .tf.lbar.findtype \
2572 findtype [mc "Exact"] [mc "IgnCase"] [mc "Regexp"]]
2573 trace add variable findtype write findcom_change
2574 set findloc [mc "All fields"]
2575 makedroplist .tf.lbar.findloc findloc [mc "All fields"] [mc "Headline"] \
2576 [mc "Comments"] [mc "Author"] [mc "Committer"]
2577 trace add variable findloc write find_change
2578 pack .tf.lbar.findloc -side right
2579 pack .tf.lbar.findtype -side right
2580 pack $fstring -side left -expand 1 -fill x
2581
2582 # Finish putting the upper half of the viewer together
2583 pack .tf.lbar -in .tf -side bottom -fill x
2584 pack .tf.bar -in .tf -side bottom -fill x
2585 pack .tf.histframe -fill both -side top -expand 1
2586 .ctop add .tf
2587
2588 # now build up the bottom
2589 ttk::panedwindow .pwbottom -orient horizontal
2590
2591 # lower left, a text box over search bar, scroll bar to the right
2592 # if we know window height, then that will set the lower text height, otherwise
2593 # we set lower text height which will drive window height
2594 if {[info exists geometry(main)]} {
2595 ttk::frame .bleft -width $geometry(botwidth)
2596 } else {
2597 ttk::frame .bleft -width $geometry(botwidth) -height $geometry(botheight)
2598 }
2599 ttk::frame .bleft.top
2600 ttk::frame .bleft.mid
2601 ttk::frame .bleft.bottom
2602
2603 # gap between sub-widgets
2604 set wgap [font measure uifont "i"]
2605
2606 ttk::button .bleft.top.search -text [mc "Search"] -command dosearch
2607 pack .bleft.top.search -side left -padx 5
2608 set sstring .bleft.top.sstring
2609 set searchstring ""
2610 ttk::entry $sstring -width 20 -textvariable searchstring
2611 lappend entries $sstring
2612 trace add variable searchstring write incrsearch
2613 pack $sstring -side left -expand 1 -fill x
2614 ttk::radiobutton .bleft.mid.diff -text [mc "Diff"] \
2615 -command changediffdisp -variable diffelide -value {0 0}
2616 ttk::radiobutton .bleft.mid.old -text [mc "Old version"] \
2617 -command changediffdisp -variable diffelide -value {0 1}
2618 ttk::radiobutton .bleft.mid.new -text [mc "New version"] \
2619 -command changediffdisp -variable diffelide -value {1 0}
2620
2621 ttk::label .bleft.mid.labeldiffcontext -text " [mc "Lines of context"]: "
2622 pack .bleft.mid.diff .bleft.mid.old .bleft.mid.new -side left -ipadx $wgap
2623 ttk::spinbox .bleft.mid.diffcontext -width 5 \
2624 -from 0 -increment 1 -to 10000000 \
2625 -validate all -validatecommand "diffcontextvalidate %P" \
2626 -textvariable diffcontextstring
2627 .bleft.mid.diffcontext set $diffcontext
2628 trace add variable diffcontextstring write diffcontextchange
2629 lappend entries .bleft.mid.diffcontext
2630 pack .bleft.mid.labeldiffcontext .bleft.mid.diffcontext -side left -ipadx $wgap
2631 ttk::checkbutton .bleft.mid.ignspace -text [mc "Ignore space change"] \
2632 -command changeignorespace -variable ignorespace
2633 pack .bleft.mid.ignspace -side left -padx 5
2634
2635 set worddiff [mc "Line diff"]
2636 makedroplist .bleft.mid.worddiff worddiff [mc "Line diff"] \
2637 [mc "Markup words"] [mc "Color words"]
2638 trace add variable worddiff write changeworddiff
2639 pack .bleft.mid.worddiff -side left -padx 5
2640
2641 set ctext .bleft.bottom.ctext
2642 text $ctext -background $bgcolor -foreground $fgcolor \
2643 -state disabled -undo 0 -font textfont \
2644 -yscrollcommand scrolltext -wrap $wrapdefault \
2645 -xscrollcommand ".bleft.bottom.sbhorizontal set"
2646 $ctext conf -tabstyle wordprocessor
2647 ttk::scrollbar .bleft.bottom.sb -command "$ctext yview"
2648 ttk::scrollbar .bleft.bottom.sbhorizontal -command "$ctext xview" -orient h
2649 pack .bleft.top -side top -fill x
2650 pack .bleft.mid -side top -fill x
2651 grid $ctext .bleft.bottom.sb -sticky nsew
2652 grid .bleft.bottom.sbhorizontal -sticky ew
2653 grid columnconfigure .bleft.bottom 0 -weight 1
2654 grid rowconfigure .bleft.bottom 0 -weight 1
2655 grid rowconfigure .bleft.bottom 1 -weight 0
2656 pack .bleft.bottom -side top -fill both -expand 1
2657 lappend bglist $ctext
2658 lappend fglist $ctext
2659
2660 $ctext tag conf comment -wrap $wrapcomment
2661 $ctext tag conf filesep -font textfontbold -fore $filesepfgcolor -back $filesepbgcolor
2662 $ctext tag conf hunksep -fore [lindex $diffcolors 2]
2663 $ctext tag conf d0 -fore [lindex $diffcolors 0]
2664 $ctext tag conf d0 -back [lindex $diffbgcolors 0]
2665 $ctext tag conf dresult -fore [lindex $diffcolors 1]
2666 $ctext tag conf dresult -back [lindex $diffbgcolors 1]
2667 $ctext tag conf m0 -fore [lindex $mergecolors 0]
2668 $ctext tag conf m1 -fore [lindex $mergecolors 1]
2669 $ctext tag conf m2 -fore [lindex $mergecolors 2]
2670 $ctext tag conf m3 -fore [lindex $mergecolors 3]
2671 $ctext tag conf m4 -fore [lindex $mergecolors 4]
2672 $ctext tag conf m5 -fore [lindex $mergecolors 5]
2673 $ctext tag conf m6 -fore [lindex $mergecolors 6]
2674 $ctext tag conf m7 -fore [lindex $mergecolors 7]
2675 $ctext tag conf m8 -fore [lindex $mergecolors 8]
2676 $ctext tag conf m9 -fore [lindex $mergecolors 9]
2677 $ctext tag conf m10 -fore [lindex $mergecolors 10]
2678 $ctext tag conf m11 -fore [lindex $mergecolors 11]
2679 $ctext tag conf m12 -fore [lindex $mergecolors 12]
2680 $ctext tag conf m13 -fore [lindex $mergecolors 13]
2681 $ctext tag conf m14 -fore [lindex $mergecolors 14]
2682 $ctext tag conf m15 -fore [lindex $mergecolors 15]
2683 $ctext tag conf mmax -fore darkgrey
2684 set mergemax 16
2685 $ctext tag conf mresult -font textfontbold
2686 $ctext tag conf msep -font textfontbold
2687 $ctext tag conf found -back $foundbgcolor
2688 $ctext tag conf currentsearchhit -back $currentsearchhitbgcolor
2689 $ctext tag conf wwrap -wrap word -lmargin2 1c
2690 $ctext tag conf bold -font textfontbold
2691 # set these to the lowest priority:
2692 $ctext tag lower currentsearchhit
2693 $ctext tag lower found
2694 $ctext tag lower filesep
2695 $ctext tag lower dresult
2696 $ctext tag lower d0
2697
2698 .pwbottom add .bleft
2699
2700 # lower right
2701 ttk::frame .bright
2702 ttk::frame .bright.mode
2703 ttk::radiobutton .bright.mode.patch -text [mc "Patch"] \
2704 -command reselectline -variable cmitmode -value "patch"
2705 ttk::radiobutton .bright.mode.tree -text [mc "Tree"] \
2706 -command reselectline -variable cmitmode -value "tree"
2707 grid .bright.mode.patch .bright.mode.tree -sticky ew
2708 pack .bright.mode -side top -fill x
2709 set cflist .bright.cfiles
2710 set indent [font measure mainfont "nn"]
2711 text $cflist \
2712 -selectbackground $selectbgcolor \
2713 -background $bgcolor -foreground $fgcolor \
2714 -font mainfont \
2715 -tabs [list $indent [expr {2 * $indent}]] \
2716 -yscrollcommand ".bright.sb set" \
2717 -cursor [. cget -cursor] \
2718 -spacing1 1 -spacing3 1
2719 lappend bglist $cflist
2720 lappend fglist $cflist
2721 ttk::scrollbar .bright.sb -command "$cflist yview"
2722 pack .bright.sb -side right -fill y
2723 pack $cflist -side left -fill both -expand 1
2724 $cflist tag configure highlight \
2725 -background [$cflist cget -selectbackground]
2726 $cflist tag configure bold -font mainfontbold
2727
2728 .pwbottom add .bright
2729 .ctop add .pwbottom
2730
2731 # restore window position if known
2732 if {[info exists geometry(main)]} {
2733 wm geometry . "$geometry(main)"
2734 }
2735
2736 if {[info exists geometry(state)] && $geometry(state) eq "zoomed"} {
2737 wm state . $geometry(state)
2738 }
2739
2740 if {[tk windowingsystem] eq {aqua}} {
2741 set M1B M1
2742 set ::BM "3"
2743 } else {
2744 set M1B Control
2745 set ::BM "2"
2746 }
2747
2748 bind .ctop <Map> {
2749 bind %W <Map> {}
2750 %W sashpos 0 $::geometry(topheight)
2751 }
2752 bind .pwbottom <Map> {
2753 bind %W <Map> {}
2754 %W sashpos 0 $::geometry(botwidth)
2755 }
2756 bind .pwbottom <Configure> {resizecdetpanes %W %w}
2757
2758 pack .ctop -fill both -expand 1
2759 bindall <1> {selcanvline %W %x %y}
2760
2761 #Mouse / touchpad scrolling
2762 if {[tk windowingsystem] == "win32" || [package vcompare $::tcl_version 8.7] >= 0} {
2763 set scroll_D0 120
2764 bind_mousewheel
2765 } elseif {[tk windowingsystem] == "x11"} {
2766 set scroll_D0 1
2767 bind_mousewheel_buttons
2768 } elseif {[tk windowingsystem] == "aqua"} {
2769 set scroll_D0 1
2770 bind_mousewheel
2771 } else {
2772 puts stderr [mc "Unknown windowing system, cannot bind mouse"]
2773 }
2774 bindall <$::BM> "canvscan mark %W %x %y"
2775 bindall <B$::BM-Motion> "canvscan dragto %W %x %y"
2776 bind all <$M1B-Key-w> {destroy [winfo toplevel %W]}
2777 bind . <$M1B-Key-w> doquit
2778 bindkey <Home> selfirstline
2779 bindkey <End> sellastline
2780 bind . <Key-Up> "selnextline -1"
2781 bind . <Key-Down> "selnextline 1"
2782 bind . <Shift-Key-Up> "dofind -1 0"
2783 bind . <Shift-Key-Down> "dofind 1 0"
2784 bindkey <<NextChar>> "goforw"
2785 bindkey <<PrevChar>> "goback"
2786 bind . <Key-Prior> "selnextpage -1"
2787 bind . <Key-Next> "selnextpage 1"
2788 bind . <$M1B-Home> "allcanvs yview moveto 0.0"
2789 bind . <$M1B-End> "allcanvs yview moveto 1.0"
2790 bind . <$M1B-Key-Up> "allcanvs yview scroll -1 units"
2791 bind . <$M1B-Key-Down> "allcanvs yview scroll 1 units"
2792 bind . <$M1B-Key-Prior> "allcanvs yview scroll -1 pages"
2793 bind . <$M1B-Key-Next> "allcanvs yview scroll 1 pages"
2794 bindkey <Key-Delete> "$ctext yview scroll -1 pages"
2795 bindkey <Key-BackSpace> "$ctext yview scroll -1 pages"
2796 bindkey <Key-space> "$ctext yview scroll 1 pages"
2797 bindkey p "selnextline -1"
2798 bindkey n "selnextline 1"
2799 bindkey z "goback"
2800 bindkey x "goforw"
2801 bindkey k "selnextline -1"
2802 bindkey j "selnextline 1"
2803 bindkey h "goback"
2804 bindkey l "goforw"
2805 bindkey b prevfile
2806 bindkey d "$ctext yview scroll 18 units"
2807 bindkey u "$ctext yview scroll -18 units"
2808 bindkey g {$sha1entry delete 0 end; focus $sha1entry}
2809 bindkey / {focus $fstring}
2810 bindkey <Key-KP_Divide> {focus $fstring}
2811 bindkey <Key-Return> {dofind 1 1}
2812 bindkey ? {dofind -1 1}
2813 bindkey f nextfile
2814 bind . <F5> updatecommits
2815 bindmodfunctionkey Shift 5 reloadcommits
2816 bind . <F2> showrefs
2817 bindmodfunctionkey Shift 4 {newview 0}
2818 bind . <F4> edit_or_newview
2819 bind . <$M1B-q> doquit
2820 bind . <$M1B-f> {dofind 1 1}
2821 bind . <$M1B-g> {dofind 1 0}
2822 bind . <$M1B-r> dosearchback
2823 bind . <$M1B-s> dosearch
2824 bind . <$M1B-equal> {incrfont 1}
2825 bind . <$M1B-plus> {incrfont 1}
2826 bind . <$M1B-KP_Add> {incrfont 1}
2827 bind . <$M1B-minus> {incrfont -1}
2828 bind . <$M1B-KP_Subtract> {incrfont -1}
2829 wm protocol . WM_DELETE_WINDOW doquit
2830 bind . <Destroy> {stop_backends}
2831 bind . <Button-1> "click %W"
2832 bind $fstring <Key-Return> {dofind 1 1}
2833 bind $sha1entry <Key-Return> {gotocommit; break}
2834 bind $sha1entry <<PasteSelection>> clearsha1
2835 bind $sha1entry <<Paste>> clearsha1
2836 bind $cflist <1> {sel_flist %W %x %y; break}
2837 bind $cflist <B1-Motion> {sel_flist %W %x %y; break}
2838 bind $cflist <ButtonRelease-1> {treeclick %W %x %y}
2839 global ctxbut
2840 bind $cflist $ctxbut {pop_flist_menu %W %X %Y %x %y}
2841 bind $ctext $ctxbut {pop_diff_menu %W %X %Y %x %y}
2842 bind $ctext <Button-1> {focus %W}
2843 bind $ctext <<Selection>> rehighlight_search_results
2844 for {set i 1} {$i < 10} {incr i} {
2845 bind . <$M1B-Key-$i> [list go_to_parent $i]
2846 }
2847
2848 set maincursor [. cget -cursor]
2849 set textcursor [$ctext cget -cursor]
2850 set curtextcursor $textcursor
2851
2852 set rowctxmenu .rowctxmenu
2853 makemenu $rowctxmenu {
2854 {mc "Diff this -> selected" command {diffvssel 0}}
2855 {mc "Diff selected -> this" command {diffvssel 1}}
2856 {mc "Make patch" command mkpatch}
2857 {mc "Create tag" command mktag}
2858 {mc "Copy commit reference" command copyreference}
2859 {mc "Write commit to file" command writecommit}
2860 {mc "Create new branch" command mkbranch}
2861 {mc "Cherry-pick this commit" command cherrypick}
2862 {mc "Reset HEAD branch to here" command resethead}
2863 {mc "Mark this commit" command markhere}
2864 {mc "Return to mark" command gotomark}
2865 {mc "Find descendant of this and mark" command find_common_desc}
2866 {mc "Compare with marked commit" command compare_commits}
2867 {mc "Diff this -> marked commit" command {diffvsmark 0}}
2868 {mc "Diff marked commit -> this" command {diffvsmark 1}}
2869 {mc "Revert this commit" command revert}
2870 }
2871 $rowctxmenu configure -tearoff 0
2872
2873 set fakerowmenu .fakerowmenu
2874 makemenu $fakerowmenu {
2875 {mc "Diff this -> selected" command {diffvssel 0}}
2876 {mc "Diff selected -> this" command {diffvssel 1}}
2877 {mc "Make patch" command mkpatch}
2878 {mc "Diff this -> marked commit" command {diffvsmark 0}}
2879 {mc "Diff marked commit -> this" command {diffvsmark 1}}
2880 }
2881 $fakerowmenu configure -tearoff 0
2882
2883 set headctxmenu .headctxmenu
2884 makemenu $headctxmenu {
2885 {mc "Check out this branch" command cobranch}
2886 {mc "Rename this branch" command mvbranch}
2887 {mc "Remove this branch" command rmbranch}
2888 {mc "Copy branch name" command {clipboard clear; clipboard append $headmenuhead}}
2889 }
2890 $headctxmenu configure -tearoff 0
2891
2892 global flist_menu
2893 set flist_menu .flistctxmenu
2894 makemenu $flist_menu {
2895 {mc "Highlight this too" command {flist_hl 0}}
2896 {mc "Highlight this only" command {flist_hl 1}}
2897 {mc "External diff" command {external_diff}}
2898 {mc "Blame parent commit" command {external_blame 1}}
2899 {mc "Copy path" command {clipboard clear; clipboard append $flist_menu_file}}
2900 }
2901 $flist_menu configure -tearoff 0
2902
2903 global diff_menu
2904 set diff_menu .diffctxmenu
2905 makemenu $diff_menu {
2906 {mc "Show origin of this line" command show_line_source}
2907 {mc "Run git gui blame on this line" command {external_blame_diff}}
2908 }
2909 $diff_menu configure -tearoff 0
2910 }
2911
2912 # Update row number label when selectedline changes
2913 proc selectedline_change {n1 n2 op} {
2914 global selectedline rownumsel
2915
2916 if {$selectedline eq {}} {
2917 set rownumsel {}
2918 } else {
2919 set rownumsel [expr {$selectedline + 1}]
2920 }
2921 }
2922
2923 # mouse-2 makes all windows scan vertically, but only the one
2924 # the cursor is in scans horizontally
2925 proc canvscan {op w x y} {
2926 global canv canv2 canv3
2927 foreach c [list $canv $canv2 $canv3] {
2928 if {$c == $w} {
2929 $c scan $op $x $y
2930 } else {
2931 $c scan $op 0 $y
2932 }
2933 }
2934 }
2935
2936 proc scrollcanv {cscroll f0 f1} {
2937 $cscroll set $f0 $f1
2938 drawvisible
2939 flushhighlights
2940 }
2941
2942 # when we make a key binding for the toplevel, make sure
2943 # it doesn't get triggered when that key is pressed in the
2944 # find string entry widget.
2945 proc bindkey {ev script} {
2946 global entries
2947 bind . $ev $script
2948 set escript [bind Entry $ev]
2949 if {$escript == {}} {
2950 set escript [bind Entry <Key>]
2951 }
2952 foreach e $entries {
2953 bind $e $ev "$escript; break"
2954 }
2955 }
2956
2957 proc bindmodfunctionkey {mod n script} {
2958 bind . <$mod-F$n> $script
2959 catch { bind . <$mod-XF86_Switch_VT_$n> $script }
2960 }
2961
2962 # set the focus back to the toplevel for any click outside
2963 # the entry widgets
2964 proc click {w} {
2965 global ctext entries
2966 foreach e [concat $entries $ctext] {
2967 if {$w == $e} return
2968 }
2969 focus .
2970 }
2971
2972 # Adjust the progress bar for a change in requested extent or canvas size
2973 proc adjustprogress {} {
2974 global progresscanv
2975 global fprogcoord
2976
2977 $progresscanv configure -value [expr {int($fprogcoord * 100)}]
2978 }
2979
2980 proc doprogupdate {} {
2981 global lastprogupdate progupdatepending
2982
2983 if {$progupdatepending} {
2984 set progupdatepending 0
2985 set lastprogupdate [clock clicks -milliseconds]
2986 update
2987 }
2988 }
2989
2990 proc config_check_tmp_exists {tries_left} {
2991 global config_file_tmp
2992
2993 if {[file exists $config_file_tmp]} {
2994 incr tries_left -1
2995 if {$tries_left > 0} {
2996 after 100 [list config_check_tmp_exists $tries_left]
2997 } else {
2998 error_popup "There appears to be a stale $config_file_tmp\
2999 file, which will prevent gitk from saving its configuration on exit.\
3000 Please remove it if it is not being used by any existing gitk process."
3001 }
3002 }
3003 }
3004
3005 proc config_init_trace {name} {
3006 global config_variable_changed config_variable_original
3007
3008 upvar #0 $name var
3009 set config_variable_changed($name) 0
3010 set config_variable_original($name) $var
3011 }
3012
3013 proc config_variable_change_cb {name name2 op} {
3014 global config_variable_changed config_variable_original
3015
3016 upvar #0 $name var
3017 if {$op eq "write" &&
3018 (![info exists config_variable_original($name)] ||
3019 $config_variable_original($name) ne $var)} {
3020 set config_variable_changed($name) 1
3021 }
3022 }
3023
3024 proc savestuff {w} {
3025 global stuffsaved
3026 global config_file config_file_tmp
3027 global config_variables config_variable_changed
3028 global viewchanged
3029
3030 upvar #0 viewname current_viewname
3031 upvar #0 viewfiles current_viewfiles
3032 upvar #0 viewargs current_viewargs
3033 upvar #0 viewargscmd current_viewargscmd
3034 upvar #0 viewperm current_viewperm
3035 upvar #0 nextviewnum current_nextviewnum
3036
3037 if {$stuffsaved} return
3038 if {![winfo viewable .]} return
3039 set remove_tmp 0
3040 if {[catch {
3041 set try_count 0
3042 while {[catch {set f [safe_open_file $config_file_tmp {WRONLY CREAT EXCL}]}]} {
3043 if {[incr try_count] > 50} {
3044 error "Unable to write config file: $config_file_tmp exists"
3045 }
3046 after 100
3047 }
3048 set remove_tmp 1
3049 if {$::tcl_platform(platform) eq {windows}} {
3050 file attributes $config_file_tmp -hidden true
3051 }
3052 if {[file exists $config_file]} {
3053 source $config_file
3054 }
3055 foreach var_name $config_variables {
3056 upvar #0 $var_name var
3057 upvar 0 $var_name old_var
3058 if {!$config_variable_changed($var_name) && [info exists old_var]} {
3059 puts $f [list set $var_name $old_var]
3060 } else {
3061 puts $f [list set $var_name $var]
3062 }
3063 }
3064
3065 puts $f "set geometry(main) [wm geometry .]"
3066 puts $f "set geometry(state) [wm state .]"
3067 puts $f "set geometry(topwidth) [winfo width .tf]"
3068 puts $f "set geometry(topheight) [winfo height .tf]"
3069 puts $f "set geometry(pwsash0) \"[.tf.histframe.pwclist sashpos 0] 1\""
3070 puts $f "set geometry(pwsash1) \"[.tf.histframe.pwclist sashpos 1] 1\""
3071 puts $f "set geometry(botwidth) [winfo width .bleft]"
3072 puts $f "set geometry(botheight) [winfo height .bleft]"
3073 unset -nocomplain geometry
3074 global geometry
3075 if {[info exists geometry(showrefs)]} {
3076 puts $f "set geometry(showrefs) $geometry(showrefs)"
3077 }
3078
3079 array set view_save {}
3080 array set views {}
3081 if {![info exists permviews]} { set permviews {} }
3082 foreach view $permviews {
3083 set view_save([lindex $view 0]) 1
3084 set views([lindex $view 0]) $view
3085 }
3086 puts -nonewline $f "set permviews {"
3087 for {set v 1} {$v < $current_nextviewnum} {incr v} {
3088 if {$viewchanged($v)} {
3089 if {$current_viewperm($v)} {
3090 set views($current_viewname($v)) [list $current_viewname($v) $current_viewfiles($v) $current_viewargs($v) $current_viewargscmd($v)]
3091 } else {
3092 set view_save($current_viewname($v)) 0
3093 }
3094 }
3095 }
3096 # write old and updated view to their places and append remaining to the end
3097 foreach view $permviews {
3098 set view_name [lindex $view 0]
3099 if {$view_save($view_name)} {
3100 puts $f "{$views($view_name)}"
3101 }
3102 unset views($view_name)
3103 }
3104 foreach view_name [array names views] {
3105 puts $f "{$views($view_name)}"
3106 }
3107 puts $f "}"
3108 close $f
3109 file rename -force $config_file_tmp $config_file
3110 set remove_tmp 0
3111 } err]} {
3112 puts "Error saving config: $err"
3113 }
3114 if {$remove_tmp} {
3115 file delete -force $config_file_tmp
3116 }
3117 set stuffsaved 1
3118 }
3119
3120 proc resizeclistpanes {win w} {
3121 global oldwidth oldsash
3122 if {[info exists oldwidth($win)]} {
3123 if {[info exists oldsash($win)]} {
3124 set s0 [lindex $oldsash($win) 0]
3125 set s1 [lindex $oldsash($win) 1]
3126 } else {
3127 set s0 [$win sashpos 0]
3128 set s1 [$win sashpos 1]
3129 }
3130 if {$w < 60} {
3131 set sash0 [expr {int($w/2 - 2)}]
3132 set sash1 [expr {int($w*5/6 - 2)}]
3133 } else {
3134 set factor [expr {1.0 * $w / $oldwidth($win)}]
3135 set sash0 [expr {int($factor * [lindex $s0 0])}]
3136 set sash1 [expr {int($factor * [lindex $s1 0])}]
3137 if {$sash0 < 30} {
3138 set sash0 30
3139 }
3140 if {$sash1 < $sash0 + 20} {
3141 set sash1 [expr {$sash0 + 20}]
3142 }
3143 if {$sash1 > $w - 10} {
3144 set sash1 [expr {$w - 10}]
3145 if {$sash0 > $sash1 - 20} {
3146 set sash0 [expr {$sash1 - 20}]
3147 }
3148 }
3149 }
3150 $win sashpos 0 $sash0
3151 $win sashpos 1 $sash1
3152 set oldsash($win) [list $sash0 $sash1]
3153 }
3154 set oldwidth($win) $w
3155 }
3156
3157 proc resizecdetpanes {win w} {
3158 global oldwidth oldsash
3159 if {[info exists oldwidth($win)]} {
3160 if {[info exists oldsash($win)]} {
3161 set s0 $oldsash($win)
3162 } else {
3163 set s0 [$win sashpos 0]
3164 }
3165 if {$w < 60} {
3166 set sash0 [expr {int($w*3/4 - 2)}]
3167 } else {
3168 set factor [expr {1.0 * $w / $oldwidth($win)}]
3169 set sash0 [expr {int($factor * [lindex $s0 0])}]
3170 if {$sash0 < 45} {
3171 set sash0 45
3172 }
3173 if {$sash0 > $w - 15} {
3174 set sash0 [expr {$w - 15}]
3175 }
3176 }
3177 $win sashpos 0 $sash0
3178 set oldsash($win) $sash0
3179 }
3180 set oldwidth($win) $w
3181 }
3182
3183 proc allcanvs args {
3184 global canv canv2 canv3
3185 eval $canv $args
3186 eval $canv2 $args
3187 eval $canv3 $args
3188 }
3189
3190 proc bindall {event action} {
3191 global canv canv2 canv3
3192 bind $canv $event $action
3193 bind $canv2 $event $action
3194 bind $canv3 $event $action
3195 }
3196
3197 proc about {} {
3198 global bgcolor
3199 set w .about
3200 if {[winfo exists $w]} {
3201 raise $w
3202 return
3203 }
3204 ttk_toplevel $w
3205 wm title $w [mc "About gitk"]
3206 make_transient $w .
3207 message $w.m -text [mc "
3208 Gitk - a commit viewer for git
3209
3210 Copyright \u00a9 2005-2016 Paul Mackerras
3211
3212 Use and redistribute under the terms of the GNU General Public License"] \
3213 -justify center -aspect 400 -border 2 -bg $bgcolor -relief groove
3214 pack $w.m -side top -fill x -padx 2 -pady 2
3215 ttk::button $w.ok -text [mc "Close"] -command "destroy $w" -default active
3216 pack $w.ok -side bottom
3217 bind $w <Visibility> "focus $w.ok"
3218 bind $w <Key-Escape> "destroy $w"
3219 bind $w <Key-Return> "destroy $w"
3220 tk::PlaceWindow $w widget .
3221 }
3222
3223 proc keys {} {
3224 global bgcolor
3225 set w .keys
3226 if {[winfo exists $w]} {
3227 raise $w
3228 return
3229 }
3230 if {[tk windowingsystem] eq {aqua}} {
3231 set M1T Cmd
3232 } else {
3233 set M1T Ctrl
3234 }
3235 ttk_toplevel $w
3236 wm title $w [mc "Gitk key bindings"]
3237 make_transient $w .
3238 message $w.m -text "
3239 [mc "Gitk key bindings:"]
3240
3241 [mc "<%s-Q> Quit" $M1T]
3242 [mc "<%s-W> Close window" $M1T]
3243 [mc "<Home> Move to first commit"]
3244 [mc "<End> Move to last commit"]
3245 [mc "<Up>, p, k Move up one commit"]
3246 [mc "<Down>, n, j Move down one commit"]
3247 [mc "<Left>, z, h Go back in history list"]
3248 [mc "<Right>, x, l Go forward in history list"]
3249 [mc "<%s-n> Go to n-th parent of current commit in history list" $M1T]
3250 [mc "<PageUp> Move up one page in commit list"]
3251 [mc "<PageDown> Move down one page in commit list"]
3252 [mc "<%s-Home> Scroll to top of commit list" $M1T]
3253 [mc "<%s-End> Scroll to bottom of commit list" $M1T]
3254 [mc "<%s-Up> Scroll commit list up one line" $M1T]
3255 [mc "<%s-Down> Scroll commit list down one line" $M1T]
3256 [mc "<%s-PageUp> Scroll commit list up one page" $M1T]
3257 [mc "<%s-PageDown> Scroll commit list down one page" $M1T]
3258 [mc "<Shift-Up> Find backwards (upwards, later commits)"]
3259 [mc "<Shift-Down> Find forwards (downwards, earlier commits)"]
3260 [mc "<Delete>, b Scroll diff view up one page"]
3261 [mc "<Backspace> Scroll diff view up one page"]
3262 [mc "<Space> Scroll diff view down one page"]
3263 [mc "u Scroll diff view up 18 lines"]
3264 [mc "d Scroll diff view down 18 lines"]
3265 [mc "<%s-F> Find" $M1T]
3266 [mc "<%s-G> Move to next find hit" $M1T]
3267 [mc "<Return> Move to next find hit"]
3268 [mc "g Go to commit"]
3269 [mc "/ Focus the search box"]
3270 [mc "? Move to previous find hit"]
3271 [mc "f Scroll diff view to next file"]
3272 [mc "<%s-S> Search for next hit in diff view" $M1T]
3273 [mc "<%s-R> Search for previous hit in diff view" $M1T]
3274 [mc "<%s-KP+> Increase font size" $M1T]
3275 [mc "<%s-plus> Increase font size" $M1T]
3276 [mc "<%s-KP-> Decrease font size" $M1T]
3277 [mc "<%s-minus> Decrease font size" $M1T]
3278 [mc "<F5> Update"]
3279 " \
3280 -justify left -bg $bgcolor -border 2 -relief groove
3281 pack $w.m -side top -fill both -padx 2 -pady 2
3282 ttk::button $w.ok -text [mc "Close"] -command "destroy $w" -default active
3283 bind $w <Key-Escape> [list destroy $w]
3284 pack $w.ok -side bottom
3285 bind $w <Visibility> "focus $w.ok"
3286 bind $w <Key-Escape> "destroy $w"
3287 bind $w <Key-Return> "destroy $w"
3288 }
3289
3290 # Procedures for manipulating the file list window at the
3291 # bottom right of the overall window.
3292
3293 proc treeview {w l openlevs} {
3294 global treecontents treediropen treeheight treeparent treeindex
3295
3296 set ix 0
3297 set treeindex() 0
3298 set lev 0
3299 set prefix {}
3300 set prefixend -1
3301 set prefendstack {}
3302 set htstack {}
3303 set ht 0
3304 set treecontents() {}
3305 $w conf -state normal
3306 foreach f $l {
3307 while {[string range $f 0 $prefixend] ne $prefix} {
3308 if {$lev <= $openlevs} {
3309 $w mark set e:$treeindex($prefix) "end -1c"
3310 $w mark gravity e:$treeindex($prefix) left
3311 }
3312 set treeheight($prefix) $ht
3313 incr ht [lindex $htstack end]
3314 set htstack [lreplace $htstack end end]
3315 set prefixend [lindex $prefendstack end]
3316 set prefendstack [lreplace $prefendstack end end]
3317 set prefix [string range $prefix 0 $prefixend]
3318 incr lev -1
3319 }
3320 set tail [string range $f [expr {$prefixend+1}] end]
3321 while {[set slash [string first "/" $tail]] >= 0} {
3322 lappend htstack $ht
3323 set ht 0
3324 lappend prefendstack $prefixend
3325 incr prefixend [expr {$slash + 1}]
3326 set d [string range $tail 0 $slash]
3327 lappend treecontents($prefix) $d
3328 set oldprefix $prefix
3329 append prefix $d
3330 set treecontents($prefix) {}
3331 set treeindex($prefix) [incr ix]
3332 set treeparent($prefix) $oldprefix
3333 set tail [string range $tail [expr {$slash+1}] end]
3334 if {$lev <= $openlevs} {
3335 set ht 1
3336 set treediropen($prefix) [expr {$lev < $openlevs}]
3337 set bm [expr {$lev == $openlevs? "tri-rt": "tri-dn"}]
3338 $w mark set d:$ix "end -1c"
3339 $w mark gravity d:$ix left
3340 set str "\n"
3341 for {set i 0} {$i < $lev} {incr i} {append str "\t"}
3342 $w insert end $str
3343 $w image create end -align center -image $bm -padx 1 \
3344 -name a:$ix
3345 $w insert end $d [highlight_tag $prefix]
3346 $w mark set s:$ix "end -1c"
3347 $w mark gravity s:$ix left
3348 }
3349 incr lev
3350 }
3351 if {$tail ne {}} {
3352 if {$lev <= $openlevs} {
3353 incr ht
3354 set str "\n"
3355 for {set i 0} {$i < $lev} {incr i} {append str "\t"}
3356 $w insert end $str
3357 $w insert end $tail [highlight_tag $f]
3358 }
3359 lappend treecontents($prefix) $tail
3360 }
3361 }
3362 while {$htstack ne {}} {
3363 set treeheight($prefix) $ht
3364 incr ht [lindex $htstack end]
3365 set htstack [lreplace $htstack end end]
3366 set prefixend [lindex $prefendstack end]
3367 set prefendstack [lreplace $prefendstack end end]
3368 set prefix [string range $prefix 0 $prefixend]
3369 }
3370 $w conf -state disabled
3371 }
3372
3373 proc linetoelt {l} {
3374 global treeheight treecontents
3375
3376 set y 2
3377 set prefix {}
3378 while {1} {
3379 foreach e $treecontents($prefix) {
3380 if {$y == $l} {
3381 return "$prefix$e"
3382 }
3383 set n 1
3384 if {[string index $e end] eq "/"} {
3385 set n $treeheight($prefix$e)
3386 if {$y + $n > $l} {
3387 append prefix $e
3388 incr y
3389 break
3390 }
3391 }
3392 incr y $n
3393 }
3394 }
3395 }
3396
3397 proc highlight_tree {y prefix} {
3398 global treeheight treecontents cflist
3399
3400 foreach e $treecontents($prefix) {
3401 set path $prefix$e
3402 if {[highlight_tag $path] ne {}} {
3403 $cflist tag add bold $y.0 "$y.0 lineend"
3404 }
3405 incr y
3406 if {[string index $e end] eq "/" && $treeheight($path) > 1} {
3407 set y [highlight_tree $y $path]
3408 }
3409 }
3410 return $y
3411 }
3412
3413 proc treeclosedir {w dir} {
3414 global treediropen treeheight treeparent treeindex
3415
3416 set ix $treeindex($dir)
3417 $w conf -state normal
3418 $w delete s:$ix e:$ix
3419 set treediropen($dir) 0
3420 $w image configure a:$ix -image tri-rt
3421 $w conf -state disabled
3422 set n [expr {1 - $treeheight($dir)}]
3423 while {$dir ne {}} {
3424 incr treeheight($dir) $n
3425 set dir $treeparent($dir)
3426 }
3427 }
3428
3429 proc treeopendir {w dir} {
3430 global treediropen treeheight treeparent treecontents treeindex
3431
3432 set ix $treeindex($dir)
3433 $w conf -state normal
3434 $w image configure a:$ix -image tri-dn
3435 $w mark set e:$ix s:$ix
3436 $w mark gravity e:$ix right
3437 set lev 0
3438 set str "\n"
3439 set n [llength $treecontents($dir)]
3440 for {set x $dir} {$x ne {}} {set x $treeparent($x)} {
3441 incr lev
3442 append str "\t"
3443 incr treeheight($x) $n
3444 }
3445 foreach e $treecontents($dir) {
3446 set de $dir$e
3447 if {[string index $e end] eq "/"} {
3448 set iy $treeindex($de)
3449 $w mark set d:$iy e:$ix
3450 $w mark gravity d:$iy left
3451 $w insert e:$ix $str
3452 set treediropen($de) 0
3453 $w image create e:$ix -align center -image tri-rt -padx 1 \
3454 -name a:$iy
3455 $w insert e:$ix $e [highlight_tag $de]
3456 $w mark set s:$iy e:$ix
3457 $w mark gravity s:$iy left
3458 set treeheight($de) 1
3459 } else {
3460 $w insert e:$ix $str
3461 $w insert e:$ix $e [highlight_tag $de]
3462 }
3463 }
3464 $w mark gravity e:$ix right
3465 $w conf -state disabled
3466 set treediropen($dir) 1
3467 set top [lindex [split [$w index @0,0] .] 0]
3468 set ht [$w cget -height]
3469 set l [lindex [split [$w index s:$ix] .] 0]
3470 if {$l < $top} {
3471 $w yview $l.0
3472 } elseif {$l + $n + 1 > $top + $ht} {
3473 set top [expr {$l + $n + 2 - $ht}]
3474 if {$l < $top} {
3475 set top $l
3476 }
3477 $w yview $top.0
3478 }
3479 }
3480
3481 proc treeclick {w x y} {
3482 global treediropen cmitmode ctext cflist cflist_top
3483
3484 if {$cmitmode ne "tree"} return
3485 if {![info exists cflist_top]} return
3486 set l [lindex [split [$w index "@$x,$y"] "."] 0]
3487 $cflist tag remove highlight $cflist_top.0 "$cflist_top.0 lineend"
3488 $cflist tag add highlight $l.0 "$l.0 lineend"
3489 set cflist_top $l
3490 if {$l == 1} {
3491 $ctext yview 1.0
3492 return
3493 }
3494 set e [linetoelt $l]
3495 if {[string index $e end] ne "/"} {
3496 showfile $e
3497 } elseif {$treediropen($e)} {
3498 treeclosedir $w $e
3499 } else {
3500 treeopendir $w $e
3501 }
3502 }
3503
3504 proc setfilelist {id} {
3505 global treefilelist cflist jump_to_here
3506
3507 treeview $cflist $treefilelist($id) 0
3508 if {$jump_to_here ne {}} {
3509 set f [lindex $jump_to_here 0]
3510 if {[lsearch -exact $treefilelist($id) $f] >= 0} {
3511 showfile $f
3512 }
3513 }
3514 }
3515
3516 image create bitmap tri-rt -background black -foreground blue -data {
3517 #define tri-rt_width 13
3518 #define tri-rt_height 13
3519 static unsigned char tri-rt_bits[] = {
3520 0x00, 0x00, 0x00, 0x00, 0x10, 0x00, 0x30, 0x00, 0x70, 0x00, 0xf0, 0x00,
3521 0xf0, 0x01, 0xf0, 0x00, 0x70, 0x00, 0x30, 0x00, 0x10, 0x00, 0x00, 0x00,
3522 0x00, 0x00};
3523 } -maskdata {
3524 #define tri-rt-mask_width 13
3525 #define tri-rt-mask_height 13
3526 static unsigned char tri-rt-mask_bits[] = {
3527 0x08, 0x00, 0x18, 0x00, 0x38, 0x00, 0x78, 0x00, 0xf8, 0x00, 0xf8, 0x01,
3528 0xf8, 0x03, 0xf8, 0x01, 0xf8, 0x00, 0x78, 0x00, 0x38, 0x00, 0x18, 0x00,
3529 0x08, 0x00};
3530 }
3531 image create bitmap tri-dn -background black -foreground blue -data {
3532 #define tri-dn_width 13
3533 #define tri-dn_height 13
3534 static unsigned char tri-dn_bits[] = {
3535 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0xfc, 0x07, 0xf8, 0x03,
3536 0xf0, 0x01, 0xe0, 0x00, 0x40, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
3537 0x00, 0x00};
3538 } -maskdata {
3539 #define tri-dn-mask_width 13
3540 #define tri-dn-mask_height 13
3541 static unsigned char tri-dn-mask_bits[] = {
3542 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0xff, 0x1f, 0xfe, 0x0f, 0xfc, 0x07,
3543 0xf8, 0x03, 0xf0, 0x01, 0xe0, 0x00, 0x40, 0x00, 0x00, 0x00, 0x00, 0x00,
3544 0x00, 0x00};
3545 }
3546
3547 image create bitmap reficon-T -background black -foreground yellow -data {
3548 #define tagicon_width 13
3549 #define tagicon_height 9
3550 static unsigned char tagicon_bits[] = {
3551 0x00, 0x00, 0x00, 0x00, 0xf0, 0x07, 0xf8, 0x07,
3552 0xfc, 0x07, 0xf8, 0x07, 0xf0, 0x07, 0x00, 0x00, 0x00, 0x00};
3553 } -maskdata {
3554 #define tagicon-mask_width 13
3555 #define tagicon-mask_height 9
3556 static unsigned char tagicon-mask_bits[] = {
3557 0x00, 0x00, 0xf0, 0x0f, 0xf8, 0x0f, 0xfc, 0x0f,
3558 0xfe, 0x0f, 0xfc, 0x0f, 0xf8, 0x0f, 0xf0, 0x0f, 0x00, 0x00};
3559 }
3560 set rectdata {
3561 #define headicon_width 13
3562 #define headicon_height 9
3563 static unsigned char headicon_bits[] = {
3564 0x00, 0x00, 0x00, 0x00, 0xf8, 0x07, 0xf8, 0x07,
3565 0xf8, 0x07, 0xf8, 0x07, 0xf8, 0x07, 0x00, 0x00, 0x00, 0x00};
3566 }
3567 set rectmask {
3568 #define headicon-mask_width 13
3569 #define headicon-mask_height 9
3570 static unsigned char headicon-mask_bits[] = {
3571 0x00, 0x00, 0xfc, 0x0f, 0xfc, 0x0f, 0xfc, 0x0f,
3572 0xfc, 0x0f, 0xfc, 0x0f, 0xfc, 0x0f, 0xfc, 0x0f, 0x00, 0x00};
3573 }
3574 image create bitmap reficon-H -background black -foreground "#00ff00" \
3575 -data $rectdata -maskdata $rectmask
3576 image create bitmap reficon-R -background black -foreground "#ffddaa" \
3577 -data $rectdata -maskdata $rectmask
3578 image create bitmap reficon-o -background black -foreground "#ddddff" \
3579 -data $rectdata -maskdata $rectmask
3580
3581 proc init_flist {first} {
3582 global cflist cflist_top difffilestart
3583
3584 $cflist conf -state normal
3585 $cflist delete 0.0 end
3586 if {$first ne {}} {
3587 $cflist insert end $first
3588 set cflist_top 1
3589 $cflist tag add highlight 1.0 "1.0 lineend"
3590 } else {
3591 unset -nocomplain cflist_top
3592 }
3593 $cflist conf -state disabled
3594 set difffilestart {}
3595 }
3596
3597 proc highlight_tag {f} {
3598 global highlight_paths
3599
3600 foreach p $highlight_paths {
3601 if {[string match $p $f]} {
3602 return "bold"
3603 }
3604 }
3605 return {}
3606 }
3607
3608 proc highlight_filelist {} {
3609 global cmitmode cflist
3610
3611 $cflist conf -state normal
3612 if {$cmitmode ne "tree"} {
3613 set end [lindex [split [$cflist index end] .] 0]
3614 for {set l 2} {$l < $end} {incr l} {
3615 set line [$cflist get $l.0 "$l.0 lineend"]
3616 if {[highlight_tag $line] ne {}} {
3617 $cflist tag add bold $l.0 "$l.0 lineend"
3618 }
3619 }
3620 } else {
3621 highlight_tree 2 {}
3622 }
3623 $cflist conf -state disabled
3624 }
3625
3626 proc unhighlight_filelist {} {
3627 global cflist
3628
3629 $cflist conf -state normal
3630 $cflist tag remove bold 1.0 end
3631 $cflist conf -state disabled
3632 }
3633
3634 proc add_flist {fl} {
3635 global cflist
3636
3637 $cflist conf -state normal
3638 foreach f $fl {
3639 $cflist insert end "\n"
3640 $cflist insert end $f [highlight_tag $f]
3641 }
3642 $cflist conf -state disabled
3643 }
3644
3645 proc sel_flist {w x y} {
3646 global ctext difffilestart cflist cflist_top cmitmode
3647
3648 if {$cmitmode eq "tree"} return
3649 if {![info exists cflist_top]} return
3650 set l [lindex [split [$w index "@$x,$y"] "."] 0]
3651 $cflist tag remove highlight $cflist_top.0 "$cflist_top.0 lineend"
3652 $cflist tag add highlight $l.0 "$l.0 lineend"
3653 set cflist_top $l
3654 if {$l == 1} {
3655 $ctext yview 1.0
3656 } else {
3657 catch {$ctext yview [lindex $difffilestart [expr {$l - 2}]]}
3658 }
3659 suppress_highlighting_file_for_current_scrollpos
3660 }
3661
3662 proc pop_flist_menu {w X Y x y} {
3663 global ctext cflist cmitmode flist_menu flist_menu_file
3664 global treediffs diffids
3665
3666 stopfinding
3667 set l [lindex [split [$w index "@$x,$y"] "."] 0]
3668 if {$l <= 1} return
3669 if {$cmitmode eq "tree"} {
3670 set e [linetoelt $l]
3671 if {[string index $e end] eq "/"} return
3672 } else {
3673 set e [lindex $treediffs($diffids) [expr {$l-2}]]
3674 }
3675 set flist_menu_file $e
3676 set xdiffstate "normal"
3677 if {$cmitmode eq "tree"} {
3678 set xdiffstate "disabled"
3679 }
3680 # Disable "External diff" item in tree mode
3681 $flist_menu entryconf 2 -state $xdiffstate
3682 tk_popup $flist_menu $X $Y
3683 }
3684
3685 proc find_ctext_fileinfo {line} {
3686 global ctext_file_names ctext_file_lines
3687
3688 set ok [bsearch $ctext_file_lines $line]
3689 set tline [lindex $ctext_file_lines $ok]
3690
3691 if {$ok >= [llength $ctext_file_lines] || $line < $tline} {
3692 return {}
3693 } else {
3694 return [list [lindex $ctext_file_names $ok] $tline]
3695 }
3696 }
3697
3698 proc pop_diff_menu {w X Y x y} {
3699 global ctext diff_menu flist_menu_file
3700 global diff_menu_txtpos diff_menu_line
3701 global diff_menu_filebase
3702
3703 set diff_menu_txtpos [split [$w index "@$x,$y"] "."]
3704 set diff_menu_line [lindex $diff_menu_txtpos 0]
3705 # don't pop up the menu on hunk-separator or file-separator lines
3706 if {[lsearch -glob [$ctext tag names $diff_menu_line.0] "*sep"] >= 0} {
3707 return
3708 }
3709 stopfinding
3710 set f [find_ctext_fileinfo $diff_menu_line]
3711 if {$f eq {}} return
3712 set flist_menu_file [lindex $f 0]
3713 set diff_menu_filebase [lindex $f 1]
3714 tk_popup $diff_menu $X $Y
3715 }
3716
3717 proc flist_hl {only} {
3718 global flist_menu_file findstring gdttype
3719
3720 set x [shellquote $flist_menu_file]
3721 if {$only || $findstring eq {} || $gdttype ne [mc "touching paths:"]} {
3722 set findstring $x
3723 } else {
3724 append findstring " " $x
3725 }
3726 set gdttype [mc "touching paths:"]
3727 }
3728
3729 proc gitknewtmpdir {} {
3730 global diffnum gitktmpdir gitdir env
3731
3732 if {![info exists gitktmpdir]} {
3733 if {[info exists env(GITK_TMPDIR)]} {
3734 set tmpdir $env(GITK_TMPDIR)
3735 } elseif {[info exists env(TMPDIR)]} {
3736 set tmpdir $env(TMPDIR)
3737 } else {
3738 set tmpdir $gitdir
3739 }
3740 set gitktmpformat [file join $tmpdir ".gitk-tmp.XXXXXX"]
3741 if {[catch {set gitktmpdir [safe_exec [list mktemp -d $gitktmpformat]]}]} {
3742 set gitktmpdir [file join $gitdir [format ".gitk-tmp.%s" [pid]]]
3743 }
3744 if {[catch {file mkdir $gitktmpdir} err]} {
3745 error_popup "[mc "Error creating temporary directory %s:" $gitktmpdir] $err"
3746 unset gitktmpdir
3747 return {}
3748 }
3749 set diffnum 0
3750 }
3751 incr diffnum
3752 set diffdir [file join $gitktmpdir $diffnum]
3753 if {[catch {file mkdir $diffdir} err]} {
3754 error_popup "[mc "Error creating temporary directory %s:" $diffdir] $err"
3755 return {}
3756 }
3757 return $diffdir
3758 }
3759
3760 proc save_file_from_commit {filename output what} {
3761 global nullfile
3762
3763 if {[catch {safe_exec_redirect [list git show $filename --] [list > $output]} err]} {
3764 if {[string match "fatal: bad revision *" $err]} {
3765 return $nullfile
3766 }
3767 error_popup "[mc "Error getting \"%s\" from %s:" $filename $what] $err"
3768 return {}
3769 }
3770 return $output
3771 }
3772
3773 proc external_diff_get_one_file {diffid filename diffdir} {
3774 global nullid nullid2 nullfile
3775 global worktree
3776
3777 if {$diffid == $nullid} {
3778 set difffile [file join $worktree $filename]
3779 if {[file exists $difffile]} {
3780 return $difffile
3781 }
3782 return $nullfile
3783 }
3784 if {$diffid == $nullid2} {
3785 set difffile [file join $diffdir "\[index\] [file tail $filename]"]
3786 return [save_file_from_commit :$filename $difffile index]
3787 }
3788 set difffile [file join $diffdir "\[$diffid\] [file tail $filename]"]
3789 return [save_file_from_commit $diffid:$filename $difffile \
3790 "revision $diffid"]
3791 }
3792
3793 proc check_for_renames_in_diff {filepath} { # renames
3794 global difffilestart ctext
3795
3796 set filename [file tail $filepath]
3797 set renames {}
3798
3799 foreach loc $difffilestart {
3800 set loclineend [string map {.0 .end} $loc]
3801 set fromlineloc "$loc + 2 lines"
3802 set tolineloc "$loc + 3 lines"
3803 set renfromline [$ctext get $fromlineloc [string map {.0 .end} $fromlineloc]]
3804 set rentoline [$ctext get $tolineloc [string map {.0 .end} $tolineloc]]
3805 if {[string equal -length 12 "rename from " $renfromline]
3806 && [string equal -length 10 "rename to " $rentoline]} {
3807 set renfrom [string range $renfromline 12 end]
3808 set rento [string range $rentoline 10 end]
3809 if {[string first $filename $renfrom] != -1
3810 || [string first $filename $rento] != -1} {
3811 lappend renames $renfrom
3812 lappend renames $rento
3813 break
3814 }
3815 }
3816 }
3817
3818 return $renames
3819 }
3820
3821 proc external_diff {} {
3822 global nullid nullid2
3823 global flist_menu_file
3824 global diffids
3825 global extdifftool
3826
3827 if {[llength $diffids] == 1} {
3828 # no reference commit given
3829 set diffidto [lindex $diffids 0]
3830 if {$diffidto eq $nullid} {
3831 # diffing working copy with index
3832 set diffidfrom $nullid2
3833 } elseif {$diffidto eq $nullid2} {
3834 # diffing index with HEAD
3835 set diffidfrom "HEAD"
3836 } else {
3837 # use first parent commit
3838 global parentlist selectedline
3839 set diffidfrom [lindex $parentlist $selectedline 0]
3840 }
3841 } else {
3842 set diffidfrom [lindex $diffids 0]
3843 set diffidto [lindex $diffids 1]
3844 }
3845
3846 # make sure that several diffs wont collide
3847 set diffdir [gitknewtmpdir]
3848 if {$diffdir eq {}} return
3849
3850 # gather files to diff
3851 set renames [check_for_renames_in_diff $flist_menu_file]
3852 set renamefrom [lindex $renames 0]
3853 set renameto [lindex $renames 1]
3854 if {$renamefrom ne {} && $renameto ne {}} {
3855 set difffromfile [external_diff_get_one_file $diffidfrom $renamefrom $diffdir]
3856 set difftofile [external_diff_get_one_file $diffidto $renameto $diffdir]
3857 } else {
3858 set difffromfile [external_diff_get_one_file $diffidfrom $flist_menu_file $diffdir]
3859 set difftofile [external_diff_get_one_file $diffidto $flist_menu_file $diffdir]
3860 }
3861
3862 if {$difffromfile ne {} && $difftofile ne {}} {
3863 set cmd [list [shellsplit $extdifftool] $difffromfile $difftofile]
3864 if {[catch {set fl [safe_open_command $cmd]} err]} {
3865 file delete -force $diffdir
3866 error_popup "$extdifftool: [mc "command failed:"] $err"
3867 } else {
3868 fconfigure $fl -blocking 0
3869 filerun $fl [list delete_at_eof $fl $diffdir]
3870 }
3871 }
3872 }
3873
3874 proc find_hunk_blamespec {base line} {
3875 global ctext
3876
3877 # Find and parse the hunk header
3878 set s_lix [$ctext search -backwards -regexp ^@@ "$line.0 lineend" $base.0]
3879 if {$s_lix eq {}} return
3880
3881 set s_line [$ctext get $s_lix "$s_lix + 1 lines"]
3882 if {![regexp {^@@@*(( -\d+(,\d+)?)+) \+(\d+)(,\d+)? @@} $s_line \
3883 s_line old_specs osz osz1 new_line nsz]} {
3884 return
3885 }
3886
3887 # base lines for the parents
3888 set base_lines [list $new_line]
3889 foreach old_spec [lrange [split $old_specs " "] 1 end] {
3890 if {![regexp -- {-(\d+)(,\d+)?} $old_spec \
3891 old_spec old_line osz]} {
3892 return
3893 }
3894 lappend base_lines $old_line
3895 }
3896
3897 # Now scan the lines to determine offset within the hunk
3898 set max_parent [expr {[llength $base_lines]-2}]
3899 set dline 0
3900 set s_lno [lindex [split $s_lix "."] 0]
3901
3902 # Determine if the line is removed
3903 set chunk [$ctext get $line.0 "$line.1 + $max_parent chars"]
3904 if {[string match {[-+ ]*} $chunk]} {
3905 set removed_idx [string first "-" $chunk]
3906 # Choose a parent index
3907 if {$removed_idx >= 0} {
3908 set parent $removed_idx
3909 } else {
3910 set unchanged_idx [string first " " $chunk]
3911 if {$unchanged_idx >= 0} {
3912 set parent $unchanged_idx
3913 } else {
3914 # blame the current commit
3915 set parent -1
3916 }
3917 }
3918 # then count other lines that belong to it
3919 for {set i $line} {[incr i -1] > $s_lno} {} {
3920 set chunk [$ctext get $i.0 "$i.1 + $max_parent chars"]
3921 # Determine if the line is removed
3922 set removed_idx [string first "-" $chunk]
3923 if {$parent >= 0} {
3924 set code [string index $chunk $parent]
3925 if {$code eq "-" || ($removed_idx < 0 && $code ne "+")} {
3926 incr dline
3927 }
3928 } else {
3929 if {$removed_idx < 0} {
3930 incr dline
3931 }
3932 }
3933 }
3934 incr parent
3935 } else {
3936 set parent 0
3937 }
3938
3939 incr dline [lindex $base_lines $parent]
3940 return [list $parent $dline]
3941 }
3942
3943 proc external_blame_diff {} {
3944 global currentid cmitmode
3945 global diff_menu_txtpos diff_menu_line
3946 global diff_menu_filebase flist_menu_file
3947
3948 if {$cmitmode eq "tree"} {
3949 set parent_idx 0
3950 set line [expr {$diff_menu_line - $diff_menu_filebase}]
3951 } else {
3952 set hinfo [find_hunk_blamespec $diff_menu_filebase $diff_menu_line]
3953 if {$hinfo ne {}} {
3954 set parent_idx [lindex $hinfo 0]
3955 set line [lindex $hinfo 1]
3956 } else {
3957 set parent_idx 0
3958 set line 0
3959 }
3960 }
3961
3962 external_blame $parent_idx $line
3963 }
3964
3965 # Find the SHA1 ID of the blob for file $fname in the index
3966 # at stage 0 or 2
3967 proc index_sha1 {fname} {
3968 set f [safe_open_command [list git ls-files -s $fname]]
3969 while {[gets $f line] >= 0} {
3970 set info [lindex [split $line "\t"] 0]
3971 set stage [lindex $info 2]
3972 if {$stage eq "0" || $stage eq "2"} {
3973 close $f
3974 return [lindex $info 1]
3975 }
3976 }
3977 close $f
3978 return {}
3979 }
3980
3981 # Turn an absolute path into one relative to the current directory
3982 proc make_relative {f} {
3983 if {[file pathtype $f] eq "relative"} {
3984 return $f
3985 }
3986 set elts [file split $f]
3987 set here [file split [pwd]]
3988 set ei 0
3989 set hi 0
3990 set res {}
3991 foreach d $here {
3992 if {$ei < $hi || $ei >= [llength $elts] || [lindex $elts $ei] ne $d} {
3993 lappend res ".."
3994 } else {
3995 incr ei
3996 }
3997 incr hi
3998 }
3999 set elts [concat $res [lrange $elts $ei end]]
4000 return [eval file join $elts]
4001 }
4002
4003 proc external_blame {parent_idx {line {}}} {
4004 global flist_menu_file cdup
4005 global nullid nullid2
4006 global parentlist selectedline currentid
4007
4008 if {$parent_idx > 0} {
4009 set base_commit [lindex $parentlist $selectedline [expr {$parent_idx-1}]]
4010 } else {
4011 set base_commit $currentid
4012 }
4013
4014 if {$base_commit eq {} || $base_commit eq $nullid || $base_commit eq $nullid2} {
4015 error_popup [mc "No such commit"]
4016 return
4017 }
4018
4019 set cmdline [list git gui blame]
4020 if {$line ne {} && $line > 1} {
4021 lappend cmdline "--line=$line"
4022 }
4023 set f [file join $cdup $flist_menu_file]
4024 # Unfortunately it seems git gui blame doesn't like
4025 # being given an absolute path...
4026 set f [make_relative $f]
4027 lappend cmdline $base_commit $f
4028 if {[catch {safe_exec_redirect $cmdline [list &]} err]} {
4029 error_popup "[mc "git gui blame: command failed:"] $err"
4030 }
4031 }
4032
4033 proc show_line_source {} {
4034 global cmitmode currentid parents curview blamestuff blameinst
4035 global diff_menu_line diff_menu_filebase flist_menu_file
4036 global nullid nullid2 gitdir cdup
4037
4038 set from_index {}
4039 if {$cmitmode eq "tree"} {
4040 set id $currentid
4041 set line [expr {$diff_menu_line - $diff_menu_filebase}]
4042 } else {
4043 set h [find_hunk_blamespec $diff_menu_filebase $diff_menu_line]
4044 if {$h eq {}} return
4045 set pi [lindex $h 0]
4046 if {$pi == 0} {
4047 mark_ctext_line $diff_menu_line
4048 return
4049 }
4050 incr pi -1
4051 if {$currentid eq $nullid} {
4052 if {$pi > 0} {
4053 # must be a merge in progress...
4054 if {[catch {
4055 # get the last line from .git/MERGE_HEAD
4056 set f [safe_open_file [file join $gitdir MERGE_HEAD] r]
4057 set id [lindex [split [read $f] "\n"] end-1]
4058 close $f
4059 } err]} {
4060 error_popup [mc "Couldn't read merge head: %s" $err]
4061 return
4062 }
4063 } elseif {$parents($curview,$currentid) eq $nullid2} {
4064 # need to do the blame from the index
4065 if {[catch {
4066 set from_index [index_sha1 $flist_menu_file]
4067 } err]} {
4068 error_popup [mc "Error reading index: %s" $err]
4069 return
4070 }
4071 } else {
4072 set id $parents($curview,$currentid)
4073 }
4074 } else {
4075 set id [lindex $parents($curview,$currentid) $pi]
4076 }
4077 set line [lindex $h 1]
4078 }
4079 set blamefile [file join $cdup $flist_menu_file]
4080 if {$from_index ne {}} {
4081 set blameargs [list \
4082 [list git cat-file blob $from_index] \
4083 [list git blame -p -L$line,+1 --contents - -- $blamefile]]
4084 } else {
4085 set blameargs [list \
4086 [list git blame -p -L$line,+1 $id -- $blamefile]]
4087 }
4088 if {[catch {
4089 set f [safe_open_pipeline $blameargs]
4090 } err]} {
4091 error_popup [mc "Couldn't start git blame: %s" $err]
4092 return
4093 }
4094 nowbusy blaming [mc "Searching"]
4095 fconfigure $f -blocking 0
4096 set i [reg_instance $f]
4097 set blamestuff($i) {}
4098 set blameinst $i
4099 filerun $f [list read_line_source $f $i]
4100 }
4101
4102 proc stopblaming {} {
4103 global blameinst
4104
4105 if {[info exists blameinst]} {
4106 stop_instance $blameinst
4107 unset blameinst
4108 notbusy blaming
4109 }
4110 }
4111
4112 proc read_line_source {fd inst} {
4113 global blamestuff curview commfd blameinst nullid nullid2
4114 global hashlength
4115
4116 while {[gets $fd line] >= 0} {
4117 lappend blamestuff($inst) $line
4118 }
4119 if {![eof $fd]} {
4120 return 1
4121 }
4122 unset commfd($inst)
4123 unset blameinst
4124 notbusy blaming
4125 fconfigure $fd -blocking 1
4126 if {[catch {close $fd} err]} {
4127 error_popup [mc "Error running git blame: %s" $err]
4128 return 0
4129 }
4130
4131 set fname {}
4132 set line [split [lindex $blamestuff($inst) 0] " "]
4133 set id [lindex $line 0]
4134 set lnum [lindex $line 1]
4135 if {[string length $id] == $hashlength && [string is xdigit $id] &&
4136 [string is digit -strict $lnum]} {
4137 # look for "filename" line
4138 foreach l $blamestuff($inst) {
4139 if {[string match "filename *" $l]} {
4140 set fname [string range $l 9 end]
4141 break
4142 }
4143 }
4144 }
4145 if {$fname ne {}} {
4146 # all looks good, select it
4147 if {$id eq $nullid} {
4148 # blame uses all-zeroes to mean not committed,
4149 # which would mean a change in the index
4150 set id $nullid2
4151 }
4152 if {[commitinview $id $curview]} {
4153 selectline [rowofcommit $id] 1 [list $fname $lnum] 1
4154 } else {
4155 error_popup [mc "That line comes from commit %s, \
4156 which is not in this view" [shortids $id]]
4157 }
4158 } else {
4159 puts "oops couldn't parse git blame output"
4160 }
4161 return 0
4162 }
4163
4164 # delete $dir when we see eof on $f (presumably because the child has exited)
4165 proc delete_at_eof {f dir} {
4166 while {[gets $f line] >= 0} {}
4167 if {[eof $f]} {
4168 if {[catch {close $f} err]} {
4169 error_popup "[mc "External diff viewer failed:"] $err"
4170 }
4171 file delete -force $dir
4172 return 0
4173 }
4174 return 1
4175 }
4176
4177 # Functions for adding and removing shell-type quoting
4178
4179 proc shellquote {str} {
4180 if {![string match "*\['\"\\ \t]*" $str]} {
4181 return $str
4182 }
4183 if {![string match "*\['\"\\]*" $str]} {
4184 return "\"$str\""
4185 }
4186 if {![string match "*'*" $str]} {
4187 return "'$str'"
4188 }
4189 return "\"[string map {\" \\\" \\ \\\\} $str]\""
4190 }
4191
4192 proc shellarglist {l} {
4193 set str {}
4194 foreach a $l {
4195 if {$str ne {}} {
4196 append str " "
4197 }
4198 append str [shellquote $a]
4199 }
4200 return $str
4201 }
4202
4203 proc shelldequote {str} {
4204 set ret {}
4205 set used -1
4206 while {1} {
4207 incr used
4208 if {![regexp -start $used -indices "\['\"\\\\ \t]" $str first]} {
4209 append ret [string range $str $used end]
4210 set used [string length $str]
4211 break
4212 }
4213 set first [lindex $first 0]
4214 set ch [string index $str $first]
4215 if {$first > $used} {
4216 append ret [string range $str $used [expr {$first - 1}]]
4217 set used $first
4218 }
4219 if {$ch eq " " || $ch eq "\t"} break
4220 incr used
4221 if {$ch eq "'"} {
4222 set first [string first "'" $str $used]
4223 if {$first < 0} {
4224 error "unmatched single-quote"
4225 }
4226 append ret [string range $str $used [expr {$first - 1}]]
4227 set used $first
4228 continue
4229 }
4230 if {$ch eq "\\"} {
4231 if {$used >= [string length $str]} {
4232 error "trailing backslash"
4233 }
4234 append ret [string index $str $used]
4235 continue
4236 }
4237 # here ch == "\""
4238 while {1} {
4239 if {![regexp -start $used -indices "\[\"\\\\]" $str first]} {
4240 error "unmatched double-quote"
4241 }
4242 set first [lindex $first 0]
4243 set ch [string index $str $first]
4244 if {$first > $used} {
4245 append ret [string range $str $used [expr {$first - 1}]]
4246 set used $first
4247 }
4248 if {$ch eq "\""} break
4249 incr used
4250 append ret [string index $str $used]
4251 incr used
4252 }
4253 }
4254 return [list $used $ret]
4255 }
4256
4257 proc shellsplit {str} {
4258 set l {}
4259 while {1} {
4260 set str [string trimleft $str]
4261 if {$str eq {}} break
4262 set dq [shelldequote $str]
4263 set n [lindex $dq 0]
4264 set word [lindex $dq 1]
4265 set str [string range $str $n end]
4266 lappend l $word
4267 }
4268 return $l
4269 }
4270
4271 proc set_window_title {} {
4272 global appname curview viewname vrevs
4273 set rev [mc "All files"]
4274 if {$curview ne 0} {
4275 if {$viewname($curview) eq [mc "Command line"]} {
4276 set rev [string map {"--gitk-symmetric-diff-marker" "--merge"} $vrevs($curview)]
4277 } else {
4278 set rev $viewname($curview)
4279 }
4280 }
4281 wm title . "[reponame]: $rev - $appname"
4282 }
4283
4284 # Code to implement multiple views
4285
4286 proc newview {ishighlight} {
4287 global nextviewnum newviewname newishighlight
4288 global revtreeargs viewargscmd newviewopts curview
4289
4290 set newishighlight $ishighlight
4291 set top .gitkview
4292 if {[winfo exists $top]} {
4293 raise $top
4294 return
4295 }
4296 decode_view_opts $nextviewnum $revtreeargs
4297 set newviewname($nextviewnum) "[mc "View"] $nextviewnum"
4298 set newviewopts($nextviewnum,perm) 0
4299 set newviewopts($nextviewnum,cmd) $viewargscmd($curview)
4300 vieweditor $top $nextviewnum [mc "Gitk view definition"]
4301 }
4302
4303 set known_view_options {
4304 {perm b . {} {mc "Remember this view"}}
4305 {reflabel l + {} {mc "References (space separated list):"}}
4306 {refs t15 .. {} {mc "Branches & tags:"}}
4307 {allrefs b *. "--all" {mc "All refs"}}
4308 {branches b . "--branches" {mc "All (local) branches"}}
4309 {tags b . "--tags" {mc "All tags"}}
4310 {remotes b . "--remotes" {mc "All remote-tracking branches"}}
4311 {commitlbl l + {} {mc "Commit Info (regular expressions):"}}
4312 {author t15 .. "--author=*" {mc "Author:"}}
4313 {committer t15 . "--committer=*" {mc "Committer:"}}
4314 {loginfo t15 .. "--grep=*" {mc "Commit Message:"}}
4315 {allmatch b .. "--all-match" {mc "Matches all Commit Info criteria"}}
4316 {igrep b .. "--invert-grep" {mc "Matches no Commit Info criteria"}}
4317 {changes_l l + {} {mc "Changes to Files:"}}
4318 {pickaxe_s r0 . {} {mc "Fixed String"}}
4319 {pickaxe_t r1 . "--pickaxe-regex" {mc "Regular Expression"}}
4320 {pickaxe t15 .. "-S*" {mc "Search string:"}}
4321 {datelabel l + {} {mc "Commit Dates (\"2 weeks ago\", \"2009-03-17 15:27:38\", \"March 17, 2009 15:27:38\"):"}}
4322 {since t15 .. {"--since=*" "--after=*"} {mc "Since:"}}
4323 {until t15 . {"--until=*" "--before=*"} {mc "Until:"}}
4324 {limit_lbl l + {} {mc "Limit and/or skip a number of revisions (positive integer):"}}
4325 {limit t10 *. "--max-count=*" {mc "Number to show:"}}
4326 {skip t10 . "--skip=*" {mc "Number to skip:"}}
4327 {misc_lbl l + {} {mc "Miscellaneous options:"}}
4328 {dorder b *. {"--date-order" "-d"} {mc "Strictly sort by date"}}
4329 {lright b . "--left-right" {mc "Mark branch sides"}}
4330 {first b . "--first-parent" {mc "Limit to first parent"}}
4331 {smplhst b . "--simplify-by-decoration" {mc "Simple history"}}
4332 {args t50 *. {} {mc "Additional arguments to git log:"}}
4333 {allpaths path + {} {mc "Enter files and directories to include, one per line:"}}
4334 {cmd t50= + {} {mc "Command to generate more commits to include:"}}
4335 }
4336
4337 # Convert $newviewopts($n, ...) into args for git log.
4338 proc encode_view_opts {n} {
4339 global known_view_options newviewopts
4340
4341 set rargs [list]
4342 foreach opt $known_view_options {
4343 set patterns [lindex $opt 3]
4344 if {$patterns eq {}} continue
4345 set pattern [lindex $patterns 0]
4346
4347 if {[lindex $opt 1] eq "b"} {
4348 set val $newviewopts($n,[lindex $opt 0])
4349 if {$val} {
4350 lappend rargs $pattern
4351 }
4352 } elseif {[regexp {^r(\d+)$} [lindex $opt 1] type value]} {
4353 regexp {^(.*_)} [lindex $opt 0] uselessvar button_id
4354 set val $newviewopts($n,$button_id)
4355 if {$val eq $value} {
4356 lappend rargs $pattern
4357 }
4358 } else {
4359 set val $newviewopts($n,[lindex $opt 0])
4360 set val [string trim $val]
4361 if {$val ne {}} {
4362 set pfix [string range $pattern 0 end-1]
4363 lappend rargs $pfix$val
4364 }
4365 }
4366 }
4367 set rargs [concat $rargs [shellsplit $newviewopts($n,refs)]]
4368 return [concat $rargs [shellsplit $newviewopts($n,args)]]
4369 }
4370
4371 # Fill $newviewopts($n, ...) based on args for git log.
4372 proc decode_view_opts {n view_args} {
4373 global known_view_options newviewopts
4374
4375 foreach opt $known_view_options {
4376 set id [lindex $opt 0]
4377 if {[lindex $opt 1] eq "b"} {
4378 # Checkboxes
4379 set val 0
4380 } elseif {[regexp {^r(\d+)$} [lindex $opt 1]]} {
4381 # Radiobuttons
4382 regexp {^(.*_)} $id uselessvar id
4383 set val 0
4384 } else {
4385 # Text fields
4386 set val {}
4387 }
4388 set newviewopts($n,$id) $val
4389 }
4390 set oargs [list]
4391 set refargs [list]
4392 foreach arg $view_args {
4393 if {[regexp -- {^-([0-9]+)$} $arg arg cnt]
4394 && ![info exists found(limit)]} {
4395 set newviewopts($n,limit) $cnt
4396 set found(limit) 1
4397 continue
4398 }
4399 catch { unset val }
4400 foreach opt $known_view_options {
4401 set id [lindex $opt 0]
4402 if {[info exists found($id)]} continue
4403 foreach pattern [lindex $opt 3] {
4404 if {![string match $pattern $arg]} continue
4405 if {[lindex $opt 1] eq "b"} {
4406 # Check buttons
4407 set val 1
4408 } elseif {[regexp {^r(\d+)$} [lindex $opt 1] match num]} {
4409 # Radio buttons
4410 regexp {^(.*_)} $id uselessvar id
4411 set val $num
4412 } else {
4413 # Text input fields
4414 set size [string length $pattern]
4415 set val [string range $arg [expr {$size-1}] end]
4416 }
4417 set newviewopts($n,$id) $val
4418 set found($id) 1
4419 break
4420 }
4421 if {[info exists val]} break
4422 }
4423 if {[info exists val]} continue
4424 if {[regexp {^-} $arg]} {
4425 lappend oargs $arg
4426 } else {
4427 lappend refargs $arg
4428 }
4429 }
4430 set newviewopts($n,refs) [shellarglist $refargs]
4431 set newviewopts($n,args) [shellarglist $oargs]
4432 }
4433
4434 proc edit_or_newview {} {
4435 global curview
4436
4437 if {$curview > 0} {
4438 editview
4439 } else {
4440 newview 0
4441 }
4442 }
4443
4444 proc editview {} {
4445 global curview
4446 global viewname viewperm newviewname newviewopts
4447 global viewargs viewargscmd
4448
4449 set top .gitkvedit-$curview
4450 if {[winfo exists $top]} {
4451 raise $top
4452 return
4453 }
4454 decode_view_opts $curview $viewargs($curview)
4455 set newviewname($curview) $viewname($curview)
4456 set newviewopts($curview,perm) $viewperm($curview)
4457 set newviewopts($curview,cmd) $viewargscmd($curview)
4458 vieweditor $top $curview "[mc "Gitk: edit view"] $viewname($curview)"
4459 }
4460
4461 proc vieweditor {top n title} {
4462 global newviewname newviewopts viewfiles bgcolor
4463 global known_view_options
4464
4465 ttk_toplevel $top
4466 wm title $top [concat $title [mc "-- criteria for selecting revisions"]]
4467 make_transient $top .
4468
4469 # View name
4470 ttk::frame $top.nfr
4471 ttk::label $top.nl -text [mc "View Name"]
4472 ttk::entry $top.name -width 20 -textvariable newviewname($n)
4473 pack $top.nfr -in $top -fill x -pady 5 -padx 3
4474 pack $top.nl -in $top.nfr -side left -padx {0 5}
4475 pack $top.name -in $top.nfr -side left -padx {0 25}
4476
4477 # View options
4478 set cframe $top.nfr
4479 set cexpand 0
4480 set cnt 0
4481 foreach opt $known_view_options {
4482 set id [lindex $opt 0]
4483 set type [lindex $opt 1]
4484 set flags [lindex $opt 2]
4485 set title [eval [lindex $opt 4]]
4486 set lxpad 0
4487
4488 if {$flags eq "+" || $flags eq "*"} {
4489 set cframe $top.fr$cnt
4490 incr cnt
4491 ttk::frame $cframe
4492 pack $cframe -in $top -fill x -pady 3 -padx 3
4493 set cexpand [expr {$flags eq "*"}]
4494 } elseif {$flags eq ".." || $flags eq "*."} {
4495 set cframe $top.fr$cnt
4496 incr cnt
4497 ttk::frame $cframe
4498 pack $cframe -in $top -fill x -pady 3 -padx [list 15 3]
4499 set cexpand [expr {$flags eq "*."}]
4500 } else {
4501 set lxpad 5
4502 }
4503
4504 if {$type eq "l"} {
4505 ttk::label $cframe.l_$id -text $title
4506 pack $cframe.l_$id -in $cframe -side left -pady [list 3 0] -anchor w
4507 } elseif {$type eq "b"} {
4508 ttk::checkbutton $cframe.c_$id -text $title -variable newviewopts($n,$id)
4509 pack $cframe.c_$id -in $cframe -side left \
4510 -padx [list $lxpad 0] -expand $cexpand -anchor w
4511 } elseif {[regexp {^r(\d+)$} $type type sz]} {
4512 regexp {^(.*_)} $id uselessvar button_id
4513 ttk::radiobutton $cframe.c_$id -text $title -variable newviewopts($n,$button_id) -value $sz
4514 pack $cframe.c_$id -in $cframe -side left \
4515 -padx [list $lxpad 0] -expand $cexpand -anchor w
4516 } elseif {[regexp {^t(\d+)$} $type type sz]} {
4517 ttk::label $cframe.l_$id -text $title
4518 ttk::entry $cframe.e_$id -width $sz -background $bgcolor \
4519 -textvariable newviewopts($n,$id)
4520 pack $cframe.l_$id -in $cframe -side left -padx [list $lxpad 0]
4521 pack $cframe.e_$id -in $cframe -side left -expand 1 -fill x
4522 } elseif {[regexp {^t(\d+)=$} $type type sz]} {
4523 ttk::label $cframe.l_$id -text $title
4524 ttk::entry $cframe.e_$id -width $sz -background $bgcolor \
4525 -textvariable newviewopts($n,$id)
4526 pack $cframe.l_$id -in $cframe -side top -pady [list 3 0] -anchor w
4527 pack $cframe.e_$id -in $cframe -side top -fill x
4528 } elseif {$type eq "path"} {
4529 ttk::label $top.l -text $title
4530 pack $top.l -in $top -side top -pady [list 3 0] -anchor w -padx 3
4531 text $top.t -width 40 -height 5 -background $bgcolor
4532 if {[info exists viewfiles($n)]} {
4533 foreach f $viewfiles($n) {
4534 $top.t insert end $f
4535 $top.t insert end "\n"
4536 }
4537 $top.t delete {end - 1c} end
4538 $top.t mark set insert 0.0
4539 }
4540 pack $top.t -in $top -side top -pady [list 0 5] -fill both -expand 1 -padx 3
4541 }
4542 }
4543
4544 ttk::frame $top.buts
4545 ttk::button $top.buts.ok -text [mc "OK"] -command [list newviewok $top $n]
4546 ttk::button $top.buts.apply -text [mc "Apply (F5)"] -command [list newviewok $top $n 1]
4547 ttk::button $top.buts.can -text [mc "Cancel"] -command [list destroy $top]
4548 bind $top <Control-Return> [list newviewok $top $n]
4549 bind $top <F5> [list newviewok $top $n 1]
4550 bind $top <Escape> [list destroy $top]
4551 grid $top.buts.ok $top.buts.apply $top.buts.can
4552 grid columnconfigure $top.buts 0 -weight 1 -uniform a
4553 grid columnconfigure $top.buts 1 -weight 1 -uniform a
4554 grid columnconfigure $top.buts 2 -weight 1 -uniform a
4555 pack $top.buts -in $top -side top -fill x
4556 focus $top.t
4557 }
4558
4559 proc doviewmenu {m first cmd op argv} {
4560 set nmenu [$m index end]
4561 for {set i $first} {$i <= $nmenu} {incr i} {
4562 if {[$m entrycget $i -command] eq $cmd} {
4563 eval $m $op $i $argv
4564 break
4565 }
4566 }
4567 }
4568
4569 proc allviewmenus {n op args} {
4570 # global viewhlmenu
4571
4572 doviewmenu .bar.view 5 [list showview $n] $op $args
4573 # doviewmenu $viewhlmenu 1 [list addvhighlight $n] $op $args
4574 }
4575
4576 proc newviewok {top n {apply 0}} {
4577 global nextviewnum newviewperm newviewname newishighlight
4578 global viewname viewfiles viewperm viewchanged selectedview curview
4579 global viewargs viewargscmd newviewopts viewhlmenu
4580
4581 if {[catch {
4582 set newargs [encode_view_opts $n]
4583 } err]} {
4584 error_popup "[mc "Error in commit selection arguments:"] $err" $top
4585 return
4586 }
4587 set files {}
4588 foreach f [split [$top.t get 0.0 end] "\n"] {
4589 set ft [string trim $f]
4590 if {$ft ne {}} {
4591 lappend files $ft
4592 }
4593 }
4594 if {![info exists viewfiles($n)]} {
4595 # creating a new view
4596 incr nextviewnum
4597 set viewname($n) $newviewname($n)
4598 set viewperm($n) $newviewopts($n,perm)
4599 set viewchanged($n) 1
4600 set viewfiles($n) $files
4601 set viewargs($n) $newargs
4602 set viewargscmd($n) $newviewopts($n,cmd)
4603 addviewmenu $n
4604 if {!$newishighlight} {
4605 run showview $n
4606 } else {
4607 run addvhighlight $n
4608 }
4609 } else {
4610 # editing an existing view
4611 set viewperm($n) $newviewopts($n,perm)
4612 set viewchanged($n) 1
4613 if {$newviewname($n) ne $viewname($n)} {
4614 set viewname($n) $newviewname($n)
4615 doviewmenu .bar.view 5 [list showview $n] \
4616 entryconf [list -label $viewname($n)]
4617 # doviewmenu $viewhlmenu 1 [list addvhighlight $n] \
4618 # entryconf [list -label $viewname($n) -value $viewname($n)]
4619 }
4620 if {$files ne $viewfiles($n) || $newargs ne $viewargs($n) || \
4621 $newviewopts($n,cmd) ne $viewargscmd($n)} {
4622 set viewfiles($n) $files
4623 set viewargs($n) $newargs
4624 set viewargscmd($n) $newviewopts($n,cmd)
4625 if {$curview == $n} {
4626 run reloadcommits
4627 }
4628 }
4629 }
4630 if {$apply} return
4631 catch {destroy $top}
4632 }
4633
4634 proc delview {} {
4635 global curview viewperm hlview selectedhlview viewchanged
4636
4637 if {$curview == 0} return
4638 if {[info exists hlview] && $hlview == $curview} {
4639 set selectedhlview [mc "None"]
4640 unset hlview
4641 }
4642 allviewmenus $curview delete
4643 set viewperm($curview) 0
4644 set viewchanged($curview) 1
4645 showview 0
4646 }
4647
4648 proc addviewmenu {n} {
4649 global viewname viewhlmenu
4650
4651 .bar.view add radiobutton -label $viewname($n) \
4652 -command [list showview $n] -variable selectedview -value $n
4653 #$viewhlmenu add radiobutton -label $viewname($n) \
4654 # -command [list addvhighlight $n] -variable selectedhlview
4655 }
4656
4657 proc showview {n} {
4658 global curview cached_commitrow ordertok
4659 global displayorder parentlist rowidlist rowisopt rowfinal
4660 global colormap rowtextx nextcolor canvxmax
4661 global numcommits viewcomplete
4662 global selectedline currentid canv canvy0
4663 global treediffs
4664 global pending_select mainheadid
4665 global commitidx
4666 global selectedview
4667 global hlview selectedhlview commitinterest
4668
4669 if {$n == $curview} return
4670 set selid {}
4671 set ymax [lindex [$canv cget -scrollregion] 3]
4672 set span [$canv yview]
4673 set ytop [expr {[lindex $span 0] * $ymax}]
4674 set ybot [expr {[lindex $span 1] * $ymax}]
4675 set yscreen [expr {($ybot - $ytop) / 2}]
4676 if {$selectedline ne {}} {
4677 set selid $currentid
4678 set y [yc $selectedline]
4679 if {$ytop < $y && $y < $ybot} {
4680 set yscreen [expr {$y - $ytop}]
4681 }
4682 } elseif {[info exists pending_select]} {
4683 set selid $pending_select
4684 unset pending_select
4685 }
4686 unselectline
4687 normalline
4688 unset -nocomplain treediffs
4689 clear_display
4690 if {[info exists hlview] && $hlview == $n} {
4691 unset hlview
4692 set selectedhlview [mc "None"]
4693 }
4694 unset -nocomplain commitinterest
4695 unset -nocomplain cached_commitrow
4696 unset -nocomplain ordertok
4697
4698 set curview $n
4699 set selectedview $n
4700 .bar.view entryconf [mca "&Edit view..."] -state [expr {$n == 0? "disabled": "normal"}]
4701 .bar.view entryconf [mca "&Delete view"] -state [expr {$n == 0? "disabled": "normal"}]
4702
4703 run refill_reflist
4704 if {![info exists viewcomplete($n)]} {
4705 getcommits $selid
4706 return
4707 }
4708
4709 set displayorder {}
4710 set parentlist {}
4711 set rowidlist {}
4712 set rowisopt {}
4713 set rowfinal {}
4714 set numcommits $commitidx($n)
4715
4716 unset -nocomplain colormap
4717 unset -nocomplain rowtextx
4718 set nextcolor 0
4719 set canvxmax [$canv cget -width]
4720 set curview $n
4721 set row 0
4722 setcanvscroll
4723 set yf 0
4724 set row {}
4725 if {$selid ne {} && [commitinview $selid $n]} {
4726 set row [rowofcommit $selid]
4727 # try to get the selected row in the same position on the screen
4728 set ymax [lindex [$canv cget -scrollregion] 3]
4729 set ytop [expr {[yc $row] - $yscreen}]
4730 if {$ytop < 0} {
4731 set ytop 0
4732 }
4733 set yf [expr {$ytop * 1.0 / $ymax}]
4734 }
4735 allcanvs yview moveto $yf
4736 drawvisible
4737 if {$row ne {}} {
4738 selectline $row 0
4739 } elseif {!$viewcomplete($n)} {
4740 reset_pending_select $selid
4741 } else {
4742 reset_pending_select {}
4743
4744 if {[commitinview $pending_select $curview]} {
4745 selectline [rowofcommit $pending_select] 1
4746 } else {
4747 set row [first_real_row]
4748 if {$row < $numcommits} {
4749 selectline $row 0
4750 }
4751 }
4752 }
4753 if {!$viewcomplete($n)} {
4754 if {$numcommits == 0} {
4755 show_status [mc "Reading commits..."]
4756 }
4757 } elseif {$numcommits == 0} {
4758 show_status [mc "No commits selected"]
4759 }
4760 set_window_title
4761 }
4762
4763 # Stuff relating to the highlighting facility
4764
4765 proc ishighlighted {id} {
4766 global vhighlights fhighlights nhighlights rhighlights
4767
4768 if {[info exists nhighlights($id)] && $nhighlights($id) > 0} {
4769 return $nhighlights($id)
4770 }
4771 if {[info exists vhighlights($id)] && $vhighlights($id) > 0} {
4772 return $vhighlights($id)
4773 }
4774 if {[info exists fhighlights($id)] && $fhighlights($id) > 0} {
4775 return $fhighlights($id)
4776 }
4777 if {[info exists rhighlights($id)] && $rhighlights($id) > 0} {
4778 return $rhighlights($id)
4779 }
4780 return 0
4781 }
4782
4783 proc bolden {id font} {
4784 global canv linehtag currentid boldids need_redisplay markedid
4785
4786 # need_redisplay = 1 means the display is stale and about to be redrawn
4787 if {$need_redisplay} return
4788 lappend boldids $id
4789 $canv itemconf $linehtag($id) -font $font
4790 if {[info exists currentid] && $id eq $currentid} {
4791 $canv delete secsel
4792 set t [eval $canv create rect [$canv bbox $linehtag($id)] \
4793 -outline {{}} -tags secsel \
4794 -fill [$canv cget -selectbackground]]
4795 $canv lower $t
4796 }
4797 if {[info exists markedid] && $id eq $markedid} {
4798 make_idmark $id
4799 }
4800 }
4801
4802 proc bolden_name {id font} {
4803 global canv2 linentag currentid boldnameids need_redisplay
4804
4805 if {$need_redisplay} return
4806 lappend boldnameids $id
4807 $canv2 itemconf $linentag($id) -font $font
4808 if {[info exists currentid] && $id eq $currentid} {
4809 $canv2 delete secsel
4810 set t [eval $canv2 create rect [$canv2 bbox $linentag($id)] \
4811 -outline {{}} -tags secsel \
4812 -fill [$canv2 cget -selectbackground]]
4813 $canv2 lower $t
4814 }
4815 }
4816
4817 proc unbolden {} {
4818 global boldids
4819
4820 set stillbold {}
4821 foreach id $boldids {
4822 if {![ishighlighted $id]} {
4823 bolden $id mainfont
4824 } else {
4825 lappend stillbold $id
4826 }
4827 }
4828 set boldids $stillbold
4829 }
4830
4831 proc addvhighlight {n} {
4832 global hlview viewcomplete curview vhl_done commitidx
4833
4834 if {[info exists hlview]} {
4835 delvhighlight
4836 }
4837 set hlview $n
4838 if {$n != $curview && ![info exists viewcomplete($n)]} {
4839 start_rev_list $n
4840 }
4841 set vhl_done $commitidx($hlview)
4842 if {$vhl_done > 0} {
4843 drawvisible
4844 }
4845 }
4846
4847 proc delvhighlight {} {
4848 global hlview vhighlights
4849
4850 if {![info exists hlview]} return
4851 unset hlview
4852 unset -nocomplain vhighlights
4853 unbolden
4854 }
4855
4856 proc vhighlightmore {} {
4857 global hlview vhl_done commitidx vhighlights curview
4858
4859 set max $commitidx($hlview)
4860 set vr [visiblerows]
4861 set r0 [lindex $vr 0]
4862 set r1 [lindex $vr 1]
4863 for {set i $vhl_done} {$i < $max} {incr i} {
4864 set id [commitonrow $i $hlview]
4865 if {[commitinview $id $curview]} {
4866 set row [rowofcommit $id]
4867 if {$r0 <= $row && $row <= $r1} {
4868 if {![highlighted $row]} {
4869 bolden $id mainfontbold
4870 }
4871 set vhighlights($id) 1
4872 }
4873 }
4874 }
4875 set vhl_done $max
4876 return 0
4877 }
4878
4879 proc askvhighlight {row id} {
4880 global hlview vhighlights iddrawn
4881
4882 if {[commitinview $id $hlview]} {
4883 if {[info exists iddrawn($id)] && ![ishighlighted $id]} {
4884 bolden $id mainfontbold
4885 }
4886 set vhighlights($id) 1
4887 } else {
4888 set vhighlights($id) 0
4889 }
4890 }
4891
4892 proc hfiles_change {} {
4893 global highlight_files filehighlight fhighlights fh_serial
4894 global highlight_paths
4895
4896 if {[info exists filehighlight]} {
4897 # delete previous highlights
4898 catch {close $filehighlight}
4899 unset filehighlight
4900 unset -nocomplain fhighlights
4901 unbolden
4902 unhighlight_filelist
4903 }
4904 set highlight_paths {}
4905 after cancel do_file_hl $fh_serial
4906 incr fh_serial
4907 if {$highlight_files ne {}} {
4908 after 300 do_file_hl $fh_serial
4909 }
4910 }
4911
4912 proc gdttype_change {name ix op} {
4913 global gdttype highlight_files findstring findpattern
4914
4915 stopfinding
4916 if {$findstring ne {}} {
4917 if {$gdttype eq [mc "containing:"]} {
4918 if {$highlight_files ne {}} {
4919 set highlight_files {}
4920 hfiles_change
4921 }
4922 findcom_change
4923 } else {
4924 if {$findpattern ne {}} {
4925 set findpattern {}
4926 findcom_change
4927 }
4928 set highlight_files $findstring
4929 hfiles_change
4930 }
4931 drawvisible
4932 }
4933 # enable/disable findtype/findloc menus too
4934 }
4935
4936 proc find_change {name ix op} {
4937 global gdttype findstring highlight_files
4938
4939 stopfinding
4940 if {$gdttype eq [mc "containing:"]} {
4941 findcom_change
4942 } else {
4943 if {$highlight_files ne $findstring} {
4944 set highlight_files $findstring
4945 hfiles_change
4946 }
4947 }
4948 drawvisible
4949 }
4950
4951 proc findcom_change args {
4952 global nhighlights boldnameids
4953 global findpattern findtype findstring gdttype
4954
4955 stopfinding
4956 # delete previous highlights, if any
4957 foreach id $boldnameids {
4958 bolden_name $id mainfont
4959 }
4960 set boldnameids {}
4961 unset -nocomplain nhighlights
4962 unbolden
4963 unmarkmatches
4964 if {$gdttype ne [mc "containing:"] || $findstring eq {}} {
4965 set findpattern {}
4966 } elseif {$findtype eq [mc "Regexp"]} {
4967 set findpattern $findstring
4968 } else {
4969 set e [string map {"*" "\\*" "?" "\\?" "\[" "\\\[" "\\" "\\\\"} \
4970 $findstring]
4971 set findpattern "*$e*"
4972 }
4973 }
4974
4975 proc makepatterns {l} {
4976 set ret {}
4977 foreach e $l {
4978 set ee [string map {"*" "\\*" "?" "\\?" "\[" "\\\[" "\\" "\\\\"} $e]
4979 if {[string index $ee end] eq "/"} {
4980 lappend ret "$ee*"
4981 } else {
4982 lappend ret $ee
4983 lappend ret "$ee/*"
4984 }
4985 }
4986 return $ret
4987 }
4988
4989 proc do_file_hl {serial} {
4990 global highlight_files filehighlight highlight_paths gdttype fhl_list
4991 global cdup findtype
4992
4993 if {$gdttype eq [mc "touching paths:"]} {
4994 # If "exact" match then convert backslashes to forward slashes.
4995 # Most useful to support Windows-flavoured file paths.
4996 if {$findtype eq [mc "Exact"]} {
4997 set highlight_files [string map {"\\" "/"} $highlight_files]
4998 }
4999 if {[catch {set paths [shellsplit $highlight_files]}]} return
5000 set highlight_paths [makepatterns $paths]
Showing first 5,000 of 13,036 lines. View raw