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