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