Merge git://git.kernel.org/pub/scm/gitk/gitk
[git/git.git] / gitk
1 #!/bin/sh
2 # Tcl ignores the next line -*- tcl -*- \
3 exec wish "$0" -- "$@"
4
5 # Copyright (C) 2005 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 proc gitdir {} {
11 global env
12 if {[info exists env(GIT_DIR)]} {
13 return $env(GIT_DIR)
14 } else {
15 return ".git"
16 }
17 }
18
19 proc parse_args {rargs} {
20 global parsed_args
21
22 if {[catch {
23 set parse_args [concat --default HEAD $rargs]
24 set parsed_args [split [eval exec git-rev-parse $parse_args] "\n"]
25 }]} {
26 # if git-rev-parse failed for some reason...
27 if {$rargs == {}} {
28 set rargs HEAD
29 }
30 set parsed_args $rargs
31 }
32 return $parsed_args
33 }
34
35 proc start_rev_list {rlargs} {
36 global startmsecs nextupdate ncmupdate
37 global commfd leftover tclencoding
38
39 set startmsecs [clock clicks -milliseconds]
40 set nextupdate [expr {$startmsecs + 100}]
41 set ncmupdate 1
42 if {[catch {
43 set commfd [open [concat | git-rev-list --header --topo-order \
44 --parents $rlargs] r]
45 } err]} {
46 puts stderr "Error executing git-rev-list: $err"
47 exit 1
48 }
49 set leftover {}
50 fconfigure $commfd -blocking 0 -translation lf
51 if {$tclencoding != {}} {
52 fconfigure $commfd -encoding $tclencoding
53 }
54 fileevent $commfd readable [list getcommitlines $commfd]
55 . config -cursor watch
56 settextcursor watch
57 }
58
59 proc getcommits {rargs} {
60 global oldcommits commits phase canv mainfont env
61
62 # check that we can find a .git directory somewhere...
63 set gitdir [gitdir]
64 if {![file isdirectory $gitdir]} {
65 error_popup "Cannot find the git directory \"$gitdir\"."
66 exit 1
67 }
68 set oldcommits {}
69 set commits {}
70 set phase getcommits
71 start_rev_list [parse_args $rargs]
72 $canv delete all
73 $canv create text 3 3 -anchor nw -text "Reading commits..." \
74 -font $mainfont -tags textitems
75 }
76
77 proc getcommitlines {commfd} {
78 global oldcommits commits parents cdate children nchildren
79 global commitlisted phase nextupdate
80 global stopped redisplaying leftover
81 global canv
82
83 set stuff [read $commfd]
84 if {$stuff == {}} {
85 if {![eof $commfd]} return
86 # set it blocking so we wait for the process to terminate
87 fconfigure $commfd -blocking 1
88 if {![catch {close $commfd} err]} {
89 after idle finishcommits
90 return
91 }
92 if {[string range $err 0 4] == "usage"} {
93 set err \
94 "Gitk: error reading commits: bad arguments to git-rev-list.\
95 (Note: arguments to gitk are passed to git-rev-list\
96 to allow selection of commits to be displayed.)"
97 } else {
98 set err "Error reading commits: $err"
99 }
100 error_popup $err
101 exit 1
102 }
103 set start 0
104 while 1 {
105 set i [string first "\0" $stuff $start]
106 if {$i < 0} {
107 append leftover [string range $stuff $start end]
108 return
109 }
110 set cmit [string range $stuff $start [expr {$i - 1}]]
111 if {$start == 0} {
112 set cmit "$leftover$cmit"
113 set leftover {}
114 }
115 set start [expr {$i + 1}]
116 set j [string first "\n" $cmit]
117 set ok 0
118 if {$j >= 0} {
119 set ids [string range $cmit 0 [expr {$j - 1}]]
120 set ok 1
121 foreach id $ids {
122 if {![regexp {^[0-9a-f]{40}$} $id]} {
123 set ok 0
124 break
125 }
126 }
127 }
128 if {!$ok} {
129 set shortcmit $cmit
130 if {[string length $shortcmit] > 80} {
131 set shortcmit "[string range $shortcmit 0 80]..."
132 }
133 error_popup "Can't parse git-rev-list output: {$shortcmit}"
134 exit 1
135 }
136 set id [lindex $ids 0]
137 set olds [lrange $ids 1 end]
138 set cmit [string range $cmit [expr {$j + 1}] end]
139 lappend commits $id
140 set commitlisted($id) 1
141 parsecommit $id $cmit 1 [lrange $ids 1 end]
142 drawcommit $id 1
143 if {[clock clicks -milliseconds] >= $nextupdate} {
144 doupdate 1
145 }
146 while {$redisplaying} {
147 set redisplaying 0
148 if {$stopped == 1} {
149 set stopped 0
150 set phase "getcommits"
151 foreach id $commits {
152 drawcommit $id 1
153 if {$stopped} break
154 if {[clock clicks -milliseconds] >= $nextupdate} {
155 doupdate 1
156 }
157 }
158 }
159 }
160 }
161 }
162
163 proc doupdate {reading} {
164 global commfd nextupdate numcommits ncmupdate
165
166 if {$reading} {
167 fileevent $commfd readable {}
168 }
169 update
170 set nextupdate [expr {[clock clicks -milliseconds] + 100}]
171 if {$numcommits < 100} {
172 set ncmupdate [expr {$numcommits + 1}]
173 } elseif {$numcommits < 10000} {
174 set ncmupdate [expr {$numcommits + 10}]
175 } else {
176 set ncmupdate [expr {$numcommits + 100}]
177 }
178 if {$reading} {
179 fileevent $commfd readable [list getcommitlines $commfd]
180 }
181 }
182
183 proc readcommit {id} {
184 if {[catch {set contents [exec git-cat-file commit $id]}]} return
185 parsecommit $id $contents 0 {}
186 }
187
188 proc updatecommits {rargs} {
189 global commitlisted commfd phase
190 global startmsecs nextupdate ncmupdate
191 global idtags idheads idotherrefs
192 global leftover
193 global parsed_args
194 global canv mainfont
195 global oldcommits commits
196 global parents nchildren children ncleft
197
198 set old_args $parsed_args
199 parse_args $rargs
200
201 if {$phase == "getcommits" || $phase == "incrdraw"} {
202 # havent read all the old commits, just start again from scratch
203 stopfindproc
204 set oldcommits {}
205 set commits {}
206 foreach v {children nchildren parents commitlisted commitinfo
207 selectedline matchinglines treediffs
208 mergefilelist currentid rowtextx} {
209 global $v
210 catch {unset $v}
211 }
212 readrefs
213 if {$phase == "incrdraw"} {
214 allcanvs delete all
215 $canv create text 3 3 -anchor nw -text "Reading commits..." \
216 -font $mainfont -tags textitems
217 set phase getcommits
218 }
219 start_rev_list $parsed_args
220 return
221 }
222
223 foreach id $old_args {
224 if {![regexp {^[0-9a-f]{40}$} $id]} continue
225 if {[info exists oldref($id)]} continue
226 set oldref($id) $id
227 lappend ignoreold "^$id"
228 }
229 foreach id $parsed_args {
230 if {![regexp {^[0-9a-f]{40}$} $id]} continue
231 if {[info exists ref($id)]} continue
232 set ref($id) $id
233 lappend ignorenew "^$id"
234 }
235
236 foreach a $old_args {
237 if {![info exists ref($a)]} {
238 lappend ignorenew $a
239 }
240 }
241
242 set phase updatecommits
243 set oldcommits $commits
244 set commits {}
245 set removed_commits [split [eval exec git-rev-list $ignorenew] "\n" ]
246 if {[llength $removed_commits] > 0} {
247 allcanvs delete all
248 foreach c $removed_commits {
249 set i [lsearch -exact $oldcommits $c]
250 if {$i >= 0} {
251 set oldcommits [lreplace $oldcommits $i $i]
252 unset commitlisted($c)
253 foreach p $parents($c) {
254 if {[info exists nchildren($p)]} {
255 set j [lsearch -exact $children($p) $c]
256 if {$j >= 0} {
257 set children($p) [lreplace $children($p) $j $j]
258 incr nchildren($p) -1
259 }
260 }
261 }
262 }
263 }
264 set phase removecommits
265 }
266
267 set args {}
268 foreach a $parsed_args {
269 if {![info exists oldref($a)]} {
270 lappend args $a
271 }
272 }
273
274 readrefs
275 start_rev_list [concat $ignoreold $args]
276 }
277
278 proc updatechildren {id olds} {
279 global children nchildren parents nparents ncleft
280
281 if {![info exists nchildren($id)]} {
282 set children($id) {}
283 set nchildren($id) 0
284 set ncleft($id) 0
285 }
286 set parents($id) $olds
287 set nparents($id) [llength $olds]
288 foreach p $olds {
289 if {![info exists nchildren($p)]} {
290 set children($p) [list $id]
291 set nchildren($p) 1
292 set ncleft($p) 1
293 } elseif {[lsearch -exact $children($p) $id] < 0} {
294 lappend children($p) $id
295 incr nchildren($p)
296 incr ncleft($p)
297 }
298 }
299 }
300
301 proc parsecommit {id contents listed olds} {
302 global commitinfo cdate
303
304 set inhdr 1
305 set comment {}
306 set headline {}
307 set auname {}
308 set audate {}
309 set comname {}
310 set comdate {}
311 updatechildren $id $olds
312 set hdrend [string first "\n\n" $contents]
313 if {$hdrend < 0} {
314 # should never happen...
315 set hdrend [string length $contents]
316 }
317 set header [string range $contents 0 [expr {$hdrend - 1}]]
318 set comment [string range $contents [expr {$hdrend + 2}] end]
319 foreach line [split $header "\n"] {
320 set tag [lindex $line 0]
321 if {$tag == "author"} {
322 set audate [lindex $line end-1]
323 set auname [lrange $line 1 end-2]
324 } elseif {$tag == "committer"} {
325 set comdate [lindex $line end-1]
326 set comname [lrange $line 1 end-2]
327 }
328 }
329 set headline {}
330 # take the first line of the comment as the headline
331 set i [string first "\n" $comment]
332 if {$i >= 0} {
333 set headline [string trim [string range $comment 0 $i]]
334 } else {
335 set headline $comment
336 }
337 if {!$listed} {
338 # git-rev-list indents the comment by 4 spaces;
339 # if we got this via git-cat-file, add the indentation
340 set newcomment {}
341 foreach line [split $comment "\n"] {
342 append newcomment " "
343 append newcomment $line
344 append newcomment "\n"
345 }
346 set comment $newcomment
347 }
348 if {$comdate != {}} {
349 set cdate($id) $comdate
350 }
351 set commitinfo($id) [list $headline $auname $audate \
352 $comname $comdate $comment]
353 }
354
355 proc readrefs {} {
356 global tagids idtags headids idheads tagcontents
357 global otherrefids idotherrefs
358
359 foreach v {tagids idtags headids idheads otherrefids idotherrefs} {
360 catch {unset $v}
361 }
362 set refd [open [list | git-ls-remote [gitdir]] r]
363 while {0 <= [set n [gets $refd line]]} {
364 if {![regexp {^([0-9a-f]{40}) refs/([^^]*)$} $line \
365 match id path]} {
366 continue
367 }
368 if {![regexp {^(tags|heads)/(.*)$} $path match type name]} {
369 set type others
370 set name $path
371 }
372 if {$type == "tags"} {
373 set tagids($name) $id
374 lappend idtags($id) $name
375 set obj {}
376 set type {}
377 set tag {}
378 catch {
379 set commit [exec git-rev-parse "$id^0"]
380 if {"$commit" != "$id"} {
381 set tagids($name) $commit
382 lappend idtags($commit) $name
383 }
384 }
385 catch {
386 set tagcontents($name) [exec git-cat-file tag "$id"]
387 }
388 } elseif { $type == "heads" } {
389 set headids($name) $id
390 lappend idheads($id) $name
391 } else {
392 set otherrefids($name) $id
393 lappend idotherrefs($id) $name
394 }
395 }
396 close $refd
397 }
398
399 proc error_popup msg {
400 set w .error
401 toplevel $w
402 wm transient $w .
403 message $w.m -text $msg -justify center -aspect 400
404 pack $w.m -side top -fill x -padx 20 -pady 20
405 button $w.ok -text OK -command "destroy $w"
406 pack $w.ok -side bottom -fill x
407 bind $w <Visibility> "grab $w; focus $w"
408 tkwait window $w
409 }
410
411 proc makewindow {rargs} {
412 global canv canv2 canv3 linespc charspc ctext cflist textfont
413 global findtype findtypemenu findloc findstring fstring geometry
414 global entries sha1entry sha1string sha1but
415 global maincursor textcursor curtextcursor
416 global rowctxmenu mergemax
417
418 menu .bar
419 .bar add cascade -label "File" -menu .bar.file
420 menu .bar.file
421 .bar.file add command -label "Update" -command [list updatecommits $rargs]
422 .bar.file add command -label "Reread references" -command rereadrefs
423 .bar.file add command -label "Quit" -command doquit
424 menu .bar.edit
425 .bar add cascade -label "Edit" -menu .bar.edit
426 .bar.edit add command -label "Preferences" -command doprefs
427 menu .bar.help
428 .bar add cascade -label "Help" -menu .bar.help
429 .bar.help add command -label "About gitk" -command about
430 . configure -menu .bar
431
432 if {![info exists geometry(canv1)]} {
433 set geometry(canv1) [expr {45 * $charspc}]
434 set geometry(canv2) [expr {30 * $charspc}]
435 set geometry(canv3) [expr {15 * $charspc}]
436 set geometry(canvh) [expr {25 * $linespc + 4}]
437 set geometry(ctextw) 80
438 set geometry(ctexth) 30
439 set geometry(cflistw) 30
440 }
441 panedwindow .ctop -orient vertical
442 if {[info exists geometry(width)]} {
443 .ctop conf -width $geometry(width) -height $geometry(height)
444 set texth [expr {$geometry(height) - $geometry(canvh) - 56}]
445 set geometry(ctexth) [expr {($texth - 8) /
446 [font metrics $textfont -linespace]}]
447 }
448 frame .ctop.top
449 frame .ctop.top.bar
450 pack .ctop.top.bar -side bottom -fill x
451 set cscroll .ctop.top.csb
452 scrollbar $cscroll -command {allcanvs yview} -highlightthickness 0
453 pack $cscroll -side right -fill y
454 panedwindow .ctop.top.clist -orient horizontal -sashpad 0 -handlesize 4
455 pack .ctop.top.clist -side top -fill both -expand 1
456 .ctop add .ctop.top
457 set canv .ctop.top.clist.canv
458 canvas $canv -height $geometry(canvh) -width $geometry(canv1) \
459 -bg white -bd 0 \
460 -yscrollincr $linespc -yscrollcommand "$cscroll set"
461 .ctop.top.clist add $canv
462 set canv2 .ctop.top.clist.canv2
463 canvas $canv2 -height $geometry(canvh) -width $geometry(canv2) \
464 -bg white -bd 0 -yscrollincr $linespc
465 .ctop.top.clist add $canv2
466 set canv3 .ctop.top.clist.canv3
467 canvas $canv3 -height $geometry(canvh) -width $geometry(canv3) \
468 -bg white -bd 0 -yscrollincr $linespc
469 .ctop.top.clist add $canv3
470 bind .ctop.top.clist <Configure> {resizeclistpanes %W %w}
471
472 set sha1entry .ctop.top.bar.sha1
473 set entries $sha1entry
474 set sha1but .ctop.top.bar.sha1label
475 button $sha1but -text "SHA1 ID: " -state disabled -relief flat \
476 -command gotocommit -width 8
477 $sha1but conf -disabledforeground [$sha1but cget -foreground]
478 pack .ctop.top.bar.sha1label -side left
479 entry $sha1entry -width 40 -font $textfont -textvariable sha1string
480 trace add variable sha1string write sha1change
481 pack $sha1entry -side left -pady 2
482
483 image create bitmap bm-left -data {
484 #define left_width 16
485 #define left_height 16
486 static unsigned char left_bits[] = {
487 0x00, 0x00, 0xc0, 0x01, 0xe0, 0x00, 0x70, 0x00, 0x38, 0x00, 0x1c, 0x00,
488 0x0e, 0x00, 0xff, 0x7f, 0xff, 0x7f, 0xff, 0x7f, 0x0e, 0x00, 0x1c, 0x00,
489 0x38, 0x00, 0x70, 0x00, 0xe0, 0x00, 0xc0, 0x01};
490 }
491 image create bitmap bm-right -data {
492 #define right_width 16
493 #define right_height 16
494 static unsigned char right_bits[] = {
495 0x00, 0x00, 0xc0, 0x01, 0x80, 0x03, 0x00, 0x07, 0x00, 0x0e, 0x00, 0x1c,
496 0x00, 0x38, 0xff, 0x7f, 0xff, 0x7f, 0xff, 0x7f, 0x00, 0x38, 0x00, 0x1c,
497 0x00, 0x0e, 0x00, 0x07, 0x80, 0x03, 0xc0, 0x01};
498 }
499 button .ctop.top.bar.leftbut -image bm-left -command goback \
500 -state disabled -width 26
501 pack .ctop.top.bar.leftbut -side left -fill y
502 button .ctop.top.bar.rightbut -image bm-right -command goforw \
503 -state disabled -width 26
504 pack .ctop.top.bar.rightbut -side left -fill y
505
506 button .ctop.top.bar.findbut -text "Find" -command dofind
507 pack .ctop.top.bar.findbut -side left
508 set findstring {}
509 set fstring .ctop.top.bar.findstring
510 lappend entries $fstring
511 entry $fstring -width 30 -font $textfont -textvariable findstring
512 pack $fstring -side left -expand 1 -fill x
513 set findtype Exact
514 set findtypemenu [tk_optionMenu .ctop.top.bar.findtype \
515 findtype Exact IgnCase Regexp]
516 set findloc "All fields"
517 tk_optionMenu .ctop.top.bar.findloc findloc "All fields" Headline \
518 Comments Author Committer Files Pickaxe
519 pack .ctop.top.bar.findloc -side right
520 pack .ctop.top.bar.findtype -side right
521 # for making sure type==Exact whenever loc==Pickaxe
522 trace add variable findloc write findlocchange
523
524 panedwindow .ctop.cdet -orient horizontal
525 .ctop add .ctop.cdet
526 frame .ctop.cdet.left
527 set ctext .ctop.cdet.left.ctext
528 text $ctext -bg white -state disabled -font $textfont \
529 -width $geometry(ctextw) -height $geometry(ctexth) \
530 -yscrollcommand ".ctop.cdet.left.sb set" -wrap none
531 scrollbar .ctop.cdet.left.sb -command "$ctext yview"
532 pack .ctop.cdet.left.sb -side right -fill y
533 pack $ctext -side left -fill both -expand 1
534 .ctop.cdet add .ctop.cdet.left
535
536 $ctext tag conf filesep -font [concat $textfont bold] -back "#aaaaaa"
537 $ctext tag conf hunksep -fore blue
538 $ctext tag conf d0 -fore red
539 $ctext tag conf d1 -fore "#00a000"
540 $ctext tag conf m0 -fore red
541 $ctext tag conf m1 -fore blue
542 $ctext tag conf m2 -fore green
543 $ctext tag conf m3 -fore purple
544 $ctext tag conf m4 -fore brown
545 $ctext tag conf m5 -fore "#009090"
546 $ctext tag conf m6 -fore magenta
547 $ctext tag conf m7 -fore "#808000"
548 $ctext tag conf m8 -fore "#009000"
549 $ctext tag conf m9 -fore "#ff0080"
550 $ctext tag conf m10 -fore cyan
551 $ctext tag conf m11 -fore "#b07070"
552 $ctext tag conf m12 -fore "#70b0f0"
553 $ctext tag conf m13 -fore "#70f0b0"
554 $ctext tag conf m14 -fore "#f0b070"
555 $ctext tag conf m15 -fore "#ff70b0"
556 $ctext tag conf mmax -fore darkgrey
557 set mergemax 16
558 $ctext tag conf mresult -font [concat $textfont bold]
559 $ctext tag conf msep -font [concat $textfont bold]
560 $ctext tag conf found -back yellow
561
562 frame .ctop.cdet.right
563 set cflist .ctop.cdet.right.cfiles
564 listbox $cflist -bg white -selectmode extended -width $geometry(cflistw) \
565 -yscrollcommand ".ctop.cdet.right.sb set"
566 scrollbar .ctop.cdet.right.sb -command "$cflist yview"
567 pack .ctop.cdet.right.sb -side right -fill y
568 pack $cflist -side left -fill both -expand 1
569 .ctop.cdet add .ctop.cdet.right
570 bind .ctop.cdet <Configure> {resizecdetpanes %W %w}
571
572 pack .ctop -side top -fill both -expand 1
573
574 bindall <1> {selcanvline %W %x %y}
575 #bindall <B1-Motion> {selcanvline %W %x %y}
576 bindall <ButtonRelease-4> "allcanvs yview scroll -5 units"
577 bindall <ButtonRelease-5> "allcanvs yview scroll 5 units"
578 bindall <2> "allcanvs scan mark 0 %y"
579 bindall <B2-Motion> "allcanvs scan dragto 0 %y"
580 bind . <Key-Up> "selnextline -1"
581 bind . <Key-Down> "selnextline 1"
582 bind . <Key-Right> "goforw"
583 bind . <Key-Left> "goback"
584 bind . <Key-Prior> "allcanvs yview scroll -1 pages"
585 bind . <Key-Next> "allcanvs yview scroll 1 pages"
586 bindkey <Key-Delete> "$ctext yview scroll -1 pages"
587 bindkey <Key-BackSpace> "$ctext yview scroll -1 pages"
588 bindkey <Key-space> "$ctext yview scroll 1 pages"
589 bindkey p "selnextline -1"
590 bindkey n "selnextline 1"
591 bindkey z "goback"
592 bindkey x "goforw"
593 bindkey i "selnextline -1"
594 bindkey k "selnextline 1"
595 bindkey j "goback"
596 bindkey l "goforw"
597 bindkey b "$ctext yview scroll -1 pages"
598 bindkey d "$ctext yview scroll 18 units"
599 bindkey u "$ctext yview scroll -18 units"
600 bindkey / {findnext 1}
601 bindkey <Key-Return> {findnext 0}
602 bindkey ? findprev
603 bindkey f nextfile
604 bind . <Control-q> doquit
605 bind . <Control-f> dofind
606 bind . <Control-g> {findnext 0}
607 bind . <Control-r> findprev
608 bind . <Control-equal> {incrfont 1}
609 bind . <Control-KP_Add> {incrfont 1}
610 bind . <Control-minus> {incrfont -1}
611 bind . <Control-KP_Subtract> {incrfont -1}
612 bind $cflist <<ListboxSelect>> listboxsel
613 bind . <Destroy> {savestuff %W}
614 bind . <Button-1> "click %W"
615 bind $fstring <Key-Return> dofind
616 bind $sha1entry <Key-Return> gotocommit
617 bind $sha1entry <<PasteSelection>> clearsha1
618
619 set maincursor [. cget -cursor]
620 set textcursor [$ctext cget -cursor]
621 set curtextcursor $textcursor
622
623 set rowctxmenu .rowctxmenu
624 menu $rowctxmenu -tearoff 0
625 $rowctxmenu add command -label "Diff this -> selected" \
626 -command {diffvssel 0}
627 $rowctxmenu add command -label "Diff selected -> this" \
628 -command {diffvssel 1}
629 $rowctxmenu add command -label "Make patch" -command mkpatch
630 $rowctxmenu add command -label "Create tag" -command mktag
631 $rowctxmenu add command -label "Write commit to file" -command writecommit
632 }
633
634 # when we make a key binding for the toplevel, make sure
635 # it doesn't get triggered when that key is pressed in the
636 # find string entry widget.
637 proc bindkey {ev script} {
638 global entries
639 bind . $ev $script
640 set escript [bind Entry $ev]
641 if {$escript == {}} {
642 set escript [bind Entry <Key>]
643 }
644 foreach e $entries {
645 bind $e $ev "$escript; break"
646 }
647 }
648
649 # set the focus back to the toplevel for any click outside
650 # the entry widgets
651 proc click {w} {
652 global entries
653 foreach e $entries {
654 if {$w == $e} return
655 }
656 focus .
657 }
658
659 proc savestuff {w} {
660 global canv canv2 canv3 ctext cflist mainfont textfont
661 global stuffsaved findmergefiles maxgraphpct
662 global maxwidth
663
664 if {$stuffsaved} return
665 if {![winfo viewable .]} return
666 catch {
667 set f [open "~/.gitk-new" w]
668 puts $f [list set mainfont $mainfont]
669 puts $f [list set textfont $textfont]
670 puts $f [list set findmergefiles $findmergefiles]
671 puts $f [list set maxgraphpct $maxgraphpct]
672 puts $f [list set maxwidth $maxwidth]
673 puts $f "set geometry(width) [winfo width .ctop]"
674 puts $f "set geometry(height) [winfo height .ctop]"
675 puts $f "set geometry(canv1) [expr {[winfo width $canv]-2}]"
676 puts $f "set geometry(canv2) [expr {[winfo width $canv2]-2}]"
677 puts $f "set geometry(canv3) [expr {[winfo width $canv3]-2}]"
678 puts $f "set geometry(canvh) [expr {[winfo height $canv]-2}]"
679 set wid [expr {([winfo width $ctext] - 8) \
680 / [font measure $textfont "0"]}]
681 puts $f "set geometry(ctextw) $wid"
682 set wid [expr {([winfo width $cflist] - 11) \
683 / [font measure [$cflist cget -font] "0"]}]
684 puts $f "set geometry(cflistw) $wid"
685 close $f
686 file rename -force "~/.gitk-new" "~/.gitk"
687 }
688 set stuffsaved 1
689 }
690
691 proc resizeclistpanes {win w} {
692 global oldwidth
693 if {[info exists oldwidth($win)]} {
694 set s0 [$win sash coord 0]
695 set s1 [$win sash coord 1]
696 if {$w < 60} {
697 set sash0 [expr {int($w/2 - 2)}]
698 set sash1 [expr {int($w*5/6 - 2)}]
699 } else {
700 set factor [expr {1.0 * $w / $oldwidth($win)}]
701 set sash0 [expr {int($factor * [lindex $s0 0])}]
702 set sash1 [expr {int($factor * [lindex $s1 0])}]
703 if {$sash0 < 30} {
704 set sash0 30
705 }
706 if {$sash1 < $sash0 + 20} {
707 set sash1 [expr {$sash0 + 20}]
708 }
709 if {$sash1 > $w - 10} {
710 set sash1 [expr {$w - 10}]
711 if {$sash0 > $sash1 - 20} {
712 set sash0 [expr {$sash1 - 20}]
713 }
714 }
715 }
716 $win sash place 0 $sash0 [lindex $s0 1]
717 $win sash place 1 $sash1 [lindex $s1 1]
718 }
719 set oldwidth($win) $w
720 }
721
722 proc resizecdetpanes {win w} {
723 global oldwidth
724 if {[info exists oldwidth($win)]} {
725 set s0 [$win sash coord 0]
726 if {$w < 60} {
727 set sash0 [expr {int($w*3/4 - 2)}]
728 } else {
729 set factor [expr {1.0 * $w / $oldwidth($win)}]
730 set sash0 [expr {int($factor * [lindex $s0 0])}]
731 if {$sash0 < 45} {
732 set sash0 45
733 }
734 if {$sash0 > $w - 15} {
735 set sash0 [expr {$w - 15}]
736 }
737 }
738 $win sash place 0 $sash0 [lindex $s0 1]
739 }
740 set oldwidth($win) $w
741 }
742
743 proc allcanvs args {
744 global canv canv2 canv3
745 eval $canv $args
746 eval $canv2 $args
747 eval $canv3 $args
748 }
749
750 proc bindall {event action} {
751 global canv canv2 canv3
752 bind $canv $event $action
753 bind $canv2 $event $action
754 bind $canv3 $event $action
755 }
756
757 proc about {} {
758 set w .about
759 if {[winfo exists $w]} {
760 raise $w
761 return
762 }
763 toplevel $w
764 wm title $w "About gitk"
765 message $w.m -text {
766 Gitk version 1.2
767
768 Copyright © 2005 Paul Mackerras
769
770 Use and redistribute under the terms of the GNU General Public License} \
771 -justify center -aspect 400
772 pack $w.m -side top -fill x -padx 20 -pady 20
773 button $w.ok -text Close -command "destroy $w"
774 pack $w.ok -side bottom
775 }
776
777 proc assigncolor {id} {
778 global colormap commcolors colors nextcolor
779 global parents nparents children nchildren
780 global cornercrossings crossings
781
782 if {[info exists colormap($id)]} return
783 set ncolors [llength $colors]
784 if {$nparents($id) <= 1 && $nchildren($id) == 1} {
785 set child [lindex $children($id) 0]
786 if {[info exists colormap($child)]
787 && $nparents($child) == 1} {
788 set colormap($id) $colormap($child)
789 return
790 }
791 }
792 set badcolors {}
793 if {[info exists cornercrossings($id)]} {
794 foreach x $cornercrossings($id) {
795 if {[info exists colormap($x)]
796 && [lsearch -exact $badcolors $colormap($x)] < 0} {
797 lappend badcolors $colormap($x)
798 }
799 }
800 if {[llength $badcolors] >= $ncolors} {
801 set badcolors {}
802 }
803 }
804 set origbad $badcolors
805 if {[llength $badcolors] < $ncolors - 1} {
806 if {[info exists crossings($id)]} {
807 foreach x $crossings($id) {
808 if {[info exists colormap($x)]
809 && [lsearch -exact $badcolors $colormap($x)] < 0} {
810 lappend badcolors $colormap($x)
811 }
812 }
813 if {[llength $badcolors] >= $ncolors} {
814 set badcolors $origbad
815 }
816 }
817 set origbad $badcolors
818 }
819 if {[llength $badcolors] < $ncolors - 1} {
820 foreach child $children($id) {
821 if {[info exists colormap($child)]
822 && [lsearch -exact $badcolors $colormap($child)] < 0} {
823 lappend badcolors $colormap($child)
824 }
825 if {[info exists parents($child)]} {
826 foreach p $parents($child) {
827 if {[info exists colormap($p)]
828 && [lsearch -exact $badcolors $colormap($p)] < 0} {
829 lappend badcolors $colormap($p)
830 }
831 }
832 }
833 }
834 if {[llength $badcolors] >= $ncolors} {
835 set badcolors $origbad
836 }
837 }
838 for {set i 0} {$i <= $ncolors} {incr i} {
839 set c [lindex $colors $nextcolor]
840 if {[incr nextcolor] >= $ncolors} {
841 set nextcolor 0
842 }
843 if {[lsearch -exact $badcolors $c]} break
844 }
845 set colormap($id) $c
846 }
847
848 proc initgraph {} {
849 global canvy canvy0 lineno numcommits nextcolor linespc
850 global nchildren ncleft
851 global displist nhyperspace
852
853 allcanvs delete all
854 set nextcolor 0
855 set canvy $canvy0
856 set lineno -1
857 set numcommits 0
858 foreach v {mainline mainlinearrow sidelines colormap cornercrossings
859 crossings idline lineid} {
860 global $v
861 catch {unset $v}
862 }
863 foreach id [array names nchildren] {
864 set ncleft($id) $nchildren($id)
865 }
866 set displist {}
867 set nhyperspace 0
868 }
869
870 proc bindline {t id} {
871 global canv
872
873 $canv bind $t <Enter> "lineenter %x %y $id"
874 $canv bind $t <Motion> "linemotion %x %y $id"
875 $canv bind $t <Leave> "lineleave $id"
876 $canv bind $t <Button-1> "lineclick %x %y $id 1"
877 }
878
879 proc drawlines {id xtra delold} {
880 global mainline mainlinearrow sidelines lthickness colormap canv
881
882 if {$delold} {
883 $canv delete lines.$id
884 }
885 if {[info exists mainline($id)]} {
886 set t [$canv create line $mainline($id) \
887 -width [expr {($xtra + 1) * $lthickness}] \
888 -fill $colormap($id) -tags lines.$id \
889 -arrow $mainlinearrow($id)]
890 $canv lower $t
891 bindline $t $id
892 }
893 if {[info exists sidelines($id)]} {
894 foreach ls $sidelines($id) {
895 set coords [lindex $ls 0]
896 set thick [lindex $ls 1]
897 set arrow [lindex $ls 2]
898 set t [$canv create line $coords -fill $colormap($id) \
899 -width [expr {($thick + $xtra) * $lthickness}] \
900 -arrow $arrow -tags lines.$id]
901 $canv lower $t
902 bindline $t $id
903 }
904 }
905 }
906
907 # level here is an index in displist
908 proc drawcommitline {level} {
909 global parents children nparents displist
910 global canv canv2 canv3 mainfont namefont canvy linespc
911 global lineid linehtag linentag linedtag commitinfo
912 global colormap numcommits currentparents dupparents
913 global idtags idline idheads idotherrefs
914 global lineno lthickness mainline mainlinearrow sidelines
915 global commitlisted rowtextx idpos lastuse displist
916 global oldnlines olddlevel olddisplist
917
918 incr numcommits
919 incr lineno
920 set id [lindex $displist $level]
921 set lastuse($id) $lineno
922 set lineid($lineno) $id
923 set idline($id) $lineno
924 set ofill [expr {[info exists commitlisted($id)]? "blue": "white"}]
925 if {![info exists commitinfo($id)]} {
926 readcommit $id
927 if {![info exists commitinfo($id)]} {
928 set commitinfo($id) {"No commit information available"}
929 set nparents($id) 0
930 }
931 }
932 assigncolor $id
933 set currentparents {}
934 set dupparents {}
935 if {[info exists commitlisted($id)] && [info exists parents($id)]} {
936 foreach p $parents($id) {
937 if {[lsearch -exact $currentparents $p] < 0} {
938 lappend currentparents $p
939 } else {
940 # remember that this parent was listed twice
941 lappend dupparents $p
942 }
943 }
944 }
945 set x [xcoord $level $level $lineno]
946 set y1 $canvy
947 set canvy [expr {$canvy + $linespc}]
948 allcanvs conf -scrollregion \
949 [list 0 0 0 [expr {$y1 + 0.5 * $linespc + 2}]]
950 if {[info exists mainline($id)]} {
951 lappend mainline($id) $x $y1
952 if {$mainlinearrow($id) ne "none"} {
953 set mainline($id) [trimdiagstart $mainline($id)]
954 }
955 }
956 drawlines $id 0 0
957 set orad [expr {$linespc / 3}]
958 set t [$canv create oval [expr {$x - $orad}] [expr {$y1 - $orad}] \
959 [expr {$x + $orad - 1}] [expr {$y1 + $orad - 1}] \
960 -fill $ofill -outline black -width 1]
961 $canv raise $t
962 $canv bind $t <1> {selcanvline {} %x %y}
963 set xt [xcoord [llength $displist] $level $lineno]
964 if {[llength $currentparents] > 2} {
965 set xt [expr {$xt + ([llength $currentparents] - 2) * $linespc}]
966 }
967 set rowtextx($lineno) $xt
968 set idpos($id) [list $x $xt $y1]
969 if {[info exists idtags($id)] || [info exists idheads($id)]
970 || [info exists idotherrefs($id)]} {
971 set xt [drawtags $id $x $xt $y1]
972 }
973 set headline [lindex $commitinfo($id) 0]
974 set name [lindex $commitinfo($id) 1]
975 set date [lindex $commitinfo($id) 2]
976 set date [formatdate $date]
977 set linehtag($lineno) [$canv create text $xt $y1 -anchor w \
978 -text $headline -font $mainfont ]
979 $canv bind $linehtag($lineno) <Button-3> "rowmenu %X %Y $id"
980 set linentag($lineno) [$canv2 create text 3 $y1 -anchor w \
981 -text $name -font $namefont]
982 set linedtag($lineno) [$canv3 create text 3 $y1 -anchor w \
983 -text $date -font $mainfont]
984
985 set olddlevel $level
986 set olddisplist $displist
987 set oldnlines [llength $displist]
988 }
989
990 proc drawtags {id x xt y1} {
991 global idtags idheads idotherrefs
992 global linespc lthickness
993 global canv mainfont idline rowtextx
994
995 set marks {}
996 set ntags 0
997 set nheads 0
998 if {[info exists idtags($id)]} {
999 set marks $idtags($id)
1000 set ntags [llength $marks]
1001 }
1002 if {[info exists idheads($id)]} {
1003 set marks [concat $marks $idheads($id)]
1004 set nheads [llength $idheads($id)]
1005 }
1006 if {[info exists idotherrefs($id)]} {
1007 set marks [concat $marks $idotherrefs($id)]
1008 }
1009 if {$marks eq {}} {
1010 return $xt
1011 }
1012
1013 set delta [expr {int(0.5 * ($linespc - $lthickness))}]
1014 set yt [expr {$y1 - 0.5 * $linespc}]
1015 set yb [expr {$yt + $linespc - 1}]
1016 set xvals {}
1017 set wvals {}
1018 foreach tag $marks {
1019 set wid [font measure $mainfont $tag]
1020 lappend xvals $xt
1021 lappend wvals $wid
1022 set xt [expr {$xt + $delta + $wid + $lthickness + $linespc}]
1023 }
1024 set t [$canv create line $x $y1 [lindex $xvals end] $y1 \
1025 -width $lthickness -fill black -tags tag.$id]
1026 $canv lower $t
1027 foreach tag $marks x $xvals wid $wvals {
1028 set xl [expr {$x + $delta}]
1029 set xr [expr {$x + $delta + $wid + $lthickness}]
1030 if {[incr ntags -1] >= 0} {
1031 # draw a tag
1032 set t [$canv create polygon $x [expr {$yt + $delta}] $xl $yt \
1033 $xr $yt $xr $yb $xl $yb $x [expr {$yb - $delta}] \
1034 -width 1 -outline black -fill yellow -tags tag.$id]
1035 $canv bind $t <1> [list showtag $tag 1]
1036 set rowtextx($idline($id)) [expr {$xr + $linespc}]
1037 } else {
1038 # draw a head or other ref
1039 if {[incr nheads -1] >= 0} {
1040 set col green
1041 } else {
1042 set col "#ddddff"
1043 }
1044 set xl [expr {$xl - $delta/2}]
1045 $canv create polygon $x $yt $xr $yt $xr $yb $x $yb \
1046 -width 1 -outline black -fill $col -tags tag.$id
1047 }
1048 set t [$canv create text $xl $y1 -anchor w -text $tag \
1049 -font $mainfont -tags tag.$id]
1050 if {$ntags >= 0} {
1051 $canv bind $t <1> [list showtag $tag 1]
1052 }
1053 }
1054 return $xt
1055 }
1056
1057 proc notecrossings {id lo hi corner} {
1058 global olddisplist crossings cornercrossings
1059
1060 for {set i $lo} {[incr i] < $hi} {} {
1061 set p [lindex $olddisplist $i]
1062 if {$p == {}} continue
1063 if {$i == $corner} {
1064 if {![info exists cornercrossings($id)]
1065 || [lsearch -exact $cornercrossings($id) $p] < 0} {
1066 lappend cornercrossings($id) $p
1067 }
1068 if {![info exists cornercrossings($p)]
1069 || [lsearch -exact $cornercrossings($p) $id] < 0} {
1070 lappend cornercrossings($p) $id
1071 }
1072 } else {
1073 if {![info exists crossings($id)]
1074 || [lsearch -exact $crossings($id) $p] < 0} {
1075 lappend crossings($id) $p
1076 }
1077 if {![info exists crossings($p)]
1078 || [lsearch -exact $crossings($p) $id] < 0} {
1079 lappend crossings($p) $id
1080 }
1081 }
1082 }
1083 }
1084
1085 proc xcoord {i level ln} {
1086 global canvx0 xspc1 xspc2
1087
1088 set x [expr {$canvx0 + $i * $xspc1($ln)}]
1089 if {$i > 0 && $i == $level} {
1090 set x [expr {$x + 0.5 * ($xspc2 - $xspc1($ln))}]
1091 } elseif {$i > $level} {
1092 set x [expr {$x + $xspc2 - $xspc1($ln)}]
1093 }
1094 return $x
1095 }
1096
1097 # it seems Tk can't draw arrows on the end of diagonal line segments...
1098 proc trimdiagend {line} {
1099 while {[llength $line] > 4} {
1100 set x1 [lindex $line end-3]
1101 set y1 [lindex $line end-2]
1102 set x2 [lindex $line end-1]
1103 set y2 [lindex $line end]
1104 if {($x1 == $x2) != ($y1 == $y2)} break
1105 set line [lreplace $line end-1 end]
1106 }
1107 return $line
1108 }
1109
1110 proc trimdiagstart {line} {
1111 while {[llength $line] > 4} {
1112 set x1 [lindex $line 0]
1113 set y1 [lindex $line 1]
1114 set x2 [lindex $line 2]
1115 set y2 [lindex $line 3]
1116 if {($x1 == $x2) != ($y1 == $y2)} break
1117 set line [lreplace $line 0 1]
1118 }
1119 return $line
1120 }
1121
1122 proc drawslants {id needonscreen nohs} {
1123 global canv mainline mainlinearrow sidelines
1124 global canvx0 canvy xspc1 xspc2 lthickness
1125 global currentparents dupparents
1126 global lthickness linespc canvy colormap lineno geometry
1127 global maxgraphpct maxwidth
1128 global displist onscreen lastuse
1129 global parents commitlisted
1130 global oldnlines olddlevel olddisplist
1131 global nhyperspace numcommits nnewparents
1132
1133 if {$lineno < 0} {
1134 lappend displist $id
1135 set onscreen($id) 1
1136 return 0
1137 }
1138
1139 set y1 [expr {$canvy - $linespc}]
1140 set y2 $canvy
1141
1142 # work out what we need to get back on screen
1143 set reins {}
1144 if {$onscreen($id) < 0} {
1145 # next to do isn't displayed, better get it on screen...
1146 lappend reins [list $id 0]
1147 }
1148 # make sure all the previous commits's parents are on the screen
1149 foreach p $currentparents {
1150 if {$onscreen($p) < 0} {
1151 lappend reins [list $p 0]
1152 }
1153 }
1154 # bring back anything requested by caller
1155 if {$needonscreen ne {}} {
1156 lappend reins $needonscreen
1157 }
1158
1159 # try the shortcut
1160 if {$currentparents == $id && $onscreen($id) == 0 && $reins eq {}} {
1161 set dlevel $olddlevel
1162 set x [xcoord $dlevel $dlevel $lineno]
1163 set mainline($id) [list $x $y1]
1164 set mainlinearrow($id) none
1165 set lastuse($id) $lineno
1166 set displist [lreplace $displist $dlevel $dlevel $id]
1167 set onscreen($id) 1
1168 set xspc1([expr {$lineno + 1}]) $xspc1($lineno)
1169 return $dlevel
1170 }
1171
1172 # update displist
1173 set displist [lreplace $displist $olddlevel $olddlevel]
1174 set j $olddlevel
1175 foreach p $currentparents {
1176 set lastuse($p) $lineno
1177 if {$onscreen($p) == 0} {
1178 set displist [linsert $displist $j $p]
1179 set onscreen($p) 1
1180 incr j
1181 }
1182 }
1183 if {$onscreen($id) == 0} {
1184 lappend displist $id
1185 set onscreen($id) 1
1186 }
1187
1188 # remove the null entry if present
1189 set nullentry [lsearch -exact $displist {}]
1190 if {$nullentry >= 0} {
1191 set displist [lreplace $displist $nullentry $nullentry]
1192 }
1193
1194 # bring back the ones we need now (if we did it earlier
1195 # it would change displist and invalidate olddlevel)
1196 foreach pi $reins {
1197 # test again in case of duplicates in reins
1198 set p [lindex $pi 0]
1199 if {$onscreen($p) < 0} {
1200 set onscreen($p) 1
1201 set lastuse($p) $lineno
1202 set displist [linsert $displist [lindex $pi 1] $p]
1203 incr nhyperspace -1
1204 }
1205 }
1206
1207 set lastuse($id) $lineno
1208
1209 # see if we need to make any lines jump off into hyperspace
1210 set displ [llength $displist]
1211 if {$displ > $maxwidth} {
1212 set ages {}
1213 foreach x $displist {
1214 lappend ages [list $lastuse($x) $x]
1215 }
1216 set ages [lsort -integer -index 0 $ages]
1217 set k 0
1218 while {$displ > $maxwidth} {
1219 set use [lindex $ages $k 0]
1220 set victim [lindex $ages $k 1]
1221 if {$use >= $lineno - 5} break
1222 incr k
1223 if {[lsearch -exact $nohs $victim] >= 0} continue
1224 set i [lsearch -exact $displist $victim]
1225 set displist [lreplace $displist $i $i]
1226 set onscreen($victim) -1
1227 incr nhyperspace
1228 incr displ -1
1229 if {$i < $nullentry} {
1230 incr nullentry -1
1231 }
1232 set x [lindex $mainline($victim) end-1]
1233 lappend mainline($victim) $x $y1
1234 set line [trimdiagend $mainline($victim)]
1235 set arrow "last"
1236 if {$mainlinearrow($victim) ne "none"} {
1237 set line [trimdiagstart $line]
1238 set arrow "both"
1239 }
1240 lappend sidelines($victim) [list $line 1 $arrow]
1241 unset mainline($victim)
1242 }
1243 }
1244
1245 set dlevel [lsearch -exact $displist $id]
1246
1247 # If we are reducing, put in a null entry
1248 if {$displ < $oldnlines} {
1249 # does the next line look like a merge?
1250 # i.e. does it have > 1 new parent?
1251 if {$nnewparents($id) > 1} {
1252 set i [expr {$dlevel + 1}]
1253 } elseif {$nnewparents([lindex $olddisplist $olddlevel]) == 0} {
1254 set i $olddlevel
1255 if {$nullentry >= 0 && $nullentry < $i} {
1256 incr i -1
1257 }
1258 } elseif {$nullentry >= 0} {
1259 set i $nullentry
1260 while {$i < $displ
1261 && [lindex $olddisplist $i] == [lindex $displist $i]} {
1262 incr i
1263 }
1264 } else {
1265 set i $olddlevel
1266 if {$dlevel >= $i} {
1267 incr i
1268 }
1269 }
1270 if {$i < $displ} {
1271 set displist [linsert $displist $i {}]
1272 incr displ
1273 if {$dlevel >= $i} {
1274 incr dlevel
1275 }
1276 }
1277 }
1278
1279 # decide on the line spacing for the next line
1280 set lj [expr {$lineno + 1}]
1281 set maxw [expr {$maxgraphpct * $geometry(canv1) / 100}]
1282 if {$displ <= 1 || $canvx0 + $displ * $xspc2 <= $maxw} {
1283 set xspc1($lj) $xspc2
1284 } else {
1285 set xspc1($lj) [expr {($maxw - $canvx0 - $xspc2) / ($displ - 1)}]
1286 if {$xspc1($lj) < $lthickness} {
1287 set xspc1($lj) $lthickness
1288 }
1289 }
1290
1291 foreach idi $reins {
1292 set id [lindex $idi 0]
1293 set j [lsearch -exact $displist $id]
1294 set xj [xcoord $j $dlevel $lj]
1295 set mainline($id) [list $xj $y2]
1296 set mainlinearrow($id) first
1297 }
1298
1299 set i -1
1300 foreach id $olddisplist {
1301 incr i
1302 if {$id == {}} continue
1303 if {$onscreen($id) <= 0} continue
1304 set xi [xcoord $i $olddlevel $lineno]
1305 if {$i == $olddlevel} {
1306 foreach p $currentparents {
1307 set j [lsearch -exact $displist $p]
1308 set coords [list $xi $y1]
1309 set xj [xcoord $j $dlevel $lj]
1310 if {$xj < $xi - $linespc} {
1311 lappend coords [expr {$xj + $linespc}] $y1
1312 notecrossings $p $j $i [expr {$j + 1}]
1313 } elseif {$xj > $xi + $linespc} {
1314 lappend coords [expr {$xj - $linespc}] $y1
1315 notecrossings $p $i $j [expr {$j - 1}]
1316 }
1317 if {[lsearch -exact $dupparents $p] >= 0} {
1318 # draw a double-width line to indicate the doubled parent
1319 lappend coords $xj $y2
1320 lappend sidelines($p) [list $coords 2 none]
1321 if {![info exists mainline($p)]} {
1322 set mainline($p) [list $xj $y2]
1323 set mainlinearrow($p) none
1324 }
1325 } else {
1326 # normal case, no parent duplicated
1327 set yb $y2
1328 set dx [expr {abs($xi - $xj)}]
1329 if {0 && $dx < $linespc} {
1330 set yb [expr {$y1 + $dx}]
1331 }
1332 if {![info exists mainline($p)]} {
1333 if {$xi != $xj} {
1334 lappend coords $xj $yb
1335 }
1336 set mainline($p) $coords
1337 set mainlinearrow($p) none
1338 } else {
1339 lappend coords $xj $yb
1340 if {$yb < $y2} {
1341 lappend coords $xj $y2
1342 }
1343 lappend sidelines($p) [list $coords 1 none]
1344 }
1345 }
1346 }
1347 } else {
1348 set j $i
1349 if {[lindex $displist $i] != $id} {
1350 set j [lsearch -exact $displist $id]
1351 }
1352 if {$j != $i || $xspc1($lineno) != $xspc1($lj)
1353 || ($olddlevel < $i && $i < $dlevel)
1354 || ($dlevel < $i && $i < $olddlevel)} {
1355 set xj [xcoord $j $dlevel $lj]
1356 lappend mainline($id) $xi $y1 $xj $y2
1357 }
1358 }
1359 }
1360 return $dlevel
1361 }
1362
1363 # search for x in a list of lists
1364 proc llsearch {llist x} {
1365 set i 0
1366 foreach l $llist {
1367 if {$l == $x || [lsearch -exact $l $x] >= 0} {
1368 return $i
1369 }
1370 incr i
1371 }
1372 return -1
1373 }
1374
1375 proc drawmore {reading} {
1376 global displayorder numcommits ncmupdate nextupdate
1377 global stopped nhyperspace parents commitlisted
1378 global maxwidth onscreen displist currentparents olddlevel
1379
1380 set n [llength $displayorder]
1381 while {$numcommits < $n} {
1382 set id [lindex $displayorder $numcommits]
1383 set ctxend [expr {$numcommits + 10}]
1384 if {!$reading && $ctxend > $n} {
1385 set ctxend $n
1386 }
1387 set dlist {}
1388 if {$numcommits > 0} {
1389 set dlist [lreplace $displist $olddlevel $olddlevel]
1390 set i $olddlevel
1391 foreach p $currentparents {
1392 if {$onscreen($p) == 0} {
1393 set dlist [linsert $dlist $i $p]
1394 incr i
1395 }
1396 }
1397 }
1398 set nohs {}
1399 set reins {}
1400 set isfat [expr {[llength $dlist] > $maxwidth}]
1401 if {$nhyperspace > 0 || $isfat} {
1402 if {$ctxend > $n} break
1403 # work out what to bring back and
1404 # what we want to don't want to send into hyperspace
1405 set room 1
1406 for {set k $numcommits} {$k < $ctxend} {incr k} {
1407 set x [lindex $displayorder $k]
1408 set i [llsearch $dlist $x]
1409 if {$i < 0} {
1410 set i [llength $dlist]
1411 lappend dlist $x
1412 }
1413 if {[lsearch -exact $nohs $x] < 0} {
1414 lappend nohs $x
1415 }
1416 if {$reins eq {} && $onscreen($x) < 0 && $room} {
1417 set reins [list $x $i]
1418 }
1419 set newp {}
1420 if {[info exists commitlisted($x)]} {
1421 set right 0
1422 foreach p $parents($x) {
1423 if {[llsearch $dlist $p] < 0} {
1424 lappend newp $p
1425 if {[lsearch -exact $nohs $p] < 0} {
1426 lappend nohs $p
1427 }
1428 if {$reins eq {} && $onscreen($p) < 0 && $room} {
1429 set reins [list $p [expr {$i + $right}]]
1430 }
1431 }
1432 set right 1
1433 }
1434 }
1435 set l [lindex $dlist $i]
1436 if {[llength $l] == 1} {
1437 set l $newp
1438 } else {
1439 set j [lsearch -exact $l $x]
1440 set l [concat [lreplace $l $j $j] $newp]
1441 }
1442 set dlist [lreplace $dlist $i $i $l]
1443 if {$room && $isfat && [llength $newp] <= 1} {
1444 set room 0
1445 }
1446 }
1447 }
1448
1449 set dlevel [drawslants $id $reins $nohs]
1450 drawcommitline $dlevel
1451 if {[clock clicks -milliseconds] >= $nextupdate
1452 && $numcommits >= $ncmupdate} {
1453 doupdate $reading
1454 if {$stopped} break
1455 }
1456 }
1457 }
1458
1459 # level here is an index in todo
1460 proc updatetodo {level noshortcut} {
1461 global ncleft todo nnewparents
1462 global commitlisted parents onscreen
1463
1464 set id [lindex $todo $level]
1465 set olds {}
1466 if {[info exists commitlisted($id)]} {
1467 foreach p $parents($id) {
1468 if {[lsearch -exact $olds $p] < 0} {
1469 lappend olds $p
1470 }
1471 }
1472 }
1473 if {!$noshortcut && [llength $olds] == 1} {
1474 set p [lindex $olds 0]
1475 if {$ncleft($p) == 1 && [lsearch -exact $todo $p] < 0} {
1476 set ncleft($p) 0
1477 set todo [lreplace $todo $level $level $p]
1478 set onscreen($p) 0
1479 set nnewparents($id) 1
1480 return 0
1481 }
1482 }
1483
1484 set todo [lreplace $todo $level $level]
1485 set i $level
1486 set n 0
1487 foreach p $olds {
1488 incr ncleft($p) -1
1489 set k [lsearch -exact $todo $p]
1490 if {$k < 0} {
1491 set todo [linsert $todo $i $p]
1492 set onscreen($p) 0
1493 incr i
1494 incr n
1495 }
1496 }
1497 set nnewparents($id) $n
1498
1499 return 1
1500 }
1501
1502 proc decidenext {{noread 0}} {
1503 global ncleft todo
1504 global datemode cdate
1505 global commitinfo
1506
1507 # choose which one to do next time around
1508 set todol [llength $todo]
1509 set level -1
1510 set latest {}
1511 for {set k $todol} {[incr k -1] >= 0} {} {
1512 set p [lindex $todo $k]
1513 if {$ncleft($p) == 0} {
1514 if {$datemode} {
1515 if {![info exists commitinfo($p)]} {
1516 if {$noread} {
1517 return {}
1518 }
1519 readcommit $p
1520 }
1521 if {$latest == {} || $cdate($p) > $latest} {
1522 set level $k
1523 set latest $cdate($p)
1524 }
1525 } else {
1526 set level $k
1527 break
1528 }
1529 }
1530 }
1531
1532 return $level
1533 }
1534
1535 proc drawcommit {id reading} {
1536 global phase todo nchildren datemode nextupdate revlistorder ncleft
1537 global numcommits ncmupdate displayorder todo onscreen parents
1538 global commitlisted commitordered
1539
1540 if {$phase != "incrdraw"} {
1541 set phase incrdraw
1542 set displayorder {}
1543 set todo {}
1544 initgraph
1545 catch {unset commitordered}
1546 }
1547 set commitordered($id) 1
1548 if {$nchildren($id) == 0} {
1549 lappend todo $id
1550 set onscreen($id) 0
1551 }
1552 if {$revlistorder} {
1553 set level [lsearch -exact $todo $id]
1554 if {$level < 0} {
1555 error_popup "oops, $id isn't in todo"
1556 return
1557 }
1558 lappend displayorder $id
1559 updatetodo $level 0
1560 } else {
1561 set level [decidenext 1]
1562 if {$level == {} || $level < 0} return
1563 while 1 {
1564 set id [lindex $todo $level]
1565 if {![info exists commitordered($id)]} {
1566 break
1567 }
1568 lappend displayorder [lindex $todo $level]
1569 if {[updatetodo $level $datemode]} {
1570 set level [decidenext 1]
1571 if {$level == {} || $level < 0} break
1572 }
1573 }
1574 }
1575 drawmore $reading
1576 }
1577
1578 proc finishcommits {} {
1579 global phase oldcommits commits
1580 global canv mainfont ctext maincursor textcursor
1581 global parents displayorder todo
1582
1583 if {$phase == "incrdraw" || $phase == "removecommits"} {
1584 foreach id $oldcommits {
1585 lappend commits $id
1586 drawcommit $id 0
1587 }
1588 set oldcommits {}
1589 drawrest
1590 } elseif {$phase == "updatecommits"} {
1591 # there were no new commits, in fact
1592 set commits $oldcommits
1593 set oldcommits {}
1594 set phase {}
1595 } else {
1596 $canv delete all
1597 $canv create text 3 3 -anchor nw -text "No commits selected" \
1598 -font $mainfont -tags textitems
1599 set phase {}
1600 }
1601 . config -cursor $maincursor
1602 settextcursor $textcursor
1603 }
1604
1605 # Don't change the text pane cursor if it is currently the hand cursor,
1606 # showing that we are over a sha1 ID link.
1607 proc settextcursor {c} {
1608 global ctext curtextcursor
1609
1610 if {[$ctext cget -cursor] == $curtextcursor} {
1611 $ctext config -cursor $c
1612 }
1613 set curtextcursor $c
1614 }
1615
1616 proc drawgraph {} {
1617 global nextupdate startmsecs ncmupdate
1618 global displayorder onscreen
1619
1620 if {$displayorder == {}} return
1621 set startmsecs [clock clicks -milliseconds]
1622 set nextupdate [expr {$startmsecs + 100}]
1623 set ncmupdate 1
1624 initgraph
1625 foreach id $displayorder {
1626 set onscreen($id) 0
1627 }
1628 drawmore 0
1629 }
1630
1631 proc drawrest {} {
1632 global phase stopped redisplaying selectedline
1633 global datemode todo displayorder ncleft
1634 global numcommits ncmupdate
1635 global nextupdate startmsecs revlistorder
1636
1637 set level [decidenext]
1638 if {$level >= 0} {
1639 set phase drawgraph
1640 while 1 {
1641 lappend displayorder [lindex $todo $level]
1642 set hard [updatetodo $level $datemode]
1643 if {$hard} {
1644 set level [decidenext]
1645 if {$level < 0} break
1646 }
1647 }
1648 }
1649 if {$todo != {}} {
1650 puts "ERROR: none of the pending commits can be done yet:"
1651 foreach p $todo {
1652 puts " $p ($ncleft($p))"
1653 }
1654 }
1655
1656 drawmore 0
1657 set phase {}
1658 set drawmsecs [expr {[clock clicks -milliseconds] - $startmsecs}]
1659 #puts "overall $drawmsecs ms for $numcommits commits"
1660 if {$redisplaying} {
1661 if {$stopped == 0 && [info exists selectedline]} {
1662 selectline $selectedline 0
1663 }
1664 if {$stopped == 1} {
1665 set stopped 0
1666 after idle drawgraph
1667 } else {
1668 set redisplaying 0
1669 }
1670 }
1671 }
1672
1673 proc findmatches {f} {
1674 global findtype foundstring foundstrlen
1675 if {$findtype == "Regexp"} {
1676 set matches [regexp -indices -all -inline $foundstring $f]
1677 } else {
1678 if {$findtype == "IgnCase"} {
1679 set str [string tolower $f]
1680 } else {
1681 set str $f
1682 }
1683 set matches {}
1684 set i 0
1685 while {[set j [string first $foundstring $str $i]] >= 0} {
1686 lappend matches [list $j [expr {$j+$foundstrlen-1}]]
1687 set i [expr {$j + $foundstrlen}]
1688 }
1689 }
1690 return $matches
1691 }
1692
1693 proc dofind {} {
1694 global findtype findloc findstring markedmatches commitinfo
1695 global numcommits lineid linehtag linentag linedtag
1696 global mainfont namefont canv canv2 canv3 selectedline
1697 global matchinglines foundstring foundstrlen
1698
1699 stopfindproc
1700 unmarkmatches
1701 focus .
1702 set matchinglines {}
1703 if {$findloc == "Pickaxe"} {
1704 findpatches
1705 return
1706 }
1707 if {$findtype == "IgnCase"} {
1708 set foundstring [string tolower $findstring]
1709 } else {
1710 set foundstring $findstring
1711 }
1712 set foundstrlen [string length $findstring]
1713 if {$foundstrlen == 0} return
1714 if {$findloc == "Files"} {
1715 findfiles
1716 return
1717 }
1718 if {![info exists selectedline]} {
1719 set oldsel -1
1720 } else {
1721 set oldsel $selectedline
1722 }
1723 set didsel 0
1724 set fldtypes {Headline Author Date Committer CDate Comment}
1725 for {set l 0} {$l < $numcommits} {incr l} {
1726 set id $lineid($l)
1727 set info $commitinfo($id)
1728 set doesmatch 0
1729 foreach f $info ty $fldtypes {
1730 if {$findloc != "All fields" && $findloc != $ty} {
1731 continue
1732 }
1733 set matches [findmatches $f]
1734 if {$matches == {}} continue
1735 set doesmatch 1
1736 if {$ty == "Headline"} {
1737 markmatches $canv $l $f $linehtag($l) $matches $mainfont
1738 } elseif {$ty == "Author"} {
1739 markmatches $canv2 $l $f $linentag($l) $matches $namefont
1740 } elseif {$ty == "Date"} {
1741 markmatches $canv3 $l $f $linedtag($l) $matches $mainfont
1742 }
1743 }
1744 if {$doesmatch} {
1745 lappend matchinglines $l
1746 if {!$didsel && $l > $oldsel} {
1747 findselectline $l
1748 set didsel 1
1749 }
1750 }
1751 }
1752 if {$matchinglines == {}} {
1753 bell
1754 } elseif {!$didsel} {
1755 findselectline [lindex $matchinglines 0]
1756 }
1757 }
1758
1759 proc findselectline {l} {
1760 global findloc commentend ctext
1761 selectline $l 1
1762 if {$findloc == "All fields" || $findloc == "Comments"} {
1763 # highlight the matches in the comments
1764 set f [$ctext get 1.0 $commentend]
1765 set matches [findmatches $f]
1766 foreach match $matches {
1767 set start [lindex $match 0]
1768 set end [expr {[lindex $match 1] + 1}]
1769 $ctext tag add found "1.0 + $start c" "1.0 + $end c"
1770 }
1771 }
1772 }
1773
1774 proc findnext {restart} {
1775 global matchinglines selectedline
1776 if {![info exists matchinglines]} {
1777 if {$restart} {
1778 dofind
1779 }
1780 return
1781 }
1782 if {![info exists selectedline]} return
1783 foreach l $matchinglines {
1784 if {$l > $selectedline} {
1785 findselectline $l
1786 return
1787 }
1788 }
1789 bell
1790 }
1791
1792 proc findprev {} {
1793 global matchinglines selectedline
1794 if {![info exists matchinglines]} {
1795 dofind
1796 return
1797 }
1798 if {![info exists selectedline]} return
1799 set prev {}
1800 foreach l $matchinglines {
1801 if {$l >= $selectedline} break
1802 set prev $l
1803 }
1804 if {$prev != {}} {
1805 findselectline $prev
1806 } else {
1807 bell
1808 }
1809 }
1810
1811 proc findlocchange {name ix op} {
1812 global findloc findtype findtypemenu
1813 if {$findloc == "Pickaxe"} {
1814 set findtype Exact
1815 set state disabled
1816 } else {
1817 set state normal
1818 }
1819 $findtypemenu entryconf 1 -state $state
1820 $findtypemenu entryconf 2 -state $state
1821 }
1822
1823 proc stopfindproc {{done 0}} {
1824 global findprocpid findprocfile findids
1825 global ctext findoldcursor phase maincursor textcursor
1826 global findinprogress
1827
1828 catch {unset findids}
1829 if {[info exists findprocpid]} {
1830 if {!$done} {
1831 catch {exec kill $findprocpid}
1832 }
1833 catch {close $findprocfile}
1834 unset findprocpid
1835 }
1836 if {[info exists findinprogress]} {
1837 unset findinprogress
1838 if {$phase != "incrdraw"} {
1839 . config -cursor $maincursor
1840 settextcursor $textcursor
1841 }
1842 }
1843 }
1844
1845 proc findpatches {} {
1846 global findstring selectedline numcommits
1847 global findprocpid findprocfile
1848 global finddidsel ctext lineid findinprogress
1849 global findinsertpos
1850
1851 if {$numcommits == 0} return
1852
1853 # make a list of all the ids to search, starting at the one
1854 # after the selected line (if any)
1855 if {[info exists selectedline]} {
1856 set l $selectedline
1857 } else {
1858 set l -1
1859 }
1860 set inputids {}
1861 for {set i 0} {$i < $numcommits} {incr i} {
1862 if {[incr l] >= $numcommits} {
1863 set l 0
1864 }
1865 append inputids $lineid($l) "\n"
1866 }
1867
1868 if {[catch {
1869 set f [open [list | git-diff-tree --stdin -s -r -S$findstring \
1870 << $inputids] r]
1871 } err]} {
1872 error_popup "Error starting search process: $err"
1873 return
1874 }
1875
1876 set findinsertpos end
1877 set findprocfile $f
1878 set findprocpid [pid $f]
1879 fconfigure $f -blocking 0
1880 fileevent $f readable readfindproc
1881 set finddidsel 0
1882 . config -cursor watch
1883 settextcursor watch
1884 set findinprogress 1
1885 }
1886
1887 proc readfindproc {} {
1888 global findprocfile finddidsel
1889 global idline matchinglines findinsertpos
1890
1891 set n [gets $findprocfile line]
1892 if {$n < 0} {
1893 if {[eof $findprocfile]} {
1894 stopfindproc 1
1895 if {!$finddidsel} {
1896 bell
1897 }
1898 }
1899 return
1900 }
1901 if {![regexp {^[0-9a-f]{40}} $line id]} {
1902 error_popup "Can't parse git-diff-tree output: $line"
1903 stopfindproc
1904 return
1905 }
1906 if {![info exists idline($id)]} {
1907 puts stderr "spurious id: $id"
1908 return
1909 }
1910 set l $idline($id)
1911 insertmatch $l $id
1912 }
1913
1914 proc insertmatch {l id} {
1915 global matchinglines findinsertpos finddidsel
1916
1917 if {$findinsertpos == "end"} {
1918 if {$matchinglines != {} && $l < [lindex $matchinglines 0]} {
1919 set matchinglines [linsert $matchinglines 0 $l]
1920 set findinsertpos 1
1921 } else {
1922 lappend matchinglines $l
1923 }
1924 } else {
1925 set matchinglines [linsert $matchinglines $findinsertpos $l]
1926 incr findinsertpos
1927 }
1928 markheadline $l $id
1929 if {!$finddidsel} {
1930 findselectline $l
1931 set finddidsel 1
1932 }
1933 }
1934
1935 proc findfiles {} {
1936 global selectedline numcommits lineid ctext
1937 global ffileline finddidsel parents nparents
1938 global findinprogress findstartline findinsertpos
1939 global treediffs fdiffids fdiffsneeded fdiffpos
1940 global findmergefiles
1941
1942 if {$numcommits == 0} return
1943
1944 if {[info exists selectedline]} {
1945 set l [expr {$selectedline + 1}]
1946 } else {
1947 set l 0
1948 }
1949 set ffileline $l
1950 set findstartline $l
1951 set diffsneeded {}
1952 set fdiffsneeded {}
1953 while 1 {
1954 set id $lineid($l)
1955 if {$findmergefiles || $nparents($id) == 1} {
1956 foreach p $parents($id) {
1957 if {![info exists treediffs([list $id $p])]} {
1958 append diffsneeded "$id $p\n"
1959 lappend fdiffsneeded [list $id $p]
1960 }
1961 }
1962 }
1963 if {[incr l] >= $numcommits} {
1964 set l 0
1965 }
1966 if {$l == $findstartline} break
1967 }
1968
1969 # start off a git-diff-tree process if needed
1970 if {$diffsneeded ne {}} {
1971 if {[catch {
1972 set df [open [list | git-diff-tree -r --stdin << $diffsneeded] r]
1973 } err ]} {
1974 error_popup "Error starting search process: $err"
1975 return
1976 }
1977 catch {unset fdiffids}
1978 set fdiffpos 0
1979 fconfigure $df -blocking 0
1980 fileevent $df readable [list readfilediffs $df]
1981 }
1982
1983 set finddidsel 0
1984 set findinsertpos end
1985 set id $lineid($l)
1986 set p [lindex $parents($id) 0]
1987 . config -cursor watch
1988 settextcursor watch
1989 set findinprogress 1
1990 findcont [list $id $p]
1991 update
1992 }
1993
1994 proc readfilediffs {df} {
1995 global findids fdiffids fdiffs
1996
1997 set n [gets $df line]
1998 if {$n < 0} {
1999 if {[eof $df]} {
2000 donefilediff
2001 if {[catch {close $df} err]} {
2002 stopfindproc
2003 bell
2004 error_popup "Error in git-diff-tree: $err"
2005 } elseif {[info exists findids]} {
2006 set ids $findids
2007 stopfindproc
2008 bell
2009 error_popup "Couldn't find diffs for {$ids}"
2010 }
2011 }
2012 return
2013 }
2014 if {[regexp {^([0-9a-f]{40}) \(from ([0-9a-f]{40})\)} $line match id p]} {
2015 # start of a new string of diffs
2016 donefilediff
2017 set fdiffids [list $id $p]
2018 set fdiffs {}
2019 } elseif {[string match ":*" $line]} {
2020 lappend fdiffs [lindex $line 5]
2021 }
2022 }
2023
2024 proc donefilediff {} {
2025 global fdiffids fdiffs treediffs findids
2026 global fdiffsneeded fdiffpos
2027
2028 if {[info exists fdiffids]} {
2029 while {[lindex $fdiffsneeded $fdiffpos] ne $fdiffids
2030 && $fdiffpos < [llength $fdiffsneeded]} {
2031 # git-diff-tree doesn't output anything for a commit
2032 # which doesn't change anything
2033 set nullids [lindex $fdiffsneeded $fdiffpos]
2034 set treediffs($nullids) {}
2035 if {[info exists findids] && $nullids eq $findids} {
2036 unset findids
2037 findcont $nullids
2038 }
2039 incr fdiffpos
2040 }
2041 incr fdiffpos
2042
2043 if {![info exists treediffs($fdiffids)]} {
2044 set treediffs($fdiffids) $fdiffs
2045 }
2046 if {[info exists findids] && $fdiffids eq $findids} {
2047 unset findids
2048 findcont $fdiffids
2049 }
2050 }
2051 }
2052
2053 proc findcont {ids} {
2054 global findids treediffs parents nparents
2055 global ffileline findstartline finddidsel
2056 global lineid numcommits matchinglines findinprogress
2057 global findmergefiles
2058
2059 set id [lindex $ids 0]
2060 set p [lindex $ids 1]
2061 set pi [lsearch -exact $parents($id) $p]
2062 set l $ffileline
2063 while 1 {
2064 if {$findmergefiles || $nparents($id) == 1} {
2065 if {![info exists treediffs($ids)]} {
2066 set findids $ids
2067 set ffileline $l
2068 return
2069 }
2070 set doesmatch 0
2071 foreach f $treediffs($ids) {
2072 set x [findmatches $f]
2073 if {$x != {}} {
2074 set doesmatch 1
2075 break
2076 }
2077 }
2078 if {$doesmatch} {
2079 insertmatch $l $id
2080 set pi $nparents($id)
2081 }
2082 } else {
2083 set pi $nparents($id)
2084 }
2085 if {[incr pi] >= $nparents($id)} {
2086 set pi 0
2087 if {[incr l] >= $numcommits} {
2088 set l 0
2089 }
2090 if {$l == $findstartline} break
2091 set id $lineid($l)
2092 }
2093 set p [lindex $parents($id) $pi]
2094 set ids [list $id $p]
2095 }
2096 stopfindproc
2097 if {!$finddidsel} {
2098 bell
2099 }
2100 }
2101
2102 # mark a commit as matching by putting a yellow background
2103 # behind the headline
2104 proc markheadline {l id} {
2105 global canv mainfont linehtag commitinfo
2106
2107 set bbox [$canv bbox $linehtag($l)]
2108 set t [$canv create rect $bbox -outline {} -tags matches -fill yellow]
2109 $canv lower $t
2110 }
2111
2112 # mark the bits of a headline, author or date that match a find string
2113 proc markmatches {canv l str tag matches font} {
2114 set bbox [$canv bbox $tag]
2115 set x0 [lindex $bbox 0]
2116 set y0 [lindex $bbox 1]
2117 set y1 [lindex $bbox 3]
2118 foreach match $matches {
2119 set start [lindex $match 0]
2120 set end [lindex $match 1]
2121 if {$start > $end} continue
2122 set xoff [font measure $font [string range $str 0 [expr {$start-1}]]]
2123 set xlen [font measure $font [string range $str 0 [expr {$end}]]]
2124 set t [$canv create rect [expr {$x0+$xoff}] $y0 \
2125 [expr {$x0+$xlen+2}] $y1 \
2126 -outline {} -tags matches -fill yellow]
2127 $canv lower $t
2128 }
2129 }
2130
2131 proc unmarkmatches {} {
2132 global matchinglines findids
2133 allcanvs delete matches
2134 catch {unset matchinglines}
2135 catch {unset findids}
2136 }
2137
2138 proc selcanvline {w x y} {
2139 global canv canvy0 ctext linespc
2140 global lineid linehtag linentag linedtag rowtextx
2141 set ymax [lindex [$canv cget -scrollregion] 3]
2142 if {$ymax == {}} return
2143 set yfrac [lindex [$canv yview] 0]
2144 set y [expr {$y + $yfrac * $ymax}]
2145 set l [expr {int(($y - $canvy0) / $linespc + 0.5)}]
2146 if {$l < 0} {
2147 set l 0
2148 }
2149 if {$w eq $canv} {
2150 if {![info exists rowtextx($l)] || $x < $rowtextx($l)} return
2151 }
2152 unmarkmatches
2153 selectline $l 1
2154 }
2155
2156 proc commit_descriptor {p} {
2157 global commitinfo
2158 set l "..."
2159 if {[info exists commitinfo($p)]} {
2160 set l [lindex $commitinfo($p) 0]
2161 }
2162 return "$p ($l)"
2163 }
2164
2165 # append some text to the ctext widget, and make any SHA1 ID
2166 # that we know about be a clickable link.
2167 proc appendwithlinks {text} {
2168 global ctext idline linknum
2169
2170 set start [$ctext index "end - 1c"]
2171 $ctext insert end $text
2172 $ctext insert end "\n"
2173 set links [regexp -indices -all -inline {[0-9a-f]{40}} $text]
2174 foreach l $links {
2175 set s [lindex $l 0]
2176 set e [lindex $l 1]
2177 set linkid [string range $text $s $e]
2178 if {![info exists idline($linkid)]} continue
2179 incr e
2180 $ctext tag add link "$start + $s c" "$start + $e c"
2181 $ctext tag add link$linknum "$start + $s c" "$start + $e c"
2182 $ctext tag bind link$linknum <1> [list selectline $idline($linkid) 1]
2183 incr linknum
2184 }
2185 $ctext tag conf link -foreground blue -underline 1
2186 $ctext tag bind link <Enter> { %W configure -cursor hand2 }
2187 $ctext tag bind link <Leave> { %W configure -cursor $curtextcursor }
2188 }
2189
2190 proc selectline {l isnew} {
2191 global canv canv2 canv3 ctext commitinfo selectedline
2192 global lineid linehtag linentag linedtag
2193 global canvy0 linespc parents nparents children
2194 global cflist currentid sha1entry
2195 global commentend idtags idline linknum
2196 global mergemax
2197
2198 $canv delete hover
2199 normalline
2200 if {![info exists lineid($l)] || ![info exists linehtag($l)]} return
2201 $canv delete secsel
2202 set t [eval $canv create rect [$canv bbox $linehtag($l)] -outline {{}} \
2203 -tags secsel -fill [$canv cget -selectbackground]]
2204 $canv lower $t
2205 $canv2 delete secsel
2206 set t [eval $canv2 create rect [$canv2 bbox $linentag($l)] -outline {{}} \
2207 -tags secsel -fill [$canv2 cget -selectbackground]]
2208 $canv2 lower $t
2209 $canv3 delete secsel
2210 set t [eval $canv3 create rect [$canv3 bbox $linedtag($l)] -outline {{}} \
2211 -tags secsel -fill [$canv3 cget -selectbackground]]
2212 $canv3 lower $t
2213 set y [expr {$canvy0 + $l * $linespc}]
2214 set ymax [lindex [$canv cget -scrollregion] 3]
2215 set ytop [expr {$y - $linespc - 1}]
2216 set ybot [expr {$y + $linespc + 1}]
2217 set wnow [$canv yview]
2218 set wtop [expr {[lindex $wnow 0] * $ymax}]
2219 set wbot [expr {[lindex $wnow 1] * $ymax}]
2220 set wh [expr {$wbot - $wtop}]
2221 set newtop $wtop
2222 if {$ytop < $wtop} {
2223 if {$ybot < $wtop} {
2224 set newtop [expr {$y - $wh / 2.0}]
2225 } else {
2226 set newtop $ytop
2227 if {$newtop > $wtop - $linespc} {
2228 set newtop [expr {$wtop - $linespc}]
2229 }
2230 }
2231 } elseif {$ybot > $wbot} {
2232 if {$ytop > $wbot} {
2233 set newtop [expr {$y - $wh / 2.0}]
2234 } else {
2235 set newtop [expr {$ybot - $wh}]
2236 if {$newtop < $wtop + $linespc} {
2237 set newtop [expr {$wtop + $linespc}]
2238 }
2239 }
2240 }
2241 if {$newtop != $wtop} {
2242 if {$newtop < 0} {
2243 set newtop 0
2244 }
2245 allcanvs yview moveto [expr {$newtop * 1.0 / $ymax}]
2246 }
2247
2248 if {$isnew} {
2249 addtohistory [list selectline $l 0]
2250 }
2251
2252 set selectedline $l
2253
2254 set id $lineid($l)
2255 set currentid $id
2256 $sha1entry delete 0 end
2257 $sha1entry insert 0 $id
2258 $sha1entry selection from 0
2259 $sha1entry selection to end
2260
2261 $ctext conf -state normal
2262 $ctext delete 0.0 end
2263 set linknum 0
2264 $ctext mark set fmark.0 0.0
2265 $ctext mark gravity fmark.0 left
2266 set info $commitinfo($id)
2267 set date [formatdate [lindex $info 2]]
2268 $ctext insert end "Author: [lindex $info 1] $date\n"
2269 set date [formatdate [lindex $info 4]]
2270 $ctext insert end "Committer: [lindex $info 3] $date\n"
2271 if {[info exists idtags($id)]} {
2272 $ctext insert end "Tags:"
2273 foreach tag $idtags($id) {
2274 $ctext insert end " $tag"
2275 }
2276 $ctext insert end "\n"
2277 }
2278
2279 set comment {}
2280 if {$nparents($id) > 1} {
2281 set np 0
2282 foreach p $parents($id) {
2283 if {$np >= $mergemax} {
2284 set tag mmax
2285 } else {
2286 set tag m$np
2287 }
2288 $ctext insert end "Parent: " $tag
2289 appendwithlinks [commit_descriptor $p]
2290 incr np
2291 }
2292 } else {
2293 if {[info exists parents($id)]} {
2294 foreach p $parents($id) {
2295 append comment "Parent: [commit_descriptor $p]\n"
2296 }
2297 }
2298 }
2299
2300 if {[info exists children($id)]} {
2301 foreach c $children($id) {
2302 append comment "Child: [commit_descriptor $c]\n"
2303 }
2304 }
2305 append comment "\n"
2306 append comment [lindex $info 5]
2307
2308 # make anything that looks like a SHA1 ID be a clickable link
2309 appendwithlinks $comment
2310
2311 $ctext tag delete Comments
2312 $ctext tag remove found 1.0 end
2313 $ctext conf -state disabled
2314 set commentend [$ctext index "end - 1c"]
2315
2316 $cflist delete 0 end
2317 $cflist insert end "Comments"
2318 if {$nparents($id) == 1} {
2319 startdiff $id
2320 } elseif {$nparents($id) > 1} {
2321 mergediff $id
2322 }
2323 }
2324
2325 proc selnextline {dir} {
2326 global selectedline
2327 if {![info exists selectedline]} return
2328 set l [expr {$selectedline + $dir}]
2329 unmarkmatches
2330 selectline $l 1
2331 }
2332
2333 proc unselectline {} {
2334 global selectedline
2335
2336 catch {unset selectedline}
2337 allcanvs delete secsel
2338 }
2339
2340 proc addtohistory {cmd} {
2341 global history historyindex
2342
2343 if {$historyindex > 0
2344 && [lindex $history [expr {$historyindex - 1}]] == $cmd} {
2345 return
2346 }
2347
2348 if {$historyindex < [llength $history]} {
2349 set history [lreplace $history $historyindex end $cmd]
2350 } else {
2351 lappend history $cmd
2352 }
2353 incr historyindex
2354 if {$historyindex > 1} {
2355 .ctop.top.bar.leftbut conf -state normal
2356 } else {
2357 .ctop.top.bar.leftbut conf -state disabled
2358 }
2359 .ctop.top.bar.rightbut conf -state disabled
2360 }
2361
2362 proc goback {} {
2363 global history historyindex
2364
2365 if {$historyindex > 1} {
2366 incr historyindex -1
2367 set cmd [lindex $history [expr {$historyindex - 1}]]
2368 eval $cmd
2369 .ctop.top.bar.rightbut conf -state normal
2370 }
2371 if {$historyindex <= 1} {
2372 .ctop.top.bar.leftbut conf -state disabled
2373 }
2374 }
2375
2376 proc goforw {} {
2377 global history historyindex
2378
2379 if {$historyindex < [llength $history]} {
2380 set cmd [lindex $history $historyindex]
2381 incr historyindex
2382 eval $cmd
2383 .ctop.top.bar.leftbut conf -state normal
2384 }
2385 if {$historyindex >= [llength $history]} {
2386 .ctop.top.bar.rightbut conf -state disabled
2387 }
2388 }
2389
2390 proc mergediff {id} {
2391 global parents diffmergeid diffopts mdifffd
2392 global difffilestart
2393
2394 set diffmergeid $id
2395 catch {unset difffilestart}
2396 # this doesn't seem to actually affect anything...
2397 set env(GIT_DIFF_OPTS) $diffopts
2398 set cmd [concat | git-diff-tree --no-commit-id --cc $id]
2399 if {[catch {set mdf [open $cmd r]} err]} {
2400 error_popup "Error getting merge diffs: $err"
2401 return
2402 }
2403 fconfigure $mdf -blocking 0
2404 set mdifffd($id) $mdf
2405 fileevent $mdf readable [list getmergediffline $mdf $id]
2406 set nextupdate [expr {[clock clicks -milliseconds] + 100}]
2407 }
2408
2409 proc getmergediffline {mdf id} {
2410 global diffmergeid ctext cflist nextupdate nparents mergemax
2411 global difffilestart
2412
2413 set n [gets $mdf line]
2414 if {$n < 0} {
2415 if {[eof $mdf]} {
2416 close $mdf
2417 }
2418 return
2419 }
2420 if {![info exists diffmergeid] || $id != $diffmergeid} {
2421 return
2422 }
2423 $ctext conf -state normal
2424 if {[regexp {^diff --cc (.*)} $line match fname]} {
2425 # start of a new file
2426 $ctext insert end "\n"
2427 set here [$ctext index "end - 1c"]
2428 set i [$cflist index end]
2429 $ctext mark set fmark.$i $here
2430 $ctext mark gravity fmark.$i left
2431 set difffilestart([expr {$i-1}]) $here
2432 $cflist insert end $fname
2433 set l [expr {(78 - [string length $fname]) / 2}]
2434 set pad [string range "----------------------------------------" 1 $l]
2435 $ctext insert end "$pad $fname $pad\n" filesep
2436 } elseif {[regexp {^@@} $line]} {
2437 $ctext insert end "$line\n" hunksep
2438 } elseif {[regexp {^[0-9a-f]{40}$} $line] || [regexp {^index} $line]} {
2439 # do nothing
2440 } else {
2441 # parse the prefix - one ' ', '-' or '+' for each parent
2442 set np $nparents($id)
2443 set spaces {}
2444 set minuses {}
2445 set pluses {}
2446 set isbad 0
2447 for {set j 0} {$j < $np} {incr j} {
2448 set c [string range $line $j $j]
2449 if {$c == " "} {
2450 lappend spaces $j
2451 } elseif {$c == "-"} {
2452 lappend minuses $j
2453 } elseif {$c == "+"} {
2454 lappend pluses $j
2455 } else {
2456 set isbad 1
2457 break
2458 }
2459 }
2460 set tags {}
2461 set num {}
2462 if {!$isbad && $minuses ne {} && $pluses eq {}} {
2463 # line doesn't appear in result, parents in $minuses have the line
2464 set num [lindex $minuses 0]
2465 } elseif {!$isbad && $pluses ne {} && $minuses eq {}} {
2466 # line appears in result, parents in $pluses don't have the line
2467 lappend tags mresult
2468 set num [lindex $spaces 0]
2469 }
2470 if {$num ne {}} {
2471 if {$num >= $mergemax} {
2472 set num "max"
2473 }
2474 lappend tags m$num
2475 }
2476 $ctext insert end "$line\n" $tags
2477 }
2478 $ctext conf -state disabled
2479 if {[clock clicks -milliseconds] >= $nextupdate} {
2480 incr nextupdate 100
2481 fileevent $mdf readable {}
2482 update
2483 fileevent $mdf readable [list getmergediffline $mdf $id]
2484 }
2485 }
2486
2487 proc startdiff {ids} {
2488 global treediffs diffids treepending diffmergeid
2489
2490 set diffids $ids
2491 catch {unset diffmergeid}
2492 if {![info exists treediffs($ids)]} {
2493 if {![info exists treepending]} {
2494 gettreediffs $ids
2495 }
2496 } else {
2497 addtocflist $ids
2498 }
2499 }
2500
2501 proc addtocflist {ids} {
2502 global treediffs cflist
2503 foreach f $treediffs($ids) {
2504 $cflist insert end $f
2505 }
2506 getblobdiffs $ids
2507 }
2508
2509 proc gettreediffs {ids} {
2510 global treediff parents treepending
2511 set treepending $ids
2512 set treediff {}
2513 if {[catch \
2514 {set gdtf [open [concat | git-diff-tree --no-commit-id -r $ids] r]} \
2515 ]} return
2516 fconfigure $gdtf -blocking 0
2517 fileevent $gdtf readable [list gettreediffline $gdtf $ids]
2518 }
2519
2520 proc gettreediffline {gdtf ids} {
2521 global treediff treediffs treepending diffids diffmergeid
2522
2523 set n [gets $gdtf line]
2524 if {$n < 0} {
2525 if {![eof $gdtf]} return
2526 close $gdtf
2527 set treediffs($ids) $treediff
2528 unset treepending
2529 if {$ids != $diffids} {
2530 gettreediffs $diffids
2531 } else {
2532 if {[info exists diffmergeid]} {
2533 contmergediff $ids
2534 } else {
2535 addtocflist $ids
2536 }
2537 }
2538 return
2539 }
2540 set file [lindex $line 5]
2541 lappend treediff $file
2542 }
2543
2544 proc getblobdiffs {ids} {
2545 global diffopts blobdifffd diffids env curdifftag curtagstart
2546 global difffilestart nextupdate diffinhdr treediffs
2547
2548 set env(GIT_DIFF_OPTS) $diffopts
2549 set cmd [concat | git-diff-tree --no-commit-id -r -p -C $ids]
2550 if {[catch {set bdf [open $cmd r]} err]} {
2551 puts "error getting diffs: $err"
2552 return
2553 }
2554 set diffinhdr 0
2555 fconfigure $bdf -blocking 0
2556 set blobdifffd($ids) $bdf
2557 set curdifftag Comments
2558 set curtagstart 0.0
2559 catch {unset difffilestart}
2560 fileevent $bdf readable [list getblobdiffline $bdf $diffids]
2561 set nextupdate [expr {[clock clicks -milliseconds] + 100}]
2562 }
2563
2564 proc getblobdiffline {bdf ids} {
2565 global diffids blobdifffd ctext curdifftag curtagstart
2566 global diffnexthead diffnextnote difffilestart
2567 global nextupdate diffinhdr treediffs
2568
2569 set n [gets $bdf line]
2570 if {$n < 0} {
2571 if {[eof $bdf]} {
2572 close $bdf
2573 if {$ids == $diffids && $bdf == $blobdifffd($ids)} {
2574 $ctext tag add $curdifftag $curtagstart end
2575 }
2576 }
2577 return
2578 }
2579 if {$ids != $diffids || $bdf != $blobdifffd($ids)} {
2580 return
2581 }
2582 $ctext conf -state normal
2583 if {[regexp {^diff --git a/(.*) b/(.*)} $line match fname newname]} {
2584 # start of a new file
2585 $ctext insert end "\n"
2586 $ctext tag add $curdifftag $curtagstart end
2587 set curtagstart [$ctext index "end - 1c"]
2588 set header $newname
2589 set here [$ctext index "end - 1c"]
2590 set i [lsearch -exact $treediffs($diffids) $fname]
2591 if {$i >= 0} {
2592 set difffilestart($i) $here
2593 incr i
2594 $ctext mark set fmark.$i $here
2595 $ctext mark gravity fmark.$i left
2596 }
2597 if {$newname != $fname} {
2598 set i [lsearch -exact $treediffs($diffids) $newname]
2599 if {$i >= 0} {
2600 set difffilestart($i) $here
2601 incr i
2602 $ctext mark set fmark.$i $here
2603 $ctext mark gravity fmark.$i left
2604 }
2605 }
2606 set curdifftag "f:$fname"
2607 $ctext tag delete $curdifftag
2608 set l [expr {(78 - [string length $header]) / 2}]
2609 set pad [string range "----------------------------------------" 1 $l]
2610 $ctext insert end "$pad $header $pad\n" filesep
2611 set diffinhdr 1
2612 } elseif {[regexp {^(---|\+\+\+)} $line]} {
2613 set diffinhdr 0
2614 } elseif {[regexp {^@@ -([0-9]+),([0-9]+) \+([0-9]+),([0-9]+) @@(.*)} \
2615 $line match f1l f1c f2l f2c rest]} {
2616 $ctext insert end "$line\n" hunksep
2617 set diffinhdr 0
2618 } else {
2619 set x [string range $line 0 0]
2620 if {$x == "-" || $x == "+"} {
2621 set tag [expr {$x == "+"}]
2622 $ctext insert end "$line\n" d$tag
2623 } elseif {$x == " "} {
2624 $ctext insert end "$line\n"
2625 } elseif {$diffinhdr || $x == "\\"} {
2626 # e.g. "\ No newline at end of file"
2627 $ctext insert end "$line\n" filesep
2628 } else {
2629 # Something else we don't recognize
2630 if {$curdifftag != "Comments"} {
2631 $ctext insert end "\n"
2632 $ctext tag add $curdifftag $curtagstart end
2633 set curtagstart [$ctext index "end - 1c"]
2634 set curdifftag Comments
2635 }
2636 $ctext insert end "$line\n" filesep
2637 }
2638 }
2639 $ctext conf -state disabled
2640 if {[clock clicks -milliseconds] >= $nextupdate} {
2641 incr nextupdate 100
2642 fileevent $bdf readable {}
2643 update
2644 fileevent $bdf readable "getblobdiffline $bdf {$ids}"
2645 }
2646 }
2647
2648 proc nextfile {} {
2649 global difffilestart ctext
2650 set here [$ctext index @0,0]
2651 for {set i 0} {[info exists difffilestart($i)]} {incr i} {
2652 if {[$ctext compare $difffilestart($i) > $here]} {
2653 if {![info exists pos]
2654 || [$ctext compare $difffilestart($i) < $pos]} {
2655 set pos $difffilestart($i)
2656 }
2657 }
2658 }
2659 if {[info exists pos]} {
2660 $ctext yview $pos
2661 }
2662 }
2663
2664 proc listboxsel {} {
2665 global ctext cflist currentid
2666 if {![info exists currentid]} return
2667 set sel [lsort [$cflist curselection]]
2668 if {$sel eq {}} return
2669 set first [lindex $sel 0]
2670 catch {$ctext yview fmark.$first}
2671 }
2672
2673 proc setcoords {} {
2674 global linespc charspc canvx0 canvy0 mainfont
2675 global xspc1 xspc2 lthickness
2676
2677 set linespc [font metrics $mainfont -linespace]
2678 set charspc [font measure $mainfont "m"]
2679 set canvy0 [expr {3 + 0.5 * $linespc}]
2680 set canvx0 [expr {3 + 0.5 * $linespc}]
2681 set lthickness [expr {int($linespc / 9) + 1}]
2682 set xspc1(0) $linespc
2683 set xspc2 $linespc
2684 }
2685
2686 proc redisplay {} {
2687 global stopped redisplaying phase
2688 if {$stopped > 1} return
2689 if {$phase == "getcommits"} return
2690 set redisplaying 1
2691 if {$phase == "drawgraph" || $phase == "incrdraw"} {
2692 set stopped 1
2693 } else {
2694 drawgraph
2695 }
2696 }
2697
2698 proc incrfont {inc} {
2699 global mainfont namefont textfont ctext canv phase
2700 global stopped entries
2701 unmarkmatches
2702 set mainfont [lreplace $mainfont 1 1 [expr {[lindex $mainfont 1] + $inc}]]
2703 set namefont [lreplace $namefont 1 1 [expr {[lindex $namefont 1] + $inc}]]
2704 set textfont [lreplace $textfont 1 1 [expr {[lindex $textfont 1] + $inc}]]
2705 setcoords
2706 $ctext conf -font $textfont
2707 $ctext tag conf filesep -font [concat $textfont bold]
2708 foreach e $entries {
2709 $e conf -font $mainfont
2710 }
2711 if {$phase == "getcommits"} {
2712 $canv itemconf textitems -font $mainfont
2713 }
2714 redisplay
2715 }
2716
2717 proc clearsha1 {} {
2718 global sha1entry sha1string
2719 if {[string length $sha1string] == 40} {
2720 $sha1entry delete 0 end
2721 }
2722 }
2723
2724 proc sha1change {n1 n2 op} {
2725 global sha1string currentid sha1but
2726 if {$sha1string == {}
2727 || ([info exists currentid] && $sha1string == $currentid)} {
2728 set state disabled
2729 } else {
2730 set state normal
2731 }
2732 if {[$sha1but cget -state] == $state} return
2733 if {$state == "normal"} {
2734 $sha1but conf -state normal -relief raised -text "Goto: "
2735 } else {
2736 $sha1but conf -state disabled -relief flat -text "SHA1 ID: "
2737 }
2738 }
2739
2740 proc gotocommit {} {
2741 global sha1string currentid idline tagids
2742 global lineid numcommits
2743
2744 if {$sha1string == {}
2745 || ([info exists currentid] && $sha1string == $currentid)} return
2746 if {[info exists tagids($sha1string)]} {
2747 set id $tagids($sha1string)
2748 } else {
2749 set id [string tolower $sha1string]
2750 if {[regexp {^[0-9a-f]{4,39}$} $id]} {
2751 set matches {}
2752 for {set l 0} {$l < $numcommits} {incr l} {
2753 if {[string match $id* $lineid($l)]} {
2754 lappend matches $lineid($l)
2755 }
2756 }
2757 if {$matches ne {}} {
2758 if {[llength $matches] > 1} {
2759 error_popup "Short SHA1 id $id is ambiguous"
2760 return
2761 }
2762 set id [lindex $matches 0]
2763 }
2764 }
2765 }
2766 if {[info exists idline($id)]} {
2767 selectline $idline($id) 1
2768 return
2769 }
2770 if {[regexp {^[0-9a-fA-F]{4,}$} $sha1string]} {
2771 set type "SHA1 id"
2772 } else {
2773 set type "Tag"
2774 }
2775 error_popup "$type $sha1string is not known"
2776 }
2777
2778 proc lineenter {x y id} {
2779 global hoverx hovery hoverid hovertimer
2780 global commitinfo canv
2781
2782 if {![info exists commitinfo($id)]} return
2783 set hoverx $x
2784 set hovery $y
2785 set hoverid $id
2786 if {[info exists hovertimer]} {
2787 after cancel $hovertimer
2788 }
2789 set hovertimer [after 500 linehover]
2790 $canv delete hover
2791 }
2792
2793 proc linemotion {x y id} {
2794 global hoverx hovery hoverid hovertimer
2795
2796 if {[info exists hoverid] && $id == $hoverid} {
2797 set hoverx $x
2798 set hovery $y
2799 if {[info exists hovertimer]} {
2800 after cancel $hovertimer
2801 }
2802 set hovertimer [after 500 linehover]
2803 }
2804 }
2805
2806 proc lineleave {id} {
2807 global hoverid hovertimer canv
2808
2809 if {[info exists hoverid] && $id == $hoverid} {
2810 $canv delete hover
2811 if {[info exists hovertimer]} {
2812 after cancel $hovertimer
2813 unset hovertimer
2814 }
2815 unset hoverid
2816 }
2817 }
2818
2819 proc linehover {} {
2820 global hoverx hovery hoverid hovertimer
2821 global canv linespc lthickness
2822 global commitinfo mainfont
2823
2824 set text [lindex $commitinfo($hoverid) 0]
2825 set ymax [lindex [$canv cget -scrollregion] 3]
2826 if {$ymax == {}} return
2827 set yfrac [lindex [$canv yview] 0]
2828 set x [expr {$hoverx + 2 * $linespc}]
2829 set y [expr {$hovery + $yfrac * $ymax - $linespc / 2}]
2830 set x0 [expr {$x - 2 * $lthickness}]
2831 set y0 [expr {$y - 2 * $lthickness}]
2832 set x1 [expr {$x + [font measure $mainfont $text] + 2 * $lthickness}]
2833 set y1 [expr {$y + $linespc + 2 * $lthickness}]
2834 set t [$canv create rectangle $x0 $y0 $x1 $y1 \
2835 -fill \#ffff80 -outline black -width 1 -tags hover]
2836 $canv raise $t
2837 set t [$canv create text $x $y -anchor nw -text $text -tags hover -font $mainfont]
2838 $canv raise $t
2839 }
2840
2841 proc clickisonarrow {id y} {
2842 global mainline mainlinearrow sidelines lthickness
2843
2844 set thresh [expr {2 * $lthickness + 6}]
2845 if {[info exists mainline($id)]} {
2846 if {$mainlinearrow($id) ne "none"} {
2847 if {abs([lindex $mainline($id) 1] - $y) < $thresh} {
2848 return "up"
2849 }
2850 }
2851 }
2852 if {[info exists sidelines($id)]} {
2853 foreach ls $sidelines($id) {
2854 set coords [lindex $ls 0]
2855 set arrow [lindex $ls 2]
2856 if {$arrow eq "first" || $arrow eq "both"} {
2857 if {abs([lindex $coords 1] - $y) < $thresh} {
2858 return "up"
2859 }
2860 }
2861 if {$arrow eq "last" || $arrow eq "both"} {
2862 if {abs([lindex $coords end] - $y) < $thresh} {
2863 return "down"
2864 }
2865 }
2866 }
2867 }
2868 return {}
2869 }
2870
2871 proc arrowjump {id dirn y} {
2872 global mainline sidelines canv canv2 canv3
2873
2874 set yt {}
2875 if {$dirn eq "down"} {
2876 if {[info exists mainline($id)]} {
2877 set y1 [lindex $mainline($id) 1]
2878 if {$y1 > $y} {
2879 set yt $y1
2880 }
2881 }
2882 if {[info exists sidelines($id)]} {
2883 foreach ls $sidelines($id) {
2884 set y1 [lindex $ls 0 1]
2885 if {$y1 > $y && ($yt eq {} || $y1 < $yt)} {
2886 set yt $y1
2887 }
2888 }
2889 }
2890 } else {
2891 if {[info exists sidelines($id)]} {
2892 foreach ls $sidelines($id) {
2893 set y1 [lindex $ls 0 end]
2894 if {$y1 < $y && ($yt eq {} || $y1 > $yt)} {
2895 set yt $y1
2896 }
2897 }
2898 }
2899 }
2900 if {$yt eq {}} return
2901 set ymax [lindex [$canv cget -scrollregion] 3]
2902 if {$ymax eq {} || $ymax <= 0} return
2903 set view [$canv yview]
2904 set yspan [expr {[lindex $view 1] - [lindex $view 0]}]
2905 set yfrac [expr {$yt / $ymax - $yspan / 2}]
2906 if {$yfrac < 0} {
2907 set yfrac 0
2908 }
2909 $canv yview moveto $yfrac
2910 $canv2 yview moveto $yfrac
2911 $canv3 yview moveto $yfrac
2912 }
2913
2914 proc lineclick {x y id isnew} {
2915 global ctext commitinfo children cflist canv thickerline
2916
2917 unmarkmatches
2918 unselectline
2919 normalline
2920 $canv delete hover
2921 # draw this line thicker than normal
2922 drawlines $id 1 1
2923 set thickerline $id
2924 if {$isnew} {
2925 set ymax [lindex [$canv cget -scrollregion] 3]
2926 if {$ymax eq {}} return
2927 set yfrac [lindex [$canv yview] 0]
2928 set y [expr {$y + $yfrac * $ymax}]
2929 }
2930 set dirn [clickisonarrow $id $y]
2931 if {$dirn ne {}} {
2932 arrowjump $id $dirn $y
2933 return
2934 }
2935
2936 if {$isnew} {
2937 addtohistory [list lineclick $x $y $id 0]
2938 }
2939 # fill the details pane with info about this line
2940 $ctext conf -state normal
2941 $ctext delete 0.0 end
2942 $ctext tag conf link -foreground blue -underline 1
2943 $ctext tag bind link <Enter> { %W configure -cursor hand2 }
2944 $ctext tag bind link <Leave> { %W configure -cursor $curtextcursor }
2945 $ctext insert end "Parent:\t"
2946 $ctext insert end $id [list link link0]
2947 $ctext tag bind link0 <1> [list selbyid $id]
2948 set info $commitinfo($id)
2949 $ctext insert end "\n\t[lindex $info 0]\n"
2950 $ctext insert end "\tAuthor:\t[lindex $info 1]\n"
2951 set date [formatdate [lindex $info 2]]
2952 $ctext insert end "\tDate:\t$date\n"
2953 if {[info exists children($id)]} {
2954 $ctext insert end "\nChildren:"
2955 set i 0
2956 foreach child $children($id) {
2957 incr i
2958 set info $commitinfo($child)
2959 $ctext insert end "\n\t"
2960 $ctext insert end $child [list link link$i]
2961 $ctext tag bind link$i <1> [list selbyid $child]
2962 $ctext insert end "\n\t[lindex $info 0]"
2963 $ctext insert end "\n\tAuthor:\t[lindex $info 1]"
2964 set date [formatdate [lindex $info 2]]
2965 $ctext insert end "\n\tDate:\t$date\n"
2966 }
2967 }
2968 $ctext conf -state disabled
2969
2970 $cflist delete 0 end
2971 }
2972
2973 proc normalline {} {
2974 global thickerline
2975 if {[info exists thickerline]} {
2976 drawlines $thickerline 0 1
2977 unset thickerline
2978 }
2979 }
2980
2981 proc selbyid {id} {
2982 global idline
2983 if {[info exists idline($id)]} {
2984 selectline $idline($id) 1
2985 }
2986 }
2987
2988 proc mstime {} {
2989 global startmstime
2990 if {![info exists startmstime]} {
2991 set startmstime [clock clicks -milliseconds]
2992 }
2993 return [format "%.3f" [expr {([clock click -milliseconds] - $startmstime) / 1000.0}]]
2994 }
2995
2996 proc rowmenu {x y id} {
2997 global rowctxmenu idline selectedline rowmenuid
2998
2999 if {![info exists selectedline] || $idline($id) eq $selectedline} {
3000 set state disabled
3001 } else {
3002 set state normal
3003 }
3004 $rowctxmenu entryconfigure 0 -state $state
3005 $rowctxmenu entryconfigure 1 -state $state
3006 $rowctxmenu entryconfigure 2 -state $state
3007 set rowmenuid $id
3008 tk_popup $rowctxmenu $x $y
3009 }
3010
3011 proc diffvssel {dirn} {
3012 global rowmenuid selectedline lineid
3013
3014 if {![info exists selectedline]} return
3015 if {$dirn} {
3016 set oldid $lineid($selectedline)
3017 set newid $rowmenuid
3018 } else {
3019 set oldid $rowmenuid
3020 set newid $lineid($selectedline)
3021 }
3022 addtohistory [list doseldiff $oldid $newid]
3023 doseldiff $oldid $newid
3024 }
3025
3026 proc doseldiff {oldid newid} {
3027 global ctext cflist
3028 global commitinfo
3029
3030 $ctext conf -state normal
3031 $ctext delete 0.0 end
3032 $ctext mark set fmark.0 0.0
3033 $ctext mark gravity fmark.0 left
3034 $cflist delete 0 end
3035 $cflist insert end "Top"
3036 $ctext insert end "From "
3037 $ctext tag conf link -foreground blue -underline 1
3038 $ctext tag bind link <Enter> { %W configure -cursor hand2 }
3039 $ctext tag bind link <Leave> { %W configure -cursor $curtextcursor }
3040 $ctext tag bind link0 <1> [list selbyid $oldid]
3041 $ctext insert end $oldid [list link link0]
3042 $ctext insert end "\n "
3043 $ctext insert end [lindex $commitinfo($oldid) 0]
3044 $ctext insert end "\n\nTo "
3045 $ctext tag bind link1 <1> [list selbyid $newid]
3046 $ctext insert end $newid [list link link1]
3047 $ctext insert end "\n "
3048 $ctext insert end [lindex $commitinfo($newid) 0]
3049 $ctext insert end "\n"
3050 $ctext conf -state disabled
3051 $ctext tag delete Comments
3052 $ctext tag remove found 1.0 end
3053 startdiff [list $oldid $newid]
3054 }
3055
3056 proc mkpatch {} {
3057 global rowmenuid currentid commitinfo patchtop patchnum
3058
3059 if {![info exists currentid]} return
3060 set oldid $currentid
3061 set oldhead [lindex $commitinfo($oldid) 0]
3062 set newid $rowmenuid
3063 set newhead [lindex $commitinfo($newid) 0]
3064 set top .patch
3065 set patchtop $top
3066 catch {destroy $top}
3067 toplevel $top
3068 label $top.title -text "Generate patch"
3069 grid $top.title - -pady 10
3070 label $top.from -text "From:"
3071 entry $top.fromsha1 -width 40 -relief flat
3072 $top.fromsha1 insert 0 $oldid
3073 $top.fromsha1 conf -state readonly
3074 grid $top.from $top.fromsha1 -sticky w
3075 entry $top.fromhead -width 60 -relief flat
3076 $top.fromhead insert 0 $oldhead
3077 $top.fromhead conf -state readonly
3078 grid x $top.fromhead -sticky w
3079 label $top.to -text "To:"
3080 entry $top.tosha1 -width 40 -relief flat
3081 $top.tosha1 insert 0 $newid
3082 $top.tosha1 conf -state readonly
3083 grid $top.to $top.tosha1 -sticky w
3084 entry $top.tohead -width 60 -relief flat
3085 $top.tohead insert 0 $newhead
3086 $top.tohead conf -state readonly
3087 grid x $top.tohead -sticky w
3088 button $top.rev -text "Reverse" -command mkpatchrev -padx 5
3089 grid $top.rev x -pady 10
3090 label $top.flab -text "Output file:"
3091 entry $top.fname -width 60
3092 $top.fname insert 0 [file normalize "patch$patchnum.patch"]
3093 incr patchnum
3094 grid $top.flab $top.fname -sticky w
3095 frame $top.buts
3096 button $top.buts.gen -text "Generate" -command mkpatchgo
3097 button $top.buts.can -text "Cancel" -command mkpatchcan
3098 grid $top.buts.gen $top.buts.can
3099 grid columnconfigure $top.buts 0 -weight 1 -uniform a
3100 grid columnconfigure $top.buts 1 -weight 1 -uniform a
3101 grid $top.buts - -pady 10 -sticky ew
3102 focus $top.fname
3103 }
3104
3105 proc mkpatchrev {} {
3106 global patchtop
3107
3108 set oldid [$patchtop.fromsha1 get]
3109 set oldhead [$patchtop.fromhead get]
3110 set newid [$patchtop.tosha1 get]
3111 set newhead [$patchtop.tohead get]
3112 foreach e [list fromsha1 fromhead tosha1 tohead] \
3113 v [list $newid $newhead $oldid $oldhead] {
3114 $patchtop.$e conf -state normal
3115 $patchtop.$e delete 0 end
3116 $patchtop.$e insert 0 $v
3117 $patchtop.$e conf -state readonly
3118 }
3119 }
3120
3121 proc mkpatchgo {} {
3122 global patchtop
3123
3124 set oldid [$patchtop.fromsha1 get]
3125 set newid [$patchtop.tosha1 get]
3126 set fname [$patchtop.fname get]
3127 if {[catch {exec git-diff-tree -p $oldid $newid >$fname &} err]} {
3128 error_popup "Error creating patch: $err"
3129 }
3130 catch {destroy $patchtop}
3131 unset patchtop
3132 }
3133
3134 proc mkpatchcan {} {
3135 global patchtop
3136
3137 catch {destroy $patchtop}
3138 unset patchtop
3139 }
3140
3141 proc mktag {} {
3142 global rowmenuid mktagtop commitinfo
3143
3144 set top .maketag
3145 set mktagtop $top
3146 catch {destroy $top}
3147 toplevel $top
3148 label $top.title -text "Create tag"
3149 grid $top.title - -pady 10
3150 label $top.id -text "ID:"
3151 entry $top.sha1 -width 40 -relief flat
3152 $top.sha1 insert 0 $rowmenuid
3153 $top.sha1 conf -state readonly
3154 grid $top.id $top.sha1 -sticky w
3155 entry $top.head -width 60 -relief flat
3156 $top.head insert 0 [lindex $commitinfo($rowmenuid) 0]
3157 $top.head conf -state readonly
3158 grid x $top.head -sticky w
3159 label $top.tlab -text "Tag name:"
3160 entry $top.tag -width 60
3161 grid $top.tlab $top.tag -sticky w
3162 frame $top.buts
3163 button $top.buts.gen -text "Create" -command mktaggo
3164 button $top.buts.can -text "Cancel" -command mktagcan
3165 grid $top.buts.gen $top.buts.can
3166 grid columnconfigure $top.buts 0 -weight 1 -uniform a
3167 grid columnconfigure $top.buts 1 -weight 1 -uniform a
3168 grid $top.buts - -pady 10 -sticky ew
3169 focus $top.tag
3170 }
3171
3172 proc domktag {} {
3173 global mktagtop env tagids idtags
3174
3175 set id [$mktagtop.sha1 get]
3176 set tag [$mktagtop.tag get]
3177 if {$tag == {}} {
3178 error_popup "No tag name specified"
3179 return
3180 }
3181 if {[info exists tagids($tag)]} {
3182 error_popup "Tag \"$tag\" already exists"
3183 return
3184 }
3185 if {[catch {
3186 set dir [gitdir]
3187 set fname [file join $dir "refs/tags" $tag]
3188 set f [open $fname w]
3189 puts $f $id
3190 close $f
3191 } err]} {
3192 error_popup "Error creating tag: $err"
3193 return
3194 }
3195
3196 set tagids($tag) $id
3197 lappend idtags($id) $tag
3198 redrawtags $id
3199 }
3200
3201 proc redrawtags {id} {
3202 global canv linehtag idline idpos selectedline
3203
3204 if {![info exists idline($id)]} return
3205 $canv delete tag.$id
3206 set xt [eval drawtags $id $idpos($id)]
3207 $canv coords $linehtag($idline($id)) $xt [lindex $idpos($id) 2]
3208 if {[info exists selectedline] && $selectedline == $idline($id)} {
3209 selectline $selectedline 0
3210 }
3211 }
3212
3213 proc mktagcan {} {
3214 global mktagtop
3215
3216 catch {destroy $mktagtop}
3217 unset mktagtop
3218 }
3219
3220 proc mktaggo {} {
3221 domktag
3222 mktagcan
3223 }
3224
3225 proc writecommit {} {
3226 global rowmenuid wrcomtop commitinfo wrcomcmd
3227
3228 set top .writecommit
3229 set wrcomtop $top
3230 catch {destroy $top}
3231 toplevel $top
3232 label $top.title -text "Write commit to file"
3233 grid $top.title - -pady 10
3234 label $top.id -text "ID:"
3235 entry $top.sha1 -width 40 -relief flat
3236 $top.sha1 insert 0 $rowmenuid
3237 $top.sha1 conf -state readonly
3238 grid $top.id $top.sha1 -sticky w
3239 entry $top.head -width 60 -relief flat
3240 $top.head insert 0 [lindex $commitinfo($rowmenuid) 0]
3241 $top.head conf -state readonly
3242 grid x $top.head -sticky w
3243 label $top.clab -text "Command:"
3244 entry $top.cmd -width 60 -textvariable wrcomcmd
3245 grid $top.clab $top.cmd -sticky w -pady 10
3246 label $top.flab -text "Output file:"
3247 entry $top.fname -width 60
3248 $top.fname insert 0 [file normalize "commit-[string range $rowmenuid 0 6]"]
3249 grid $top.flab $top.fname -sticky w
3250 frame $top.buts
3251 button $top.buts.gen -text "Write" -command wrcomgo
3252 button $top.buts.can -text "Cancel" -command wrcomcan
3253 grid $top.buts.gen $top.buts.can
3254 grid columnconfigure $top.buts 0 -weight 1 -uniform a
3255 grid columnconfigure $top.buts 1 -weight 1 -uniform a
3256 grid $top.buts - -pady 10 -sticky ew
3257 focus $top.fname
3258 }
3259
3260 proc wrcomgo {} {
3261 global wrcomtop
3262
3263 set id [$wrcomtop.sha1 get]
3264 set cmd "echo $id | [$wrcomtop.cmd get]"
3265 set fname [$wrcomtop.fname get]
3266 if {[catch {exec sh -c $cmd >$fname &} err]} {
3267 error_popup "Error writing commit: $err"
3268 }
3269 catch {destroy $wrcomtop}
3270 unset wrcomtop
3271 }
3272
3273 proc wrcomcan {} {
3274 global wrcomtop
3275
3276 catch {destroy $wrcomtop}
3277 unset wrcomtop
3278 }
3279
3280 proc listrefs {id} {
3281 global idtags idheads idotherrefs
3282
3283 set x {}
3284 if {[info exists idtags($id)]} {
3285 set x $idtags($id)
3286 }
3287 set y {}
3288 if {[info exists idheads($id)]} {
3289 set y $idheads($id)
3290 }
3291 set z {}
3292 if {[info exists idotherrefs($id)]} {
3293 set z $idotherrefs($id)
3294 }
3295 return [list $x $y $z]
3296 }
3297
3298 proc rereadrefs {} {
3299 global idtags idheads idotherrefs
3300 global tagids headids otherrefids
3301
3302 set refids [concat [array names idtags] \
3303 [array names idheads] [array names idotherrefs]]
3304 foreach id $refids {
3305 if {![info exists ref($id)]} {
3306 set ref($id) [listrefs $id]
3307 }
3308 }
3309 readrefs
3310 set refids [lsort -unique [concat $refids [array names idtags] \
3311 [array names idheads] [array names idotherrefs]]]
3312 foreach id $refids {
3313 set v [listrefs $id]
3314 if {![info exists ref($id)] || $ref($id) != $v} {
3315 redrawtags $id
3316 }
3317 }
3318 }
3319
3320 proc showtag {tag isnew} {
3321 global ctext cflist tagcontents tagids linknum
3322
3323 if {$isnew} {
3324 addtohistory [list showtag $tag 0]
3325 }
3326 $ctext conf -state normal
3327 $ctext delete 0.0 end
3328 set linknum 0
3329 if {[info exists tagcontents($tag)]} {
3330 set text $tagcontents($tag)
3331 } else {
3332 set text "Tag: $tag\nId: $tagids($tag)"
3333 }
3334 appendwithlinks $text
3335 $ctext conf -state disabled
3336 $cflist delete 0 end
3337 }
3338
3339 proc doquit {} {
3340 global stopped
3341 set stopped 100
3342 destroy .
3343 }
3344
3345 proc doprefs {} {
3346 global maxwidth maxgraphpct diffopts findmergefiles
3347 global oldprefs prefstop
3348
3349 set top .gitkprefs
3350 set prefstop $top
3351 if {[winfo exists $top]} {
3352 raise $top
3353 return
3354 }
3355 foreach v {maxwidth maxgraphpct diffopts findmergefiles} {
3356 set oldprefs($v) [set $v]
3357 }
3358 toplevel $top
3359 wm title $top "Gitk preferences"
3360 label $top.ldisp -text "Commit list display options"
3361 grid $top.ldisp - -sticky w -pady 10
3362 label $top.spacer -text " "
3363 label $top.maxwidthl -text "Maximum graph width (lines)" \
3364 -font optionfont
3365 spinbox $top.maxwidth -from 0 -to 100 -width 4 -textvariable maxwidth
3366 grid $top.spacer $top.maxwidthl $top.maxwidth -sticky w
3367 label $top.maxpctl -text "Maximum graph width (% of pane)" \
3368 -font optionfont
3369 spinbox $top.maxpct -from 1 -to 100 -width 4 -textvariable maxgraphpct
3370 grid x $top.maxpctl $top.maxpct -sticky w
3371 checkbutton $top.findm -variable findmergefiles
3372 label $top.findml -text "Include merges for \"Find\" in \"Files\"" \
3373 -font optionfont
3374 grid $top.findm $top.findml - -sticky w
3375 label $top.ddisp -text "Diff display options"
3376 grid $top.ddisp - -sticky w -pady 10
3377 label $top.diffoptl -text "Options for diff program" \
3378 -font optionfont
3379 entry $top.diffopt -width 20 -textvariable diffopts
3380 grid x $top.diffoptl $top.diffopt -sticky w
3381 frame $top.buts
3382 button $top.buts.ok -text "OK" -command prefsok
3383 button $top.buts.can -text "Cancel" -command prefscan
3384 grid $top.buts.ok $top.buts.can
3385 grid columnconfigure $top.buts 0 -weight 1 -uniform a
3386 grid columnconfigure $top.buts 1 -weight 1 -uniform a
3387 grid $top.buts - - -pady 10 -sticky ew
3388 }
3389
3390 proc prefscan {} {
3391 global maxwidth maxgraphpct diffopts findmergefiles
3392 global oldprefs prefstop
3393
3394 foreach v {maxwidth maxgraphpct diffopts findmergefiles} {
3395 set $v $oldprefs($v)
3396 }
3397 catch {destroy $prefstop}
3398 unset prefstop
3399 }
3400
3401 proc prefsok {} {
3402 global maxwidth maxgraphpct
3403 global oldprefs prefstop
3404
3405 catch {destroy $prefstop}
3406 unset prefstop
3407 if {$maxwidth != $oldprefs(maxwidth)
3408 || $maxgraphpct != $oldprefs(maxgraphpct)} {
3409 redisplay
3410 }
3411 }
3412
3413 proc formatdate {d} {
3414 return [clock format $d -format "%Y-%m-%d %H:%M:%S"]
3415 }
3416
3417 # This list of encoding names and aliases is distilled from
3418 # http://www.iana.org/assignments/character-sets.
3419 # Not all of them are supported by Tcl.
3420 set encoding_aliases {
3421 { ANSI_X3.4-1968 iso-ir-6 ANSI_X3.4-1986 ISO_646.irv:1991 ASCII
3422 ISO646-US US-ASCII us IBM367 cp367 csASCII }
3423 { ISO-10646-UTF-1 csISO10646UTF1 }
3424 { ISO_646.basic:1983 ref csISO646basic1983 }
3425 { INVARIANT csINVARIANT }
3426 { ISO_646.irv:1983 iso-ir-2 irv csISO2IntlRefVersion }
3427 { BS_4730 iso-ir-4 ISO646-GB gb uk csISO4UnitedKingdom }
3428 { NATS-SEFI iso-ir-8-1 csNATSSEFI }
3429 { NATS-SEFI-ADD iso-ir-8-2 csNATSSEFIADD }
3430 { NATS-DANO iso-ir-9-1 csNATSDANO }
3431 { NATS-DANO-ADD iso-ir-9-2 csNATSDANOADD }
3432 { SEN_850200_B iso-ir-10 FI ISO646-FI ISO646-SE se csISO10Swedish }
3433 { SEN_850200_C iso-ir-11 ISO646-SE2 se2 csISO11SwedishForNames }
3434 { KS_C_5601-1987 iso-ir-149 KS_C_5601-1989 KSC_5601 korean csKSC56011987 }
3435 { ISO-2022-KR csISO2022KR }
3436 { EUC-KR csEUCKR }
3437 { ISO-2022-JP csISO2022JP }
3438 { ISO-2022-JP-2 csISO2022JP2 }
3439 { JIS_C6220-1969-jp JIS_C6220-1969 iso-ir-13 katakana x0201-7
3440 csISO13JISC6220jp }
3441 { JIS_C6220-1969-ro iso-ir-14 jp ISO646-JP csISO14JISC6220ro }
3442 { IT iso-ir-15 ISO646-IT csISO15Italian }
3443 { PT iso-ir-16 ISO646-PT csISO16Portuguese }
3444 { ES iso-ir-17 ISO646-ES csISO17Spanish }
3445 { greek7-old iso-ir-18 csISO18Greek7Old }
3446 { latin-greek iso-ir-19 csISO19LatinGreek }
3447 { DIN_66003 iso-ir-21 de ISO646-DE csISO21German }
3448 { NF_Z_62-010_(1973) iso-ir-25 ISO646-FR1 csISO25French }
3449 { Latin-greek-1 iso-ir-27 csISO27LatinGreek1 }
3450 { ISO_5427 iso-ir-37 csISO5427Cyrillic }
3451 { JIS_C6226-1978 iso-ir-42 csISO42JISC62261978 }
3452 { BS_viewdata iso-ir-47 csISO47BSViewdata }
3453 { INIS iso-ir-49 csISO49INIS }
3454 { INIS-8 iso-ir-50 csISO50INIS8 }
3455 { INIS-cyrillic iso-ir-51 csISO51INISCyrillic }
3456 { ISO_5427:1981 iso-ir-54 ISO5427Cyrillic1981 }
3457 { ISO_5428:1980 iso-ir-55 csISO5428Greek }
3458 { GB_1988-80 iso-ir-57 cn ISO646-CN csISO57GB1988 }
3459 { GB_2312-80 iso-ir-58 chinese csISO58GB231280 }
3460 { NS_4551-1 iso-ir-60 ISO646-NO no csISO60DanishNorwegian
3461 csISO60Norwegian1 }
3462 { NS_4551-2 ISO646-NO2 iso-ir-61 no2 csISO61Norwegian2 }
3463 { NF_Z_62-010 iso-ir-69 ISO646-FR fr csISO69French }
3464 { videotex-suppl iso-ir-70 csISO70VideotexSupp1 }
3465 { PT2 iso-ir-84 ISO646-PT2 csISO84Portuguese2 }
3466 { ES2 iso-ir-85 ISO646-ES2 csISO85Spanish2 }
3467 { MSZ_7795.3 iso-ir-86 ISO646-HU hu csISO86Hungarian }
3468 { JIS_C6226-1983 iso-ir-87 x0208 JIS_X0208-1983 csISO87JISX0208 }
3469 { greek7 iso-ir-88 csISO88Greek7 }
3470 { ASMO_449 ISO_9036 arabic7 iso-ir-89 csISO89ASMO449 }
3471 { iso-ir-90 csISO90 }
3472 { JIS_C6229-1984-a iso-ir-91 jp-ocr-a csISO91JISC62291984a }
3473 { JIS_C6229-1984-b iso-ir-92 ISO646-JP-OCR-B jp-ocr-b
3474 csISO92JISC62991984b }
3475 { JIS_C6229-1984-b-add iso-ir-93 jp-ocr-b-add csISO93JIS62291984badd }
3476 { JIS_C6229-1984-hand iso-ir-94 jp-ocr-hand csISO94JIS62291984hand }
3477 { JIS_C6229-1984-hand-add iso-ir-95 jp-ocr-hand-add
3478 csISO95JIS62291984handadd }
3479 { JIS_C6229-1984-kana iso-ir-96 csISO96JISC62291984kana }
3480 { ISO_2033-1983 iso-ir-98 e13b csISO2033 }
3481 { ANSI_X3.110-1983 iso-ir-99 CSA_T500-1983 NAPLPS csISO99NAPLPS }
3482 { ISO_8859-1:1987 iso-ir-100 ISO_8859-1 ISO-8859-1 latin1 l1 IBM819
3483 CP819 csISOLatin1 }
3484 { ISO_8859-2:1987 iso-ir-101 ISO_8859-2 ISO-8859-2 latin2 l2 csISOLatin2 }
3485 { T.61-7bit iso-ir-102 csISO102T617bit }
3486 { T.61-8bit T.61 iso-ir-103 csISO103T618bit }
3487 { ISO_8859-3:1988 iso-ir-109 ISO_8859-3 ISO-8859-3 latin3 l3 csISOLatin3 }
3488 { ISO_8859-4:1988 iso-ir-110 ISO_8859-4 ISO-8859-4 latin4 l4 csISOLatin4 }
3489 { ECMA-cyrillic iso-ir-111 KOI8-E csISO111ECMACyrillic }
3490 { CSA_Z243.4-1985-1 iso-ir-121 ISO646-CA csa7-1 ca csISO121Canadian1 }
3491 { CSA_Z243.4-1985-2 iso-ir-122 ISO646-CA2 csa7-2 csISO122Canadian2 }
3492 { CSA_Z243.4-1985-gr iso-ir-123 csISO123CSAZ24341985gr }
3493 { ISO_8859-6:1987 iso-ir-127 ISO_8859-6 ISO-8859-6 ECMA-114 ASMO-708
3494 arabic csISOLatinArabic }
3495 { ISO_8859-6-E csISO88596E ISO-8859-6-E }
3496 { ISO_8859-6-I csISO88596I ISO-8859-6-I }
3497 { ISO_8859-7:1987 iso-ir-126 ISO_8859-7 ISO-8859-7 ELOT_928 ECMA-118
3498 greek greek8 csISOLatinGreek }
3499 { T.101-G2 iso-ir-128 csISO128T101G2 }
3500 { ISO_8859-8:1988 iso-ir-138 ISO_8859-8 ISO-8859-8 hebrew
3501 csISOLatinHebrew }
3502 { ISO_8859-8-E csISO88598E ISO-8859-8-E }
3503 { ISO_8859-8-I csISO88598I ISO-8859-8-I }
3504 { CSN_369103 iso-ir-139 csISO139CSN369103 }
3505 { JUS_I.B1.002 iso-ir-141 ISO646-YU js yu csISO141JUSIB1002 }
3506 { ISO_6937-2-add iso-ir-142 csISOTextComm }
3507 { IEC_P27-1 iso-ir-143 csISO143IECP271 }
3508 { ISO_8859-5:1988 iso-ir-144 ISO_8859-5 ISO-8859-5 cyrillic
3509 csISOLatinCyrillic }
3510 { JUS_I.B1.003-serb iso-ir-146 serbian csISO146Serbian }
3511 { JUS_I.B1.003-mac macedonian iso-ir-147 csISO147Macedonian }
3512 { ISO_8859-9:1989 iso-ir-148 ISO_8859-9 ISO-8859-9 latin5 l5 csISOLatin5 }
3513 { greek-ccitt iso-ir-150 csISO150 csISO150GreekCCITT }
3514 { NC_NC00-10:81 cuba iso-ir-151 ISO646-CU csISO151Cuba }
3515 { ISO_6937-2-25 iso-ir-152 csISO6937Add }
3516 { GOST_19768-74 ST_SEV_358-88 iso-ir-153 csISO153GOST1976874 }
3517 { ISO_8859-supp iso-ir-154 latin1-2-5 csISO8859Supp }
3518 { ISO_10367-box iso-ir-155 csISO10367Box }
3519 { ISO-8859-10 iso-ir-157 l6 ISO_8859-10:1992 csISOLatin6 latin6 }
3520 { latin-lap lap iso-ir-158 csISO158Lap }
3521 { JIS_X0212-1990 x0212 iso-ir-159 csISO159JISX02121990 }
3522 { DS_2089 DS2089 ISO646-DK dk csISO646Danish }
3523 { us-dk csUSDK }
3524 { dk-us csDKUS }
3525 { JIS_X0201 X0201 csHalfWidthKatakana }
3526 { KSC5636 ISO646-KR csKSC5636 }
3527 { ISO-10646-UCS-2 csUnicode }
3528 { ISO-10646-UCS-4 csUCS4 }
3529 { DEC-MCS dec csDECMCS }
3530 { hp-roman8 roman8 r8 csHPRoman8 }
3531 { macintosh mac csMacintosh }
3532 { IBM037 cp037 ebcdic-cp-us ebcdic-cp-ca ebcdic-cp-wt ebcdic-cp-nl
3533 csIBM037 }
3534 { IBM038 EBCDIC-INT cp038 csIBM038 }
3535 { IBM273 CP273 csIBM273 }
3536 { IBM274 EBCDIC-BE CP274 csIBM274 }
3537 { IBM275 EBCDIC-BR cp275 csIBM275 }
3538 { IBM277 EBCDIC-CP-DK EBCDIC-CP-NO csIBM277 }
3539 { IBM278 CP278 ebcdic-cp-fi ebcdic-cp-se csIBM278 }
3540 { IBM280 CP280 ebcdic-cp-it csIBM280 }
3541 { IBM281 EBCDIC-JP-E cp281 csIBM281 }
3542 { IBM284 CP284 ebcdic-cp-es csIBM284 }
3543 { IBM285 CP285 ebcdic-cp-gb csIBM285 }
3544 { IBM290 cp290 EBCDIC-JP-kana csIBM290 }
3545 { IBM297 cp297 ebcdic-cp-fr csIBM297 }
3546 { IBM420 cp420 ebcdic-cp-ar1 csIBM420 }
3547 { IBM423 cp423 ebcdic-cp-gr csIBM423 }
3548 { IBM424 cp424 ebcdic-cp-he csIBM424 }
3549 { IBM437 cp437 437 csPC8CodePage437 }
3550 { IBM500 CP500 ebcdic-cp-be ebcdic-cp-ch csIBM500 }
3551 { IBM775 cp775 csPC775Baltic }
3552 { IBM850 cp850 850 csPC850Multilingual }
3553 { IBM851 cp851 851 csIBM851 }
3554 { IBM852 cp852 852 csPCp852 }
3555 { IBM855 cp855 855 csIBM855 }
3556 { IBM857 cp857 857 csIBM857 }
3557 { IBM860 cp860 860 csIBM860 }
3558 { IBM861 cp861 861 cp-is csIBM861 }
3559 { IBM862 cp862 862 csPC862LatinHebrew }
3560 { IBM863 cp863 863 csIBM863 }
3561 { IBM864 cp864 csIBM864 }
3562 { IBM865 cp865 865 csIBM865 }
3563 { IBM866 cp866 866 csIBM866 }
3564 { IBM868 CP868 cp-ar csIBM868 }
3565 { IBM869 cp869 869 cp-gr csIBM869 }
3566 { IBM870 CP870 ebcdic-cp-roece ebcdic-cp-yu csIBM870 }
3567 { IBM871 CP871 ebcdic-cp-is csIBM871 }
3568 { IBM880 cp880 EBCDIC-Cyrillic csIBM880 }
3569 { IBM891 cp891 csIBM891 }
3570 { IBM903 cp903 csIBM903 }
3571 { IBM904 cp904 904 csIBBM904 }
3572 { IBM905 CP905 ebcdic-cp-tr csIBM905 }
3573 { IBM918 CP918 ebcdic-cp-ar2 csIBM918 }
3574 { IBM1026 CP1026 csIBM1026 }
3575 { EBCDIC-AT-DE csIBMEBCDICATDE }
3576 { EBCDIC-AT-DE-A csEBCDICATDEA }
3577 { EBCDIC-CA-FR csEBCDICCAFR }
3578 { EBCDIC-DK-NO csEBCDICDKNO }
3579 { EBCDIC-DK-NO-A csEBCDICDKNOA }
3580 { EBCDIC-FI-SE csEBCDICFISE }
3581 { EBCDIC-FI-SE-A csEBCDICFISEA }
3582 { EBCDIC-FR csEBCDICFR }
3583 { EBCDIC-IT csEBCDICIT }
3584 { EBCDIC-PT csEBCDICPT }
3585 { EBCDIC-ES csEBCDICES }
3586 { EBCDIC-ES-A csEBCDICESA }
3587 { EBCDIC-ES-S csEBCDICESS }
3588 { EBCDIC-UK csEBCDICUK }
3589 { EBCDIC-US csEBCDICUS }
3590 { UNKNOWN-8BIT csUnknown8BiT }
3591 { MNEMONIC csMnemonic }
3592 { MNEM csMnem }
3593 { VISCII csVISCII }
3594 { VIQR csVIQR }
3595 { KOI8-R csKOI8R }
3596 { IBM00858 CCSID00858 CP00858 PC-Multilingual-850+euro }
3597 { IBM00924 CCSID00924 CP00924 ebcdic-Latin9--euro }
3598 { IBM01140 CCSID01140 CP01140 ebcdic-us-37+euro }
3599 { IBM01141 CCSID01141 CP01141 ebcdic-de-273+euro }
3600 { IBM01142 CCSID01142 CP01142 ebcdic-dk-277+euro ebcdic-no-277+euro }
3601 { IBM01143 CCSID01143 CP01143 ebcdic-fi-278+euro ebcdic-se-278+euro }
3602 { IBM01144 CCSID01144 CP01144 ebcdic-it-280+euro }
3603 { IBM01145 CCSID01145 CP01145 ebcdic-es-284+euro }
3604 { IBM01146 CCSID01146 CP01146 ebcdic-gb-285+euro }
3605 { IBM01147 CCSID01147 CP01147 ebcdic-fr-297+euro }
3606 { IBM01148 CCSID01148 CP01148 ebcdic-international-500+euro }
3607 { IBM01149 CCSID01149 CP01149 ebcdic-is-871+euro }
3608 { IBM1047 IBM-1047 }
3609 { PTCP154 csPTCP154 PT154 CP154 Cyrillic-Asian }
3610 { Amiga-1251 Ami1251 Amiga1251 Ami-1251 }
3611 { UNICODE-1-1 csUnicode11 }
3612 { CESU-8 csCESU-8 }
3613 { BOCU-1 csBOCU-1 }
3614 { UNICODE-1-1-UTF-7 csUnicode11UTF7 }
3615 { ISO-8859-14 iso-ir-199 ISO_8859-14:1998 ISO_8859-14 latin8 iso-celtic
3616 l8 }
3617 { ISO-8859-15 ISO_8859-15 Latin-9 }
3618 { ISO-8859-16 iso-ir-226 ISO_8859-16:2001 ISO_8859-16 latin10 l10 }
3619 { GBK CP936 MS936 windows-936 }
3620 { JIS_Encoding csJISEncoding }
3621 { Shift_JIS MS_Kanji csShiftJIS }
3622 { Extended_UNIX_Code_Packed_Format_for_Japanese csEUCPkdFmtJapanese
3623 EUC-JP }
3624 { Extended_UNIX_Code_Fixed_Width_for_Japanese csEUCFixWidJapanese }
3625 { ISO-10646-UCS-Basic csUnicodeASCII }
3626 { ISO-10646-Unicode-Latin1 csUnicodeLatin1 ISO-10646 }
3627 { ISO-Unicode-IBM-1261 csUnicodeIBM1261 }
3628 { ISO-Unicode-IBM-1268 csUnicodeIBM1268 }
3629 { ISO-Unicode-IBM-1276 csUnicodeIBM1276 }
3630 { ISO-Unicode-IBM-1264 csUnicodeIBM1264 }
3631 { ISO-Unicode-IBM-1265 csUnicodeIBM1265 }
3632 { ISO-8859-1-Windows-3.0-Latin-1 csWindows30Latin1 }
3633 { ISO-8859-1-Windows-3.1-Latin-1 csWindows31Latin1 }
3634 { ISO-8859-2-Windows-Latin-2 csWindows31Latin2 }
3635 { ISO-8859-9-Windows-Latin-5 csWindows31Latin5 }
3636 { Adobe-Standard-Encoding csAdobeStandardEncoding }
3637 { Ventura-US csVenturaUS }
3638 { Ventura-International csVenturaInternational }
3639 { PC8-Danish-Norwegian csPC8DanishNorwegian }
3640 { PC8-Turkish csPC8Turkish }
3641 { IBM-Symbols csIBMSymbols }
3642 { IBM-Thai csIBMThai }
3643 { HP-Legal csHPLegal }
3644 { HP-Pi-font csHPPiFont }
3645 { HP-Math8 csHPMath8 }
3646 { Adobe-Symbol-Encoding csHPPSMath }
3647 { HP-DeskTop csHPDesktop }
3648 { Ventura-Math csVenturaMath }
3649 { Microsoft-Publishing csMicrosoftPublishing }
3650 { Windows-31J csWindows31J }
3651 { GB2312 csGB2312 }
3652 { Big5 csBig5 }
3653 }
3654
3655 proc tcl_encoding {enc} {
3656 global encoding_aliases
3657 set names [encoding names]
3658 set lcnames [string tolower $names]
3659 set enc [string tolower $enc]
3660 set i [lsearch -exact $lcnames $enc]
3661 if {$i < 0} {
3662 # look for "isonnn" instead of "iso-nnn" or "iso_nnn"
3663 if {[regsub {^iso[-_]} $enc iso encx]} {
3664 set i [lsearch -exact $lcnames $encx]
3665 }
3666 }
3667 if {$i < 0} {
3668 foreach l $encoding_aliases {
3669 set ll [string tolower $l]
3670 if {[lsearch -exact $ll $enc] < 0} continue
3671 # look through the aliases for one that tcl knows about
3672 foreach e $ll {
3673 set i [lsearch -exact $lcnames $e]
3674 if {$i < 0} {
3675 if {[regsub {^iso[-_]} $e iso ex]} {
3676 set i [lsearch -exact $lcnames $ex]
3677 }
3678 }
3679 if {$i >= 0} break
3680 }
3681 break
3682 }
3683 }
3684 if {$i >= 0} {
3685 return [lindex $names $i]
3686 }
3687 return {}
3688 }
3689
3690 # defaults...
3691 set datemode 0
3692 set diffopts "-U 5 -p"
3693 set wrcomcmd "git-diff-tree --stdin -p --pretty"
3694
3695 set gitencoding {}
3696 catch {
3697 set gitencoding [exec git-repo-config --get i18n.commitencoding]
3698 }
3699 if {$gitencoding == ""} {
3700 set gitencoding "utf-8"
3701 }
3702 set tclencoding [tcl_encoding $gitencoding]
3703 if {$tclencoding == {}} {
3704 puts stderr "Warning: encoding $gitencoding is not supported by Tcl/Tk"
3705 }
3706
3707 set mainfont {Helvetica 9}
3708 set textfont {Courier 9}
3709 set findmergefiles 0
3710 set maxgraphpct 50
3711 set maxwidth 16
3712 set revlistorder 0
3713 set fastdate 0
3714
3715 set colors {green red blue magenta darkgrey brown orange}
3716
3717 catch {source ~/.gitk}
3718
3719 set namefont $mainfont
3720
3721 font create optionfont -family sans-serif -size -12
3722
3723 set revtreeargs {}
3724 foreach arg $argv {
3725 switch -regexp -- $arg {
3726 "^$" { }
3727 "^-d" { set datemode 1 }
3728 "^-r" { set revlistorder 1 }
3729 default {
3730 lappend revtreeargs $arg
3731 }
3732 }
3733 }
3734
3735 set history {}
3736 set historyindex 0
3737
3738 set stopped 0
3739 set redisplaying 0
3740 set stuffsaved 0
3741 set patchnum 0
3742 setcoords
3743 makewindow $revtreeargs
3744 readrefs
3745 getcommits $revtreeargs