gitk: Remove the unused stopfindproc function
[git] / gitk
1 #!/bin/sh
2 # Tcl ignores the next line -*- tcl -*- \
3 exec wish "$0" -- "$@"
4
5 # Copyright (C) 2005-2006 Paul Mackerras.  All rights reserved.
6 # This program is free software; it may be used, copied, modified
7 # and distributed under the terms of the GNU General Public Licence,
8 # either version 2, or (at your option) any later version.
9
10 proc gitdir {} {
11     global env
12     if {[info exists env(GIT_DIR)]} {
13         return $env(GIT_DIR)
14     } else {
15         return [exec git rev-parse --git-dir]
16     }
17 }
18
19 # A simple scheduler for compute-intensive stuff.
20 # The aim is to make sure that event handlers for GUI actions can
21 # run at least every 50-100 ms.  Unfortunately fileevent handlers are
22 # run before X event handlers, so reading from a fast source can
23 # make the GUI completely unresponsive.
24 proc run args {
25     global isonrunq runq
26
27     set script $args
28     if {[info exists isonrunq($script)]} return
29     if {$runq eq {}} {
30         after idle dorunq
31     }
32     lappend runq [list {} $script]
33     set isonrunq($script) 1
34 }
35
36 proc filerun {fd script} {
37     fileevent $fd readable [list filereadable $fd $script]
38 }
39
40 proc filereadable {fd script} {
41     global runq
42
43     fileevent $fd readable {}
44     if {$runq eq {}} {
45         after idle dorunq
46     }
47     lappend runq [list $fd $script]
48 }
49
50 proc dorunq {} {
51     global isonrunq runq
52
53     set tstart [clock clicks -milliseconds]
54     set t0 $tstart
55     while {$runq ne {}} {
56         set fd [lindex $runq 0 0]
57         set script [lindex $runq 0 1]
58         set repeat [eval $script]
59         set t1 [clock clicks -milliseconds]
60         set t [expr {$t1 - $t0}]
61         set runq [lrange $runq 1 end]
62         if {$repeat ne {} && $repeat} {
63             if {$fd eq {} || $repeat == 2} {
64                 # script returns 1 if it wants to be readded
65                 # file readers return 2 if they could do more straight away
66                 lappend runq [list $fd $script]
67             } else {
68                 fileevent $fd readable [list filereadable $fd $script]
69             }
70         } elseif {$fd eq {}} {
71             unset isonrunq($script)
72         }
73         set t0 $t1
74         if {$t1 - $tstart >= 80} break
75     }
76     if {$runq ne {}} {
77         after idle dorunq
78     }
79 }
80
81 # Start off a git rev-list process and arrange to read its output
82 proc start_rev_list {view} {
83     global startmsecs
84     global commfd leftover tclencoding datemode
85     global viewargs viewfiles commitidx
86     global lookingforhead showlocalchanges
87
88     set startmsecs [clock clicks -milliseconds]
89     set commitidx($view) 0
90     set args $viewargs($view)
91     if {$viewfiles($view) ne {}} {
92         set args [concat $args "--" $viewfiles($view)]
93     }
94     set order "--topo-order"
95     if {$datemode} {
96         set order "--date-order"
97     }
98     if {[catch {
99         set fd [open [concat | git rev-list --header $order \
100                           --parents --boundary --default HEAD $args] r]
101     } err]} {
102         puts stderr "Error executing git rev-list: $err"
103         exit 1
104     }
105     set commfd($view) $fd
106     set leftover($view) {}
107     set lookingforhead $showlocalchanges
108     fconfigure $fd -blocking 0 -translation lf
109     if {$tclencoding != {}} {
110         fconfigure $fd -encoding $tclencoding
111     }
112     filerun $fd [list getcommitlines $fd $view]
113     nowbusy $view
114 }
115
116 proc stop_rev_list {} {
117     global commfd curview
118
119     if {![info exists commfd($curview)]} return
120     set fd $commfd($curview)
121     catch {
122         set pid [pid $fd]
123         exec kill $pid
124     }
125     catch {close $fd}
126     unset commfd($curview)
127 }
128
129 proc getcommits {} {
130     global phase canv mainfont curview
131
132     set phase getcommits
133     initlayout
134     start_rev_list $curview
135     show_status "Reading commits..."
136 }
137
138 proc getcommitlines {fd view}  {
139     global commitlisted
140     global leftover commfd
141     global displayorder commitidx commitrow commitdata
142     global parentlist children curview hlview
143     global vparentlist vdisporder vcmitlisted
144
145     set stuff [read $fd 500000]
146     if {$stuff == {}} {
147         if {![eof $fd]} {
148             return 1
149         }
150         global viewname
151         unset commfd($view)
152         notbusy $view
153         # set it blocking so we wait for the process to terminate
154         fconfigure $fd -blocking 1
155         if {[catch {close $fd} err]} {
156             set fv {}
157             if {$view != $curview} {
158                 set fv " for the \"$viewname($view)\" view"
159             }
160             if {[string range $err 0 4] == "usage"} {
161                 set err "Gitk: error reading commits$fv:\
162                         bad arguments to git rev-list."
163                 if {$viewname($view) eq "Command line"} {
164                     append err \
165                         "  (Note: arguments to gitk are passed to git rev-list\
166                          to allow selection of commits to be displayed.)"
167                 }
168             } else {
169                 set err "Error reading commits$fv: $err"
170             }
171             error_popup $err
172         }
173         if {$view == $curview} {
174             run chewcommits $view
175         }
176         return 0
177     }
178     set start 0
179     set gotsome 0
180     while 1 {
181         set i [string first "\0" $stuff $start]
182         if {$i < 0} {
183             append leftover($view) [string range $stuff $start end]
184             break
185         }
186         if {$start == 0} {
187             set cmit $leftover($view)
188             append cmit [string range $stuff 0 [expr {$i - 1}]]
189             set leftover($view) {}
190         } else {
191             set cmit [string range $stuff $start [expr {$i - 1}]]
192         }
193         set start [expr {$i + 1}]
194         set j [string first "\n" $cmit]
195         set ok 0
196         set listed 1
197         if {$j >= 0} {
198             set ids [string range $cmit 0 [expr {$j - 1}]]
199             if {[string range $ids 0 0] == "-"} {
200                 set listed 0
201                 set ids [string range $ids 1 end]
202             }
203             set ok 1
204             foreach id $ids {
205                 if {[string length $id] != 40} {
206                     set ok 0
207                     break
208                 }
209             }
210         }
211         if {!$ok} {
212             set shortcmit $cmit
213             if {[string length $shortcmit] > 80} {
214                 set shortcmit "[string range $shortcmit 0 80]..."
215             }
216             error_popup "Can't parse git rev-list output: {$shortcmit}"
217             exit 1
218         }
219         set id [lindex $ids 0]
220         if {$listed} {
221             set olds [lrange $ids 1 end]
222             set i 0
223             foreach p $olds {
224                 if {$i == 0 || [lsearch -exact $olds $p] >= $i} {
225                     lappend children($view,$p) $id
226                 }
227                 incr i
228             }
229         } else {
230             set olds {}
231         }
232         if {![info exists children($view,$id)]} {
233             set children($view,$id) {}
234         }
235         set commitdata($id) [string range $cmit [expr {$j + 1}] end]
236         set commitrow($view,$id) $commitidx($view)
237         incr commitidx($view)
238         if {$view == $curview} {
239             lappend parentlist $olds
240             lappend displayorder $id
241             lappend commitlisted $listed
242         } else {
243             lappend vparentlist($view) $olds
244             lappend vdisporder($view) $id
245             lappend vcmitlisted($view) $listed
246         }
247         set gotsome 1
248     }
249     if {$gotsome} {
250         run chewcommits $view
251     }
252     return 2
253 }
254
255 proc chewcommits {view} {
256     global curview hlview commfd
257     global selectedline pending_select
258
259     set more 0
260     if {$view == $curview} {
261         set allread [expr {![info exists commfd($view)]}]
262         set tlimit [expr {[clock clicks -milliseconds] + 50}]
263         set more [layoutmore $tlimit $allread]
264         if {$allread && !$more} {
265             global displayorder nullid commitidx phase
266             global numcommits startmsecs
267
268             if {[info exists pending_select]} {
269                 set row [expr {[lindex $displayorder 0] eq $nullid}]
270                 selectline $row 1
271             }
272             if {$commitidx($curview) > 0} {
273                 #set ms [expr {[clock clicks -milliseconds] - $startmsecs}]
274                 #puts "overall $ms ms for $numcommits commits"
275             } else {
276                 show_status "No commits selected"
277             }
278             notbusy layout
279             set phase {}
280         }
281     }
282     if {[info exists hlview] && $view == $hlview} {
283         vhighlightmore
284     }
285     return $more
286 }
287
288 proc readcommit {id} {
289     if {[catch {set contents [exec git cat-file commit $id]}]} return
290     parsecommit $id $contents 0
291 }
292
293 proc updatecommits {} {
294     global viewdata curview phase displayorder
295     global children commitrow selectedline thickerline
296
297     if {$phase ne {}} {
298         stop_rev_list
299         set phase {}
300     }
301     set n $curview
302     foreach id $displayorder {
303         catch {unset children($n,$id)}
304         catch {unset commitrow($n,$id)}
305     }
306     set curview -1
307     catch {unset selectedline}
308     catch {unset thickerline}
309     catch {unset viewdata($n)}
310     readrefs
311     changedrefs
312     regetallcommits
313     showview $n
314 }
315
316 proc parsecommit {id contents listed} {
317     global commitinfo cdate
318
319     set inhdr 1
320     set comment {}
321     set headline {}
322     set auname {}
323     set audate {}
324     set comname {}
325     set comdate {}
326     set hdrend [string first "\n\n" $contents]
327     if {$hdrend < 0} {
328         # should never happen...
329         set hdrend [string length $contents]
330     }
331     set header [string range $contents 0 [expr {$hdrend - 1}]]
332     set comment [string range $contents [expr {$hdrend + 2}] end]
333     foreach line [split $header "\n"] {
334         set tag [lindex $line 0]
335         if {$tag == "author"} {
336             set audate [lindex $line end-1]
337             set auname [lrange $line 1 end-2]
338         } elseif {$tag == "committer"} {
339             set comdate [lindex $line end-1]
340             set comname [lrange $line 1 end-2]
341         }
342     }
343     set headline {}
344     # take the first non-blank line of the comment as the headline
345     set headline [string trimleft $comment]
346     set i [string first "\n" $headline]
347     if {$i >= 0} {
348         set headline [string range $headline 0 $i]
349     }
350     set headline [string trimright $headline]
351     set i [string first "\r" $headline]
352     if {$i >= 0} {
353         set headline [string trimright [string range $headline 0 $i]]
354     }
355     if {!$listed} {
356         # git rev-list indents the comment by 4 spaces;
357         # if we got this via git cat-file, add the indentation
358         set newcomment {}
359         foreach line [split $comment "\n"] {
360             append newcomment "    "
361             append newcomment $line
362             append newcomment "\n"
363         }
364         set comment $newcomment
365     }
366     if {$comdate != {}} {
367         set cdate($id) $comdate
368     }
369     set commitinfo($id) [list $headline $auname $audate \
370                              $comname $comdate $comment]
371 }
372
373 proc getcommit {id} {
374     global commitdata commitinfo
375
376     if {[info exists commitdata($id)]} {
377         parsecommit $id $commitdata($id) 1
378     } else {
379         readcommit $id
380         if {![info exists commitinfo($id)]} {
381             set commitinfo($id) {"No commit information available"}
382         }
383     }
384     return 1
385 }
386
387 proc readrefs {} {
388     global tagids idtags headids idheads tagobjid
389     global otherrefids idotherrefs mainhead mainheadid
390
391     foreach v {tagids idtags headids idheads otherrefids idotherrefs} {
392         catch {unset $v}
393     }
394     set refd [open [list | git show-ref -d] r]
395     while {[gets $refd line] >= 0} {
396         if {[string index $line 40] ne " "} continue
397         set id [string range $line 0 39]
398         set ref [string range $line 41 end]
399         if {![string match "refs/*" $ref]} continue
400         set name [string range $ref 5 end]
401         if {[string match "remotes/*" $name]} {
402             if {![string match "*/HEAD" $name]} {
403                 set headids($name) $id
404                 lappend idheads($id) $name
405             }
406         } elseif {[string match "heads/*" $name]} {
407             set name [string range $name 6 end]
408             set headids($name) $id
409             lappend idheads($id) $name
410         } elseif {[string match "tags/*" $name]} {
411             # this lets refs/tags/foo^{} overwrite refs/tags/foo,
412             # which is what we want since the former is the commit ID
413             set name [string range $name 5 end]
414             if {[string match "*^{}" $name]} {
415                 set name [string range $name 0 end-3]
416             } else {
417                 set tagobjid($name) $id
418             }
419             set tagids($name) $id
420             lappend idtags($id) $name
421         } else {
422             set otherrefids($name) $id
423             lappend idotherrefs($id) $name
424         }
425     }
426     close $refd
427     set mainhead {}
428     set mainheadid {}
429     catch {
430         set thehead [exec git symbolic-ref HEAD]
431         if {[string match "refs/heads/*" $thehead]} {
432             set mainhead [string range $thehead 11 end]
433             if {[info exists headids($mainhead)]} {
434                 set mainheadid $headids($mainhead)
435             }
436         }
437     }
438 }
439
440 # update things for a head moved to a child of its previous location
441 proc movehead {id name} {
442     global headids idheads
443
444     removehead $headids($name) $name
445     set headids($name) $id
446     lappend idheads($id) $name
447 }
448
449 # update things when a head has been removed
450 proc removehead {id name} {
451     global headids idheads
452
453     if {$idheads($id) eq $name} {
454         unset idheads($id)
455     } else {
456         set i [lsearch -exact $idheads($id) $name]
457         if {$i >= 0} {
458             set idheads($id) [lreplace $idheads($id) $i $i]
459         }
460     }
461     unset headids($name)
462 }
463
464 proc show_error {w top msg} {
465     message $w.m -text $msg -justify center -aspect 400
466     pack $w.m -side top -fill x -padx 20 -pady 20
467     button $w.ok -text OK -command "destroy $top"
468     pack $w.ok -side bottom -fill x
469     bind $top <Visibility> "grab $top; focus $top"
470     bind $top <Key-Return> "destroy $top"
471     tkwait window $top
472 }
473
474 proc error_popup msg {
475     set w .error
476     toplevel $w
477     wm transient $w .
478     show_error $w $w $msg
479 }
480
481 proc confirm_popup msg {
482     global confirm_ok
483     set confirm_ok 0
484     set w .confirm
485     toplevel $w
486     wm transient $w .
487     message $w.m -text $msg -justify center -aspect 400
488     pack $w.m -side top -fill x -padx 20 -pady 20
489     button $w.ok -text OK -command "set confirm_ok 1; destroy $w"
490     pack $w.ok -side left -fill x
491     button $w.cancel -text Cancel -command "destroy $w"
492     pack $w.cancel -side right -fill x
493     bind $w <Visibility> "grab $w; focus $w"
494     tkwait window $w
495     return $confirm_ok
496 }
497
498 proc makewindow {} {
499     global canv canv2 canv3 linespc charspc ctext cflist
500     global textfont mainfont uifont tabstop
501     global findtype findtypemenu findloc findstring fstring geometry
502     global entries sha1entry sha1string sha1but
503     global maincursor textcursor curtextcursor
504     global rowctxmenu fakerowmenu mergemax wrapcomment
505     global highlight_files gdttype
506     global searchstring sstring
507     global bgcolor fgcolor bglist fglist diffcolors selectbgcolor
508     global headctxmenu
509
510     menu .bar
511     .bar add cascade -label "File" -menu .bar.file
512     .bar configure -font $uifont
513     menu .bar.file
514     .bar.file add command -label "Update" -command updatecommits
515     .bar.file add command -label "Reread references" -command rereadrefs
516     .bar.file add command -label "Quit" -command doquit
517     .bar.file configure -font $uifont
518     menu .bar.edit
519     .bar add cascade -label "Edit" -menu .bar.edit
520     .bar.edit add command -label "Preferences" -command doprefs
521     .bar.edit configure -font $uifont
522
523     menu .bar.view -font $uifont
524     .bar add cascade -label "View" -menu .bar.view
525     .bar.view add command -label "New view..." -command {newview 0}
526     .bar.view add command -label "Edit view..." -command editview \
527         -state disabled
528     .bar.view add command -label "Delete view" -command delview -state disabled
529     .bar.view add separator
530     .bar.view add radiobutton -label "All files" -command {showview 0} \
531         -variable selectedview -value 0
532
533     menu .bar.help
534     .bar add cascade -label "Help" -menu .bar.help
535     .bar.help add command -label "About gitk" -command about
536     .bar.help add command -label "Key bindings" -command keys
537     .bar.help configure -font $uifont
538     . configure -menu .bar
539
540     # the gui has upper and lower half, parts of a paned window.
541     panedwindow .ctop -orient vertical
542
543     # possibly use assumed geometry
544     if {![info exists geometry(pwsash0)]} {
545         set geometry(topheight) [expr {15 * $linespc}]
546         set geometry(topwidth) [expr {80 * $charspc}]
547         set geometry(botheight) [expr {15 * $linespc}]
548         set geometry(botwidth) [expr {50 * $charspc}]
549         set geometry(pwsash0) "[expr {40 * $charspc}] 2"
550         set geometry(pwsash1) "[expr {60 * $charspc}] 2"
551     }
552
553     # the upper half will have a paned window, a scroll bar to the right, and some stuff below
554     frame .tf -height $geometry(topheight) -width $geometry(topwidth)
555     frame .tf.histframe
556     panedwindow .tf.histframe.pwclist -orient horizontal -sashpad 0 -handlesize 4
557
558     # create three canvases
559     set cscroll .tf.histframe.csb
560     set canv .tf.histframe.pwclist.canv
561     canvas $canv \
562         -selectbackground $selectbgcolor \
563         -background $bgcolor -bd 0 \
564         -yscrollincr $linespc -yscrollcommand "scrollcanv $cscroll"
565     .tf.histframe.pwclist add $canv
566     set canv2 .tf.histframe.pwclist.canv2
567     canvas $canv2 \
568         -selectbackground $selectbgcolor \
569         -background $bgcolor -bd 0 -yscrollincr $linespc
570     .tf.histframe.pwclist add $canv2
571     set canv3 .tf.histframe.pwclist.canv3
572     canvas $canv3 \
573         -selectbackground $selectbgcolor \
574         -background $bgcolor -bd 0 -yscrollincr $linespc
575     .tf.histframe.pwclist add $canv3
576     eval .tf.histframe.pwclist sash place 0 $geometry(pwsash0)
577     eval .tf.histframe.pwclist sash place 1 $geometry(pwsash1)
578
579     # a scroll bar to rule them
580     scrollbar $cscroll -command {allcanvs yview} -highlightthickness 0
581     pack $cscroll -side right -fill y
582     bind .tf.histframe.pwclist <Configure> {resizeclistpanes %W %w}
583     lappend bglist $canv $canv2 $canv3
584     pack .tf.histframe.pwclist -fill both -expand 1 -side left
585
586     # we have two button bars at bottom of top frame. Bar 1
587     frame .tf.bar
588     frame .tf.lbar -height 15
589
590     set sha1entry .tf.bar.sha1
591     set entries $sha1entry
592     set sha1but .tf.bar.sha1label
593     button $sha1but -text "SHA1 ID: " -state disabled -relief flat \
594         -command gotocommit -width 8 -font $uifont
595     $sha1but conf -disabledforeground [$sha1but cget -foreground]
596     pack .tf.bar.sha1label -side left
597     entry $sha1entry -width 40 -font $textfont -textvariable sha1string
598     trace add variable sha1string write sha1change
599     pack $sha1entry -side left -pady 2
600
601     image create bitmap bm-left -data {
602         #define left_width 16
603         #define left_height 16
604         static unsigned char left_bits[] = {
605         0x00, 0x00, 0xc0, 0x01, 0xe0, 0x00, 0x70, 0x00, 0x38, 0x00, 0x1c, 0x00,
606         0x0e, 0x00, 0xff, 0x7f, 0xff, 0x7f, 0xff, 0x7f, 0x0e, 0x00, 0x1c, 0x00,
607         0x38, 0x00, 0x70, 0x00, 0xe0, 0x00, 0xc0, 0x01};
608     }
609     image create bitmap bm-right -data {
610         #define right_width 16
611         #define right_height 16
612         static unsigned char right_bits[] = {
613         0x00, 0x00, 0xc0, 0x01, 0x80, 0x03, 0x00, 0x07, 0x00, 0x0e, 0x00, 0x1c,
614         0x00, 0x38, 0xff, 0x7f, 0xff, 0x7f, 0xff, 0x7f, 0x00, 0x38, 0x00, 0x1c,
615         0x00, 0x0e, 0x00, 0x07, 0x80, 0x03, 0xc0, 0x01};
616     }
617     button .tf.bar.leftbut -image bm-left -command goback \
618         -state disabled -width 26
619     pack .tf.bar.leftbut -side left -fill y
620     button .tf.bar.rightbut -image bm-right -command goforw \
621         -state disabled -width 26
622     pack .tf.bar.rightbut -side left -fill y
623
624     button .tf.bar.findbut -text "Find" -command dofind -font $uifont
625     pack .tf.bar.findbut -side left
626     set findstring {}
627     set fstring .tf.bar.findstring
628     lappend entries $fstring
629     entry $fstring -width 30 -font $textfont -textvariable findstring
630     trace add variable findstring write find_change
631     pack $fstring -side left -expand 1 -fill x -in .tf.bar
632     set findtype Exact
633     set findtypemenu [tk_optionMenu .tf.bar.findtype \
634                       findtype Exact IgnCase Regexp]
635     trace add variable findtype write find_change
636     .tf.bar.findtype configure -font $uifont
637     .tf.bar.findtype.menu configure -font $uifont
638     set findloc "All fields"
639     tk_optionMenu .tf.bar.findloc findloc "All fields" Headline \
640         Comments Author Committer
641     trace add variable findloc write find_change
642     .tf.bar.findloc configure -font $uifont
643     .tf.bar.findloc.menu configure -font $uifont
644     pack .tf.bar.findloc -side right
645     pack .tf.bar.findtype -side right
646
647     # build up the bottom bar of upper window
648     label .tf.lbar.flabel -text "Highlight:  Commits " \
649     -font $uifont
650     pack .tf.lbar.flabel -side left -fill y
651     set gdttype "touching paths:"
652     set gm [tk_optionMenu .tf.lbar.gdttype gdttype "touching paths:" \
653         "adding/removing string:"]
654     trace add variable gdttype write hfiles_change
655     $gm conf -font $uifont
656     .tf.lbar.gdttype conf -font $uifont
657     pack .tf.lbar.gdttype -side left -fill y
658     entry .tf.lbar.fent -width 25 -font $textfont \
659         -textvariable highlight_files
660     trace add variable highlight_files write hfiles_change
661     lappend entries .tf.lbar.fent
662     pack .tf.lbar.fent -side left -fill x -expand 1
663     label .tf.lbar.vlabel -text " OR in view" -font $uifont
664     pack .tf.lbar.vlabel -side left -fill y
665     global viewhlmenu selectedhlview
666     set viewhlmenu [tk_optionMenu .tf.lbar.vhl selectedhlview None]
667     $viewhlmenu entryconf None -command delvhighlight
668     $viewhlmenu conf -font $uifont
669     .tf.lbar.vhl conf -font $uifont
670     pack .tf.lbar.vhl -side left -fill y
671     label .tf.lbar.rlabel -text " OR " -font $uifont
672     pack .tf.lbar.rlabel -side left -fill y
673     global highlight_related
674     set m [tk_optionMenu .tf.lbar.relm highlight_related None \
675         "Descendent" "Not descendent" "Ancestor" "Not ancestor"]
676     $m conf -font $uifont
677     .tf.lbar.relm conf -font $uifont
678     trace add variable highlight_related write vrel_change
679     pack .tf.lbar.relm -side left -fill y
680
681     # Finish putting the upper half of the viewer together
682     pack .tf.lbar -in .tf -side bottom -fill x
683     pack .tf.bar -in .tf -side bottom -fill x
684     pack .tf.histframe -fill both -side top -expand 1
685     .ctop add .tf
686     .ctop paneconfigure .tf -height $geometry(topheight)
687     .ctop paneconfigure .tf -width $geometry(topwidth)
688
689     # now build up the bottom
690     panedwindow .pwbottom -orient horizontal
691
692     # lower left, a text box over search bar, scroll bar to the right
693     # if we know window height, then that will set the lower text height, otherwise
694     # we set lower text height which will drive window height
695     if {[info exists geometry(main)]} {
696         frame .bleft -width $geometry(botwidth)
697     } else {
698         frame .bleft -width $geometry(botwidth) -height $geometry(botheight)
699     }
700     frame .bleft.top
701     frame .bleft.mid
702
703     button .bleft.top.search -text "Search" -command dosearch \
704         -font $uifont
705     pack .bleft.top.search -side left -padx 5
706     set sstring .bleft.top.sstring
707     entry $sstring -width 20 -font $textfont -textvariable searchstring
708     lappend entries $sstring
709     trace add variable searchstring write incrsearch
710     pack $sstring -side left -expand 1 -fill x
711     radiobutton .bleft.mid.diff -text "Diff" \
712         -command changediffdisp -variable diffelide -value {0 0}
713     radiobutton .bleft.mid.old -text "Old version" \
714         -command changediffdisp -variable diffelide -value {0 1}
715     radiobutton .bleft.mid.new -text "New version" \
716         -command changediffdisp -variable diffelide -value {1 0}
717     pack .bleft.mid.diff .bleft.mid.old .bleft.mid.new -side left
718     set ctext .bleft.ctext
719     text $ctext -background $bgcolor -foreground $fgcolor \
720         -tabs "[expr {$tabstop * $charspc}]" \
721         -state disabled -font $textfont \
722         -yscrollcommand scrolltext -wrap none
723     scrollbar .bleft.sb -command "$ctext yview"
724     pack .bleft.top -side top -fill x
725     pack .bleft.mid -side top -fill x
726     pack .bleft.sb -side right -fill y
727     pack $ctext -side left -fill both -expand 1
728     lappend bglist $ctext
729     lappend fglist $ctext
730
731     $ctext tag conf comment -wrap $wrapcomment
732     $ctext tag conf filesep -font [concat $textfont bold] -back "#aaaaaa"
733     $ctext tag conf hunksep -fore [lindex $diffcolors 2]
734     $ctext tag conf d0 -fore [lindex $diffcolors 0]
735     $ctext tag conf d1 -fore [lindex $diffcolors 1]
736     $ctext tag conf m0 -fore red
737     $ctext tag conf m1 -fore blue
738     $ctext tag conf m2 -fore green
739     $ctext tag conf m3 -fore purple
740     $ctext tag conf m4 -fore brown
741     $ctext tag conf m5 -fore "#009090"
742     $ctext tag conf m6 -fore magenta
743     $ctext tag conf m7 -fore "#808000"
744     $ctext tag conf m8 -fore "#009000"
745     $ctext tag conf m9 -fore "#ff0080"
746     $ctext tag conf m10 -fore cyan
747     $ctext tag conf m11 -fore "#b07070"
748     $ctext tag conf m12 -fore "#70b0f0"
749     $ctext tag conf m13 -fore "#70f0b0"
750     $ctext tag conf m14 -fore "#f0b070"
751     $ctext tag conf m15 -fore "#ff70b0"
752     $ctext tag conf mmax -fore darkgrey
753     set mergemax 16
754     $ctext tag conf mresult -font [concat $textfont bold]
755     $ctext tag conf msep -font [concat $textfont bold]
756     $ctext tag conf found -back yellow
757
758     .pwbottom add .bleft
759     .pwbottom paneconfigure .bleft -width $geometry(botwidth)
760
761     # lower right
762     frame .bright
763     frame .bright.mode
764     radiobutton .bright.mode.patch -text "Patch" \
765         -command reselectline -variable cmitmode -value "patch"
766     .bright.mode.patch configure -font $uifont
767     radiobutton .bright.mode.tree -text "Tree" \
768         -command reselectline -variable cmitmode -value "tree"
769     .bright.mode.tree configure -font $uifont
770     grid .bright.mode.patch .bright.mode.tree -sticky ew
771     pack .bright.mode -side top -fill x
772     set cflist .bright.cfiles
773     set indent [font measure $mainfont "nn"]
774     text $cflist \
775         -selectbackground $selectbgcolor \
776         -background $bgcolor -foreground $fgcolor \
777         -font $mainfont \
778         -tabs [list $indent [expr {2 * $indent}]] \
779         -yscrollcommand ".bright.sb set" \
780         -cursor [. cget -cursor] \
781         -spacing1 1 -spacing3 1
782     lappend bglist $cflist
783     lappend fglist $cflist
784     scrollbar .bright.sb -command "$cflist yview"
785     pack .bright.sb -side right -fill y
786     pack $cflist -side left -fill both -expand 1
787     $cflist tag configure highlight \
788         -background [$cflist cget -selectbackground]
789     $cflist tag configure bold -font [concat $mainfont bold]
790
791     .pwbottom add .bright
792     .ctop add .pwbottom
793
794     # restore window position if known
795     if {[info exists geometry(main)]} {
796         wm geometry . "$geometry(main)"
797     }
798
799     bind .pwbottom <Configure> {resizecdetpanes %W %w}
800     pack .ctop -fill both -expand 1
801     bindall <1> {selcanvline %W %x %y}
802     #bindall <B1-Motion> {selcanvline %W %x %y}
803     bindall <ButtonRelease-4> "allcanvs yview scroll -5 units"
804     bindall <ButtonRelease-5> "allcanvs yview scroll 5 units"
805     bindall <2> "canvscan mark %W %x %y"
806     bindall <B2-Motion> "canvscan dragto %W %x %y"
807     bindkey <Home> selfirstline
808     bindkey <End> sellastline
809     bind . <Key-Up> "selnextline -1"
810     bind . <Key-Down> "selnextline 1"
811     bind . <Shift-Key-Up> "next_highlight -1"
812     bind . <Shift-Key-Down> "next_highlight 1"
813     bindkey <Key-Right> "goforw"
814     bindkey <Key-Left> "goback"
815     bind . <Key-Prior> "selnextpage -1"
816     bind . <Key-Next> "selnextpage 1"
817     bind . <Control-Home> "allcanvs yview moveto 0.0"
818     bind . <Control-End> "allcanvs yview moveto 1.0"
819     bind . <Control-Key-Up> "allcanvs yview scroll -1 units"
820     bind . <Control-Key-Down> "allcanvs yview scroll 1 units"
821     bind . <Control-Key-Prior> "allcanvs yview scroll -1 pages"
822     bind . <Control-Key-Next> "allcanvs yview scroll 1 pages"
823     bindkey <Key-Delete> "$ctext yview scroll -1 pages"
824     bindkey <Key-BackSpace> "$ctext yview scroll -1 pages"
825     bindkey <Key-space> "$ctext yview scroll 1 pages"
826     bindkey p "selnextline -1"
827     bindkey n "selnextline 1"
828     bindkey z "goback"
829     bindkey x "goforw"
830     bindkey i "selnextline -1"
831     bindkey k "selnextline 1"
832     bindkey j "goback"
833     bindkey l "goforw"
834     bindkey b "$ctext yview scroll -1 pages"
835     bindkey d "$ctext yview scroll 18 units"
836     bindkey u "$ctext yview scroll -18 units"
837     bindkey / {findnext 1}
838     bindkey <Key-Return> {findnext 0}
839     bindkey ? findprev
840     bindkey f nextfile
841     bindkey <F5> updatecommits
842     bind . <Control-q> doquit
843     bind . <Control-f> dofind
844     bind . <Control-g> {findnext 0}
845     bind . <Control-r> dosearchback
846     bind . <Control-s> dosearch
847     bind . <Control-equal> {incrfont 1}
848     bind . <Control-KP_Add> {incrfont 1}
849     bind . <Control-minus> {incrfont -1}
850     bind . <Control-KP_Subtract> {incrfont -1}
851     wm protocol . WM_DELETE_WINDOW doquit
852     bind . <Button-1> "click %W"
853     bind $fstring <Key-Return> dofind
854     bind $sha1entry <Key-Return> gotocommit
855     bind $sha1entry <<PasteSelection>> clearsha1
856     bind $cflist <1> {sel_flist %W %x %y; break}
857     bind $cflist <B1-Motion> {sel_flist %W %x %y; break}
858     bind $cflist <ButtonRelease-1> {treeclick %W %x %y}
859
860     set maincursor [. cget -cursor]
861     set textcursor [$ctext cget -cursor]
862     set curtextcursor $textcursor
863
864     set rowctxmenu .rowctxmenu
865     menu $rowctxmenu -tearoff 0
866     $rowctxmenu add command -label "Diff this -> selected" \
867         -command {diffvssel 0}
868     $rowctxmenu add command -label "Diff selected -> this" \
869         -command {diffvssel 1}
870     $rowctxmenu add command -label "Make patch" -command mkpatch
871     $rowctxmenu add command -label "Create tag" -command mktag
872     $rowctxmenu add command -label "Write commit to file" -command writecommit
873     $rowctxmenu add command -label "Create new branch" -command mkbranch
874     $rowctxmenu add command -label "Cherry-pick this commit" \
875         -command cherrypick
876     $rowctxmenu add command -label "Reset HEAD branch to here" \
877         -command resethead
878
879     set fakerowmenu .fakerowmenu
880     menu $fakerowmenu -tearoff 0
881     $fakerowmenu add command -label "Diff this -> selected" \
882         -command {diffvssel 0}
883     $fakerowmenu add command -label "Diff selected -> this" \
884         -command {diffvssel 1}
885     $fakerowmenu add command -label "Make patch" -command mkpatch
886 #    $fakerowmenu add command -label "Commit" -command {mkcommit 0}
887 #    $fakerowmenu add command -label "Commit all" -command {mkcommit 1}
888 #    $fakerowmenu add command -label "Revert local changes" -command revertlocal
889
890     set headctxmenu .headctxmenu
891     menu $headctxmenu -tearoff 0
892     $headctxmenu add command -label "Check out this branch" \
893         -command cobranch
894     $headctxmenu add command -label "Remove this branch" \
895         -command rmbranch
896 }
897
898 # mouse-2 makes all windows scan vertically, but only the one
899 # the cursor is in scans horizontally
900 proc canvscan {op w x y} {
901     global canv canv2 canv3
902     foreach c [list $canv $canv2 $canv3] {
903         if {$c == $w} {
904             $c scan $op $x $y
905         } else {
906             $c scan $op 0 $y
907         }
908     }
909 }
910
911 proc scrollcanv {cscroll f0 f1} {
912     $cscroll set $f0 $f1
913     drawfrac $f0 $f1
914     flushhighlights
915 }
916
917 # when we make a key binding for the toplevel, make sure
918 # it doesn't get triggered when that key is pressed in the
919 # find string entry widget.
920 proc bindkey {ev script} {
921     global entries
922     bind . $ev $script
923     set escript [bind Entry $ev]
924     if {$escript == {}} {
925         set escript [bind Entry <Key>]
926     }
927     foreach e $entries {
928         bind $e $ev "$escript; break"
929     }
930 }
931
932 # set the focus back to the toplevel for any click outside
933 # the entry widgets
934 proc click {w} {
935     global entries
936     foreach e $entries {
937         if {$w == $e} return
938     }
939     focus .
940 }
941
942 proc savestuff {w} {
943     global canv canv2 canv3 ctext cflist mainfont textfont uifont tabstop
944     global stuffsaved findmergefiles maxgraphpct
945     global maxwidth showneartags showlocalchanges
946     global viewname viewfiles viewargs viewperm nextviewnum
947     global cmitmode wrapcomment
948     global colors bgcolor fgcolor diffcolors selectbgcolor
949
950     if {$stuffsaved} return
951     if {![winfo viewable .]} return
952     catch {
953         set f [open "~/.gitk-new" w]
954         puts $f [list set mainfont $mainfont]
955         puts $f [list set textfont $textfont]
956         puts $f [list set uifont $uifont]
957         puts $f [list set tabstop $tabstop]
958         puts $f [list set findmergefiles $findmergefiles]
959         puts $f [list set maxgraphpct $maxgraphpct]
960         puts $f [list set maxwidth $maxwidth]
961         puts $f [list set cmitmode $cmitmode]
962         puts $f [list set wrapcomment $wrapcomment]
963         puts $f [list set showneartags $showneartags]
964         puts $f [list set showlocalchanges $showlocalchanges]
965         puts $f [list set bgcolor $bgcolor]
966         puts $f [list set fgcolor $fgcolor]
967         puts $f [list set colors $colors]
968         puts $f [list set diffcolors $diffcolors]
969         puts $f [list set selectbgcolor $selectbgcolor]
970
971         puts $f "set geometry(main) [wm geometry .]"
972         puts $f "set geometry(topwidth) [winfo width .tf]"
973         puts $f "set geometry(topheight) [winfo height .tf]"
974         puts $f "set geometry(pwsash0) \"[.tf.histframe.pwclist sash coord 0]\""
975         puts $f "set geometry(pwsash1) \"[.tf.histframe.pwclist sash coord 1]\""
976         puts $f "set geometry(botwidth) [winfo width .bleft]"
977         puts $f "set geometry(botheight) [winfo height .bleft]"
978
979         puts -nonewline $f "set permviews {"
980         for {set v 0} {$v < $nextviewnum} {incr v} {
981             if {$viewperm($v)} {
982                 puts $f "{[list $viewname($v) $viewfiles($v) $viewargs($v)]}"
983             }
984         }
985         puts $f "}"
986         close $f
987         file rename -force "~/.gitk-new" "~/.gitk"
988     }
989     set stuffsaved 1
990 }
991
992 proc resizeclistpanes {win w} {
993     global oldwidth
994     if {[info exists oldwidth($win)]} {
995         set s0 [$win sash coord 0]
996         set s1 [$win sash coord 1]
997         if {$w < 60} {
998             set sash0 [expr {int($w/2 - 2)}]
999             set sash1 [expr {int($w*5/6 - 2)}]
1000         } else {
1001             set factor [expr {1.0 * $w / $oldwidth($win)}]
1002             set sash0 [expr {int($factor * [lindex $s0 0])}]
1003             set sash1 [expr {int($factor * [lindex $s1 0])}]
1004             if {$sash0 < 30} {
1005                 set sash0 30
1006             }
1007             if {$sash1 < $sash0 + 20} {
1008                 set sash1 [expr {$sash0 + 20}]
1009             }
1010             if {$sash1 > $w - 10} {
1011                 set sash1 [expr {$w - 10}]
1012                 if {$sash0 > $sash1 - 20} {
1013                     set sash0 [expr {$sash1 - 20}]
1014                 }
1015             }
1016         }
1017         $win sash place 0 $sash0 [lindex $s0 1]
1018         $win sash place 1 $sash1 [lindex $s1 1]
1019     }
1020     set oldwidth($win) $w
1021 }
1022
1023 proc resizecdetpanes {win w} {
1024     global oldwidth
1025     if {[info exists oldwidth($win)]} {
1026         set s0 [$win sash coord 0]
1027         if {$w < 60} {
1028             set sash0 [expr {int($w*3/4 - 2)}]
1029         } else {
1030             set factor [expr {1.0 * $w / $oldwidth($win)}]
1031             set sash0 [expr {int($factor * [lindex $s0 0])}]
1032             if {$sash0 < 45} {
1033                 set sash0 45
1034             }
1035             if {$sash0 > $w - 15} {
1036                 set sash0 [expr {$w - 15}]
1037             }
1038         }
1039         $win sash place 0 $sash0 [lindex $s0 1]
1040     }
1041     set oldwidth($win) $w
1042 }
1043
1044 proc allcanvs args {
1045     global canv canv2 canv3
1046     eval $canv $args
1047     eval $canv2 $args
1048     eval $canv3 $args
1049 }
1050
1051 proc bindall {event action} {
1052     global canv canv2 canv3
1053     bind $canv $event $action
1054     bind $canv2 $event $action
1055     bind $canv3 $event $action
1056 }
1057
1058 proc about {} {
1059     global uifont
1060     set w .about
1061     if {[winfo exists $w]} {
1062         raise $w
1063         return
1064     }
1065     toplevel $w
1066     wm title $w "About gitk"
1067     message $w.m -text {
1068 Gitk - a commit viewer for git
1069
1070 Copyright Â© 2005-2006 Paul Mackerras
1071
1072 Use and redistribute under the terms of the GNU General Public License} \
1073             -justify center -aspect 400 -border 2 -bg white -relief groove
1074     pack $w.m -side top -fill x -padx 2 -pady 2
1075     $w.m configure -font $uifont
1076     button $w.ok -text Close -command "destroy $w" -default active
1077     pack $w.ok -side bottom
1078     $w.ok configure -font $uifont
1079     bind $w <Visibility> "focus $w.ok"
1080     bind $w <Key-Escape> "destroy $w"
1081     bind $w <Key-Return> "destroy $w"
1082 }
1083
1084 proc keys {} {
1085     global uifont
1086     set w .keys
1087     if {[winfo exists $w]} {
1088         raise $w
1089         return
1090     }
1091     toplevel $w
1092     wm title $w "Gitk key bindings"
1093     message $w.m -text {
1094 Gitk key bindings:
1095
1096 <Ctrl-Q>                Quit
1097 <Home>          Move to first commit
1098 <End>           Move to last commit
1099 <Up>, p, i      Move up one commit
1100 <Down>, n, k    Move down one commit
1101 <Left>, z, j    Go back in history list
1102 <Right>, x, l   Go forward in history list
1103 <PageUp>        Move up one page in commit list
1104 <PageDown>      Move down one page in commit list
1105 <Ctrl-Home>     Scroll to top of commit list
1106 <Ctrl-End>      Scroll to bottom of commit list
1107 <Ctrl-Up>       Scroll commit list up one line
1108 <Ctrl-Down>     Scroll commit list down one line
1109 <Ctrl-PageUp>   Scroll commit list up one page
1110 <Ctrl-PageDown> Scroll commit list down one page
1111 <Shift-Up>      Move to previous highlighted line
1112 <Shift-Down>    Move to next highlighted line
1113 <Delete>, b     Scroll diff view up one page
1114 <Backspace>     Scroll diff view up one page
1115 <Space>         Scroll diff view down one page
1116 u               Scroll diff view up 18 lines
1117 d               Scroll diff view down 18 lines
1118 <Ctrl-F>                Find
1119 <Ctrl-G>                Move to next find hit
1120 <Return>        Move to next find hit
1121 /               Move to next find hit, or redo find
1122 ?               Move to previous find hit
1123 f               Scroll diff view to next file
1124 <Ctrl-S>                Search for next hit in diff view
1125 <Ctrl-R>                Search for previous hit in diff view
1126 <Ctrl-KP+>      Increase font size
1127 <Ctrl-plus>     Increase font size
1128 <Ctrl-KP->      Decrease font size
1129 <Ctrl-minus>    Decrease font size
1130 <F5>            Update
1131 } \
1132             -justify left -bg white -border 2 -relief groove
1133     pack $w.m -side top -fill both -padx 2 -pady 2
1134     $w.m configure -font $uifont
1135     button $w.ok -text Close -command "destroy $w" -default active
1136     pack $w.ok -side bottom
1137     $w.ok configure -font $uifont
1138     bind $w <Visibility> "focus $w.ok"
1139     bind $w <Key-Escape> "destroy $w"
1140     bind $w <Key-Return> "destroy $w"
1141 }
1142
1143 # Procedures for manipulating the file list window at the
1144 # bottom right of the overall window.
1145
1146 proc treeview {w l openlevs} {
1147     global treecontents treediropen treeheight treeparent treeindex
1148
1149     set ix 0
1150     set treeindex() 0
1151     set lev 0
1152     set prefix {}
1153     set prefixend -1
1154     set prefendstack {}
1155     set htstack {}
1156     set ht 0
1157     set treecontents() {}
1158     $w conf -state normal
1159     foreach f $l {
1160         while {[string range $f 0 $prefixend] ne $prefix} {
1161             if {$lev <= $openlevs} {
1162                 $w mark set e:$treeindex($prefix) "end -1c"
1163                 $w mark gravity e:$treeindex($prefix) left
1164             }
1165             set treeheight($prefix) $ht
1166             incr ht [lindex $htstack end]
1167             set htstack [lreplace $htstack end end]
1168             set prefixend [lindex $prefendstack end]
1169             set prefendstack [lreplace $prefendstack end end]
1170             set prefix [string range $prefix 0 $prefixend]
1171             incr lev -1
1172         }
1173         set tail [string range $f [expr {$prefixend+1}] end]
1174         while {[set slash [string first "/" $tail]] >= 0} {
1175             lappend htstack $ht
1176             set ht 0
1177             lappend prefendstack $prefixend
1178             incr prefixend [expr {$slash + 1}]
1179             set d [string range $tail 0 $slash]
1180             lappend treecontents($prefix) $d
1181             set oldprefix $prefix
1182             append prefix $d
1183             set treecontents($prefix) {}
1184             set treeindex($prefix) [incr ix]
1185             set treeparent($prefix) $oldprefix
1186             set tail [string range $tail [expr {$slash+1}] end]
1187             if {$lev <= $openlevs} {
1188                 set ht 1
1189                 set treediropen($prefix) [expr {$lev < $openlevs}]
1190                 set bm [expr {$lev == $openlevs? "tri-rt": "tri-dn"}]
1191                 $w mark set d:$ix "end -1c"
1192                 $w mark gravity d:$ix left
1193                 set str "\n"
1194                 for {set i 0} {$i < $lev} {incr i} {append str "\t"}
1195                 $w insert end $str
1196                 $w image create end -align center -image $bm -padx 1 \
1197                     -name a:$ix
1198                 $w insert end $d [highlight_tag $prefix]
1199                 $w mark set s:$ix "end -1c"
1200                 $w mark gravity s:$ix left
1201             }
1202             incr lev
1203         }
1204         if {$tail ne {}} {
1205             if {$lev <= $openlevs} {
1206                 incr ht
1207                 set str "\n"
1208                 for {set i 0} {$i < $lev} {incr i} {append str "\t"}
1209                 $w insert end $str
1210                 $w insert end $tail [highlight_tag $f]
1211             }
1212             lappend treecontents($prefix) $tail
1213         }
1214     }
1215     while {$htstack ne {}} {
1216         set treeheight($prefix) $ht
1217         incr ht [lindex $htstack end]
1218         set htstack [lreplace $htstack end end]
1219     }
1220     $w conf -state disabled
1221 }
1222
1223 proc linetoelt {l} {
1224     global treeheight treecontents
1225
1226     set y 2
1227     set prefix {}
1228     while {1} {
1229         foreach e $treecontents($prefix) {
1230             if {$y == $l} {
1231                 return "$prefix$e"
1232             }
1233             set n 1
1234             if {[string index $e end] eq "/"} {
1235                 set n $treeheight($prefix$e)
1236                 if {$y + $n > $l} {
1237                     append prefix $e
1238                     incr y
1239                     break
1240                 }
1241             }
1242             incr y $n
1243         }
1244     }
1245 }
1246
1247 proc highlight_tree {y prefix} {
1248     global treeheight treecontents cflist
1249
1250     foreach e $treecontents($prefix) {
1251         set path $prefix$e
1252         if {[highlight_tag $path] ne {}} {
1253             $cflist tag add bold $y.0 "$y.0 lineend"
1254         }
1255         incr y
1256         if {[string index $e end] eq "/" && $treeheight($path) > 1} {
1257             set y [highlight_tree $y $path]
1258         }
1259     }
1260     return $y
1261 }
1262
1263 proc treeclosedir {w dir} {
1264     global treediropen treeheight treeparent treeindex
1265
1266     set ix $treeindex($dir)
1267     $w conf -state normal
1268     $w delete s:$ix e:$ix
1269     set treediropen($dir) 0
1270     $w image configure a:$ix -image tri-rt
1271     $w conf -state disabled
1272     set n [expr {1 - $treeheight($dir)}]
1273     while {$dir ne {}} {
1274         incr treeheight($dir) $n
1275         set dir $treeparent($dir)
1276     }
1277 }
1278
1279 proc treeopendir {w dir} {
1280     global treediropen treeheight treeparent treecontents treeindex
1281
1282     set ix $treeindex($dir)
1283     $w conf -state normal
1284     $w image configure a:$ix -image tri-dn
1285     $w mark set e:$ix s:$ix
1286     $w mark gravity e:$ix right
1287     set lev 0
1288     set str "\n"
1289     set n [llength $treecontents($dir)]
1290     for {set x $dir} {$x ne {}} {set x $treeparent($x)} {
1291         incr lev
1292         append str "\t"
1293         incr treeheight($x) $n
1294     }
1295     foreach e $treecontents($dir) {
1296         set de $dir$e
1297         if {[string index $e end] eq "/"} {
1298             set iy $treeindex($de)
1299             $w mark set d:$iy e:$ix
1300             $w mark gravity d:$iy left
1301             $w insert e:$ix $str
1302             set treediropen($de) 0
1303             $w image create e:$ix -align center -image tri-rt -padx 1 \
1304                 -name a:$iy
1305             $w insert e:$ix $e [highlight_tag $de]
1306             $w mark set s:$iy e:$ix
1307             $w mark gravity s:$iy left
1308             set treeheight($de) 1
1309         } else {
1310             $w insert e:$ix $str
1311             $w insert e:$ix $e [highlight_tag $de]
1312         }
1313     }
1314     $w mark gravity e:$ix left
1315     $w conf -state disabled
1316     set treediropen($dir) 1
1317     set top [lindex [split [$w index @0,0] .] 0]
1318     set ht [$w cget -height]
1319     set l [lindex [split [$w index s:$ix] .] 0]
1320     if {$l < $top} {
1321         $w yview $l.0
1322     } elseif {$l + $n + 1 > $top + $ht} {
1323         set top [expr {$l + $n + 2 - $ht}]
1324         if {$l < $top} {
1325             set top $l
1326         }
1327         $w yview $top.0
1328     }
1329 }
1330
1331 proc treeclick {w x y} {
1332     global treediropen cmitmode ctext cflist cflist_top
1333
1334     if {$cmitmode ne "tree"} return
1335     if {![info exists cflist_top]} return
1336     set l [lindex [split [$w index "@$x,$y"] "."] 0]
1337     $cflist tag remove highlight $cflist_top.0 "$cflist_top.0 lineend"
1338     $cflist tag add highlight $l.0 "$l.0 lineend"
1339     set cflist_top $l
1340     if {$l == 1} {
1341         $ctext yview 1.0
1342         return
1343     }
1344     set e [linetoelt $l]
1345     if {[string index $e end] ne "/"} {
1346         showfile $e
1347     } elseif {$treediropen($e)} {
1348         treeclosedir $w $e
1349     } else {
1350         treeopendir $w $e
1351     }
1352 }
1353
1354 proc setfilelist {id} {
1355     global treefilelist cflist
1356
1357     treeview $cflist $treefilelist($id) 0
1358 }
1359
1360 image create bitmap tri-rt -background black -foreground blue -data {
1361     #define tri-rt_width 13
1362     #define tri-rt_height 13
1363     static unsigned char tri-rt_bits[] = {
1364        0x00, 0x00, 0x00, 0x00, 0x10, 0x00, 0x30, 0x00, 0x70, 0x00, 0xf0, 0x00,
1365        0xf0, 0x01, 0xf0, 0x00, 0x70, 0x00, 0x30, 0x00, 0x10, 0x00, 0x00, 0x00,
1366        0x00, 0x00};
1367 } -maskdata {
1368     #define tri-rt-mask_width 13
1369     #define tri-rt-mask_height 13
1370     static unsigned char tri-rt-mask_bits[] = {
1371        0x08, 0x00, 0x18, 0x00, 0x38, 0x00, 0x78, 0x00, 0xf8, 0x00, 0xf8, 0x01,
1372        0xf8, 0x03, 0xf8, 0x01, 0xf8, 0x00, 0x78, 0x00, 0x38, 0x00, 0x18, 0x00,
1373        0x08, 0x00};
1374 }
1375 image create bitmap tri-dn -background black -foreground blue -data {
1376     #define tri-dn_width 13
1377     #define tri-dn_height 13
1378     static unsigned char tri-dn_bits[] = {
1379        0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0xfc, 0x07, 0xf8, 0x03,
1380        0xf0, 0x01, 0xe0, 0x00, 0x40, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
1381        0x00, 0x00};
1382 } -maskdata {
1383     #define tri-dn-mask_width 13
1384     #define tri-dn-mask_height 13
1385     static unsigned char tri-dn-mask_bits[] = {
1386        0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0xff, 0x1f, 0xfe, 0x0f, 0xfc, 0x07,
1387        0xf8, 0x03, 0xf0, 0x01, 0xe0, 0x00, 0x40, 0x00, 0x00, 0x00, 0x00, 0x00,
1388        0x00, 0x00};
1389 }
1390
1391 proc init_flist {first} {
1392     global cflist cflist_top selectedline difffilestart
1393
1394     $cflist conf -state normal
1395     $cflist delete 0.0 end
1396     if {$first ne {}} {
1397         $cflist insert end $first
1398         set cflist_top 1
1399         $cflist tag add highlight 1.0 "1.0 lineend"
1400     } else {
1401         catch {unset cflist_top}
1402     }
1403     $cflist conf -state disabled
1404     set difffilestart {}
1405 }
1406
1407 proc highlight_tag {f} {
1408     global highlight_paths
1409
1410     foreach p $highlight_paths {
1411         if {[string match $p $f]} {
1412             return "bold"
1413         }
1414     }
1415     return {}
1416 }
1417
1418 proc highlight_filelist {} {
1419     global cmitmode cflist
1420
1421     $cflist conf -state normal
1422     if {$cmitmode ne "tree"} {
1423         set end [lindex [split [$cflist index end] .] 0]
1424         for {set l 2} {$l < $end} {incr l} {
1425             set line [$cflist get $l.0 "$l.0 lineend"]
1426             if {[highlight_tag $line] ne {}} {
1427                 $cflist tag add bold $l.0 "$l.0 lineend"
1428             }
1429         }
1430     } else {
1431         highlight_tree 2 {}
1432     }
1433     $cflist conf -state disabled
1434 }
1435
1436 proc unhighlight_filelist {} {
1437     global cflist
1438
1439     $cflist conf -state normal
1440     $cflist tag remove bold 1.0 end
1441     $cflist conf -state disabled
1442 }
1443
1444 proc add_flist {fl} {
1445     global cflist
1446
1447     $cflist conf -state normal
1448     foreach f $fl {
1449         $cflist insert end "\n"
1450         $cflist insert end $f [highlight_tag $f]
1451     }
1452     $cflist conf -state disabled
1453 }
1454
1455 proc sel_flist {w x y} {
1456     global ctext difffilestart cflist cflist_top cmitmode
1457
1458     if {$cmitmode eq "tree"} return
1459     if {![info exists cflist_top]} return
1460     set l [lindex [split [$w index "@$x,$y"] "."] 0]
1461     $cflist tag remove highlight $cflist_top.0 "$cflist_top.0 lineend"
1462     $cflist tag add highlight $l.0 "$l.0 lineend"
1463     set cflist_top $l
1464     if {$l == 1} {
1465         $ctext yview 1.0
1466     } else {
1467         catch {$ctext yview [lindex $difffilestart [expr {$l - 2}]]}
1468     }
1469 }
1470
1471 # Functions for adding and removing shell-type quoting
1472
1473 proc shellquote {str} {
1474     if {![string match "*\['\"\\ \t]*" $str]} {
1475         return $str
1476     }
1477     if {![string match "*\['\"\\]*" $str]} {
1478         return "\"$str\""
1479     }
1480     if {![string match "*'*" $str]} {
1481         return "'$str'"
1482     }
1483     return "\"[string map {\" \\\" \\ \\\\} $str]\""
1484 }
1485
1486 proc shellarglist {l} {
1487     set str {}
1488     foreach a $l {
1489         if {$str ne {}} {
1490             append str " "
1491         }
1492         append str [shellquote $a]
1493     }
1494     return $str
1495 }
1496
1497 proc shelldequote {str} {
1498     set ret {}
1499     set used -1
1500     while {1} {
1501         incr used
1502         if {![regexp -start $used -indices "\['\"\\\\ \t]" $str first]} {
1503             append ret [string range $str $used end]
1504             set used [string length $str]
1505             break
1506         }
1507         set first [lindex $first 0]
1508         set ch [string index $str $first]
1509         if {$first > $used} {
1510             append ret [string range $str $used [expr {$first - 1}]]
1511             set used $first
1512         }
1513         if {$ch eq " " || $ch eq "\t"} break
1514         incr used
1515         if {$ch eq "'"} {
1516             set first [string first "'" $str $used]
1517             if {$first < 0} {
1518                 error "unmatched single-quote"
1519             }
1520             append ret [string range $str $used [expr {$first - 1}]]
1521             set used $first
1522             continue
1523         }
1524         if {$ch eq "\\"} {
1525             if {$used >= [string length $str]} {
1526                 error "trailing backslash"
1527             }
1528             append ret [string index $str $used]
1529             continue
1530         }
1531         # here ch == "\""
1532         while {1} {
1533             if {![regexp -start $used -indices "\[\"\\\\]" $str first]} {
1534                 error "unmatched double-quote"
1535             }
1536             set first [lindex $first 0]
1537             set ch [string index $str $first]
1538             if {$first > $used} {
1539                 append ret [string range $str $used [expr {$first - 1}]]
1540                 set used $first
1541             }
1542             if {$ch eq "\""} break
1543             incr used
1544             append ret [string index $str $used]
1545             incr used
1546         }
1547     }
1548     return [list $used $ret]
1549 }
1550
1551 proc shellsplit {str} {
1552     set l {}
1553     while {1} {
1554         set str [string trimleft $str]
1555         if {$str eq {}} break
1556         set dq [shelldequote $str]
1557         set n [lindex $dq 0]
1558         set word [lindex $dq 1]
1559         set str [string range $str $n end]
1560         lappend l $word
1561     }
1562     return $l
1563 }
1564
1565 # Code to implement multiple views
1566
1567 proc newview {ishighlight} {
1568     global nextviewnum newviewname newviewperm uifont newishighlight
1569     global newviewargs revtreeargs
1570
1571     set newishighlight $ishighlight
1572     set top .gitkview
1573     if {[winfo exists $top]} {
1574         raise $top
1575         return
1576     }
1577     set newviewname($nextviewnum) "View $nextviewnum"
1578     set newviewperm($nextviewnum) 0
1579     set newviewargs($nextviewnum) [shellarglist $revtreeargs]
1580     vieweditor $top $nextviewnum "Gitk view definition"
1581 }
1582
1583 proc editview {} {
1584     global curview
1585     global viewname viewperm newviewname newviewperm
1586     global viewargs newviewargs
1587
1588     set top .gitkvedit-$curview
1589     if {[winfo exists $top]} {
1590         raise $top
1591         return
1592     }
1593     set newviewname($curview) $viewname($curview)
1594     set newviewperm($curview) $viewperm($curview)
1595     set newviewargs($curview) [shellarglist $viewargs($curview)]
1596     vieweditor $top $curview "Gitk: edit view $viewname($curview)"
1597 }
1598
1599 proc vieweditor {top n title} {
1600     global newviewname newviewperm viewfiles
1601     global uifont
1602
1603     toplevel $top
1604     wm title $top $title
1605     label $top.nl -text "Name" -font $uifont
1606     entry $top.name -width 20 -textvariable newviewname($n) -font $uifont
1607     grid $top.nl $top.name -sticky w -pady 5
1608     checkbutton $top.perm -text "Remember this view" -variable newviewperm($n) \
1609         -font $uifont
1610     grid $top.perm - -pady 5 -sticky w
1611     message $top.al -aspect 1000 -font $uifont \
1612         -text "Commits to include (arguments to git rev-list):"
1613     grid $top.al - -sticky w -pady 5
1614     entry $top.args -width 50 -textvariable newviewargs($n) \
1615         -background white -font $uifont
1616     grid $top.args - -sticky ew -padx 5
1617     message $top.l -aspect 1000 -font $uifont \
1618         -text "Enter files and directories to include, one per line:"
1619     grid $top.l - -sticky w
1620     text $top.t -width 40 -height 10 -background white -font $uifont
1621     if {[info exists viewfiles($n)]} {
1622         foreach f $viewfiles($n) {
1623             $top.t insert end $f
1624             $top.t insert end "\n"
1625         }
1626         $top.t delete {end - 1c} end
1627         $top.t mark set insert 0.0
1628     }
1629     grid $top.t - -sticky ew -padx 5
1630     frame $top.buts
1631     button $top.buts.ok -text "OK" -command [list newviewok $top $n] \
1632         -font $uifont
1633     button $top.buts.can -text "Cancel" -command [list destroy $top] \
1634         -font $uifont
1635     grid $top.buts.ok $top.buts.can
1636     grid columnconfigure $top.buts 0 -weight 1 -uniform a
1637     grid columnconfigure $top.buts 1 -weight 1 -uniform a
1638     grid $top.buts - -pady 10 -sticky ew
1639     focus $top.t
1640 }
1641
1642 proc doviewmenu {m first cmd op argv} {
1643     set nmenu [$m index end]
1644     for {set i $first} {$i <= $nmenu} {incr i} {
1645         if {[$m entrycget $i -command] eq $cmd} {
1646             eval $m $op $i $argv
1647             break
1648         }
1649     }
1650 }
1651
1652 proc allviewmenus {n op args} {
1653     global viewhlmenu
1654
1655     doviewmenu .bar.view 5 [list showview $n] $op $args
1656     doviewmenu $viewhlmenu 1 [list addvhighlight $n] $op $args
1657 }
1658
1659 proc newviewok {top n} {
1660     global nextviewnum newviewperm newviewname newishighlight
1661     global viewname viewfiles viewperm selectedview curview
1662     global viewargs newviewargs viewhlmenu
1663
1664     if {[catch {
1665         set newargs [shellsplit $newviewargs($n)]
1666     } err]} {
1667         error_popup "Error in commit selection arguments: $err"
1668         wm raise $top
1669         focus $top
1670         return
1671     }
1672     set files {}
1673     foreach f [split [$top.t get 0.0 end] "\n"] {
1674         set ft [string trim $f]
1675         if {$ft ne {}} {
1676             lappend files $ft
1677         }
1678     }
1679     if {![info exists viewfiles($n)]} {
1680         # creating a new view
1681         incr nextviewnum
1682         set viewname($n) $newviewname($n)
1683         set viewperm($n) $newviewperm($n)
1684         set viewfiles($n) $files
1685         set viewargs($n) $newargs
1686         addviewmenu $n
1687         if {!$newishighlight} {
1688             run showview $n
1689         } else {
1690             run addvhighlight $n
1691         }
1692     } else {
1693         # editing an existing view
1694         set viewperm($n) $newviewperm($n)
1695         if {$newviewname($n) ne $viewname($n)} {
1696             set viewname($n) $newviewname($n)
1697             doviewmenu .bar.view 5 [list showview $n] \
1698                 entryconf [list -label $viewname($n)]
1699             doviewmenu $viewhlmenu 1 [list addvhighlight $n] \
1700                 entryconf [list -label $viewname($n) -value $viewname($n)]
1701         }
1702         if {$files ne $viewfiles($n) || $newargs ne $viewargs($n)} {
1703             set viewfiles($n) $files
1704             set viewargs($n) $newargs
1705             if {$curview == $n} {
1706                 run updatecommits
1707             }
1708         }
1709     }
1710     catch {destroy $top}
1711 }
1712
1713 proc delview {} {
1714     global curview viewdata viewperm hlview selectedhlview
1715
1716     if {$curview == 0} return
1717     if {[info exists hlview] && $hlview == $curview} {
1718         set selectedhlview None
1719         unset hlview
1720     }
1721     allviewmenus $curview delete
1722     set viewdata($curview) {}
1723     set viewperm($curview) 0
1724     showview 0
1725 }
1726
1727 proc addviewmenu {n} {
1728     global viewname viewhlmenu
1729
1730     .bar.view add radiobutton -label $viewname($n) \
1731         -command [list showview $n] -variable selectedview -value $n
1732     $viewhlmenu add radiobutton -label $viewname($n) \
1733         -command [list addvhighlight $n] -variable selectedhlview
1734 }
1735
1736 proc flatten {var} {
1737     global $var
1738
1739     set ret {}
1740     foreach i [array names $var] {
1741         lappend ret $i [set $var\($i\)]
1742     }
1743     return $ret
1744 }
1745
1746 proc unflatten {var l} {
1747     global $var
1748
1749     catch {unset $var}
1750     foreach {i v} $l {
1751         set $var\($i\) $v
1752     }
1753 }
1754
1755 proc showview {n} {
1756     global curview viewdata viewfiles
1757     global displayorder parentlist rowidlist rowoffsets
1758     global colormap rowtextx commitrow nextcolor canvxmax
1759     global numcommits rowrangelist commitlisted idrowranges rowchk
1760     global selectedline currentid canv canvy0
1761     global treediffs
1762     global pending_select phase
1763     global commitidx rowlaidout rowoptim
1764     global commfd
1765     global selectedview selectfirst
1766     global vparentlist vdisporder vcmitlisted
1767     global hlview selectedhlview
1768
1769     if {$n == $curview} return
1770     set selid {}
1771     if {[info exists selectedline]} {
1772         set selid $currentid
1773         set y [yc $selectedline]
1774         set ymax [lindex [$canv cget -scrollregion] 3]
1775         set span [$canv yview]
1776         set ytop [expr {[lindex $span 0] * $ymax}]
1777         set ybot [expr {[lindex $span 1] * $ymax}]
1778         if {$ytop < $y && $y < $ybot} {
1779             set yscreen [expr {$y - $ytop}]
1780         } else {
1781             set yscreen [expr {($ybot - $ytop) / 2}]
1782         }
1783     } elseif {[info exists pending_select]} {
1784         set selid $pending_select
1785         unset pending_select
1786     }
1787     unselectline
1788     normalline
1789     if {$curview >= 0} {
1790         set vparentlist($curview) $parentlist
1791         set vdisporder($curview) $displayorder
1792         set vcmitlisted($curview) $commitlisted
1793         if {$phase ne {}} {
1794             set viewdata($curview) \
1795                 [list $phase $rowidlist $rowoffsets $rowrangelist \
1796                      [flatten idrowranges] [flatten idinlist] \
1797                      $rowlaidout $rowoptim $numcommits]
1798         } elseif {![info exists viewdata($curview)]
1799                   || [lindex $viewdata($curview) 0] ne {}} {
1800             set viewdata($curview) \
1801                 [list {} $rowidlist $rowoffsets $rowrangelist]
1802         }
1803     }
1804     catch {unset treediffs}
1805     clear_display
1806     if {[info exists hlview] && $hlview == $n} {
1807         unset hlview
1808         set selectedhlview None
1809     }
1810
1811     set curview $n
1812     set selectedview $n
1813     .bar.view entryconf Edit* -state [expr {$n == 0? "disabled": "normal"}]
1814     .bar.view entryconf Delete* -state [expr {$n == 0? "disabled": "normal"}]
1815
1816     if {![info exists viewdata($n)]} {
1817         if {$selid ne {}} {
1818             set pending_select $selid
1819         }
1820         getcommits
1821         return
1822     }
1823
1824     set v $viewdata($n)
1825     set phase [lindex $v 0]
1826     set displayorder $vdisporder($n)
1827     set parentlist $vparentlist($n)
1828     set commitlisted $vcmitlisted($n)
1829     set rowidlist [lindex $v 1]
1830     set rowoffsets [lindex $v 2]
1831     set rowrangelist [lindex $v 3]
1832     if {$phase eq {}} {
1833         set numcommits [llength $displayorder]
1834         catch {unset idrowranges}
1835     } else {
1836         unflatten idrowranges [lindex $v 4]
1837         unflatten idinlist [lindex $v 5]
1838         set rowlaidout [lindex $v 6]
1839         set rowoptim [lindex $v 7]
1840         set numcommits [lindex $v 8]
1841         catch {unset rowchk}
1842     }
1843
1844     catch {unset colormap}
1845     catch {unset rowtextx}
1846     set nextcolor 0
1847     set canvxmax [$canv cget -width]
1848     set curview $n
1849     set row 0
1850     setcanvscroll
1851     set yf 0
1852     set row {}
1853     set selectfirst 0
1854     if {$selid ne {} && [info exists commitrow($n,$selid)]} {
1855         set row $commitrow($n,$selid)
1856         # try to get the selected row in the same position on the screen
1857         set ymax [lindex [$canv cget -scrollregion] 3]
1858         set ytop [expr {[yc $row] - $yscreen}]
1859         if {$ytop < 0} {
1860             set ytop 0
1861         }
1862         set yf [expr {$ytop * 1.0 / $ymax}]
1863     }
1864     allcanvs yview moveto $yf
1865     drawvisible
1866     if {$row ne {}} {
1867         selectline $row 0
1868     } elseif {$selid ne {}} {
1869         set pending_select $selid
1870     } else {
1871         set row [expr {[lindex $displayorder 0] eq $nullid}]
1872         if {$row < $numcommits} {
1873             selectline $row 0
1874         } else {
1875             set selectfirst 1
1876         }
1877     }
1878     if {$phase ne {}} {
1879         if {$phase eq "getcommits"} {
1880             show_status "Reading commits..."
1881         }
1882         run chewcommits $n
1883     } elseif {$numcommits == 0} {
1884         show_status "No commits selected"
1885     }
1886 }
1887
1888 # Stuff relating to the highlighting facility
1889
1890 proc ishighlighted {row} {
1891     global vhighlights fhighlights nhighlights rhighlights
1892
1893     if {[info exists nhighlights($row)] && $nhighlights($row) > 0} {
1894         return $nhighlights($row)
1895     }
1896     if {[info exists vhighlights($row)] && $vhighlights($row) > 0} {
1897         return $vhighlights($row)
1898     }
1899     if {[info exists fhighlights($row)] && $fhighlights($row) > 0} {
1900         return $fhighlights($row)
1901     }
1902     if {[info exists rhighlights($row)] && $rhighlights($row) > 0} {
1903         return $rhighlights($row)
1904     }
1905     return 0
1906 }
1907
1908 proc bolden {row font} {
1909     global canv linehtag selectedline boldrows
1910
1911     lappend boldrows $row
1912     $canv itemconf $linehtag($row) -font $font
1913     if {[info exists selectedline] && $row == $selectedline} {
1914         $canv delete secsel
1915         set t [eval $canv create rect [$canv bbox $linehtag($row)] \
1916                    -outline {{}} -tags secsel \
1917                    -fill [$canv cget -selectbackground]]
1918         $canv lower $t
1919     }
1920 }
1921
1922 proc bolden_name {row font} {
1923     global canv2 linentag selectedline boldnamerows
1924
1925     lappend boldnamerows $row
1926     $canv2 itemconf $linentag($row) -font $font
1927     if {[info exists selectedline] && $row == $selectedline} {
1928         $canv2 delete secsel
1929         set t [eval $canv2 create rect [$canv2 bbox $linentag($row)] \
1930                    -outline {{}} -tags secsel \
1931                    -fill [$canv2 cget -selectbackground]]
1932         $canv2 lower $t
1933     }
1934 }
1935
1936 proc unbolden {} {
1937     global mainfont boldrows
1938
1939     set stillbold {}
1940     foreach row $boldrows {
1941         if {![ishighlighted $row]} {
1942             bolden $row $mainfont
1943         } else {
1944             lappend stillbold $row
1945         }
1946     }
1947     set boldrows $stillbold
1948 }
1949
1950 proc addvhighlight {n} {
1951     global hlview curview viewdata vhl_done vhighlights commitidx
1952
1953     if {[info exists hlview]} {
1954         delvhighlight
1955     }
1956     set hlview $n
1957     if {$n != $curview && ![info exists viewdata($n)]} {
1958         set viewdata($n) [list getcommits {{}} {{}} {} {} {} 0 0 0 {}]
1959         set vparentlist($n) {}
1960         set vdisporder($n) {}
1961         set vcmitlisted($n) {}
1962         start_rev_list $n
1963     }
1964     set vhl_done $commitidx($hlview)
1965     if {$vhl_done > 0} {
1966         drawvisible
1967     }
1968 }
1969
1970 proc delvhighlight {} {
1971     global hlview vhighlights
1972
1973     if {![info exists hlview]} return
1974     unset hlview
1975     catch {unset vhighlights}
1976     unbolden
1977 }
1978
1979 proc vhighlightmore {} {
1980     global hlview vhl_done commitidx vhighlights
1981     global displayorder vdisporder curview mainfont
1982
1983     set font [concat $mainfont bold]
1984     set max $commitidx($hlview)
1985     if {$hlview == $curview} {
1986         set disp $displayorder
1987     } else {
1988         set disp $vdisporder($hlview)
1989     }
1990     set vr [visiblerows]
1991     set r0 [lindex $vr 0]
1992     set r1 [lindex $vr 1]
1993     for {set i $vhl_done} {$i < $max} {incr i} {
1994         set id [lindex $disp $i]
1995         if {[info exists commitrow($curview,$id)]} {
1996             set row $commitrow($curview,$id)
1997             if {$r0 <= $row && $row <= $r1} {
1998                 if {![highlighted $row]} {
1999                     bolden $row $font
2000                 }
2001                 set vhighlights($row) 1
2002             }
2003         }
2004     }
2005     set vhl_done $max
2006 }
2007
2008 proc askvhighlight {row id} {
2009     global hlview vhighlights commitrow iddrawn mainfont
2010
2011     if {[info exists commitrow($hlview,$id)]} {
2012         if {[info exists iddrawn($id)] && ![ishighlighted $row]} {
2013             bolden $row [concat $mainfont bold]
2014         }
2015         set vhighlights($row) 1
2016     } else {
2017         set vhighlights($row) 0
2018     }
2019 }
2020
2021 proc hfiles_change {name ix op} {
2022     global highlight_files filehighlight fhighlights fh_serial
2023     global mainfont highlight_paths
2024
2025     if {[info exists filehighlight]} {
2026         # delete previous highlights
2027         catch {close $filehighlight}
2028         unset filehighlight
2029         catch {unset fhighlights}
2030         unbolden
2031         unhighlight_filelist
2032     }
2033     set highlight_paths {}
2034     after cancel do_file_hl $fh_serial
2035     incr fh_serial
2036     if {$highlight_files ne {}} {
2037         after 300 do_file_hl $fh_serial
2038     }
2039 }
2040
2041 proc makepatterns {l} {
2042     set ret {}
2043     foreach e $l {
2044         set ee [string map {"*" "\\*" "?" "\\?" "\[" "\\\[" "\\" "\\\\"} $e]
2045         if {[string index $ee end] eq "/"} {
2046             lappend ret "$ee*"
2047         } else {
2048             lappend ret $ee
2049             lappend ret "$ee/*"
2050         }
2051     }
2052     return $ret
2053 }
2054
2055 proc do_file_hl {serial} {
2056     global highlight_files filehighlight highlight_paths gdttype fhl_list
2057
2058     if {$gdttype eq "touching paths:"} {
2059         if {[catch {set paths [shellsplit $highlight_files]}]} return
2060         set highlight_paths [makepatterns $paths]
2061         highlight_filelist
2062         set gdtargs [concat -- $paths]
2063     } else {
2064         set gdtargs [list "-S$highlight_files"]
2065     }
2066     set cmd [concat | git diff-tree -r -s --stdin $gdtargs]
2067     set filehighlight [open $cmd r+]
2068     fconfigure $filehighlight -blocking 0
2069     filerun $filehighlight readfhighlight
2070     set fhl_list {}
2071     drawvisible
2072     flushhighlights
2073 }
2074
2075 proc flushhighlights {} {
2076     global filehighlight fhl_list
2077
2078     if {[info exists filehighlight]} {
2079         lappend fhl_list {}
2080         puts $filehighlight ""
2081         flush $filehighlight
2082     }
2083 }
2084
2085 proc askfilehighlight {row id} {
2086     global filehighlight fhighlights fhl_list
2087
2088     lappend fhl_list $id
2089     set fhighlights($row) -1
2090     puts $filehighlight $id
2091 }
2092
2093 proc readfhighlight {} {
2094     global filehighlight fhighlights commitrow curview mainfont iddrawn
2095     global fhl_list
2096
2097     if {![info exists filehighlight]} {
2098         return 0
2099     }
2100     set nr 0
2101     while {[incr nr] <= 100 && [gets $filehighlight line] >= 0} {
2102         set line [string trim $line]
2103         set i [lsearch -exact $fhl_list $line]
2104         if {$i < 0} continue
2105         for {set j 0} {$j < $i} {incr j} {
2106             set id [lindex $fhl_list $j]
2107             if {[info exists commitrow($curview,$id)]} {
2108                 set fhighlights($commitrow($curview,$id)) 0
2109             }
2110         }
2111         set fhl_list [lrange $fhl_list [expr {$i+1}] end]
2112         if {$line eq {}} continue
2113         if {![info exists commitrow($curview,$line)]} continue
2114         set row $commitrow($curview,$line)
2115         if {[info exists iddrawn($line)] && ![ishighlighted $row]} {
2116             bolden $row [concat $mainfont bold]
2117         }
2118         set fhighlights($row) 1
2119     }
2120     if {[eof $filehighlight]} {
2121         # strange...
2122         puts "oops, git diff-tree died"
2123         catch {close $filehighlight}
2124         unset filehighlight
2125         return 0
2126     }
2127     next_hlcont
2128     return 1
2129 }
2130
2131 proc find_change {name ix op} {
2132     global nhighlights mainfont boldnamerows
2133     global findstring findpattern findtype markingmatches
2134
2135     # delete previous highlights, if any
2136     foreach row $boldnamerows {
2137         bolden_name $row $mainfont
2138     }
2139     set boldnamerows {}
2140     catch {unset nhighlights}
2141     unbolden
2142     unmarkmatches
2143     if {$findtype ne "Regexp"} {
2144         set e [string map {"*" "\\*" "?" "\\?" "\[" "\\\[" "\\" "\\\\"} \
2145                    $findstring]
2146         set findpattern "*$e*"
2147     }
2148     set markingmatches [expr {$findstring ne {}}]
2149     drawvisible
2150 }
2151
2152 proc doesmatch {f} {
2153     global findtype findstring findpattern
2154
2155     if {$findtype eq "Regexp"} {
2156         return [regexp $findstring $f]
2157     } elseif {$findtype eq "IgnCase"} {
2158         return [string match -nocase $findpattern $f]
2159     } else {
2160         return [string match $findpattern $f]
2161     }
2162 }
2163
2164 proc askfindhighlight {row id} {
2165     global nhighlights commitinfo iddrawn mainfont
2166     global findloc
2167     global markingmatches
2168
2169     if {![info exists commitinfo($id)]} {
2170         getcommit $id
2171     }
2172     set info $commitinfo($id)
2173     set isbold 0
2174     set fldtypes {Headline Author Date Committer CDate Comments}
2175     foreach f $info ty $fldtypes {
2176         if {($findloc eq "All fields" || $findloc eq $ty) &&
2177             [doesmatch $f]} {
2178             if {$ty eq "Author"} {
2179                 set isbold 2
2180                 break
2181             }
2182             set isbold 1
2183         }
2184     }
2185     if {$isbold && [info exists iddrawn($id)]} {
2186         set f [concat $mainfont bold]
2187         if {![ishighlighted $row]} {
2188             bolden $row $f
2189             if {$isbold > 1} {
2190                 bolden_name $row $f
2191             }
2192         }
2193         if {$markingmatches} {
2194             markrowmatches $row [lindex $info 0] [lindex $info 1]
2195         }
2196     }
2197     set nhighlights($row) $isbold
2198 }
2199
2200 proc markrowmatches {row headline author} {
2201     global canv canv2 linehtag linentag
2202
2203     $canv delete match$row
2204     $canv2 delete match$row
2205     set m [findmatches $headline]
2206     if {$m ne {}} {
2207         markmatches $canv $row $headline $linehtag($row) $m \
2208             [$canv itemcget $linehtag($row) -font]
2209     }
2210     set m [findmatches $author]
2211     if {$m ne {}} {
2212         markmatches $canv2 $row $author $linentag($row) $m \
2213             [$canv2 itemcget $linentag($row) -font]
2214     }
2215 }
2216
2217 proc vrel_change {name ix op} {
2218     global highlight_related
2219
2220     rhighlight_none
2221     if {$highlight_related ne "None"} {
2222         run drawvisible
2223     }
2224 }
2225
2226 # prepare for testing whether commits are descendents or ancestors of a
2227 proc rhighlight_sel {a} {
2228     global descendent desc_todo ancestor anc_todo
2229     global highlight_related rhighlights
2230
2231     catch {unset descendent}
2232     set desc_todo [list $a]
2233     catch {unset ancestor}
2234     set anc_todo [list $a]
2235     if {$highlight_related ne "None"} {
2236         rhighlight_none
2237         run drawvisible
2238     }
2239 }
2240
2241 proc rhighlight_none {} {
2242     global rhighlights
2243
2244     catch {unset rhighlights}
2245     unbolden
2246 }
2247
2248 proc is_descendent {a} {
2249     global curview children commitrow descendent desc_todo
2250
2251     set v $curview
2252     set la $commitrow($v,$a)
2253     set todo $desc_todo
2254     set leftover {}
2255     set done 0
2256     for {set i 0} {$i < [llength $todo]} {incr i} {
2257         set do [lindex $todo $i]
2258         if {$commitrow($v,$do) < $la} {
2259             lappend leftover $do
2260             continue
2261         }
2262         foreach nk $children($v,$do) {
2263             if {![info exists descendent($nk)]} {
2264                 set descendent($nk) 1
2265                 lappend todo $nk
2266                 if {$nk eq $a} {
2267                     set done 1
2268                 }
2269             }
2270         }
2271         if {$done} {
2272             set desc_todo [concat $leftover [lrange $todo [expr {$i+1}] end]]
2273             return
2274         }
2275     }
2276     set descendent($a) 0
2277     set desc_todo $leftover
2278 }
2279
2280 proc is_ancestor {a} {
2281     global curview parentlist commitrow ancestor anc_todo
2282
2283     set v $curview
2284     set la $commitrow($v,$a)
2285     set todo $anc_todo
2286     set leftover {}
2287     set done 0
2288     for {set i 0} {$i < [llength $todo]} {incr i} {
2289         set do [lindex $todo $i]
2290         if {![info exists commitrow($v,$do)] || $commitrow($v,$do) > $la} {
2291             lappend leftover $do
2292             continue
2293         }
2294         foreach np [lindex $parentlist $commitrow($v,$do)] {
2295             if {![info exists ancestor($np)]} {
2296                 set ancestor($np) 1
2297                 lappend todo $np
2298                 if {$np eq $a} {
2299                     set done 1
2300                 }
2301             }
2302         }
2303         if {$done} {
2304             set anc_todo [concat $leftover [lrange $todo [expr {$i+1}] end]]
2305             return
2306         }
2307     }
2308     set ancestor($a) 0
2309     set anc_todo $leftover
2310 }
2311
2312 proc askrelhighlight {row id} {
2313     global descendent highlight_related iddrawn mainfont rhighlights
2314     global selectedline ancestor
2315
2316     if {![info exists selectedline]} return
2317     set isbold 0
2318     if {$highlight_related eq "Descendent" ||
2319         $highlight_related eq "Not descendent"} {
2320         if {![info exists descendent($id)]} {
2321             is_descendent $id
2322         }
2323         if {$descendent($id) == ($highlight_related eq "Descendent")} {
2324             set isbold 1
2325         }
2326     } elseif {$highlight_related eq "Ancestor" ||
2327               $highlight_related eq "Not ancestor"} {
2328         if {![info exists ancestor($id)]} {
2329             is_ancestor $id
2330         }
2331         if {$ancestor($id) == ($highlight_related eq "Ancestor")} {
2332             set isbold 1
2333         }
2334     }
2335     if {[info exists iddrawn($id)]} {
2336         if {$isbold && ![ishighlighted $row]} {
2337             bolden $row [concat $mainfont bold]
2338         }
2339     }
2340     set rhighlights($row) $isbold
2341 }
2342
2343 proc next_hlcont {} {
2344     global fhl_row fhl_dirn displayorder numcommits
2345     global vhighlights fhighlights nhighlights rhighlights
2346     global hlview filehighlight findstring highlight_related
2347
2348     if {![info exists fhl_dirn] || $fhl_dirn == 0} return
2349     set row $fhl_row
2350     while {1} {
2351         if {$row < 0 || $row >= $numcommits} {
2352             bell
2353             set fhl_dirn 0
2354             return
2355         }
2356         set id [lindex $displayorder $row]
2357         if {[info exists hlview]} {
2358             if {![info exists vhighlights($row)]} {
2359                 askvhighlight $row $id
2360             }
2361             if {$vhighlights($row) > 0} break
2362         }
2363         if {$findstring ne {}} {
2364             if {![info exists nhighlights($row)]} {
2365                 askfindhighlight $row $id
2366             }
2367             if {$nhighlights($row) > 0} break
2368         }
2369         if {$highlight_related ne "None"} {
2370             if {![info exists rhighlights($row)]} {
2371                 askrelhighlight $row $id
2372             }
2373             if {$rhighlights($row) > 0} break
2374         }
2375         if {[info exists filehighlight]} {
2376             if {![info exists fhighlights($row)]} {
2377                 # ask for a few more while we're at it...
2378                 set r $row
2379                 for {set n 0} {$n < 100} {incr n} {
2380                     if {![info exists fhighlights($r)]} {
2381                         askfilehighlight $r [lindex $displayorder $r]
2382                     }
2383                     incr r $fhl_dirn
2384                     if {$r < 0 || $r >= $numcommits} break
2385                 }
2386                 flushhighlights
2387             }
2388             if {$fhighlights($row) < 0} {
2389                 set fhl_row $row
2390                 return
2391             }
2392             if {$fhighlights($row) > 0} break
2393         }
2394         incr row $fhl_dirn
2395     }
2396     set fhl_dirn 0
2397     selectline $row 1
2398 }
2399
2400 proc next_highlight {dirn} {
2401     global selectedline fhl_row fhl_dirn
2402     global hlview filehighlight findstring highlight_related
2403
2404     if {![info exists selectedline]} return
2405     if {!([info exists hlview] || $findstring ne {} ||
2406           $highlight_related ne "None" || [info exists filehighlight])} return
2407     set fhl_row [expr {$selectedline + $dirn}]
2408     set fhl_dirn $dirn
2409     next_hlcont
2410 }
2411
2412 proc cancel_next_highlight {} {
2413     global fhl_dirn
2414
2415     set fhl_dirn 0
2416 }
2417
2418 # Graph layout functions
2419
2420 proc shortids {ids} {
2421     set res {}
2422     foreach id $ids {
2423         if {[llength $id] > 1} {
2424             lappend res [shortids $id]
2425         } elseif {[regexp {^[0-9a-f]{40}$} $id]} {
2426             lappend res [string range $id 0 7]
2427         } else {
2428             lappend res $id
2429         }
2430     }
2431     return $res
2432 }
2433
2434 proc incrange {l x o} {
2435     set n [llength $l]
2436     while {$x < $n} {
2437         set e [lindex $l $x]
2438         if {$e ne {}} {
2439             lset l $x [expr {$e + $o}]
2440         }
2441         incr x
2442     }
2443     return $l
2444 }
2445
2446 proc ntimes {n o} {
2447     set ret {}
2448     for {} {$n > 0} {incr n -1} {
2449         lappend ret $o
2450     }
2451     return $ret
2452 }
2453
2454 proc usedinrange {id l1 l2} {
2455     global children commitrow curview
2456
2457     if {[info exists commitrow($curview,$id)]} {
2458         set r $commitrow($curview,$id)
2459         if {$l1 <= $r && $r <= $l2} {
2460             return [expr {$r - $l1 + 1}]
2461         }
2462     }
2463     set kids $children($curview,$id)
2464     foreach c $kids {
2465         set r $commitrow($curview,$c)
2466         if {$l1 <= $r && $r <= $l2} {
2467             return [expr {$r - $l1 + 1}]
2468         }
2469     }
2470     return 0
2471 }
2472
2473 proc sanity {row {full 0}} {
2474     global rowidlist rowoffsets
2475
2476     set col -1
2477     set ids [lindex $rowidlist $row]
2478     foreach id $ids {
2479         incr col
2480         if {$id eq {}} continue
2481         if {$col < [llength $ids] - 1 &&
2482             [lsearch -exact -start [expr {$col+1}] $ids $id] >= 0} {
2483             puts "oops: [shortids $id] repeated in row $row col $col: {[shortids [lindex $rowidlist $row]]}"
2484         }
2485         set o [lindex $rowoffsets $row $col]
2486         set y $row
2487         set x $col
2488         while {$o ne {}} {
2489             incr y -1
2490             incr x $o
2491             if {[lindex $rowidlist $y $x] != $id} {
2492                 puts "oops: rowoffsets wrong at row [expr {$y+1}] col [expr {$x-$o}]"
2493                 puts "  id=[shortids $id] check started at row $row"
2494                 for {set i $row} {$i >= $y} {incr i -1} {
2495                     puts "  row $i ids={[shortids [lindex $rowidlist $i]]} offs={[lindex $rowoffsets $i]}"
2496                 }
2497                 break
2498             }
2499             if {!$full} break
2500             set o [lindex $rowoffsets $y $x]
2501         }
2502     }
2503 }
2504
2505 proc makeuparrow {oid x y z} {
2506     global rowidlist rowoffsets uparrowlen idrowranges displayorder
2507
2508     for {set i 1} {$i < $uparrowlen && $y > 1} {incr i} {
2509         incr y -1
2510         incr x $z
2511         set off0 [lindex $rowoffsets $y]
2512         for {set x0 $x} {1} {incr x0} {
2513             if {$x0 >= [llength $off0]} {
2514                 set x0 [llength [lindex $rowoffsets [expr {$y-1}]]]
2515                 break
2516             }
2517             set z [lindex $off0 $x0]
2518             if {$z ne {}} {
2519                 incr x0 $z
2520                 break
2521             }
2522         }
2523         set z [expr {$x0 - $x}]
2524         lset rowidlist $y [linsert [lindex $rowidlist $y] $x $oid]
2525         lset rowoffsets $y [linsert [lindex $rowoffsets $y] $x $z]
2526     }
2527     set tmp [lreplace [lindex $rowoffsets $y] $x $x {}]
2528     lset rowoffsets $y [incrange $tmp [expr {$x+1}] -1]
2529     lappend idrowranges($oid) [lindex $displayorder $y]
2530 }
2531
2532 proc initlayout {} {
2533     global rowidlist rowoffsets displayorder commitlisted
2534     global rowlaidout rowoptim
2535     global idinlist rowchk rowrangelist idrowranges
2536     global numcommits canvxmax canv
2537     global nextcolor
2538     global parentlist
2539     global colormap rowtextx
2540     global selectfirst
2541
2542     set numcommits 0
2543     set displayorder {}
2544     set commitlisted {}
2545     set parentlist {}
2546     set rowrangelist {}
2547     set nextcolor 0
2548     set rowidlist {{}}
2549     set rowoffsets {{}}
2550     catch {unset idinlist}
2551     catch {unset rowchk}
2552     set rowlaidout 0
2553     set rowoptim 0
2554     set canvxmax [$canv cget -width]
2555     catch {unset colormap}
2556     catch {unset rowtextx}
2557     catch {unset idrowranges}
2558     set selectfirst 1
2559 }
2560
2561 proc setcanvscroll {} {
2562     global canv canv2 canv3 numcommits linespc canvxmax canvy0
2563
2564     set ymax [expr {$canvy0 + ($numcommits - 0.5) * $linespc + 2}]
2565     $canv conf -scrollregion [list 0 0 $canvxmax $ymax]
2566     $canv2 conf -scrollregion [list 0 0 0 $ymax]
2567     $canv3 conf -scrollregion [list 0 0 0 $ymax]
2568 }
2569
2570 proc visiblerows {} {
2571     global canv numcommits linespc
2572
2573     set ymax [lindex [$canv cget -scrollregion] 3]
2574     if {$ymax eq {} || $ymax == 0} return
2575     set f [$canv yview]
2576     set y0 [expr {int([lindex $f 0] * $ymax)}]
2577     set r0 [expr {int(($y0 - 3) / $linespc) - 1}]
2578     if {$r0 < 0} {
2579         set r0 0
2580     }
2581     set y1 [expr {int([lindex $f 1] * $ymax)}]
2582     set r1 [expr {int(($y1 - 3) / $linespc) + 1}]
2583     if {$r1 >= $numcommits} {
2584         set r1 [expr {$numcommits - 1}]
2585     }
2586     return [list $r0 $r1]
2587 }
2588
2589 proc layoutmore {tmax allread} {
2590     global rowlaidout rowoptim commitidx numcommits optim_delay
2591     global uparrowlen curview rowidlist idinlist
2592
2593     set showlast 0
2594     set showdelay $optim_delay
2595     set optdelay [expr {$uparrowlen + 1}]
2596     while {1} {
2597         if {$rowoptim - $showdelay > $numcommits} {
2598             showstuff [expr {$rowoptim - $showdelay}] $showlast
2599         } elseif {$rowlaidout - $optdelay > $rowoptim} {
2600             set nr [expr {$rowlaidout - $optdelay - $rowoptim}]
2601             if {$nr > 100} {
2602                 set nr 100
2603             }
2604             optimize_rows $rowoptim 0 [expr {$rowoptim + $nr}]
2605             incr rowoptim $nr
2606         } elseif {$commitidx($curview) > $rowlaidout} {
2607             set nr [expr {$commitidx($curview) - $rowlaidout}]
2608             # may need to increase this threshold if uparrowlen or
2609             # mingaplen are increased...
2610             if {$nr > 150} {
2611                 set nr 150
2612             }
2613             set row $rowlaidout
2614             set rowlaidout [layoutrows $row [expr {$row + $nr}] $allread]
2615             if {$rowlaidout == $row} {
2616                 return 0
2617             }
2618         } elseif {$allread} {
2619             set optdelay 0
2620             set nrows $commitidx($curview)
2621             if {[lindex $rowidlist $nrows] ne {} ||
2622                 [array names idinlist] ne {}} {
2623                 layouttail
2624                 set rowlaidout $commitidx($curview)
2625             } elseif {$rowoptim == $nrows} {
2626                 set showdelay 0
2627                 set showlast 1
2628                 if {$numcommits == $nrows} {
2629                     return 0
2630                 }
2631             }
2632         } else {
2633             return 0
2634         }
2635         if {$tmax ne {} && [clock clicks -milliseconds] >= $tmax} {
2636             return 1
2637         }
2638     }
2639 }
2640
2641 proc showstuff {canshow last} {
2642     global numcommits commitrow pending_select selectedline curview
2643     global lookingforhead mainheadid displayorder nullid selectfirst
2644     global lastscrollset
2645
2646     if {$numcommits == 0} {
2647         global phase
2648         set phase "incrdraw"
2649         allcanvs delete all
2650     }
2651     set r0 $numcommits
2652     set prev $numcommits
2653     set numcommits $canshow
2654     set t [clock clicks -milliseconds]
2655     if {$prev < 100 || $last || $t - $lastscrollset > 500} {
2656         set lastscrollset $t
2657         setcanvscroll
2658     }
2659     set rows [visiblerows]
2660     set r1 [lindex $rows 1]
2661     if {$r1 >= $canshow} {
2662         set r1 [expr {$canshow - 1}]
2663     }
2664     if {$r0 <= $r1} {
2665         drawcommits $r0 $r1
2666     }
2667     if {[info exists pending_select] &&
2668         [info exists commitrow($curview,$pending_select)] &&
2669         $commitrow($curview,$pending_select) < $numcommits} {
2670         selectline $commitrow($curview,$pending_select) 1
2671     }
2672     if {$selectfirst} {
2673         if {[info exists selectedline] || [info exists pending_select]} {
2674             set selectfirst 0
2675         } else {
2676             set l [expr {[lindex $displayorder 0] eq $nullid}]
2677             selectline $l 1
2678             set selectfirst 0
2679         }
2680     }
2681     if {$lookingforhead && [info exists commitrow($curview,$mainheadid)]
2682         && ($last || $commitrow($curview,$mainheadid) < $numcommits - 1)} {
2683         set lookingforhead 0
2684         dodiffindex
2685     }
2686 }
2687
2688 proc doshowlocalchanges {} {
2689     global lookingforhead curview mainheadid phase commitrow
2690
2691     if {[info exists commitrow($curview,$mainheadid)] &&
2692         ($phase eq {} || $commitrow($curview,$mainheadid) < $numcommits - 1)} {
2693         dodiffindex
2694     } elseif {$phase ne {}} {
2695         set lookingforhead 1
2696     }
2697 }
2698
2699 proc dohidelocalchanges {} {
2700     global lookingforhead localrow lserial
2701
2702     set lookingforhead 0
2703     if {$localrow >= 0} {
2704         removerow $localrow
2705         set localrow -1
2706     }
2707     incr lserial
2708 }
2709
2710 # spawn off a process to do git diff-index HEAD
2711 proc dodiffindex {} {
2712     global localrow lserial
2713
2714     incr lserial
2715     set localrow -1
2716     set fd [open "|git diff-index HEAD" r]
2717     fconfigure $fd -blocking 0
2718     filerun $fd [list readdiffindex $fd $lserial]
2719 }
2720
2721 proc readdiffindex {fd serial} {
2722     global localrow commitrow mainheadid nullid curview
2723     global commitinfo commitdata lserial
2724
2725     if {[gets $fd line] < 0} {
2726         if {[eof $fd]} {
2727             close $fd
2728             return 0
2729         }
2730         return 1
2731     }
2732     # we only need to see one line and we don't really care what it says...
2733     close $fd
2734
2735     if {$serial == $lserial && $localrow == -1} {
2736         # add the line for the local diff to the graph
2737         set localrow $commitrow($curview,$mainheadid)
2738         set hl "Local uncommitted changes"
2739         set commitinfo($nullid) [list  $hl {} {} {} {} "    $hl\n"]
2740         set commitdata($nullid) "\n    $hl\n"
2741         insertrow $localrow $nullid
2742     }
2743     return 0
2744 }
2745
2746 proc layoutrows {row endrow last} {
2747     global rowidlist rowoffsets displayorder
2748     global uparrowlen downarrowlen maxwidth mingaplen
2749     global children parentlist
2750     global idrowranges
2751     global commitidx curview
2752     global idinlist rowchk rowrangelist
2753
2754     set idlist [lindex $rowidlist $row]
2755     set offs [lindex $rowoffsets $row]
2756     while {$row < $endrow} {
2757         set id [lindex $displayorder $row]
2758         set oldolds {}
2759         set newolds {}
2760         foreach p [lindex $parentlist $row] {
2761             if {![info exists idinlist($p)]} {
2762                 lappend newolds $p
2763             } elseif {!$idinlist($p)} {
2764                 lappend oldolds $p
2765             }
2766         }
2767         set nev [expr {[llength $idlist] + [llength $newolds]
2768                        + [llength $oldolds] - $maxwidth + 1}]
2769         if {$nev > 0} {
2770             if {!$last &&
2771                 $row + $uparrowlen + $mingaplen >= $commitidx($curview)} break
2772             for {set x [llength $idlist]} {[incr x -1] >= 0} {} {
2773                 set i [lindex $idlist $x]
2774                 if {![info exists rowchk($i)] || $row >= $rowchk($i)} {
2775                     set r [usedinrange $i [expr {$row - $downarrowlen}] \
2776                                [expr {$row + $uparrowlen + $mingaplen}]]
2777                     if {$r == 0} {
2778                         set idlist [lreplace $idlist $x $x]
2779                         set offs [lreplace $offs $x $x]
2780                         set offs [incrange $offs $x 1]
2781                         set idinlist($i) 0
2782                         set rm1 [expr {$row - 1}]
2783                         lappend idrowranges($i) [lindex $displayorder $rm1]
2784                         if {[incr nev -1] <= 0} break
2785                         continue
2786                     }
2787                     set rowchk($id) [expr {$row + $r}]
2788                 }
2789             }
2790             lset rowidlist $row $idlist
2791             lset rowoffsets $row $offs
2792         }
2793         set col [lsearch -exact $idlist $id]
2794         if {$col < 0} {
2795             set col [llength $idlist]
2796             lappend idlist $id
2797             lset rowidlist $row $idlist
2798             set z {}
2799             if {$children($curview,$id) ne {}} {
2800                 set z [expr {[llength [lindex $rowidlist [expr {$row-1}]]] - $col}]
2801                 unset idinlist($id)
2802             }
2803             lappend offs $z
2804             lset rowoffsets $row $offs
2805             if {$z ne {}} {
2806                 makeuparrow $id $col $row $z
2807             }
2808         } else {
2809             unset idinlist($id)
2810         }
2811         set ranges {}
2812         if {[info exists idrowranges($id)]} {
2813             set ranges $idrowranges($id)
2814             lappend ranges $id
2815             unset idrowranges($id)
2816         }
2817         lappend rowrangelist $ranges
2818         incr row
2819         set offs [ntimes [llength $idlist] 0]
2820         set l [llength $newolds]
2821         set idlist [eval lreplace \$idlist $col $col $newolds]
2822         set o 0
2823         if {$l != 1} {
2824             set offs [lrange $offs 0 [expr {$col - 1}]]
2825             foreach x $newolds {
2826                 lappend offs {}
2827                 incr o -1
2828             }
2829             incr o
2830             set tmp [expr {[llength $idlist] - [llength $offs]}]
2831             if {$tmp > 0} {
2832                 set offs [concat $offs [ntimes $tmp $o]]
2833             }
2834         } else {
2835             lset offs $col {}
2836         }
2837         foreach i $newolds {
2838             set idinlist($i) 1
2839             set idrowranges($i) $id
2840         }
2841         incr col $l
2842         foreach oid $oldolds {
2843             set idinlist($oid) 1
2844             set idlist [linsert $idlist $col $oid]
2845             set offs [linsert $offs $col $o]
2846             makeuparrow $oid $col $row $o
2847             incr col
2848         }
2849         lappend rowidlist $idlist
2850         lappend rowoffsets $offs
2851     }
2852     return $row
2853 }
2854
2855 proc addextraid {id row} {
2856     global displayorder commitrow commitinfo
2857     global commitidx commitlisted
2858     global parentlist children curview
2859
2860     incr commitidx($curview)
2861     lappend displayorder $id
2862     lappend commitlisted 0
2863     lappend parentlist {}
2864     set commitrow($curview,$id) $row
2865     readcommit $id
2866     if {![info exists commitinfo($id)]} {
2867         set commitinfo($id) {"No commit information available"}
2868     }
2869     if {![info exists children($curview,$id)]} {
2870         set children($curview,$id) {}
2871     }
2872 }
2873
2874 proc layouttail {} {
2875     global rowidlist rowoffsets idinlist commitidx curview
2876     global idrowranges rowrangelist
2877
2878     set row $commitidx($curview)
2879     set idlist [lindex $rowidlist $row]
2880     while {$idlist ne {}} {
2881         set col [expr {[llength $idlist] - 1}]
2882         set id [lindex $idlist $col]
2883         addextraid $id $row
2884         unset idinlist($id)
2885         lappend idrowranges($id) $row
2886         lappend rowrangelist $idrowranges($id)
2887         unset idrowranges($id)
2888         incr row
2889         set offs [ntimes $col 0]
2890         set idlist [lreplace $idlist $col $col]
2891         lappend rowidlist $idlist
2892         lappend rowoffsets $offs
2893     }
2894
2895     foreach id [array names idinlist] {
2896         unset idinlist($id)
2897         addextraid $id $row
2898         lset rowidlist $row [list $id]
2899         lset rowoffsets $row 0
2900         makeuparrow $id 0 $row 0
2901         lappend idrowranges($id) $row
2902         lappend rowrangelist $idrowranges($id)
2903         unset idrowranges($id)
2904         incr row
2905         lappend rowidlist {}
2906         lappend rowoffsets {}
2907     }
2908 }
2909
2910 proc insert_pad {row col npad} {
2911     global rowidlist rowoffsets
2912
2913     set pad [ntimes $npad {}]
2914     lset rowidlist $row [eval linsert [list [lindex $rowidlist $row]] $col $pad]
2915     set tmp [eval linsert [list [lindex $rowoffsets $row]] $col $pad]
2916     lset rowoffsets $row [incrange $tmp [expr {$col + $npad}] [expr {-$npad}]]
2917 }
2918
2919 proc optimize_rows {row col endrow} {
2920     global rowidlist rowoffsets displayorder
2921
2922     for {} {$row < $endrow} {incr row} {
2923         set idlist [lindex $rowidlist $row]
2924         set offs [lindex $rowoffsets $row]
2925         set haspad 0
2926         for {} {$col < [llength $offs]} {incr col} {
2927             if {[lindex $idlist $col] eq {}} {
2928                 set haspad 1
2929                 continue
2930             }
2931             set z [lindex $offs $col]
2932             if {$z eq {}} continue
2933             set isarrow 0
2934             set x0 [expr {$col + $z}]
2935             set y0 [expr {$row - 1}]
2936             set z0 [lindex $rowoffsets $y0 $x0]
2937             if {$z0 eq {}} {
2938                 set id [lindex $idlist $col]
2939                 set ranges [rowranges $id]
2940                 if {$ranges ne {} && $y0 > [lindex $ranges 0]} {
2941                     set isarrow 1
2942                 }
2943             }
2944             # Looking at lines from this row to the previous row,
2945             # make them go straight up if they end in an arrow on
2946             # the previous row; otherwise make them go straight up
2947             # or at 45 degrees.
2948             if {$z < -1 || ($z < 0 && $isarrow)} {
2949                 # Line currently goes left too much;
2950                 # insert pads in the previous row, then optimize it
2951                 set npad [expr {-1 - $z + $isarrow}]
2952                 set offs [incrange $offs $col $npad]
2953                 insert_pad $y0 $x0 $npad
2954                 if {$y0 > 0} {
2955                     optimize_rows $y0 $x0 $row
2956                 }
2957                 set z [lindex $offs $col]
2958                 set x0 [expr {$col + $z}]
2959                 set z0 [lindex $rowoffsets $y0 $x0]
2960             } elseif {$z > 1 || ($z > 0 && $isarrow)} {
2961                 # Line currently goes right too much;
2962                 # insert pads in this line and adjust the next's rowoffsets
2963                 set npad [expr {$z - 1 + $isarrow}]
2964                 set y1 [expr {$row + 1}]
2965                 set offs2 [lindex $rowoffsets $y1]
2966                 set x1 -1
2967                 foreach z $offs2 {
2968                     incr x1
2969                     if {$z eq {} || $x1 + $z < $col} continue
2970                     if {$x1 + $z > $col} {
2971                         incr npad
2972                     }
2973                     lset rowoffsets $y1 [incrange $offs2 $x1 $npad]
2974                     break
2975                 }
2976                 set pad [ntimes $npad {}]
2977                 set idlist [eval linsert \$idlist $col $pad]
2978                 set tmp [eval linsert \$offs $col $pad]
2979                 incr col $npad
2980                 set offs [incrange $tmp $col [expr {-$npad}]]
2981                 set z [lindex $offs $col]
2982                 set haspad 1
2983             }
2984             if {$z0 eq {} && !$isarrow} {
2985                 # this line links to its first child on row $row-2
2986                 set rm2 [expr {$row - 2}]
2987                 set id [lindex $displayorder $rm2]
2988                 set xc [lsearch -exact [lindex $rowidlist $rm2] $id]
2989                 if {$xc >= 0} {
2990                     set z0 [expr {$xc - $x0}]
2991                 }
2992             }
2993             # avoid lines jigging left then immediately right
2994             if {$z0 ne {} && $z < 0 && $z0 > 0} {
2995                 insert_pad $y0 $x0 1
2996                 set offs [incrange $offs $col 1]
2997                 optimize_rows $y0 [expr {$x0 + 1}] $row
2998             }
2999         }
3000         if {!$haspad} {
3001             set o {}
3002             # Find the first column that doesn't have a line going right
3003             for {set col [llength $idlist]} {[incr col -1] >= 0} {} {
3004                 set o [lindex $offs $col]
3005                 if {$o eq {}} {
3006                     # check if this is the link to the first child
3007                     set id [lindex $idlist $col]
3008                     set ranges [rowranges $id]
3009                     if {$ranges ne {} && $row == [lindex $ranges 0]} {
3010                         # it is, work out offset to child
3011                         set y0 [expr {$row - 1}]
3012                         set id [lindex $displayorder $y0]
3013                         set x0 [lsearch -exact [lindex $rowidlist $y0] $id]
3014                         if {$x0 >= 0} {
3015                             set o [expr {$x0 - $col}]
3016                         }
3017                     }
3018                 }
3019                 if {$o eq {} || $o <= 0} break
3020             }
3021             # Insert a pad at that column as long as it has a line and
3022             # isn't the last column, and adjust the next row' offsets
3023             if {$o ne {} && [incr col] < [llength $idlist]} {
3024                 set y1 [expr {$row + 1}]
3025                 set offs2 [lindex $rowoffsets $y1]
3026                 set x1 -1
3027                 foreach z $offs2 {
3028                     incr x1
3029                     if {$z eq {} || $x1 + $z < $col} continue
3030                     lset rowoffsets $y1 [incrange $offs2 $x1 1]
3031                     break
3032                 }
3033                 set idlist [linsert $idlist $col {}]
3034                 set tmp [linsert $offs $col {}]
3035                 incr col
3036                 set offs [incrange $tmp $col -1]
3037             }
3038         }
3039         lset rowidlist $row $idlist
3040         lset rowoffsets $row $offs
3041         set col 0
3042     }
3043 }
3044
3045 proc xc {row col} {
3046     global canvx0 linespc
3047     return [expr {$canvx0 + $col * $linespc}]
3048 }
3049
3050 proc yc {row} {
3051     global canvy0 linespc
3052     return [expr {$canvy0 + $row * $linespc}]
3053 }
3054
3055 proc linewidth {id} {
3056     global thickerline lthickness
3057
3058     set wid $lthickness
3059     if {[info exists thickerline] && $id eq $thickerline} {
3060         set wid [expr {2 * $lthickness}]
3061     }
3062     return $wid
3063 }
3064
3065 proc rowranges {id} {
3066     global phase idrowranges commitrow rowlaidout rowrangelist curview
3067
3068     set ranges {}
3069     if {$phase eq {} ||
3070         ([info exists commitrow($curview,$id)]
3071          && $commitrow($curview,$id) < $rowlaidout)} {
3072         set ranges [lindex $rowrangelist $commitrow($curview,$id)]
3073     } elseif {[info exists idrowranges($id)]} {
3074         set ranges $idrowranges($id)
3075     }
3076     set linenos {}
3077     foreach rid $ranges {
3078         lappend linenos $commitrow($curview,$rid)
3079     }
3080     if {$linenos ne {}} {
3081         lset linenos 0 [expr {[lindex $linenos 0] + 1}]
3082     }
3083     return $linenos
3084 }
3085
3086 # work around tk8.4 refusal to draw arrows on diagonal segments
3087 proc adjarrowhigh {coords} {
3088     global linespc
3089
3090     set x0 [lindex $coords 0]
3091     set x1 [lindex $coords 2]
3092     if {$x0 != $x1} {
3093         set y0 [lindex $coords 1]
3094         set y1 [lindex $coords 3]
3095         if {$y0 - $y1 <= 2 * $linespc && $x1 == [lindex $coords 4]} {
3096             # we have a nearby vertical segment, just trim off the diag bit
3097             set coords [lrange $coords 2 end]
3098         } else {
3099             set slope [expr {($x0 - $x1) / ($y0 - $y1)}]
3100             set xi [expr {$x0 - $slope * $linespc / 2}]
3101             set yi [expr {$y0 - $linespc / 2}]
3102             set coords [lreplace $coords 0 1 $xi $y0 $xi $yi]
3103         }
3104     }
3105     return $coords
3106 }
3107
3108 proc drawlineseg {id row endrow arrowlow} {
3109     global rowidlist displayorder iddrawn linesegs
3110     global canv colormap linespc curview maxlinelen
3111
3112     set cols [list [lsearch -exact [lindex $rowidlist $row] $id]]
3113     set le [expr {$row + 1}]
3114     set arrowhigh 1
3115     while {1} {
3116         set c [lsearch -exact [lindex $rowidlist $le] $id]
3117         if {$c < 0} {
3118             incr le -1
3119             break
3120         }
3121         lappend cols $c
3122         set x [lindex $displayorder $le]
3123         if {$x eq $id} {
3124             set arrowhigh 0
3125             break
3126         }
3127         if {[info exists iddrawn($x)] || $le == $endrow} {
3128             set c [lsearch -exact [lindex $rowidlist [expr {$le+1}]] $id]
3129             if {$c >= 0} {
3130                 lappend cols $c
3131                 set arrowhigh 0
3132             }
3133             break
3134         }
3135         incr le
3136     }
3137     if {$le <= $row} {
3138         return $row
3139     }
3140
3141     set lines {}
3142     set i 0
3143     set joinhigh 0
3144     if {[info exists linesegs($id)]} {
3145         set lines $linesegs($id)
3146         foreach li $lines {
3147             set r0 [lindex $li 0]
3148             if {$r0 > $row} {
3149                 if {$r0 == $le && [lindex $li 1] - $row <= $maxlinelen} {
3150                     set joinhigh 1
3151                 }
3152                 break
3153             }
3154             incr i
3155         }
3156     }
3157     set joinlow 0
3158     if {$i > 0} {
3159         set li [lindex $lines [expr {$i-1}]]
3160         set r1 [lindex $li 1]
3161         if {$r1 == $row && $le - [lindex $li 0] <= $maxlinelen} {
3162             set joinlow 1
3163         }
3164     }
3165
3166     set x [lindex $cols [expr {$le - $row}]]
3167     set xp [lindex $cols [expr {$le - 1 - $row}]]
3168     set dir [expr {$xp - $x}]
3169     if {$joinhigh} {
3170         set ith [lindex $lines $i 2]
3171         set coords [$canv coords $ith]
3172         set ah [$canv itemcget $ith -arrow]
3173         set arrowhigh [expr {$ah eq "first" || $ah eq "both"}]
3174         set x2 [lindex $cols [expr {$le + 1 - $row}]]
3175         if {$x2 ne {} && $x - $x2 == $dir} {
3176             set coords [lrange $coords 0 end-2]
3177         }
3178     } else {
3179         set coords [list [xc $le $x] [yc $le]]
3180     }
3181     if {$joinlow} {
3182         set itl [lindex $lines [expr {$i-1}] 2]
3183         set al [$canv itemcget $itl -arrow]
3184         set arrowlow [expr {$al eq "last" || $al eq "both"}]
3185     } elseif {$arrowlow &&
3186               [lsearch -exact [lindex $rowidlist [expr {$row-1}]] $id] >= 0} {
3187         set arrowlow 0
3188     }
3189     set arrow [lindex {none first last both} [expr {$arrowhigh + 2*$arrowlow}]]
3190     for {set y $le} {[incr y -1] > $row} {} {
3191         set x $xp
3192         set xp [lindex $cols [expr {$y - 1 - $row}]]
3193         set ndir [expr {$xp - $x}]
3194         if {$dir != $ndir || $xp < 0} {
3195             lappend coords [xc $y $x] [yc $y]
3196         }
3197         set dir $ndir
3198     }
3199     if {!$joinlow} {
3200         if {$xp < 0} {
3201             # join parent line to first child
3202             set ch [lindex $displayorder $row]
3203             set xc [lsearch -exact [lindex $rowidlist $row] $ch]
3204             if {$xc < 0} {
3205                 puts "oops: drawlineseg: child $ch not on row $row"
3206             } else {
3207                 if {$xc < $x - 1} {
3208                     lappend coords [xc $row [expr {$x-1}]] [yc $row]
3209                 } elseif {$xc > $x + 1} {
3210                     lappend coords [xc $row [expr {$x+1}]] [yc $row]
3211                 }
3212                 set x $xc
3213             }
3214             lappend coords [xc $row $x] [yc $row]
3215         } else {
3216             set xn [xc $row $xp]
3217             set yn [yc $row]
3218             # work around tk8.4 refusal to draw arrows on diagonal segments
3219             if {$arrowlow && $xn != [lindex $coords end-1]} {
3220                 if {[llength $coords] < 4 ||
3221                     [lindex $coords end-3] != [lindex $coords end-1] ||
3222                     [lindex $coords end] - $yn > 2 * $linespc} {
3223                     set xn [xc $row [expr {$xp - 0.5 * $dir}]]
3224                     set yo [yc [expr {$row + 0.5}]]
3225                     lappend coords $xn $yo $xn $yn
3226                 }
3227             } else {
3228                 lappend coords $xn $yn
3229             }
3230         }
3231         if {!$joinhigh} {
3232             if {$arrowhigh} {
3233                 set coords [adjarrowhigh $coords]
3234             }
3235             assigncolor $id
3236             set t [$canv create line $coords -width [linewidth $id] \
3237                        -fill $colormap($id) -tags lines.$id -arrow $arrow]
3238             $canv lower $t
3239             bindline $t $id
3240             set lines [linsert $lines $i [list $row $le $t]]
3241         } else {
3242             $canv coords $ith $coords
3243             if {$arrow ne $ah} {
3244                 $canv itemconf $ith -arrow $arrow
3245             }
3246             lset lines $i 0 $row
3247         }
3248     } else {
3249         set xo [lsearch -exact [lindex $rowidlist [expr {$row - 1}]] $id]
3250         set ndir [expr {$xo - $xp}]
3251         set clow [$canv coords $itl]
3252         if {$dir == $ndir} {
3253             set clow [lrange $clow 2 end]
3254         }
3255         set coords [concat $coords $clow]
3256         if {!$joinhigh} {
3257             lset lines [expr {$i-1}] 1 $le
3258             if {$arrowhigh} {
3259                 set coords [adjarrowhigh $coords]
3260             }
3261         } else {
3262             # coalesce two pieces
3263             $canv delete $ith
3264             set b [lindex $lines [expr {$i-1}] 0]
3265             set e [lindex $lines $i 1]
3266             set lines [lreplace $lines [expr {$i-1}] $i [list $b $e $itl]]
3267         }
3268         $canv coords $itl $coords
3269         if {$arrow ne $al} {
3270             $canv itemconf $itl -arrow $arrow
3271         }
3272     }
3273
3274     set linesegs($id) $lines
3275     return $le
3276 }
3277
3278 proc drawparentlinks {id row} {
3279     global rowidlist canv colormap curview parentlist
3280     global idpos
3281
3282     set rowids [lindex $rowidlist $row]
3283     set col [lsearch -exact $rowids $id]
3284     if {$col < 0} return
3285     set olds [lindex $parentlist $row]
3286     set row2 [expr {$row + 1}]
3287     set x [xc $row $col]
3288     set y [yc $row]
3289     set y2 [yc $row2]
3290     set ids [lindex $rowidlist $row2]
3291     # rmx = right-most X coord used
3292     set rmx 0
3293     foreach p $olds {
3294         set i [lsearch -exact $ids $p]
3295         if {$i < 0} {
3296             puts "oops, parent $p of $id not in list"
3297             continue
3298         }
3299         set x2 [xc $row2 $i]
3300         if {$x2 > $rmx} {
3301             set rmx $x2
3302         }
3303         if {[lsearch -exact $rowids $p] < 0} {
3304             # drawlineseg will do this one for us
3305             continue
3306         }
3307         assigncolor $p
3308         # should handle duplicated parents here...
3309         set coords [list $x $y]
3310         if {$i < $col - 1} {
3311             lappend coords [xc $row [expr {$i + 1}]] $y
3312         } elseif {$i > $col + 1} {
3313             lappend coords [xc $row [expr {$i - 1}]] $y
3314         }
3315         lappend coords $x2 $y2
3316         set t [$canv create line $coords -width [linewidth $p] \
3317                    -fill $colormap($p) -tags lines.$p]
3318         $canv lower $t
3319         bindline $t $p
3320     }
3321     if {$rmx > [lindex $idpos($id) 1]} {
3322         lset idpos($id) 1 $rmx
3323         redrawtags $id
3324     }
3325 }
3326
3327 proc drawlines {id} {
3328     global canv
3329
3330     $canv itemconf lines.$id -width [linewidth $id]
3331 }
3332
3333 proc drawcmittext {id row col} {
3334     global linespc canv canv2 canv3 canvy0 fgcolor
3335     global commitlisted commitinfo rowidlist parentlist
3336     global rowtextx idpos idtags idheads idotherrefs
3337     global linehtag linentag linedtag markingmatches
3338     global mainfont canvxmax boldrows boldnamerows fgcolor nullid
3339
3340     if {$id eq $nullid} {
3341         set ofill red
3342     } else {
3343         set ofill [expr {[lindex $commitlisted $row]? "blue": "white"}]
3344     }
3345     set x [xc $row $col]
3346     set y [yc $row]
3347     set orad [expr {$linespc / 3}]
3348     set t [$canv create oval [expr {$x - $orad}] [expr {$y - $orad}] \
3349                [expr {$x + $orad - 1}] [expr {$y + $orad - 1}] \
3350                -fill $ofill -outline $fgcolor -width 1 -tags circle]
3351     $canv raise $t
3352     $canv bind $t <1> {selcanvline {} %x %y}
3353     set rmx [llength [lindex $rowidlist $row]]
3354     set olds [lindex $parentlist $row]
3355     if {$olds ne {}} {
3356         set nextids [lindex $rowidlist [expr {$row + 1}]]
3357         foreach p $olds {
3358             set i [lsearch -exact $nextids $p]
3359             if {$i > $rmx} {
3360                 set rmx $i
3361             }
3362         }
3363     }
3364     set xt [xc $row $rmx]
3365     set rowtextx($row) $xt
3366     set idpos($id) [list $x $xt $y]
3367     if {[info exists idtags($id)] || [info exists idheads($id)]
3368         || [info exists idotherrefs($id)]} {
3369         set xt [drawtags $id $x $xt $y]
3370     }
3371     set headline [lindex $commitinfo($id) 0]
3372     set name [lindex $commitinfo($id) 1]
3373     set date [lindex $commitinfo($id) 2]
3374     set date [formatdate $date]
3375     set font $mainfont
3376     set nfont $mainfont
3377     set isbold [ishighlighted $row]
3378     if {$isbold > 0} {
3379         lappend boldrows $row
3380         lappend font bold
3381         if {$isbold > 1} {
3382             lappend boldnamerows $row
3383             lappend nfont bold
3384         }
3385     }
3386     set linehtag($row) [$canv create text $xt $y -anchor w -fill $fgcolor \
3387                             -text $headline -font $font -tags text]
3388     $canv bind $linehtag($row) <Button-3> "rowmenu %X %Y $id"
3389     set linentag($row) [$canv2 create text 3 $y -anchor w -fill $fgcolor \
3390                             -text $name -font $nfont -tags text]
3391     set linedtag($row) [$canv3 create text 3 $y -anchor w -fill $fgcolor \
3392                             -text $date -font $mainfont -tags text]
3393     set xr [expr {$xt + [font measure $mainfont $headline]}]
3394     if {$markingmatches} {
3395         markrowmatches $row $headline $name
3396     }
3397     if {$xr > $canvxmax} {
3398         set canvxmax $xr
3399         setcanvscroll
3400     }
3401 }
3402
3403 proc drawcmitrow {row} {
3404     global displayorder rowidlist
3405     global iddrawn
3406     global commitinfo parentlist numcommits
3407     global filehighlight fhighlights findstring nhighlights
3408     global hlview vhighlights
3409     global highlight_related rhighlights
3410
3411     if {$row >= $numcommits} return
3412
3413     set id [lindex $displayorder $row]
3414     if {[info exists hlview] && ![info exists vhighlights($row)]} {
3415         askvhighlight $row $id
3416     }
3417     if {[info exists filehighlight] && ![info exists fhighlights($row)]} {
3418         askfilehighlight $row $id
3419     }
3420     if {$findstring ne {} && ![info exists nhighlights($row)]} {
3421         askfindhighlight $row $id
3422     }
3423     if {$highlight_related ne "None" && ![info exists rhighlights($row)]} {
3424         askrelhighlight $row $id
3425     }
3426     if {[info exists iddrawn($id)]} return
3427     set col [lsearch -exact [lindex $rowidlist $row] $id]
3428     if {$col < 0} {
3429         puts "oops, row $row id $id not in list"
3430         return
3431     }
3432     if {![info exists commitinfo($id)]} {
3433         getcommit $id
3434     }
3435     assigncolor $id
3436     drawcmittext $id $row $col
3437     set iddrawn($id) 1
3438 }
3439
3440 proc drawcommits {row {endrow {}}} {
3441     global numcommits iddrawn displayorder curview
3442     global parentlist rowidlist
3443
3444     if {$row < 0} {
3445         set row 0
3446     }
3447     if {$endrow eq {}} {
3448         set endrow $row
3449     }
3450     if {$endrow >= $numcommits} {
3451         set endrow [expr {$numcommits - 1}]
3452     }
3453
3454     # make the lines join to already-drawn rows either side
3455     set r [expr {$row - 1}]
3456     if {$r < 0 || ![info exists iddrawn([lindex $displayorder $r])]} {
3457         set r $row
3458     }
3459     set er [expr {$endrow + 1}]
3460     if {$er >= $numcommits ||
3461         ![info exists iddrawn([lindex $displayorder $er])]} {
3462         set er $endrow
3463     }
3464     for {} {$r <= $er} {incr r} {
3465         set id [lindex $displayorder $r]
3466         set wasdrawn [info exists iddrawn($id)]
3467         drawcmitrow $r
3468         if {$r == $er} break
3469         set nextid [lindex $displayorder [expr {$r + 1}]]
3470         if {$wasdrawn && [info exists iddrawn($nextid)]} {
3471             catch {unset prevlines}
3472             continue
3473         }
3474         drawparentlinks $id $r
3475
3476         if {[info exists lineends($r)]} {
3477             foreach lid $lineends($r) {
3478                 unset prevlines($lid)
3479             }
3480         }
3481         set rowids [lindex $rowidlist $r]
3482         foreach lid $rowids {
3483             if {$lid eq {}} continue
3484             if {$lid eq $id} {
3485                 # see if this is the first child of any of its parents
3486                 foreach p [lindex $parentlist $r] {
3487                     if {[lsearch -exact $rowids $p] < 0} {
3488                         # make this line extend up to the child
3489                         set le [drawlineseg $p $r $er 0]
3490                         lappend lineends($le) $p
3491                         set prevlines($p) 1
3492                     }
3493                 }
3494             } elseif {![info exists prevlines($lid)]} {
3495                 set le [drawlineseg $lid $r $er 1]
3496                 lappend lineends($le) $lid
3497                 set prevlines($lid) 1
3498             }
3499         }
3500     }
3501 }
3502
3503 proc drawfrac {f0 f1} {
3504     global canv linespc
3505
3506     set ymax [lindex [$canv cget -scrollregion] 3]
3507     if {$ymax eq {} || $ymax == 0} return
3508     set y0 [expr {int($f0 * $ymax)}]
3509     set row [expr {int(($y0 - 3) / $linespc) - 1}]
3510     set y1 [expr {int($f1 * $ymax)}]
3511     set endrow [expr {int(($y1 - 3) / $linespc) + 1}]
3512     drawcommits $row $endrow
3513 }
3514
3515 proc drawvisible {} {
3516     global canv
3517     eval drawfrac [$canv yview]
3518 }
3519
3520 proc clear_display {} {
3521     global iddrawn linesegs
3522     global vhighlights fhighlights nhighlights rhighlights
3523
3524     allcanvs delete all
3525     catch {unset iddrawn}
3526     catch {unset linesegs}
3527     catch {unset vhighlights}
3528     catch {unset fhighlights}
3529     catch {unset nhighlights}
3530     catch {unset rhighlights}
3531 }
3532
3533 proc findcrossings {id} {
3534     global rowidlist parentlist numcommits rowoffsets displayorder
3535
3536     set cross {}
3537     set ccross {}
3538     foreach {s e} [rowranges $id] {
3539         if {$e >= $numcommits} {
3540             set e [expr {$numcommits - 1}]
3541         }
3542         if {$e <= $s} continue
3543         set x [lsearch -exact [lindex $rowidlist $e] $id]
3544         if {$x < 0} {
3545             puts "findcrossings: oops, no [shortids $id] in row $e"
3546             continue
3547         }
3548         for {set row $e} {[incr row -1] >= $s} {} {
3549             set olds [lindex $parentlist $row]
3550             set kid [lindex $displayorder $row]
3551             set kidx [lsearch -exact [lindex $rowidlist $row] $kid]
3552             if {$kidx < 0} continue
3553             set nextrow [lindex $rowidlist [expr {$row + 1}]]
3554             foreach p $olds {
3555                 set px [lsearch -exact $nextrow $p]
3556                 if {$px < 0} continue
3557                 if {($kidx < $x && $x < $px) || ($px < $x && $x < $kidx)} {
3558                     if {[lsearch -exact $ccross $p] >= 0} continue
3559                     if {$x == $px + ($kidx < $px? -1: 1)} {
3560                         lappend ccross $p
3561                     } elseif {[lsearch -exact $cross $p] < 0} {
3562                         lappend cross $p
3563                     }
3564                 }
3565             }
3566             set inc [lindex $rowoffsets $row $x]
3567             if {$inc eq {}} break
3568             incr x $inc
3569         }
3570     }
3571     return [concat $ccross {{}} $cross]
3572 }
3573
3574 proc assigncolor {id} {
3575     global colormap colors nextcolor
3576     global commitrow parentlist children children curview
3577
3578     if {[info exists colormap($id)]} return
3579     set ncolors [llength $colors]
3580     if {[info exists children($curview,$id)]} {
3581         set kids $children($curview,$id)
3582     } else {
3583         set kids {}
3584     }
3585     if {[llength $kids] == 1} {
3586         set child [lindex $kids 0]
3587         if {[info exists colormap($child)]
3588             && [llength [lindex $parentlist $commitrow($curview,$child)]] == 1} {
3589             set colormap($id) $colormap($child)
3590             return
3591         }
3592     }
3593     set badcolors {}
3594     set origbad {}
3595     foreach x [findcrossings $id] {
3596         if {$x eq {}} {
3597             # delimiter between corner crossings and other crossings
3598             if {[llength $badcolors] >= $ncolors - 1} break
3599             set origbad $badcolors
3600         }
3601         if {[info exists colormap($x)]
3602             && [lsearch -exact $badcolors $colormap($x)] < 0} {
3603             lappend badcolors $colormap($x)
3604         }
3605     }
3606     if {[llength $badcolors] >= $ncolors} {
3607         set badcolors $origbad
3608     }
3609     set origbad $badcolors
3610     if {[llength $badcolors] < $ncolors - 1} {
3611         foreach child $kids {
3612             if {[info exists colormap($child)]
3613                 && [lsearch -exact $badcolors $colormap($child)] < 0} {
3614                 lappend badcolors $colormap($child)
3615             }
3616             foreach p [lindex $parentlist $commitrow($curview,$child)] {
3617                 if {[info exists colormap($p)]
3618                     && [lsearch -exact $badcolors $colormap($p)] < 0} {
3619                     lappend badcolors $colormap($p)
3620                 }
3621             }
3622         }
3623         if {[llength $badcolors] >= $ncolors} {
3624             set badcolors $origbad
3625         }
3626     }
3627     for {set i 0} {$i <= $ncolors} {incr i} {
3628         set c [lindex $colors $nextcolor]
3629         if {[incr nextcolor] >= $ncolors} {
3630             set nextcolor 0
3631         }
3632         if {[lsearch -exact $badcolors $c]} break
3633     }
3634     set colormap($id) $c
3635 }
3636
3637 proc bindline {t id} {
3638     global canv
3639
3640     $canv bind $t <Enter> "lineenter %x %y $id"
3641     $canv bind $t <Motion> "linemotion %x %y $id"
3642     $canv bind $t <Leave> "lineleave $id"
3643     $canv bind $t <Button-1> "lineclick %x %y $id 1"
3644 }
3645
3646 proc drawtags {id x xt y1} {
3647     global idtags idheads idotherrefs mainhead
3648     global linespc lthickness
3649     global canv mainfont commitrow rowtextx curview fgcolor bgcolor
3650
3651     set marks {}
3652     set ntags 0
3653     set nheads 0
3654     if {[info exists idtags($id)]} {
3655         set marks $idtags($id)
3656         set ntags [llength $marks]
3657     }
3658     if {[info exists idheads($id)]} {
3659         set marks [concat $marks $idheads($id)]
3660         set nheads [llength $idheads($id)]
3661     }
3662     if {[info exists idotherrefs($id)]} {
3663         set marks [concat $marks $idotherrefs($id)]
3664     }
3665     if {$marks eq {}} {
3666         return $xt
3667     }
3668
3669     set delta [expr {int(0.5 * ($linespc - $lthickness))}]
3670     set yt [expr {$y1 - 0.5 * $linespc}]
3671     set yb [expr {$yt + $linespc - 1}]
3672     set xvals {}
3673     set wvals {}
3674     set i -1
3675     foreach tag $marks {
3676         incr i
3677         if {$i >= $ntags && $i < $ntags + $nheads && $tag eq $mainhead} {
3678             set wid [font measure [concat $mainfont bold] $tag]
3679         } else {
3680             set wid [font measure $mainfont $tag]
3681         }
3682         lappend xvals $xt
3683         lappend wvals $wid
3684         set xt [expr {$xt + $delta + $wid + $lthickness + $linespc}]
3685     }
3686     set t [$canv create line $x $y1 [lindex $xvals end] $y1 \
3687                -width $lthickness -fill black -tags tag.$id]
3688     $canv lower $t
3689     foreach tag $marks x $xvals wid $wvals {
3690         set xl [expr {$x + $delta}]
3691         set xr [expr {$x + $delta + $wid + $lthickness}]
3692         set font $mainfont
3693         if {[incr ntags -1] >= 0} {
3694             # draw a tag
3695             set t [$canv create polygon $x [expr {$yt + $delta}] $xl $yt \
3696                        $xr $yt $xr $yb $xl $yb $x [expr {$yb - $delta}] \
3697                        -width 1 -outline black -fill yellow -tags tag.$id]
3698             $canv bind $t <1> [list showtag $tag 1]
3699             set rowtextx($commitrow($curview,$id)) [expr {$xr + $linespc}]
3700         } else {
3701             # draw a head or other ref
3702             if {[incr nheads -1] >= 0} {
3703                 set col green
3704                 if {$tag eq $mainhead} {
3705                     lappend font bold
3706                 }
3707             } else {
3708                 set col "#ddddff"
3709             }
3710             set xl [expr {$xl - $delta/2}]
3711             $canv create polygon $x $yt $xr $yt $xr $yb $x $yb \
3712                 -width 1 -outline black -fill $col -tags tag.$id
3713             if {[regexp {^(remotes/.*/|remotes/)} $tag match remoteprefix]} {
3714                 set rwid [font measure $mainfont $remoteprefix]
3715                 set xi [expr {$x + 1}]
3716                 set yti [expr {$yt + 1}]
3717                 set xri [expr {$x + $rwid}]
3718                 $canv create polygon $xi $yti $xri $yti $xri $yb $xi $yb \
3719                         -width 0 -fill "#ffddaa" -tags tag.$id
3720             }
3721         }
3722         set t [$canv create text $xl $y1 -anchor w -text $tag -fill $fgcolor \
3723                    -font $font -tags [list tag.$id text]]
3724         if {$ntags >= 0} {
3725             $canv bind $t <1> [list showtag $tag 1]
3726         } elseif {$nheads >= 0} {
3727             $canv bind $t <Button-3> [list headmenu %X %Y $id $tag]
3728         }
3729     }
3730     return $xt
3731 }
3732
3733 proc xcoord {i level ln} {
3734     global canvx0 xspc1 xspc2
3735
3736     set x [expr {$canvx0 + $i * $xspc1($ln)}]
3737     if {$i > 0 && $i == $level} {
3738         set x [expr {$x + 0.5 * ($xspc2 - $xspc1($ln))}]
3739     } elseif {$i > $level} {
3740         set x [expr {$x + $xspc2 - $xspc1($ln)}]
3741     }
3742     return $x
3743 }
3744
3745 proc show_status {msg} {
3746     global canv mainfont fgcolor
3747
3748     clear_display
3749     $canv create text 3 3 -anchor nw -text $msg -font $mainfont \
3750         -tags text -fill $fgcolor
3751 }
3752
3753 # Insert a new commit as the child of the commit on row $row.
3754 # The new commit will be displayed on row $row and the commits
3755 # on that row and below will move down one row.
3756 proc insertrow {row newcmit} {
3757     global displayorder parentlist commitlisted children
3758     global commitrow curview rowidlist rowoffsets numcommits
3759     global rowrangelist rowlaidout rowoptim numcommits
3760     global selectedline rowchk commitidx
3761
3762     if {$row >= $numcommits} {
3763         puts "oops, inserting new row $row but only have $numcommits rows"
3764         return
3765     }
3766     set p [lindex $displayorder $row]
3767     set displayorder [linsert $displayorder $row $newcmit]
3768     set parentlist [linsert $parentlist $row $p]
3769     set kids $children($curview,$p)
3770     lappend kids $newcmit
3771     set children($curview,$p) $kids
3772     set children($curview,$newcmit) {}
3773     set commitlisted [linsert $commitlisted $row 1]
3774     set l [llength $displayorder]
3775     for {set r $row} {$r < $l} {incr r} {
3776         set id [lindex $displayorder $r]
3777         set commitrow($curview,$id) $r
3778     }
3779     incr commitidx($curview)
3780
3781     set idlist [lindex $rowidlist $row]
3782     set offs [lindex $rowoffsets $row]
3783     set newoffs {}
3784     foreach x $idlist {
3785         if {$x eq {} || ($x eq $p && [llength $kids] == 1)} {
3786             lappend newoffs {}
3787         } else {
3788             lappend newoffs 0
3789         }
3790     }
3791     if {[llength $kids] == 1} {
3792         set col [lsearch -exact $idlist $p]
3793         lset idlist $col $newcmit
3794     } else {
3795         set col [llength $idlist]
3796         lappend idlist $newcmit
3797         lappend offs {}
3798         lset rowoffsets $row $offs
3799     }
3800     set rowidlist [linsert $rowidlist $row $idlist]
3801     set rowoffsets [linsert $rowoffsets [expr {$row+1}] $newoffs]
3802
3803     set rowrangelist [linsert $rowrangelist $row {}]
3804     if {[llength $kids] > 1} {
3805         set rp1 [expr {$row + 1}]
3806         set ranges [lindex $rowrangelist $rp1]
3807         if {$ranges eq {}} {
3808             set ranges [list $newcmit $p]
3809         } elseif {[lindex $ranges end-1] eq $p} {
3810             lset ranges end-1 $newcmit
3811         }
3812         lset rowrangelist $rp1 $ranges
3813     }
3814
3815     catch {unset rowchk}
3816
3817     incr rowlaidout
3818     incr rowoptim
3819     incr numcommits
3820
3821     if {[info exists selectedline] && $selectedline >= $row} {
3822         incr selectedline
3823     }
3824     redisplay
3825 }
3826
3827 # Remove a commit that was inserted with insertrow on row $row.
3828 proc removerow {row} {
3829     global displayorder parentlist commitlisted children
3830     global commitrow curview rowidlist rowoffsets numcommits
3831     global rowrangelist idrowranges rowlaidout rowoptim numcommits
3832     global linesegends selectedline rowchk commitidx
3833
3834     if {$row >= $numcommits} {
3835         puts "oops, removing row $row but only have $numcommits rows"
3836         return
3837     }
3838     set rp1 [expr {$row + 1}]
3839     set id [lindex $displayorder $row]
3840     set p [lindex $parentlist $row]
3841     set displayorder [lreplace $displayorder $row $row]
3842     set parentlist [lreplace $parentlist $row $row]
3843     set commitlisted [lreplace $commitlisted $row $row]
3844     set kids $children($curview,$p)
3845     set i [lsearch -exact $kids $id]
3846     if {$i >= 0} {
3847         set kids [lreplace $kids $i $i]
3848         set children($curview,$p) $kids
3849     }
3850     set l [llength $displayorder]
3851     for {set r $row} {$r < $l} {incr r} {
3852         set id [lindex $displayorder $r]
3853         set commitrow($curview,$id) $r
3854     }
3855     incr commitidx($curview) -1
3856
3857     set rowidlist [lreplace $rowidlist $row $row]
3858     set rowoffsets [lreplace $rowoffsets $rp1 $rp1]
3859     if {$kids ne {}} {
3860         set offs [lindex $rowoffsets $row]
3861         set offs [lreplace $offs end end]
3862         lset rowoffsets $row $offs
3863     }
3864
3865     set rowrangelist [lreplace $rowrangelist $row $row]
3866     if {[llength $kids] > 0} {
3867         set ranges [lindex $rowrangelist $row]
3868         if {[lindex $ranges end-1] eq $id} {
3869             set ranges [lreplace $ranges end-1 end]
3870             lset rowrangelist $row $ranges
3871         }
3872     }
3873
3874     catch {unset rowchk}
3875
3876     incr rowlaidout -1
3877     incr rowoptim -1
3878     incr numcommits -1
3879
3880     if {[info exists selectedline] && $selectedline > $row} {
3881         incr selectedline -1
3882     }
3883     redisplay
3884 }
3885
3886 # Don't change the text pane cursor if it is currently the hand cursor,
3887 # showing that we are over a sha1 ID link.
3888 proc settextcursor {c} {
3889     global ctext curtextcursor
3890
3891     if {[$ctext cget -cursor] == $curtextcursor} {
3892         $ctext config -cursor $c
3893     }
3894     set curtextcursor $c
3895 }
3896
3897 proc nowbusy {what} {
3898     global isbusy
3899
3900     if {[array names isbusy] eq {}} {
3901         . config -cursor watch
3902         settextcursor watch
3903     }
3904     set isbusy($what) 1
3905 }
3906
3907 proc notbusy {what} {
3908     global isbusy maincursor textcursor
3909
3910     catch {unset isbusy($what)}
3911     if {[array names isbusy] eq {}} {
3912         . config -cursor $maincursor
3913         settextcursor $textcursor
3914     }
3915 }
3916
3917 proc findmatches {f} {
3918     global findtype findstring
3919     if {$findtype == "Regexp"} {
3920         set matches [regexp -indices -all -inline $findstring $f]
3921     } else {
3922         set fs $findstring
3923         if {$findtype == "IgnCase"} {
3924             set f [string tolower $f]
3925             set fs [string tolower $fs]
3926         }
3927         set matches {}
3928         set i 0
3929         set l [string length $fs]
3930         while {[set j [string first $fs $f $i]] >= 0} {
3931             lappend matches [list $j [expr {$j+$l-1}]]
3932             set i [expr {$j + $l}]
3933         }
3934     }
3935     return $matches
3936 }
3937
3938 proc dofind {{rev 0}} {
3939     global findstring findstartline findcurline selectedline numcommits
3940
3941     unmarkmatches
3942     cancel_next_highlight
3943     focus .
3944     if {$findstring eq {} || $numcommits == 0} return
3945     if {![info exists selectedline]} {
3946         set findstartline [lindex [visiblerows] $rev]
3947     } else {
3948         set findstartline $selectedline
3949     }
3950     set findcurline $findstartline
3951     nowbusy finding
3952     if {!$rev} {
3953         run findmore
3954     } else {
3955         set findcurline $findstartline
3956         if {$findcurline == 0} {
3957             set findcurline $numcommits
3958         }
3959         incr findcurline -1
3960         run findmorerev
3961     }
3962 }
3963
3964 proc findnext {restart} {
3965     global findcurline
3966     if {![info exists findcurline]} {
3967         if {$restart} {
3968             dofind
3969         } else {
3970             bell
3971         }
3972     } else {
3973         run findmore
3974         nowbusy finding
3975     }
3976 }
3977
3978 proc findprev {} {
3979     global findcurline
3980     if {![info exists findcurline]} {
3981         dofind 1
3982     } else {
3983         run findmorerev
3984         nowbusy finding
3985     }
3986 }
3987
3988 proc findmore {} {
3989     global commitdata commitinfo numcommits findstring findpattern findloc
3990     global findstartline findcurline markingmatches displayorder
3991
3992     set fldtypes {Headline Author Date Committer CDate Comments}
3993     set l [expr {$findcurline + 1}]
3994     if {$l >= $numcommits} {
3995         set l 0
3996     }
3997     if {$l <= $findstartline} {
3998         set lim [expr {$findstartline + 1}]
3999     } else {
4000         set lim $numcommits
4001     }
4002     if {$lim - $l > 500} {
4003         set lim [expr {$l + 500}]
4004     }
4005     set last 0
4006     for {} {$l < $lim} {incr l} {
4007         set id [lindex $displayorder $l]
4008         if {![doesmatch $commitdata($id)]} continue
4009         if {![info exists commitinfo($id)]} {
4010             getcommit $id
4011         }
4012         set info $commitinfo($id)
4013         foreach f $info ty $fldtypes {
4014             if {($findloc eq "All fields" || $findloc eq $ty) &&
4015                 [doesmatch $f]} {
4016                 set markingmatches 1
4017                 findselectline $l
4018                 notbusy finding
4019                 return 0
4020             }
4021         }
4022     }
4023     if {$l == $findstartline + 1} {
4024         bell
4025         unset findcurline
4026         notbusy finding
4027         return 0
4028     }
4029     set findcurline [expr {$l - 1}]
4030     return 1
4031 }
4032
4033 proc findmorerev {} {
4034     global commitdata commitinfo numcommits findstring findpattern findloc
4035     global findstartline findcurline markingmatches displayorder
4036
4037     set fldtypes {Headline Author Date Committer CDate Comments}
4038     set l $findcurline
4039     if {$l == 0} {
4040         set l $numcommits
4041     }
4042     incr l -1
4043     if {$l >= $findstartline} {
4044         set lim [expr {$findstartline - 1}]
4045     } else {
4046         set lim -1
4047     }
4048     if {$l - $lim > 500} {
4049         set lim [expr {$l - 500}]
4050     }
4051     set last 0
4052     for {} {$l > $lim} {incr l -1} {
4053         set id [lindex $displayorder $l]
4054         if {![doesmatch $commitdata($id)]} continue
4055         if {![info exists commitinfo($id)]} {
4056             getcommit $id
4057         }
4058         set info $commitinfo($id)
4059         foreach f $info ty $fldtypes {
4060             if {($findloc eq "All fields" || $findloc eq $ty) &&
4061                 [doesmatch $f]} {
4062                 set markingmatches 1
4063                 findselectline $l
4064                 notbusy finding
4065                 return 0
4066             }
4067         }
4068     }
4069     if {$l == -1} {
4070         bell
4071         unset findcurline
4072         notbusy finding
4073         return 0
4074     }
4075     set findcurline [expr {$l + 1}]
4076     return 1
4077 }
4078
4079 proc findselectline {l} {
4080     global findloc commentend ctext
4081     selectline $l 1
4082     if {$findloc == "All fields" || $findloc == "Comments"} {
4083         # highlight the matches in the comments
4084         set f [$ctext get 1.0 $commentend]
4085         set matches [findmatches $f]
4086         foreach match $matches {
4087             set start [lindex $match 0]
4088             set end [expr {[lindex $match 1] + 1}]
4089             $ctext tag add found "1.0 + $start c" "1.0 + $end c"
4090         }
4091     }
4092 }
4093
4094 # mark the bits of a headline or author that match a find string
4095 proc markmatches {canv l str tag matches font} {
4096     set bbox [$canv bbox $tag]
4097     set x0 [lindex $bbox 0]
4098     set y0 [lindex $bbox 1]
4099     set y1 [lindex $bbox 3]
4100     foreach match $matches {
4101         set start [lindex $match 0]
4102         set end [lindex $match 1]
4103         if {$start > $end} continue
4104         set xoff [font measure $font [string range $str 0 [expr {$start-1}]]]
4105         set xlen [font measure $font [string range $str 0 [expr {$end}]]]
4106         set t [$canv create rect [expr {$x0+$xoff}] $y0 \
4107                    [expr {$x0+$xlen+2}] $y1 \
4108                    -outline {} -tags [list match$l matches] -fill yellow]
4109         $canv lower $t
4110     }
4111 }
4112
4113 proc unmarkmatches {} {
4114     global findids markingmatches findcurline
4115
4116     allcanvs delete matches
4117     catch {unset findids}
4118     set markingmatches 0
4119     catch {unset findcurline}
4120 }
4121
4122 proc selcanvline {w x y} {
4123     global canv canvy0 ctext linespc
4124     global rowtextx
4125     set ymax [lindex [$canv cget -scrollregion] 3]
4126     if {$ymax == {}} return
4127     set yfrac [lindex [$canv yview] 0]
4128     set y [expr {$y + $yfrac * $ymax}]
4129     set l [expr {int(($y - $canvy0) / $linespc + 0.5)}]
4130     if {$l < 0} {
4131         set l 0
4132     }
4133     if {$w eq $canv} {
4134         if {![info exists rowtextx($l)] || $x < $rowtextx($l)} return
4135     }
4136     unmarkmatches
4137     selectline $l 1
4138 }
4139
4140 proc commit_descriptor {p} {
4141     global commitinfo
4142     if {![info exists commitinfo($p)]} {
4143         getcommit $p
4144     }
4145     set l "..."
4146     if {[llength $commitinfo($p)] > 1} {
4147         set l [lindex $commitinfo($p) 0]
4148     }
4149     return "$p ($l)\n"
4150 }
4151
4152 # append some text to the ctext widget, and make any SHA1 ID
4153 # that we know about be a clickable link.
4154 proc appendwithlinks {text tags} {
4155     global ctext commitrow linknum curview
4156
4157     set start [$ctext index "end - 1c"]
4158     $ctext insert end $text $tags
4159     set links [regexp -indices -all -inline {[0-9a-f]{40}} $text]
4160     foreach l $links {
4161         set s [lindex $l 0]
4162         set e [lindex $l 1]
4163         set linkid [string range $text $s $e]
4164         if {![info exists commitrow($curview,$linkid)]} continue
4165         incr e
4166         $ctext tag add link "$start + $s c" "$start + $e c"
4167         $ctext tag add link$linknum "$start + $s c" "$start + $e c"
4168         $ctext tag bind link$linknum <1> \
4169             [list selectline $commitrow($curview,$linkid) 1]
4170         incr linknum
4171     }
4172     $ctext tag conf link -foreground blue -underline 1
4173     $ctext tag bind link <Enter> { %W configure -cursor hand2 }
4174     $ctext tag bind link <Leave> { %W configure -cursor $curtextcursor }
4175 }
4176
4177 proc viewnextline {dir} {
4178     global canv linespc
4179
4180     $canv delete hover
4181     set ymax [lindex [$canv cget -scrollregion] 3]
4182     set wnow [$canv yview]
4183     set wtop [expr {[lindex $wnow 0] * $ymax}]
4184     set newtop [expr {$wtop + $dir * $linespc}]
4185     if {$newtop < 0} {
4186         set newtop 0
4187     } elseif {$newtop > $ymax} {
4188         set newtop $ymax
4189     }
4190     allcanvs yview moveto [expr {$newtop * 1.0 / $ymax}]
4191 }
4192
4193 # add a list of tag or branch names at position pos
4194 # returns the number of names inserted
4195 proc appendrefs {pos ids var} {
4196     global ctext commitrow linknum curview $var maxrefs
4197
4198     if {[catch {$ctext index $pos}]} {
4199         return 0
4200     }
4201     $ctext conf -state normal
4202     $ctext delete $pos "$pos lineend"
4203     set tags {}
4204     foreach id $ids {
4205         foreach tag [set $var\($id\)] {
4206             lappend tags [list $tag $id]
4207         }
4208     }
4209     if {[llength $tags] > $maxrefs} {
4210         $ctext insert $pos "many ([llength $tags])"
4211     } else {
4212         set tags [lsort -index 0 -decreasing $tags]
4213         set sep {}
4214         foreach ti $tags {
4215             set id [lindex $ti 1]
4216             set lk link$linknum
4217             incr linknum
4218             $ctext tag delete $lk
4219             $ctext insert $pos $sep
4220             $ctext insert $pos [lindex $ti 0] $lk
4221             if {[info exists commitrow($curview,$id)]} {
4222                 $ctext tag conf $lk -foreground blue
4223                 $ctext tag bind $lk <1> \
4224                     [list selectline $commitrow($curview,$id) 1]
4225                 $ctext tag conf $lk -underline 1
4226                 $ctext tag bind $lk <Enter> { %W configure -cursor hand2 }
4227                 $ctext tag bind $lk <Leave> \
4228                     { %W configure -cursor $curtextcursor }
4229             }
4230             set sep ", "
4231         }
4232     }
4233     $ctext conf -state disabled
4234     return [llength $tags]
4235 }
4236
4237 # called when we have finished computing the nearby tags
4238 proc dispneartags {delay} {
4239     global selectedline currentid showneartags tagphase
4240
4241     if {![info exists selectedline] || !$showneartags} return
4242     after cancel dispnexttag
4243     if {$delay} {
4244         after 200 dispnexttag
4245         set tagphase -1
4246     } else {
4247         after idle dispnexttag
4248         set tagphase 0
4249     }
4250 }
4251
4252 proc dispnexttag {} {
4253     global selectedline currentid showneartags tagphase ctext
4254
4255     if {![info exists selectedline] || !$showneartags} return
4256     switch -- $tagphase {
4257         0 {
4258             set dtags [desctags $currentid]
4259             if {$dtags ne {}} {
4260                 appendrefs precedes $dtags idtags
4261             }
4262         }
4263         1 {
4264             set atags [anctags $currentid]
4265             if {$atags ne {}} {
4266                 appendrefs follows $atags idtags
4267             }
4268         }
4269         2 {
4270             set dheads [descheads $currentid]
4271             if {$dheads ne {}} {
4272                 if {[appendrefs branch $dheads idheads] > 1
4273                     && [$ctext get "branch -3c"] eq "h"} {
4274                     # turn "Branch" into "Branches"
4275                     $ctext conf -state normal
4276                     $ctext insert "branch -2c" "es"
4277                     $ctext conf -state disabled
4278                 }
4279             }
4280         }
4281     }
4282     if {[incr tagphase] <= 2} {
4283         after idle dispnexttag
4284     }
4285 }
4286
4287 proc selectline {l isnew} {
4288     global canv canv2 canv3 ctext commitinfo selectedline
4289     global displayorder linehtag linentag linedtag
4290     global canvy0 linespc parentlist children curview
4291     global currentid sha1entry
4292     global commentend idtags linknum
4293     global mergemax numcommits pending_select
4294     global cmitmode showneartags allcommits
4295
4296     catch {unset pending_select}
4297     $canv delete hover
4298     normalline
4299     cancel_next_highlight
4300     if {$l < 0 || $l >= $numcommits} return
4301     set y [expr {$canvy0 + $l * $linespc}]
4302     set ymax [lindex [$canv cget -scrollregion] 3]
4303     set ytop [expr {$y - $linespc - 1}]
4304     set ybot [expr {$y + $linespc + 1}]
4305     set wnow [$canv yview]
4306     set wtop [expr {[lindex $wnow 0] * $ymax}]
4307     set wbot [expr {[lindex $wnow 1] * $ymax}]
4308     set wh [expr {$wbot - $wtop}]
4309     set newtop $wtop
4310     if {$ytop < $wtop} {
4311         if {$ybot < $wtop} {
4312             set newtop [expr {$y - $wh / 2.0}]
4313         } else {
4314             set newtop $ytop
4315             if {$newtop > $wtop - $linespc} {
4316                 set newtop [expr {$wtop - $linespc}]
4317             }
4318         }
4319     } elseif {$ybot > $wbot} {
4320         if {$ytop > $wbot} {
4321             set newtop [expr {$y - $wh / 2.0}]
4322         } else {
4323             set newtop [expr {$ybot - $wh}]
4324             if {$newtop < $wtop + $linespc} {
4325                 set newtop [expr {$wtop + $linespc}]
4326             }
4327         }
4328     }
4329     if {$newtop != $wtop} {
4330         if {$newtop < 0} {
4331             set newtop 0
4332         }
4333         allcanvs yview moveto [expr {$newtop * 1.0 / $ymax}]
4334         drawvisible
4335     }
4336
4337     if {![info exists linehtag($l)]} return
4338     $canv delete secsel
4339     set t [eval $canv create rect [$canv bbox $linehtag($l)] -outline {{}} \
4340                -tags secsel -fill [$canv cget -selectbackground]]
4341     $canv lower $t
4342     $canv2 delete secsel
4343     set t [eval $canv2 create rect [$canv2 bbox $linentag($l)] -outline {{}} \
4344                -tags secsel -fill [$canv2 cget -selectbackground]]
4345     $canv2 lower $t
4346     $canv3 delete secsel
4347     set t [eval $canv3 create rect [$canv3 bbox $linedtag($l)] -outline {{}} \
4348                -tags secsel -fill [$canv3 cget -selectbackground]]
4349     $canv3 lower $t
4350
4351     if {$isnew} {
4352         addtohistory [list selectline $l 0]
4353     }
4354
4355     set selectedline $l
4356
4357     set id [lindex $displayorder $l]
4358     set currentid $id
4359     $sha1entry delete 0 end
4360     $sha1entry insert 0 $id
4361     $sha1entry selection from 0
4362     $sha1entry selection to end
4363     rhighlight_sel $id
4364
4365     $ctext conf -state normal
4366     clear_ctext
4367     set linknum 0
4368     set info $commitinfo($id)
4369     set date [formatdate [lindex $info 2]]
4370     $ctext insert end "Author: [lindex $info 1]  $date\n"
4371     set date [formatdate [lindex $info 4]]
4372     $ctext insert end "Committer: [lindex $info 3]  $date\n"
4373     if {[info exists idtags($id)]} {
4374         $ctext insert end "Tags:"
4375         foreach tag $idtags($id) {
4376             $ctext insert end " $tag"
4377         }
4378         $ctext insert end "\n"
4379     }
4380
4381     set headers {}
4382     set olds [lindex $parentlist $l]
4383     if {[llength $olds] > 1} {
4384         set np 0
4385         foreach p $olds {
4386             if {$np >= $mergemax} {
4387                 set tag mmax
4388             } else {
4389                 set tag m$np
4390             }
4391             $ctext insert end "Parent: " $tag
4392             appendwithlinks [commit_descriptor $p] {}
4393             incr np
4394         }
4395     } else {
4396         foreach p $olds {
4397             append headers "Parent: [commit_descriptor $p]"
4398         }
4399     }
4400
4401     foreach c $children($curview,$id) {
4402         append headers "Child:  [commit_descriptor $c]"
4403     }
4404
4405     # make anything that looks like a SHA1 ID be a clickable link
4406     appendwithlinks $headers {}
4407     if {$showneartags} {
4408         if {![info exists allcommits]} {
4409             getallcommits
4410         }
4411         $ctext insert end "Branch: "
4412         $ctext mark set branch "end -1c"
4413         $ctext mark gravity branch left
4414         $ctext insert end "\nFollows: "
4415         $ctext mark set follows "end -1c"
4416         $ctext mark gravity follows left
4417         $ctext insert end "\nPrecedes: "
4418         $ctext mark set precedes "end -1c"
4419         $ctext mark gravity precedes left
4420         $ctext insert end "\n"
4421         dispneartags 1
4422     }
4423     $ctext insert end "\n"
4424     set comment [lindex $info 5]
4425     if {[string first "\r" $comment] >= 0} {
4426         set comment [string map {"\r" "\n    "} $comment]
4427     }
4428     appendwithlinks $comment {comment}
4429
4430     $ctext tag remove found 1.0 end
4431     $ctext conf -state disabled
4432     set commentend [$ctext index "end - 1c"]
4433
4434     init_flist "Comments"
4435     if {$cmitmode eq "tree"} {
4436         gettree $id
4437     } elseif {[llength $olds] <= 1} {
4438         startdiff $id
4439     } else {
4440         mergediff $id $l
4441     }
4442 }
4443
4444 proc selfirstline {} {
4445     unmarkmatches
4446     selectline 0 1
4447 }
4448
4449 proc sellastline {} {
4450     global numcommits
4451     unmarkmatches
4452     set l [expr {$numcommits - 1}]
4453     selectline $l 1
4454 }
4455
4456 proc selnextline {dir} {
4457     global selectedline
4458     if {![info exists selectedline]} return
4459     set l [expr {$selectedline + $dir}]
4460     unmarkmatches
4461     selectline $l 1
4462 }
4463
4464 proc selnextpage {dir} {
4465     global canv linespc selectedline numcommits
4466
4467     set lpp [expr {([winfo height $canv] - 2) / $linespc}]
4468     if {$lpp < 1} {
4469         set lpp 1
4470     }
4471     allcanvs yview scroll [expr {$dir * $lpp}] units
4472     drawvisible
4473     if {![info exists selectedline]} return
4474     set l [expr {$selectedline + $dir * $lpp}]
4475     if {$l < 0} {
4476         set l 0
4477     } elseif {$l >= $numcommits} {
4478         set l [expr $numcommits - 1]
4479     }
4480     unmarkmatches
4481     selectline $l 1
4482 }
4483
4484 proc unselectline {} {
4485     global selectedline currentid
4486
4487     catch {unset selectedline}
4488     catch {unset currentid}
4489     allcanvs delete secsel
4490     rhighlight_none
4491     cancel_next_highlight
4492 }
4493
4494 proc reselectline {} {
4495     global selectedline
4496
4497     if {[info exists selectedline]} {
4498         selectline $selectedline 0
4499     }
4500 }
4501
4502 proc addtohistory {cmd} {
4503     global history historyindex curview
4504
4505     set elt [list $curview $cmd]
4506     if {$historyindex > 0
4507         && [lindex $history [expr {$historyindex - 1}]] == $elt} {
4508         return
4509     }
4510
4511     if {$historyindex < [llength $history]} {
4512         set history [lreplace $history $historyindex end $elt]
4513     } else {
4514         lappend history $elt
4515     }
4516     incr historyindex
4517     if {$historyindex > 1} {
4518         .tf.bar.leftbut conf -state normal
4519     } else {
4520         .tf.bar.leftbut conf -state disabled
4521     }
4522     .tf.bar.rightbut conf -state disabled
4523 }
4524
4525 proc godo {elt} {
4526     global curview
4527
4528     set view [lindex $elt 0]
4529     set cmd [lindex $elt 1]
4530     if {$curview != $view} {
4531         showview $view
4532     }
4533     eval $cmd
4534 }
4535
4536 proc goback {} {
4537     global history historyindex
4538
4539     if {$historyindex > 1} {
4540         incr historyindex -1
4541         godo [lindex $history [expr {$historyindex - 1}]]
4542         .tf.bar.rightbut conf -state normal
4543     }
4544     if {$historyindex <= 1} {
4545         .tf.bar.leftbut conf -state disabled
4546     }
4547 }
4548
4549 proc goforw {} {
4550     global history historyindex
4551
4552     if {$historyindex < [llength $history]} {
4553         set cmd [lindex $history $historyindex]
4554         incr historyindex
4555         godo $cmd
4556         .tf.bar.leftbut conf -state normal
4557     }
4558     if {$historyindex >= [llength $history]} {
4559         .tf.bar.rightbut conf -state disabled
4560     }
4561 }
4562
4563 proc gettree {id} {
4564     global treefilelist treeidlist diffids diffmergeid treepending nullid
4565
4566     set diffids $id
4567     catch {unset diffmergeid}
4568     if {![info exists treefilelist($id)]} {
4569         if {![info exists treepending]} {
4570             if {$id ne $nullid} {
4571                 set cmd [concat | git ls-tree -r $id]
4572             } else {
4573                 set cmd [concat | git ls-files]
4574             }
4575             if {[catch {set gtf [open $cmd r]}]} {
4576                 return
4577             }
4578             set treepending $id
4579             set treefilelist($id) {}
4580             set treeidlist($id) {}
4581             fconfigure $gtf -blocking 0
4582             filerun $gtf [list gettreeline $gtf $id]
4583         }
4584     } else {
4585         setfilelist $id
4586     }
4587 }
4588
4589 proc gettreeline {gtf id} {
4590     global treefilelist treeidlist treepending cmitmode diffids nullid
4591
4592     set nl 0
4593     while {[incr nl] <= 1000 && [gets $gtf line] >= 0} {
4594         if {$diffids ne $nullid} {
4595             if {[lindex $line 1] ne "blob"} continue
4596             set i [string first "\t" $line]
4597             if {$i < 0} continue
4598             set sha1 [lindex $line 2]
4599             set fname [string range $line [expr {$i+1}] end]
4600             if {[string index $fname 0] eq "\""} {
4601                 set fname [lindex $fname 0]
4602             }
4603             lappend treeidlist($id) $sha1
4604         } else {
4605             set fname $line
4606         }
4607         lappend treefilelist($id) $fname
4608     }
4609     if {![eof $gtf]} {
4610         return [expr {$nl >= 1000? 2: 1}]
4611     }
4612     close $gtf
4613     unset treepending
4614     if {$cmitmode ne "tree"} {
4615         if {![info exists diffmergeid]} {
4616             gettreediffs $diffids
4617         }
4618     } elseif {$id ne $diffids} {
4619         gettree $diffids
4620     } else {
4621         setfilelist $id
4622     }
4623     return 0
4624 }
4625
4626 proc showfile {f} {
4627     global treefilelist treeidlist diffids nullid
4628     global ctext commentend
4629
4630     set i [lsearch -exact $treefilelist($diffids) $f]
4631     if {$i < 0} {
4632         puts "oops, $f not in list for id $diffids"
4633         return
4634     }
4635     if {$diffids ne $nullid} {
4636         set blob [lindex $treeidlist($diffids) $i]
4637         if {[catch {set bf [open [concat | git cat-file blob $blob] r]} err]} {
4638             puts "oops, error reading blob $blob: $err"
4639             return
4640         }
4641     } else {
4642         if {[catch {set bf [open $f r]} err]} {
4643             puts "oops, can't read $f: $err"
4644             return
4645         }
4646     }
4647     fconfigure $bf -blocking 0
4648     filerun $bf [list getblobline $bf $diffids]
4649     $ctext config -state normal
4650     clear_ctext $commentend
4651     $ctext insert end "\n"
4652     $ctext insert end "$f\n" filesep
4653     $ctext config -state disabled
4654     $ctext yview $commentend
4655 }
4656
4657 proc getblobline {bf id} {
4658     global diffids cmitmode ctext
4659
4660     if {$id ne $diffids || $cmitmode ne "tree"} {
4661         catch {close $bf}
4662         return 0
4663     }
4664     $ctext config -state normal
4665     set nl 0
4666     while {[incr nl] <= 1000 && [gets $bf line] >= 0} {
4667         $ctext insert end "$line\n"
4668     }
4669     if {[eof $bf]} {
4670         # delete last newline
4671         $ctext delete "end - 2c" "end - 1c"
4672         close $bf
4673         return 0
4674     }
4675     $ctext config -state disabled
4676     return [expr {$nl >= 1000? 2: 1}]
4677 }
4678
4679 proc mergediff {id l} {
4680     global diffmergeid diffopts mdifffd
4681     global diffids
4682     global parentlist
4683
4684     set diffmergeid $id
4685     set diffids $id
4686     # this doesn't seem to actually affect anything...
4687     set env(GIT_DIFF_OPTS) $diffopts
4688     set cmd [concat | git diff-tree --no-commit-id --cc $id]
4689     if {[catch {set mdf [open $cmd r]} err]} {
4690         error_popup "Error getting merge diffs: $err"
4691         return
4692     }
4693     fconfigure $mdf -blocking 0
4694     set mdifffd($id) $mdf
4695     set np [llength [lindex $parentlist $l]]
4696     filerun $mdf [list getmergediffline $mdf $id $np]
4697 }
4698
4699 proc getmergediffline {mdf id np} {
4700     global diffmergeid ctext cflist mergemax
4701     global difffilestart mdifffd
4702
4703     $ctext conf -state normal
4704     set nr 0
4705     while {[incr nr] <= 1000 && [gets $mdf line] >= 0} {
4706         if {![info exists diffmergeid] || $id != $diffmergeid
4707             || $mdf != $mdifffd($id)} {
4708             close $mdf
4709             return 0
4710         }
4711         if {[regexp {^diff --cc (.*)} $line match fname]} {
4712             # start of a new file
4713             $ctext insert end "\n"
4714             set here [$ctext index "end - 1c"]
4715             lappend difffilestart $here
4716             add_flist [list $fname]
4717             set l [expr {(78 - [string length $fname]) / 2}]
4718             set pad [string range "----------------------------------------" 1 $l]
4719             $ctext insert end "$pad $fname $pad\n" filesep
4720         } elseif {[regexp {^@@} $line]} {
4721             $ctext insert end "$line\n" hunksep
4722         } elseif {[regexp {^[0-9a-f]{40}$} $line] || [regexp {^index} $line]} {
4723             # do nothing
4724         } else {
4725             # parse the prefix - one ' ', '-' or '+' for each parent
4726             set spaces {}
4727             set minuses {}
4728             set pluses {}
4729             set isbad 0
4730             for {set j 0} {$j < $np} {incr j} {
4731                 set c [string range $line $j $j]
4732                 if {$c == " "} {
4733                     lappend spaces $j
4734                 } elseif {$c == "-"} {
4735                     lappend minuses $j
4736                 } elseif {$c == "+"} {
4737                     lappend pluses $j
4738                 } else {
4739                     set isbad 1
4740                     break
4741                 }
4742             }
4743             set tags {}
4744             set num {}
4745             if {!$isbad && $minuses ne {} && $pluses eq {}} {
4746                 # line doesn't appear in result, parents in $minuses have the line
4747                 set num [lindex $minuses 0]
4748             } elseif {!$isbad && $pluses ne {} && $minuses eq {}} {
4749                 # line appears in result, parents in $pluses don't have the line
4750                 lappend tags mresult
4751                 set num [lindex $spaces 0]
4752             }
4753             if {$num ne {}} {
4754                 if {$num >= $mergemax} {
4755                     set num "max"
4756                 }
4757                 lappend tags m$num
4758             }
4759             $ctext insert end "$line\n" $tags
4760         }
4761     }
4762     $ctext conf -state disabled
4763     if {[eof $mdf]} {
4764         close $mdf
4765         return 0
4766     }
4767     return [expr {$nr >= 1000? 2: 1}]
4768 }
4769
4770 proc startdiff {ids} {
4771     global treediffs diffids treepending diffmergeid nullid
4772
4773     set diffids $ids
4774     catch {unset diffmergeid}
4775     if {![info exists treediffs($ids)] || [lsearch -exact $ids $nullid] >= 0} {
4776         if {![info exists treepending]} {
4777             gettreediffs $ids
4778         }
4779     } else {
4780         addtocflist $ids
4781     }
4782 }
4783
4784 proc addtocflist {ids} {
4785     global treediffs cflist
4786     add_flist $treediffs($ids)
4787     getblobdiffs $ids
4788 }
4789
4790 proc diffcmd {ids flags} {
4791     global nullid
4792
4793     set i [lsearch -exact $ids $nullid]
4794     if {$i >= 0} {
4795         set cmd [concat | git diff-index $flags]
4796         if {[llength $ids] > 1} {
4797             if {$i == 0} {
4798                 lappend cmd -R [lindex $ids 1]
4799             } else {
4800                 lappend cmd [lindex $ids 0]
4801             }
4802         } else {
4803             lappend cmd HEAD
4804         }
4805     } else {
4806         set cmd [concat | git diff-tree --no-commit-id -r $flags $ids]
4807     }
4808     return $cmd
4809 }
4810
4811 proc gettreediffs {ids} {
4812     global treediff treepending
4813
4814     set treepending $ids
4815     set treediff {}
4816     if {[catch {set gdtf [open [diffcmd $ids {}] r]}]} return
4817     fconfigure $gdtf -blocking 0
4818     filerun $gdtf [list gettreediffline $gdtf $ids]
4819 }
4820
4821 proc gettreediffline {gdtf ids} {
4822     global treediff treediffs treepending diffids diffmergeid
4823     global cmitmode
4824
4825     set nr 0
4826     while {[incr nr] <= 1000 && [gets $gdtf line] >= 0} {
4827         set i [string first "\t" $line]
4828         if {$i >= 0} {
4829             set file [string range $line [expr {$i+1}] end]
4830             if {[string index $file 0] eq "\""} {
4831                 set file [lindex $file 0]
4832             }
4833             lappend treediff $file
4834         }
4835     }
4836     if {![eof $gdtf]} {
4837         return [expr {$nr >= 1000? 2: 1}]
4838     }
4839     close $gdtf
4840     set treediffs($ids) $treediff
4841     unset treepending
4842     if {$cmitmode eq "tree"} {
4843         gettree $diffids
4844     } elseif {$ids != $diffids} {
4845         if {![info exists diffmergeid]} {
4846             gettreediffs $diffids
4847         }
4848     } else {
4849         addtocflist $ids
4850     }
4851     return 0
4852 }
4853
4854 proc getblobdiffs {ids} {
4855     global diffopts blobdifffd diffids env
4856     global diffinhdr treediffs
4857
4858     set env(GIT_DIFF_OPTS) $diffopts
4859     if {[catch {set bdf [open [diffcmd $ids {-p -C}] r]} err]} {
4860         puts "error getting diffs: $err"
4861         return
4862     }
4863     set diffinhdr 0
4864     fconfigure $bdf -blocking 0
4865     set blobdifffd($ids) $bdf
4866     filerun $bdf [list getblobdiffline $bdf $diffids]
4867 }
4868
4869 proc setinlist {var i val} {
4870     global $var
4871
4872     while {[llength [set $var]] < $i} {
4873         lappend $var {}
4874     }
4875     if {[llength [set $var]] == $i} {
4876         lappend $var $val
4877     } else {
4878         lset $var $i $val
4879     }
4880 }
4881
4882 proc makediffhdr {fname ids} {
4883     global ctext curdiffstart treediffs
4884
4885     set i [lsearch -exact $treediffs($ids) $fname]
4886     if {$i >= 0} {
4887         setinlist difffilestart $i $curdiffstart
4888     }
4889     set l [expr {(78 - [string length $fname]) / 2}]
4890     set pad [string range "----------------------------------------" 1 $l]
4891     $ctext insert $curdiffstart "$pad $fname $pad" filesep
4892 }
4893
4894 proc getblobdiffline {bdf ids} {
4895     global diffids blobdifffd ctext curdiffstart
4896     global diffnexthead diffnextnote difffilestart
4897     global diffinhdr treediffs
4898
4899     set nr 0
4900     $ctext conf -state normal
4901     while {[incr nr] <= 1000 && [gets $bdf line] >= 0} {
4902         if {$ids != $diffids || $bdf != $blobdifffd($ids)} {
4903             close $bdf
4904             return 0
4905         }
4906         if {![string compare -length 11 "diff --git " $line]} {
4907             # trim off "diff --git "
4908             set line [string range $line 11 end]
4909             set diffinhdr 1
4910             # start of a new file
4911             $ctext insert end "\n"
4912             set curdiffstart [$ctext index "end - 1c"]
4913             $ctext insert end "\n" filesep
4914             # If the name hasn't changed the length will be odd,
4915             # the middle char will be a space, and the two bits either
4916             # side will be a/name and b/name, or "a/name" and "b/name".
4917             # If the name has changed we'll get "rename from" and
4918             # "rename to" lines following this, and we'll use them
4919             # to get the filenames.
4920             # This complexity is necessary because spaces in the filename(s)
4921             # don't get escaped.
4922             set l [string length $line]
4923             set i [expr {$l / 2}]
4924             if {!(($l & 1) && [string index $line $i] eq " " &&
4925                   [string range $line 2 [expr {$i - 1}]] eq \
4926                       [string range $line [expr {$i + 3}] end])} {
4927                 continue
4928             }
4929             # unescape if quoted and chop off the a/ from the front
4930             if {[string index $line 0] eq "\""} {
4931                 set fname [string range [lindex $line 0] 2 end]
4932             } else {
4933                 set fname [string range $line 2 [expr {$i - 1}]]
4934             }
4935             makediffhdr $fname $ids
4936
4937         } elseif {[regexp {^@@ -([0-9]+)(,[0-9]+)? \+([0-9]+)(,[0-9]+)? @@(.*)} \
4938                        $line match f1l f1c f2l f2c rest]} {
4939             $ctext insert end "$line\n" hunksep
4940             set diffinhdr 0
4941
4942         } elseif {$diffinhdr} {
4943             if {![string compare -length 12 "rename from " $line]} {
4944                 set fname [string range $line 12 end]
4945                 if {[string index $fname 0] eq "\""} {
4946                     set fname [lindex $fname 0]
4947                 }
4948                 set i [lsearch -exact $treediffs($ids) $fname]
4949                 if {$i >= 0} {
4950                     setinlist difffilestart $i $curdiffstart
4951                 }
4952             } elseif {![string compare -length 10 $line "rename to "]} {
4953                 set fname [string range $line 10 end]
4954                 if {[string index $fname 0] eq "\""} {
4955                     set fname [lindex $fname 0]
4956                 }
4957                 makediffhdr $fname $ids
4958             } elseif {[string compare -length 3 $line "---"] == 0} {
4959                 # do nothing
4960                 continue
4961             } elseif {[string compare -length 3 $line "+++"] == 0} {
4962                 set diffinhdr 0
4963                 continue
4964             }
4965             $ctext insert end "$line\n" filesep
4966
4967         } else {
4968             set x [string range $line 0 0]
4969             if {$x == "-" || $x == "+"} {
4970                 set tag [expr {$x == "+"}]
4971                 $ctext insert end "$line\n" d$tag
4972             } elseif {$x == " "} {
4973                 $ctext insert end "$line\n"
4974             } else {
4975                 # "\ No newline at end of file",
4976                 # or something else we don't recognize
4977                 $ctext insert end "$line\n" hunksep
4978             }
4979         }
4980     }
4981     $ctext conf -state disabled
4982     if {[eof $bdf]} {
4983         close $bdf
4984         return 0
4985     }
4986     return [expr {$nr >= 1000? 2: 1}]
4987 }
4988
4989 proc changediffdisp {} {
4990     global ctext diffelide
4991
4992     $ctext tag conf d0 -elide [lindex $diffelide 0]
4993     $ctext tag conf d1 -elide [lindex $diffelide 1]
4994 }
4995
4996 proc prevfile {} {
4997     global difffilestart ctext
4998     set prev [lindex $difffilestart 0]
4999     set here [$ctext index @0,0]
5000     foreach loc $difffilestart {
5001         if {[$ctext compare $loc >= $here]} {
5002             $ctext yview $prev
5003             return
5004         }
5005         set prev $loc
5006     }
5007     $ctext yview $prev
5008 }
5009
5010 proc nextfile {} {
5011     global difffilestart ctext
5012     set here [$ctext index @0,0]
5013     foreach loc $difffilestart {
5014         if {[$ctext compare $loc > $here]} {
5015             $ctext yview $loc
5016             return
5017         }
5018     }
5019 }
5020
5021 proc clear_ctext {{first 1.0}} {
5022     global ctext smarktop smarkbot
5023
5024     set l [lindex [split $first .] 0]
5025     if {![info exists smarktop] || [$ctext compare $first < $smarktop.0]} {
5026         set smarktop $l
5027     }
5028     if {![info exists smarkbot] || [$ctext compare $first < $smarkbot.0]} {
5029         set smarkbot $l
5030     }
5031     $ctext delete $first end
5032 }
5033
5034 proc incrsearch {name ix op} {
5035     global ctext searchstring searchdirn
5036
5037     $ctext tag remove found 1.0 end
5038     if {[catch {$ctext index anchor}]} {
5039         # no anchor set, use start of selection, or of visible area
5040         set sel [$ctext tag ranges sel]
5041         if {$sel ne {}} {
5042             $ctext mark set anchor [lindex $sel 0]
5043         } elseif {$searchdirn eq "-forwards"} {
5044             $ctext mark set anchor @0,0
5045         } else {
5046             $ctext mark set anchor @0,[winfo height $ctext]
5047         }
5048     }
5049     if {$searchstring ne {}} {
5050         set here [$ctext search $searchdirn -- $searchstring anchor]
5051         if {$here ne {}} {
5052             $ctext see $here
5053         }
5054         searchmarkvisible 1
5055     }
5056 }
5057
5058 proc dosearch {} {
5059     global sstring ctext searchstring searchdirn
5060
5061     focus $sstring
5062     $sstring icursor end
5063     set searchdirn -forwards
5064     if {$searchstring ne {}} {
5065         set sel [$ctext tag ranges sel]
5066         if {$sel ne {}} {
5067             set start "[lindex $sel 0] + 1c"
5068         } elseif {[catch {set start [$ctext index anchor]}]} {
5069             set start "@0,0"
5070         }
5071         set match [$ctext search -count mlen -- $searchstring $start]
5072         $ctext tag remove sel 1.0 end
5073         if {$match eq {}} {
5074             bell
5075             return
5076         }
5077         $ctext see $match
5078         set mend "$match + $mlen c"
5079         $ctext tag add sel $match $mend
5080         $ctext mark unset anchor
5081     }
5082 }
5083
5084 proc dosearchback {} {
5085     global sstring ctext searchstring searchdirn
5086
5087     focus $sstring
5088     $sstring icursor end
5089     set searchdirn -backwards
5090     if {$searchstring ne {}} {
5091         set sel [$ctext tag ranges sel]
5092         if {$sel ne {}} {
5093             set start [lindex $sel 0]
5094         } elseif {[catch {set start [$ctext index anchor]}]} {
5095             set start @0,[winfo height $ctext]
5096         }
5097         set match [$ctext search -backwards -count ml -- $searchstring $start]
5098         $ctext tag remove sel 1.0 end
5099         if {$match eq {}} {
5100             bell
5101             return
5102         }
5103         $ctext see $match
5104         set mend "$match + $ml c"
5105         $ctext tag add sel $match $mend
5106         $ctext mark unset anchor
5107     }
5108 }
5109
5110 proc searchmark {first last} {
5111     global ctext searchstring
5112
5113     set mend $first.0
5114     while {1} {
5115         set match [$ctext search -count mlen -- $searchstring $mend $last.end]
5116         if {$match eq {}} break
5117         set mend "$match + $mlen c"
5118         $ctext tag add found $match $mend
5119     }
5120 }
5121
5122 proc searchmarkvisible {doall} {
5123     global ctext smarktop smarkbot
5124
5125     set topline [lindex [split [$ctext index @0,0] .] 0]
5126     set botline [lindex [split [$ctext index @0,[winfo height $ctext]] .] 0]
5127     if {$doall || $botline < $smarktop || $topline > $smarkbot} {
5128         # no overlap with previous
5129         searchmark $topline $botline
5130         set smarktop $topline
5131         set smarkbot $botline
5132     } else {
5133         if {$topline < $smarktop} {
5134             searchmark $topline [expr {$smarktop-1}]
5135             set smarktop $topline
5136         }
5137         if {$botline > $smarkbot} {
5138             searchmark [expr {$smarkbot+1}] $botline
5139             set smarkbot $botline
5140         }
5141     }
5142 }
5143
5144 proc scrolltext {f0 f1} {
5145     global searchstring
5146
5147     .bleft.sb set $f0 $f1
5148     if {$searchstring ne {}} {
5149         searchmarkvisible 0
5150     }
5151 }
5152
5153 proc setcoords {} {
5154     global linespc charspc canvx0 canvy0 mainfont
5155     global xspc1 xspc2 lthickness
5156
5157     set linespc [font metrics $mainfont -linespace]
5158     set charspc [font measure $mainfont "m"]
5159     set canvy0 [expr {int(3 + 0.5 * $linespc)}]
5160     set canvx0 [expr {int(3 + 0.5 * $linespc)}]
5161     set lthickness [expr {int($linespc / 9) + 1}]
5162     set xspc1(0) $linespc
5163     set xspc2 $linespc
5164 }
5165
5166 proc redisplay {} {
5167     global canv
5168     global selectedline
5169
5170     set ymax [lindex [$canv cget -scrollregion] 3]
5171     if {$ymax eq {} || $ymax == 0} return
5172     set span [$canv yview]
5173     clear_display
5174     setcanvscroll
5175     allcanvs yview moveto [lindex $span 0]
5176     drawvisible
5177     if {[info exists selectedline]} {
5178         selectline $selectedline 0
5179         allcanvs yview moveto [lindex $span 0]
5180     }
5181 }
5182
5183 proc incrfont {inc} {
5184     global mainfont textfont ctext canv phase cflist
5185     global charspc tabstop
5186     global stopped entries
5187     unmarkmatches
5188     set mainfont [lreplace $mainfont 1 1 [expr {[lindex $mainfont 1] + $inc}]]
5189     set textfont [lreplace $textfont 1 1 [expr {[lindex $textfont 1] + $inc}]]
5190     setcoords
5191     $ctext conf -font $textfont -tabs "[expr {$tabstop * $charspc}]"
5192     $cflist conf -font $textfont
5193     $ctext tag conf filesep -font [concat $textfont bold]
5194     foreach e $entries {
5195         $e conf -font $mainfont
5196     }
5197     if {$phase eq "getcommits"} {
5198         $canv itemconf textitems -font $mainfont
5199     }
5200     redisplay
5201 }
5202
5203 proc clearsha1 {} {
5204     global sha1entry sha1string
5205     if {[string length $sha1string] == 40} {
5206         $sha1entry delete 0 end
5207     }
5208 }
5209
5210 proc sha1change {n1 n2 op} {
5211     global sha1string currentid sha1but
5212     if {$sha1string == {}
5213         || ([info exists currentid] && $sha1string == $currentid)} {
5214         set state disabled
5215     } else {
5216         set state normal
5217     }
5218     if {[$sha1but cget -state] == $state} return
5219     if {$state == "normal"} {
5220         $sha1but conf -state normal -relief raised -text "Goto: "
5221     } else {
5222         $sha1but conf -state disabled -relief flat -text "SHA1 ID: "
5223     }
5224 }
5225
5226 proc gotocommit {} {
5227     global sha1string currentid commitrow tagids headids
5228     global displayorder numcommits curview
5229
5230     if {$sha1string == {}
5231         || ([info exists currentid] && $sha1string == $currentid)} return
5232     if {[info exists tagids($sha1string)]} {
5233         set id $tagids($sha1string)
5234     } elseif {[info exists headids($sha1string)]} {
5235         set id $headids($sha1string)
5236     } else {
5237         set id [string tolower $sha1string]
5238         if {[regexp {^[0-9a-f]{4,39}$} $id]} {
5239             set matches {}
5240             foreach i $displayorder {
5241                 if {[string match $id* $i]} {
5242                     lappend matches $i
5243                 }
5244             }
5245             if {$matches ne {}} {
5246                 if {[llength $matches] > 1} {
5247                     error_popup "Short SHA1 id $id is ambiguous"
5248                     return
5249                 }
5250                 set id [lindex $matches 0]
5251             }
5252         }
5253     }
5254     if {[info exists commitrow($curview,$id)]} {
5255         selectline $commitrow($curview,$id) 1
5256         return
5257     }
5258     if {[regexp {^[0-9a-fA-F]{4,}$} $sha1string]} {
5259         set type "SHA1 id"
5260     } else {
5261         set type "Tag/Head"
5262     }
5263     error_popup "$type $sha1string is not known"
5264 }
5265
5266 proc lineenter {x y id} {
5267     global hoverx hovery hoverid hovertimer
5268     global commitinfo canv
5269
5270     if {![info exists commitinfo($id)] && ![getcommit $id]} return
5271     set hoverx $x
5272     set hovery $y
5273     set hoverid $id
5274     if {[info exists hovertimer]} {
5275         after cancel $hovertimer
5276     }
5277     set hovertimer [after 500 linehover]
5278     $canv delete hover
5279 }
5280
5281 proc linemotion {x y id} {
5282     global hoverx hovery hoverid hovertimer
5283
5284     if {[info exists hoverid] && $id == $hoverid} {
5285         set hoverx $x
5286         set hovery $y
5287         if {[info exists hovertimer]} {
5288             after cancel $hovertimer
5289         }
5290         set hovertimer [after 500 linehover]
5291     }
5292 }
5293
5294 proc lineleave {id} {
5295     global hoverid hovertimer canv
5296
5297     if {[info exists hoverid] && $id == $hoverid} {
5298         $canv delete hover
5299         if {[info exists hovertimer]} {
5300             after cancel $hovertimer
5301             unset hovertimer
5302         }
5303         unset hoverid
5304     }
5305 }
5306
5307 proc linehover {} {
5308     global hoverx hovery hoverid hovertimer
5309     global canv linespc lthickness
5310     global commitinfo mainfont
5311
5312     set text [lindex $commitinfo($hoverid) 0]
5313     set ymax [lindex [$canv cget -scrollregion] 3]
5314     if {$ymax == {}} return
5315     set yfrac [lindex [$canv yview] 0]
5316     set x [expr {$hoverx + 2 * $linespc}]
5317     set y [expr {$hovery + $yfrac * $ymax - $linespc / 2}]
5318     set x0 [expr {$x - 2 * $lthickness}]
5319     set y0 [expr {$y - 2 * $lthickness}]
5320     set x1 [expr {$x + [font measure $mainfont $text] + 2 * $lthickness}]
5321     set y1 [expr {$y + $linespc + 2 * $lthickness}]
5322     set t [$canv create rectangle $x0 $y0 $x1 $y1 \
5323                -fill \#ffff80 -outline black -width 1 -tags hover]
5324     $canv raise $t
5325     set t [$canv create text $x $y -anchor nw -text $text -tags hover \
5326                -font $mainfont]
5327     $canv raise $t
5328 }
5329
5330 proc clickisonarrow {id y} {
5331     global lthickness
5332
5333     set ranges [rowranges $id]
5334     set thresh [expr {2 * $lthickness + 6}]
5335     set n [expr {[llength $ranges] - 1}]
5336     for {set i 1} {$i < $n} {incr i} {
5337         set row [lindex $ranges $i]
5338         if {abs([yc $row] - $y) < $thresh} {
5339             return $i
5340         }
5341     }
5342     return {}
5343 }
5344
5345 proc arrowjump {id n y} {
5346     global canv
5347
5348     # 1 <-> 2, 3 <-> 4, etc...
5349     set n [expr {(($n - 1) ^ 1) + 1}]
5350     set row [lindex [rowranges $id] $n]
5351     set yt [yc $row]
5352     set ymax [lindex [$canv cget -scrollregion] 3]
5353     if {$ymax eq {} || $ymax <= 0} return
5354     set view [$canv yview]
5355     set yspan [expr {[lindex $view 1] - [lindex $view 0]}]
5356     set yfrac [expr {$yt / $ymax - $yspan / 2}]
5357     if {$yfrac < 0} {
5358         set yfrac 0
5359     }
5360     allcanvs yview moveto $yfrac
5361 }
5362
5363 proc lineclick {x y id isnew} {
5364     global ctext commitinfo children canv thickerline curview
5365
5366     if {![info exists commitinfo($id)] && ![getcommit $id]} return
5367     unmarkmatches
5368     unselectline
5369     normalline
5370     $canv delete hover
5371     # draw this line thicker than normal
5372     set thickerline $id
5373     drawlines $id
5374     if {$isnew} {
5375         set ymax [lindex [$canv cget -scrollregion] 3]
5376         if {$ymax eq {}} return
5377         set yfrac [lindex [$canv yview] 0]
5378         set y [expr {$y + $yfrac * $ymax}]
5379     }
5380     set dirn [clickisonarrow $id $y]
5381     if {$dirn ne {}} {
5382         arrowjump $id $dirn $y
5383         return
5384     }
5385
5386     if {$isnew} {
5387         addtohistory [list lineclick $x $y $id 0]
5388     }
5389     # fill the details pane with info about this line
5390     $ctext conf -state normal
5391     clear_ctext
5392     $ctext tag conf link -foreground blue -underline 1
5393     $ctext tag bind link <Enter> { %W configure -cursor hand2 }
5394     $ctext tag bind link <Leave> { %W configure -cursor $curtextcursor }
5395     $ctext insert end "Parent:\t"
5396     $ctext insert end $id [list link link0]
5397     $ctext tag bind link0 <1> [list selbyid $id]
5398     set info $commitinfo($id)
5399     $ctext insert end "\n\t[lindex $info 0]\n"
5400     $ctext insert end "\tAuthor:\t[lindex $info 1]\n"
5401     set date [formatdate [lindex $info 2]]
5402     $ctext insert end "\tDate:\t$date\n"
5403     set kids $children($curview,$id)
5404     if {$kids ne {}} {
5405         $ctext insert end "\nChildren:"
5406         set i 0
5407         foreach child $kids {
5408             incr i
5409             if {![info exists commitinfo($child)] && ![getcommit $child]} continue
5410             set info $commitinfo($child)
5411             $ctext insert end "\n\t"
5412             $ctext insert end $child [list link link$i]
5413             $ctext tag bind link$i <1> [list selbyid $child]
5414             $ctext insert end "\n\t[lindex $info 0]"
5415             $ctext insert end "\n\tAuthor:\t[lindex $info 1]"
5416             set date [formatdate [lindex $info 2]]
5417             $ctext insert end "\n\tDate:\t$date\n"
5418         }
5419     }
5420     $ctext conf -state disabled
5421     init_flist {}
5422 }
5423
5424 proc normalline {} {
5425     global thickerline
5426     if {[info exists thickerline]} {
5427         set id $thickerline
5428         unset thickerline
5429         drawlines $id
5430     }
5431 }
5432
5433 proc selbyid {id} {
5434     global commitrow curview
5435     if {[info exists commitrow($curview,$id)]} {
5436         selectline $commitrow($curview,$id) 1
5437     }
5438 }
5439
5440 proc mstime {} {
5441     global startmstime
5442     if {![info exists startmstime]} {
5443         set startmstime [clock clicks -milliseconds]
5444     }
5445     return [format "%.3f" [expr {([clock click -milliseconds] - $startmstime) / 1000.0}]]
5446 }
5447
5448 proc rowmenu {x y id} {
5449     global rowctxmenu commitrow selectedline rowmenuid curview
5450     global nullid fakerowmenu mainhead
5451
5452     set rowmenuid $id
5453     if {![info exists selectedline]
5454         || $commitrow($curview,$id) eq $selectedline} {
5455         set state disabled
5456     } else {
5457         set state normal
5458     }
5459     if {$id ne $nullid} {
5460         set menu $rowctxmenu
5461         $menu entryconfigure 7 -label "Reset $mainhead branch to here"
5462     } else {
5463         set menu $fakerowmenu
5464     }
5465     $menu entryconfigure "Diff this*" -state $state
5466     $menu entryconfigure "Diff selected*" -state $state
5467     $menu entryconfigure "Make patch" -state $state
5468     tk_popup $menu $x $y
5469 }
5470
5471 proc diffvssel {dirn} {
5472     global rowmenuid selectedline displayorder
5473
5474     if {![info exists selectedline]} return
5475     if {$dirn} {
5476         set oldid [lindex $displayorder $selectedline]
5477         set newid $rowmenuid
5478     } else {
5479         set oldid $rowmenuid
5480         set newid [lindex $displayorder $selectedline]
5481     }
5482     addtohistory [list doseldiff $oldid $newid]
5483     doseldiff $oldid $newid
5484 }
5485
5486 proc doseldiff {oldid newid} {
5487     global ctext
5488     global commitinfo
5489
5490     $ctext conf -state normal
5491     clear_ctext
5492     init_flist "Top"
5493     $ctext insert end "From "
5494     $ctext tag conf link -foreground blue -underline 1
5495     $ctext tag bind link <Enter> { %W configure -cursor hand2 }
5496     $ctext tag bind link <Leave> { %W configure -cursor $curtextcursor }
5497     $ctext tag bind link0 <1> [list selbyid $oldid]
5498     $ctext insert end $oldid [list link link0]
5499     $ctext insert end "\n     "
5500     $ctext insert end [lindex $commitinfo($oldid) 0]
5501     $ctext insert end "\n\nTo   "
5502     $ctext tag bind link1 <1> [list selbyid $newid]
5503     $ctext insert end $newid [list link link1]
5504     $ctext insert end "\n     "
5505     $ctext insert end [lindex $commitinfo($newid) 0]
5506     $ctext insert end "\n"
5507     $ctext conf -state disabled
5508     $ctext tag remove found 1.0 end
5509     startdiff [list $oldid $newid]
5510 }
5511
5512 proc mkpatch {} {
5513     global rowmenuid currentid commitinfo patchtop patchnum
5514
5515     if {![info exists currentid]} return
5516     set oldid $currentid
5517     set oldhead [lindex $commitinfo($oldid) 0]
5518     set newid $rowmenuid
5519     set newhead [lindex $commitinfo($newid) 0]
5520     set top .patch
5521     set patchtop $top
5522     catch {destroy $top}
5523     toplevel $top
5524     label $top.title -text "Generate patch"
5525     grid $top.title - -pady 10
5526     label $top.from -text "From:"
5527     entry $top.fromsha1 -width 40 -relief flat
5528     $top.fromsha1 insert 0 $oldid
5529     $top.fromsha1 conf -state readonly
5530     grid $top.from $top.fromsha1 -sticky w
5531     entry $top.fromhead -width 60 -relief flat
5532     $top.fromhead insert 0 $oldhead
5533     $top.fromhead conf -state readonly
5534     grid x $top.fromhead -sticky w
5535     label $top.to -text "To:"
5536     entry $top.tosha1 -width 40 -relief flat
5537     $top.tosha1 insert 0 $newid
5538     $top.tosha1 conf -state readonly
5539     grid $top.to $top.tosha1 -sticky w
5540     entry $top.tohead -width 60 -relief flat
5541     $top.tohead insert 0 $newhead
5542     $top.tohead conf -state readonly
5543     grid x $top.tohead -sticky w
5544     button $top.rev -text "Reverse" -command mkpatchrev -padx 5
5545     grid $top.rev x -pady 10
5546     label $top.flab -text "Output file:"
5547     entry $top.fname -width 60
5548     $top.fname insert 0 [file normalize "patch$patchnum.patch"]
5549     incr patchnum
5550     grid $top.flab $top.fname -sticky w
5551     frame $top.buts
5552     button $top.buts.gen -text "Generate" -command mkpatchgo
5553     button $top.buts.can -text "Cancel" -command mkpatchcan
5554     grid $top.buts.gen $top.buts.can
5555     grid columnconfigure $top.buts 0 -weight 1 -uniform a
5556     grid columnconfigure $top.buts 1 -weight 1 -uniform a
5557     grid $top.buts - -pady 10 -sticky ew
5558     focus $top.fname
5559 }
5560
5561 proc mkpatchrev {} {
5562     global patchtop
5563
5564     set oldid [$patchtop.fromsha1 get]
5565     set oldhead [$patchtop.fromhead get]
5566     set newid [$patchtop.tosha1 get]
5567     set newhead [$patchtop.tohead get]
5568     foreach e [list fromsha1 fromhead tosha1 tohead] \
5569             v [list $newid $newhead $oldid $oldhead] {
5570         $patchtop.$e conf -state normal
5571         $patchtop.$e delete 0 end
5572         $patchtop.$e insert 0 $v
5573         $patchtop.$e conf -state readonly
5574     }
5575 }
5576
5577 proc mkpatchgo {} {
5578     global patchtop nullid
5579
5580     set oldid [$patchtop.fromsha1 get]
5581     set newid [$patchtop.tosha1 get]
5582     set fname [$patchtop.fname get]
5583     if {$newid eq $nullid} {
5584         set cmd [list git diff-index -p $oldid]
5585     } elseif {$oldid eq $nullid} {
5586         set cmd [list git diff-index -p -R $newid]
5587     } else {
5588         set cmd [list git diff-tree -p $oldid $newid]
5589     }
5590     lappend cmd >$fname &
5591     if {[catch {eval exec $cmd} err]} {
5592         error_popup "Error creating patch: $err"
5593     }
5594     catch {destroy $patchtop}
5595     unset patchtop
5596 }
5597
5598 proc mkpatchcan {} {
5599     global patchtop
5600
5601     catch {destroy $patchtop}
5602     unset patchtop
5603 }
5604
5605 proc mktag {} {
5606     global rowmenuid mktagtop commitinfo
5607
5608     set top .maketag
5609     set mktagtop $top
5610     catch {destroy $top}
5611     toplevel $top
5612     label $top.title -text "Create tag"
5613     grid $top.title - -pady 10
5614     label $top.id -text "ID:"
5615     entry $top.sha1 -width 40 -relief flat
5616     $top.sha1 insert 0 $rowmenuid
5617     $top.sha1 conf -state readonly
5618     grid $top.id $top.sha1 -sticky w
5619     entry $top.head -width 60 -relief flat
5620     $top.head insert 0 [lindex $commitinfo($rowmenuid) 0]
5621     $top.head conf -state readonly
5622     grid x $top.head -sticky w
5623     label $top.tlab -text "Tag name:"
5624     entry $top.tag -width 60
5625     grid $top.tlab $top.tag -sticky w
5626     frame $top.buts
5627     button $top.buts.gen -text "Create" -command mktaggo
5628     button $top.buts.can -text "Cancel" -command mktagcan
5629     grid $top.buts.gen $top.buts.can
5630     grid columnconfigure $top.buts 0 -weight 1 -uniform a
5631     grid columnconfigure $top.buts 1 -weight 1 -uniform a
5632     grid $top.buts - -pady 10 -sticky ew
5633     focus $top.tag
5634 }
5635
5636 proc domktag {} {
5637     global mktagtop env tagids idtags
5638
5639     set id [$mktagtop.sha1 get]
5640     set tag [$mktagtop.tag get]
5641     if {$tag == {}} {
5642         error_popup "No tag name specified"
5643         return
5644     }
5645     if {[info exists tagids($tag)]} {
5646         error_popup "Tag \"$tag\" already exists"
5647         return
5648     }
5649     if {[catch {
5650         set dir [gitdir]
5651         set fname [file join $dir "refs/tags" $tag]
5652         set f [open $fname w]
5653         puts $f $id
5654         close $f
5655     } err]} {
5656         error_popup "Error creating tag: $err"
5657         return
5658     }
5659
5660     set tagids($tag) $id
5661     lappend idtags($id) $tag
5662     redrawtags $id
5663     addedtag $id
5664 }
5665
5666 proc redrawtags {id} {
5667     global canv linehtag commitrow idpos selectedline curview
5668     global mainfont canvxmax iddrawn
5669
5670     if {![info exists commitrow($curview,$id)]} return
5671     if {![info exists iddrawn($id)]} return
5672     drawcommits $commitrow($curview,$id)
5673     $canv delete tag.$id
5674     set xt [eval drawtags $id $idpos($id)]
5675     $canv coords $linehtag($commitrow($curview,$id)) $xt [lindex $idpos($id) 2]
5676     set text [$canv itemcget $linehtag($commitrow($curview,$id)) -text]
5677     set xr [expr {$xt + [font measure $mainfont $text]}]
5678     if {$xr > $canvxmax} {
5679         set canvxmax $xr
5680         setcanvscroll
5681     }
5682     if {[info exists selectedline]
5683         && $selectedline == $commitrow($curview,$id)} {
5684         selectline $selectedline 0
5685     }
5686 }
5687
5688 proc mktagcan {} {
5689     global mktagtop
5690
5691     catch {destroy $mktagtop}
5692     unset mktagtop
5693 }
5694
5695 proc mktaggo {} {
5696     domktag
5697     mktagcan
5698 }
5699
5700 proc writecommit {} {
5701     global rowmenuid wrcomtop commitinfo wrcomcmd
5702
5703     set top .writecommit
5704     set wrcomtop $top
5705     catch {destroy $top}
5706     toplevel $top
5707     label $top.title -text "Write commit to file"
5708     grid $top.title - -pady 10
5709     label $top.id -text "ID:"
5710     entry $top.sha1 -width 40 -relief flat
5711     $top.sha1 insert 0 $rowmenuid
5712     $top.sha1 conf -state readonly
5713     grid $top.id $top.sha1 -sticky w
5714     entry $top.head -width 60 -relief flat
5715     $top.head insert 0 [lindex $commitinfo($rowmenuid) 0]
5716     $top.head conf -state readonly
5717     grid x $top.head -sticky w
5718     label $top.clab -text "Command:"
5719     entry $top.cmd -width 60 -textvariable wrcomcmd
5720     grid $top.clab $top.cmd -sticky w -pady 10
5721     label $top.flab -text "Output file:"
5722     entry $top.fname -width 60
5723     $top.fname insert 0 [file normalize "commit-[string range $rowmenuid 0 6]"]
5724     grid $top.flab $top.fname -sticky w
5725     frame $top.buts
5726     button $top.buts.gen -text "Write" -command wrcomgo
5727     button $top.buts.can -text "Cancel" -command wrcomcan
5728     grid $top.buts.gen $top.buts.can
5729     grid columnconfigure $top.buts 0 -weight 1 -uniform a
5730     grid columnconfigure $top.buts 1 -weight 1 -uniform a
5731     grid $top.buts - -pady 10 -sticky ew
5732     focus $top.fname
5733 }
5734
5735 proc wrcomgo {} {
5736     global wrcomtop
5737
5738     set id [$wrcomtop.sha1 get]
5739     set cmd "echo $id | [$wrcomtop.cmd get]"
5740     set fname [$wrcomtop.fname get]
5741     if {[catch {exec sh -c $cmd >$fname &} err]} {
5742         error_popup "Error writing commit: $err"
5743     }
5744     catch {destroy $wrcomtop}
5745     unset wrcomtop
5746 }
5747
5748 proc wrcomcan {} {
5749     global wrcomtop
5750
5751     catch {destroy $wrcomtop}
5752     unset wrcomtop
5753 }
5754
5755 proc mkbranch {} {
5756     global rowmenuid mkbrtop
5757
5758     set top .makebranch
5759     catch {destroy $top}
5760     toplevel $top
5761     label $top.title -text "Create new branch"
5762     grid $top.title - -pady 10
5763     label $top.id -text "ID:"
5764     entry $top.sha1 -width 40 -relief flat
5765     $top.sha1 insert 0 $rowmenuid
5766     $top.sha1 conf -state readonly
5767     grid $top.id $top.sha1 -sticky w
5768     label $top.nlab -text "Name:"
5769     entry $top.name -width 40
5770     grid $top.nlab $top.name -sticky w
5771     frame $top.buts
5772     button $top.buts.go -text "Create" -command [list mkbrgo $top]
5773     button $top.buts.can -text "Cancel" -command "catch {destroy $top}"
5774     grid $top.buts.go $top.buts.can
5775     grid columnconfigure $top.buts 0 -weight 1 -uniform a
5776     grid columnconfigure $top.buts 1 -weight 1 -uniform a
5777     grid $top.buts - -pady 10 -sticky ew
5778     focus $top.name
5779 }
5780
5781 proc mkbrgo {top} {
5782     global headids idheads
5783
5784     set name [$top.name get]
5785     set id [$top.sha1 get]
5786     if {$name eq {}} {
5787         error_popup "Please specify a name for the new branch"
5788         return
5789     }
5790     catch {destroy $top}
5791     nowbusy newbranch
5792     update
5793     if {[catch {
5794         exec git branch $name $id
5795     } err]} {
5796         notbusy newbranch
5797         error_popup $err
5798     } else {
5799         set headids($name) $id
5800         lappend idheads($id) $name
5801         addedhead $id $name
5802         notbusy newbranch
5803         redrawtags $id
5804         dispneartags 0
5805     }
5806 }
5807
5808 proc cherrypick {} {
5809     global rowmenuid curview commitrow
5810     global mainhead
5811
5812     set oldhead [exec git rev-parse HEAD]
5813     set dheads [descheads $rowmenuid]
5814     if {$dheads ne {} && [lsearch -exact $dheads $oldhead] >= 0} {
5815         set ok [confirm_popup "Commit [string range $rowmenuid 0 7] is already\
5816                         included in branch $mainhead -- really re-apply it?"]
5817         if {!$ok} return
5818     }
5819     nowbusy cherrypick
5820     update
5821     # Unfortunately git-cherry-pick writes stuff to stderr even when
5822     # no error occurs, and exec takes that as an indication of error...
5823     if {[catch {exec sh -c "git cherry-pick -r $rowmenuid 2>&1"} err]} {
5824         notbusy cherrypick
5825         error_popup $err
5826         return
5827     }
5828     set newhead [exec git rev-parse HEAD]
5829     if {$newhead eq $oldhead} {
5830         notbusy cherrypick
5831         error_popup "No changes committed"
5832         return
5833     }
5834     addnewchild $newhead $oldhead
5835     if {[info exists commitrow($curview,$oldhead)]} {
5836         insertrow $commitrow($curview,$oldhead) $newhead
5837         if {$mainhead ne {}} {
5838             movehead $newhead $mainhead
5839             movedhead $newhead $mainhead
5840         }
5841         redrawtags $oldhead
5842         redrawtags $newhead
5843     }
5844     notbusy cherrypick
5845 }
5846
5847 proc resethead {} {
5848     global mainheadid mainhead rowmenuid confirm_ok resettype
5849     global showlocalchanges
5850
5851     set confirm_ok 0
5852     set w ".confirmreset"
5853     toplevel $w
5854     wm transient $w .
5855     wm title $w "Confirm reset"
5856     message $w.m -text \
5857         "Reset branch $mainhead to [string range $rowmenuid 0 7]?" \
5858         -justify center -aspect 1000
5859     pack $w.m -side top -fill x -padx 20 -pady 20
5860     frame $w.f -relief sunken -border 2
5861     message $w.f.rt -text "Reset type:" -aspect 1000
5862     grid $w.f.rt -sticky w
5863     set resettype mixed
5864     radiobutton $w.f.soft -value soft -variable resettype -justify left \
5865         -text "Soft: Leave working tree and index untouched"
5866     grid $w.f.soft -sticky w
5867     radiobutton $w.f.mixed -value mixed -variable resettype -justify left \
5868         -text "Mixed: Leave working tree untouched, reset index"
5869     grid $w.f.mixed -sticky w
5870     radiobutton $w.f.hard -value hard -variable resettype -justify left \
5871         -text "Hard: Reset working tree and index\n(discard ALL local changes)"
5872     grid $w.f.hard -sticky w
5873     pack $w.f -side top -fill x
5874     button $w.ok -text OK -command "set confirm_ok 1; destroy $w"
5875     pack $w.ok -side left -fill x -padx 20 -pady 20
5876     button $w.cancel -text Cancel -command "destroy $w"
5877     pack $w.cancel -side right -fill x -padx 20 -pady 20
5878     bind $w <Visibility> "grab $w; focus $w"
5879     tkwait window $w
5880     if {!$confirm_ok} return
5881     if {[catch {set fd [open \
5882             [list | sh -c "git reset --$resettype $rowmenuid 2>&1"] r]} err]} {
5883         error_popup $err
5884     } else {
5885         dohidelocalchanges
5886         set w ".resetprogress"
5887         filerun $fd [list readresetstat $fd $w]
5888         toplevel $w
5889         wm transient $w
5890         wm title $w "Reset progress"
5891         message $w.m -text "Reset in progress, please wait..." \
5892             -justify center -aspect 1000
5893         pack $w.m -side top -fill x -padx 20 -pady 5
5894         canvas $w.c -width 150 -height 20 -bg white
5895         $w.c create rect 0 0 0 20 -fill green -tags rect
5896         pack $w.c -side top -fill x -padx 20 -pady 5 -expand 1
5897         nowbusy reset
5898     }
5899 }
5900
5901 proc readresetstat {fd w} {
5902     global mainhead mainheadid showlocalchanges
5903
5904     if {[gets $fd line] >= 0} {
5905         if {[regexp {([0-9]+)% \(([0-9]+)/([0-9]+)\)} $line match p m n]} {
5906             set x [expr {($m * 150) / $n}]
5907             $w.c coords rect 0 0 $x 20
5908         }
5909         return 1
5910     }
5911     destroy $w
5912     notbusy reset
5913     if {[catch {close $fd} err]} {
5914         error_popup $err
5915     }
5916     set oldhead $mainheadid
5917     set newhead [exec git rev-parse HEAD]
5918     if {$newhead ne $oldhead} {
5919         movehead $newhead $mainhead
5920         movedhead $newhead $mainhead
5921         set mainheadid $newhead
5922         redrawtags $oldhead
5923         redrawtags $newhead
5924     }
5925     if {$showlocalchanges} {
5926         doshowlocalchanges
5927     }
5928     return 0
5929 }
5930
5931 # context menu for a head
5932 proc headmenu {x y id head} {
5933     global headmenuid headmenuhead headctxmenu mainhead
5934
5935     set headmenuid $id
5936     set headmenuhead $head
5937     set state normal
5938     if {$head eq $mainhead} {
5939         set state disabled
5940     }
5941     $headctxmenu entryconfigure 0 -state $state
5942     $headctxmenu entryconfigure 1 -state $state
5943     tk_popup $headctxmenu $x $y
5944 }
5945
5946 proc cobranch {} {
5947     global headmenuid headmenuhead mainhead headids
5948     global showlocalchanges mainheadid
5949
5950     # check the tree is clean first??
5951     set oldmainhead $mainhead
5952     nowbusy checkout
5953     update
5954     dohidelocalchanges
5955     if {[catch {
5956         exec git checkout -q $headmenuhead
5957     } err]} {
5958         notbusy checkout
5959         error_popup $err
5960     } else {
5961         notbusy checkout
5962         set mainhead $headmenuhead
5963         set mainheadid $headmenuid
5964         if {[info exists headids($oldmainhead)]} {
5965             redrawtags $headids($oldmainhead)
5966         }
5967         redrawtags $headmenuid
5968     }
5969     if {$showlocalchanges} {
5970         dodiffindex
5971     }
5972 }
5973
5974 proc rmbranch {} {
5975     global headmenuid headmenuhead mainhead
5976     global headids idheads
5977
5978     set head $headmenuhead
5979     set id $headmenuid
5980     # this check shouldn't be needed any more...
5981     if {$head eq $mainhead} {
5982         error_popup "Cannot delete the currently checked-out branch"
5983         return
5984     }
5985     set dheads [descheads $id]
5986     if {$dheads eq $headids($head)} {
5987         # the stuff on this branch isn't on any other branch
5988         if {![confirm_popup "The commits on branch $head aren't on any other\
5989                         branch.\nReally delete branch $head?"]} return
5990     }
5991     nowbusy rmbranch
5992     update
5993     if {[catch {exec git branch -D $head} err]} {
5994         notbusy rmbranch
5995         error_popup $err
5996         return
5997     }
5998     removehead $id $head
5999     removedhead $id $head
6000     redrawtags $id
6001     notbusy rmbranch
6002     dispneartags 0
6003 }
6004
6005 # Stuff for finding nearby tags
6006 proc getallcommits {} {
6007     global allcommits allids nbmp nextarc seeds
6008
6009     set allids {}
6010     set nbmp 0
6011     set nextarc 0
6012     set allcommits 0
6013     set seeds {}
6014     regetallcommits
6015 }
6016
6017 # Called when the graph might have changed
6018 proc regetallcommits {} {
6019     global allcommits seeds
6020
6021     set cmd [concat | git rev-list --all --parents]
6022     foreach id $seeds {
6023         lappend cmd "^$id"
6024     }
6025     set fd [open $cmd r]
6026     fconfigure $fd -blocking 0
6027     incr allcommits
6028     nowbusy allcommits
6029     filerun $fd [list getallclines $fd]
6030 }
6031
6032 # Since most commits have 1 parent and 1 child, we group strings of
6033 # such commits into "arcs" joining branch/merge points (BMPs), which
6034 # are commits that either don't have 1 parent or don't have 1 child.
6035 #
6036 # arcnos(id) - incoming arcs for BMP, arc we're on for other nodes
6037 # arcout(id) - outgoing arcs for BMP
6038 # arcids(a) - list of IDs on arc including end but not start
6039 # arcstart(a) - BMP ID at start of arc
6040 # arcend(a) - BMP ID at end of arc
6041 # growing(a) - arc a is still growing
6042 # arctags(a) - IDs out of arcids (excluding end) that have tags
6043 # archeads(a) - IDs out of arcids (excluding end) that have heads
6044 # The start of an arc is at the descendent end, so "incoming" means
6045 # coming from descendents, and "outgoing" means going towards ancestors.
6046
6047 proc getallclines {fd} {
6048     global allids allparents allchildren idtags idheads nextarc nbmp
6049     global arcnos arcids arctags arcout arcend arcstart archeads growing
6050     global seeds allcommits
6051
6052     set nid 0
6053     while {[incr nid] <= 1000 && [gets $fd line] >= 0} {
6054         set id [lindex $line 0]
6055         if {[info exists allparents($id)]} {
6056             # seen it already
6057             continue
6058         }
6059         lappend allids $id
6060         set olds [lrange $line 1 end]
6061         set allparents($id) $olds
6062         if {![info exists allchildren($id)]} {
6063             set allchildren($id) {}
6064             set arcnos($id) {}
6065             lappend seeds $id
6066         } else {
6067             set a $arcnos($id)
6068             if {[llength $olds] == 1 && [llength $a] == 1} {
6069                 lappend arcids($a) $id
6070                 if {[info exists idtags($id)]} {
6071                     lappend arctags($a) $id
6072                 }
6073                 if {[info exists idheads($id)]} {
6074                     lappend archeads($a) $id
6075                 }
6076                 if {[info exists allparents($olds)]} {
6077                     # seen parent already
6078                     if {![info exists arcout($olds)]} {
6079                         splitarc $olds
6080                     }
6081                     lappend arcids($a) $olds
6082                     set arcend($a) $olds
6083                     unset growing($a)
6084                 }
6085                 lappend allchildren($olds) $id
6086                 lappend arcnos($olds) $a
6087                 continue
6088             }
6089         }
6090         incr nbmp
6091         foreach a $arcnos($id) {
6092             lappend arcids($a) $id
6093             set arcend($a) $id
6094             unset growing($a)
6095         }
6096
6097         set ao {}
6098         foreach p $olds {
6099             lappend allchildren($p) $id
6100             set a [incr nextarc]
6101             set arcstart($a) $id
6102             set archeads($a) {}
6103             set arctags($a) {}
6104             set archeads($a) {}
6105             set arcids($a) {}
6106             lappend ao $a
6107             set growing($a) 1
6108             if {[info exists allparents($p)]} {
6109                 # seen it already, may need to make a new branch
6110                 if {![info exists arcout($p)]} {
6111                     splitarc $p
6112                 }
6113                 lappend arcids($a) $p
6114                 set arcend($a) $p
6115                 unset growing($a)
6116             }
6117             lappend arcnos($p) $a
6118         }
6119         set arcout($id) $ao
6120     }
6121     if {$nid > 0} {
6122         global cached_dheads cached_dtags cached_atags
6123         catch {unset cached_dheads}
6124         catch {unset cached_dtags}
6125         catch {unset cached_atags}
6126     }
6127     if {![eof $fd]} {
6128         return [expr {$nid >= 1000? 2: 1}]
6129     }
6130     close $fd
6131     if {[incr allcommits -1] == 0} {
6132         notbusy allcommits
6133     }
6134     dispneartags 0
6135     return 0
6136 }
6137
6138 proc recalcarc {a} {
6139     global arctags archeads arcids idtags idheads
6140
6141     set at {}
6142     set ah {}
6143     foreach id [lrange $arcids($a) 0 end-1] {
6144         if {[info exists idtags($id)]} {
6145             lappend at $id
6146         }
6147         if {[info exists idheads($id)]} {
6148             lappend ah $id
6149         }
6150     }
6151     set arctags($a) $at
6152     set archeads($a) $ah
6153 }
6154
6155 proc splitarc {p} {
6156     global arcnos arcids nextarc nbmp arctags archeads idtags idheads
6157     global arcstart arcend arcout allparents growing
6158
6159     set a $arcnos($p)
6160     if {[llength $a] != 1} {
6161         puts "oops splitarc called but [llength $a] arcs already"
6162         return
6163     }
6164     set a [lindex $a 0]
6165     set i [lsearch -exact $arcids($a) $p]
6166     if {$i < 0} {
6167         puts "oops splitarc $p not in arc $a"
6168         return
6169     }
6170     set na [incr nextarc]
6171     if {[info exists arcend($a)]} {
6172         set arcend($na) $arcend($a)
6173     } else {
6174         set l [lindex $allparents([lindex $arcids($a) end]) 0]
6175         set j [lsearch -exact $arcnos($l) $a]
6176         set arcnos($l) [lreplace $arcnos($l) $j $j $na]
6177     }
6178     set tail [lrange $arcids($a) [expr {$i+1}] end]
6179     set arcids($a) [lrange $arcids($a) 0 $i]
6180     set arcend($a) $p
6181     set arcstart($na) $p
6182     set arcout($p) $na
6183     set arcids($na) $tail
6184     if {[info exists growing($a)]} {
6185         set growing($na) 1
6186         unset growing($a)
6187     }
6188     incr nbmp
6189
6190     foreach id $tail {
6191         if {[llength $arcnos($id)] == 1} {
6192             set arcnos($id) $na
6193         } else {
6194             set j [lsearch -exact $arcnos($id) $a]
6195             set arcnos($id) [lreplace $arcnos($id) $j $j $na]
6196         }
6197     }
6198
6199     # reconstruct tags and heads lists
6200     if {$arctags($a) ne {} || $archeads($a) ne {}} {
6201         recalcarc $a
6202         recalcarc $na
6203     } else {
6204         set arctags($na) {}
6205         set archeads($na) {}
6206     }
6207 }
6208
6209 # Update things for a new commit added that is a child of one
6210 # existing commit.  Used when cherry-picking.
6211 proc addnewchild {id p} {
6212     global allids allparents allchildren idtags nextarc nbmp
6213     global arcnos arcids arctags arcout arcend arcstart archeads growing
6214     global seeds
6215
6216     lappend allids $id
6217     set allparents($id) [list $p]
6218     set allchildren($id) {}
6219     set arcnos($id) {}
6220     lappend seeds $id
6221     incr nbmp
6222     lappend allchildren($p) $id
6223     set a [incr nextarc]
6224     set arcstart($a) $id
6225     set archeads($a) {}
6226     set arctags($a) {}
6227     set arcids($a) [list $p]
6228     set arcend($a) $p
6229     if {![info exists arcout($p)]} {
6230         splitarc $p
6231     }
6232     lappend arcnos($p) $a
6233     set arcout($id) [list $a]
6234 }
6235
6236 # Returns 1 if a is an ancestor of b, -1 if b is an ancestor of a,
6237 # or 0 if neither is true.
6238 proc anc_or_desc {a b} {
6239     global arcout arcstart arcend arcnos cached_isanc
6240
6241     if {$arcnos($a) eq $arcnos($b)} {
6242         # Both are on the same arc(s); either both are the same BMP,
6243         # or if one is not a BMP, the other is also not a BMP or is
6244         # the BMP at end of the arc (and it only has 1 incoming arc).
6245         # Or both can be BMPs with no incoming arcs.
6246         if {$a eq $b || $arcnos($a) eq {}} {
6247             return 0
6248         }
6249         # assert {[llength $arcnos($a)] == 1}
6250         set arc [lindex $arcnos($a) 0]
6251         set i [lsearch -exact $arcids($arc) $a]
6252         set j [lsearch -exact $arcids($arc) $b]
6253         if {$i < 0 || $i > $j} {
6254             return 1
6255         } else {
6256             return -1
6257         }
6258     }
6259
6260     if {![info exists arcout($a)]} {
6261         set arc [lindex $arcnos($a) 0]
6262         if {[info exists arcend($arc)]} {
6263             set aend $arcend($arc)
6264         } else {
6265             set aend {}
6266         }
6267         set a $arcstart($arc)
6268     } else {
6269         set aend $a
6270     }
6271     if {![info exists arcout($b)]} {
6272         set arc [lindex $arcnos($b) 0]
6273         if {[info exists arcend($arc)]} {
6274             set bend $arcend($arc)
6275         } else {
6276             set bend {}
6277         }
6278         set b $arcstart($arc)
6279     } else {
6280         set bend $b
6281     }
6282     if {$a eq $bend} {
6283         return 1
6284     }
6285     if {$b eq $aend} {
6286         return -1
6287     }
6288     if {[info exists cached_isanc($a,$bend)]} {
6289         if {$cached_isanc($a,$bend)} {
6290             return 1
6291         }
6292     }
6293     if {[info exists cached_isanc($b,$aend)]} {
6294         if {$cached_isanc($b,$aend)} {
6295             return -1
6296         }
6297         if {[info exists cached_isanc($a,$bend)]} {
6298             return 0
6299         }
6300     }
6301
6302     set todo [list $a $b]
6303     set anc($a) a
6304     set anc($b) b
6305     for {set i 0} {$i < [llength $todo]} {incr i} {
6306         set x [lindex $todo $i]
6307         if {$anc($x) eq {}} {
6308             continue
6309         }
6310         foreach arc $arcnos($x) {
6311             set xd $arcstart($arc)
6312             if {$xd eq $bend} {
6313                 set cached_isanc($a,$bend) 1
6314                 set cached_isanc($b,$aend) 0
6315                 return 1
6316             } elseif {$xd eq $aend} {
6317                 set cached_isanc($b,$aend) 1
6318                 set cached_isanc($a,$bend) 0
6319                 return -1
6320             }
6321             if {![info exists anc($xd)]} {
6322                 set anc($xd) $anc($x)
6323                 lappend todo $xd
6324             } elseif {$anc($xd) ne $anc($x)} {
6325                 set anc($xd) {}
6326             }
6327         }
6328     }
6329     set cached_isanc($a,$bend) 0
6330     set cached_isanc($b,$aend) 0
6331     return 0
6332 }
6333
6334 # This identifies whether $desc has an ancestor that is
6335 # a growing tip of the graph and which is not an ancestor of $anc
6336 # and returns 0 if so and 1 if not.
6337 # If we subsequently discover a tag on such a growing tip, and that
6338 # turns out to be a descendent of $anc (which it could, since we
6339 # don't necessarily see children before parents), then $desc
6340 # isn't a good choice to display as a descendent tag of
6341 # $anc (since it is the descendent of another tag which is
6342 # a descendent of $anc).  Similarly, $anc isn't a good choice to
6343 # display as a ancestor tag of $desc.
6344 #
6345 proc is_certain {desc anc} {
6346     global arcnos arcout arcstart arcend growing problems
6347
6348     set certain {}
6349     if {[llength $arcnos($anc)] == 1} {
6350         # tags on the same arc are certain
6351         if {$arcnos($desc) eq $arcnos($anc)} {
6352             return 1
6353         }
6354         if {![info exists arcout($anc)]} {
6355             # if $anc is partway along an arc, use the start of the arc instead
6356             set a [lindex $arcnos($anc) 0]
6357             set anc $arcstart($a)
6358         }
6359     }
6360     if {[llength $arcnos($desc)] > 1 || [info exists arcout($desc)]} {
6361         set x $desc
6362     } else {
6363         set a [lindex $arcnos($desc) 0]
6364         set x $arcend($a)
6365     }
6366     if {$x == $anc} {
6367         return 1
6368     }
6369     set anclist [list $x]
6370     set dl($x) 1
6371     set nnh 1
6372     set ngrowanc 0
6373     for {set i 0} {$i < [llength $anclist] && ($nnh > 0 || $ngrowanc > 0)} {incr i} {
6374         set x [lindex $anclist $i]
6375         if {$dl($x)} {
6376             incr nnh -1
6377         }
6378         set done($x) 1
6379         foreach a $arcout($x) {
6380             if {[info exists growing($a)]} {
6381                 if {![info exists growanc($x)] && $dl($x)} {
6382                     set growanc($x) 1
6383                     incr ngrowanc
6384                 }
6385             } else {
6386                 set y $arcend($a)
6387                 if {[info exists dl($y)]} {
6388                     if {$dl($y)} {
6389                         if {!$dl($x)} {
6390                             set dl($y) 0
6391                             if {![info exists done($y)]} {
6392                                 incr nnh -1
6393                             }
6394                             if {[info exists growanc($x)]} {
6395                                 incr ngrowanc -1
6396                             }
6397                             set xl [list $y]
6398                             for {set k 0} {$k < [llength $xl]} {incr k} {
6399                                 set z [lindex $xl $k]
6400                                 foreach c $arcout($z) {
6401                                     if {[info exists arcend($c)]} {
6402                                         set v $arcend($c)
6403                                         if {[info exists dl($v)] && $dl($v)} {
6404                                             set dl($v) 0
6405                                             if {![info exists done($v)]} {
6406                                                 incr nnh -1
6407                                             }
6408                                             if {[info exists growanc($v)]} {
6409                                                 incr ngrowanc -1
6410                                             }
6411                                             lappend xl $v
6412                                         }
6413                                     }
6414                                 }
6415                             }
6416                         }
6417                     }
6418                 } elseif {$y eq $anc || !$dl($x)} {
6419                     set dl($y) 0
6420                     lappend anclist $y
6421                 } else {
6422                     set dl($y) 1
6423                     lappend anclist $y
6424                     incr nnh
6425                 }
6426             }
6427         }
6428     }
6429     foreach x [array names growanc] {
6430         if {$dl($x)} {
6431             return 0
6432         }
6433         return 0
6434     }
6435     return 1
6436 }
6437
6438 proc validate_arctags {a} {
6439     global arctags idtags
6440
6441     set i -1
6442     set na $arctags($a)
6443     foreach id $arctags($a) {
6444         incr i
6445         if {![info exists idtags($id)]} {
6446             set na [lreplace $na $i $i]
6447             incr i -1
6448         }
6449     }
6450     set arctags($a) $na
6451 }
6452
6453 proc validate_archeads {a} {
6454     global archeads idheads
6455
6456     set i -1
6457     set na $archeads($a)
6458     foreach id $archeads($a) {
6459         incr i
6460         if {![info exists idheads($id)]} {
6461             set na [lreplace $na $i $i]
6462             incr i -1
6463         }
6464     }
6465     set archeads($a) $na
6466 }
6467
6468 # Return the list of IDs that have tags that are descendents of id,
6469 # ignoring IDs that are descendents of IDs already reported.
6470 proc desctags {id} {
6471     global arcnos arcstart arcids arctags idtags allparents
6472     global growing cached_dtags
6473
6474     if {![info exists allparents($id)]} {
6475         return {}
6476     }
6477     set t1 [clock clicks -milliseconds]
6478     set argid $id
6479     if {[llength $arcnos($id)] == 1 && [llength $allparents($id)] == 1} {
6480         # part-way along an arc; check that arc first
6481         set a [lindex $arcnos($id) 0]
6482         if {$arctags($a) ne {}} {
6483             validate_arctags $a
6484             set i [lsearch -exact $arcids($a) $id]
6485             set tid {}
6486             foreach t $arctags($a) {
6487                 set j [lsearch -exact $arcids($a) $t]
6488                 if {$j >= $i} break
6489                 set tid $t
6490             }
6491             if {$tid ne {}} {
6492                 return $tid
6493             }
6494         }
6495         set id $arcstart($a)
6496         if {[info exists idtags($id)]} {
6497             return $id
6498         }
6499     }
6500     if {[info exists cached_dtags($id)]} {
6501         return $cached_dtags($id)
6502     }
6503
6504     set origid $id
6505     set todo [list $id]
6506     set queued($id) 1
6507     set nc 1
6508     for {set i 0} {$i < [llength $todo] && $nc > 0} {incr i} {
6509         set id [lindex $todo $i]
6510         set done($id) 1
6511         set ta [info exists hastaggedancestor($id)]
6512         if {!$ta} {
6513             incr nc -1
6514         }
6515         # ignore tags on starting node
6516         if {!$ta && $i > 0} {
6517             if {[info exists idtags($id)]} {
6518                 set tagloc($id) $id
6519                 set ta 1
6520             } elseif {[info exists cached_dtags($id)]} {
6521                 set tagloc($id) $cached_dtags($id)
6522                 set ta 1
6523             }
6524         }
6525         foreach a $arcnos($id) {
6526             set d $arcstart($a)
6527             if {!$ta && $arctags($a) ne {}} {
6528                 validate_arctags $a
6529                 if {$arctags($a) ne {}} {
6530                     lappend tagloc($id) [lindex $arctags($a) end]
6531                 }
6532             }
6533             if {$ta || $arctags($a) ne {}} {
6534                 set tomark [list $d]
6535                 for {set j 0} {$j < [llength $tomark]} {incr j} {
6536                     set dd [lindex $tomark $j]
6537                     if {![info exists hastaggedancestor($dd)]} {
6538                         if {[info exists done($dd)]} {
6539                             foreach b $arcnos($dd) {
6540                                 lappend tomark $arcstart($b)
6541                             }
6542                             if {[info exists tagloc($dd)]} {
6543                                 unset tagloc($dd)
6544                             }
6545                         } elseif {[info exists queued($dd)]} {
6546                             incr nc -1
6547                         }
6548                         set hastaggedancestor($dd) 1
6549                     }
6550                 }
6551             }
6552             if {![info exists queued($d)]} {
6553                 lappend todo $d
6554                 set queued($d) 1
6555                 if {![info exists hastaggedancestor($d)]} {
6556                     incr nc
6557                 }
6558             }
6559         }
6560     }
6561     set tags {}
6562     foreach id [array names tagloc] {
6563         if {![info exists hastaggedancestor($id)]} {
6564             foreach t $tagloc($id) {
6565                 if {[lsearch -exact $tags $t] < 0} {
6566                     lappend tags $t
6567                 }
6568             }
6569         }
6570     }
6571     set t2 [clock clicks -milliseconds]
6572     set loopix $i
6573
6574     # remove tags that are descendents of other tags
6575     for {set i 0} {$i < [llength $tags]} {incr i} {
6576         set a [lindex $tags $i]
6577         for {set j 0} {$j < $i} {incr j} {
6578             set b [lindex $tags $j]
6579             set r [anc_or_desc $a $b]
6580             if {$r == 1} {
6581                 set tags [lreplace $tags $j $j]
6582                 incr j -1
6583                 incr i -1
6584             } elseif {$r == -1} {
6585                 set tags [lreplace $tags $i $i]
6586                 incr i -1
6587                 break
6588             }
6589         }
6590     }
6591
6592     if {[array names growing] ne {}} {
6593         # graph isn't finished, need to check if any tag could get
6594         # eclipsed by another tag coming later.  Simply ignore any
6595         # tags that could later get eclipsed.
6596         set ctags {}
6597         foreach t $tags {
6598             if {[is_certain $t $origid]} {
6599                 lappend ctags $t
6600             }
6601         }
6602         if {$tags eq $ctags} {
6603             set cached_dtags($origid) $tags
6604         } else {
6605             set tags $ctags
6606         }
6607     } else {
6608         set cached_dtags($origid) $tags
6609     }
6610     set t3 [clock clicks -milliseconds]
6611     if {0 && $t3 - $t1 >= 100} {
6612         puts "iterating descendents ($loopix/[llength $todo] nodes) took\
6613             [expr {$t2-$t1}]+[expr {$t3-$t2}]ms, $nc candidates left"
6614     }
6615     return $tags
6616 }
6617
6618 proc anctags {id} {
6619     global arcnos arcids arcout arcend arctags idtags allparents
6620     global growing cached_atags
6621
6622     if {![info exists allparents($id)]} {
6623         return {}
6624     }
6625     set t1 [clock clicks -milliseconds]
6626     set argid $id
6627     if {[llength $arcnos($id)] == 1 && [llength $allparents($id)] == 1} {
6628         # part-way along an arc; check that arc first
6629         set a [lindex $arcnos($id) 0]
6630         if {$arctags($a) ne {}} {
6631             validate_arctags $a
6632             set i [lsearch -exact $arcids($a) $id]
6633             foreach t $arctags($a) {
6634                 set j [lsearch -exact $arcids($a) $t]
6635                 if {$j > $i} {
6636                     return $t
6637                 }
6638             }
6639         }
6640         if {![info exists arcend($a)]} {
6641             return {}
6642         }
6643         set id $arcend($a)
6644         if {[info exists idtags($id)]} {
6645             return $id
6646         }
6647     }
6648     if {[info exists cached_atags($id)]} {
6649         return $cached_atags($id)
6650     }
6651
6652     set origid $id
6653     set todo [list $id]
6654     set queued($id) 1
6655     set taglist {}
6656     set nc 1
6657     for {set i 0} {$i < [llength $todo] && $nc > 0} {incr i} {
6658         set id [lindex $todo $i]
6659         set done($id) 1
6660         set td [info exists hastaggeddescendent($id)]
6661         if {!$td} {
6662             incr nc -1
6663         }
6664         # ignore tags on starting node
6665         if {!$td && $i > 0} {
6666             if {[info exists idtags($id)]} {
6667                 set tagloc($id) $id
6668                 set td 1
6669             } elseif {[info exists cached_atags($id)]} {
6670                 set tagloc($id) $cached_atags($id)
6671                 set td 1
6672             }
6673         }
6674         foreach a $arcout($id) {
6675             if {!$td && $arctags($a) ne {}} {
6676                 validate_arctags $a
6677                 if {$arctags($a) ne {}} {
6678                     lappend tagloc($id) [lindex $arctags($a) 0]
6679                 }
6680             }
6681             if {![info exists arcend($a)]} continue
6682             set d $arcend($a)
6683             if {$td || $arctags($a) ne {}} {
6684                 set tomark [list $d]
6685                 for {set j 0} {$j < [llength $tomark]} {incr j} {
6686                     set dd [lindex $tomark $j]
6687                     if {![info exists hastaggeddescendent($dd)]} {
6688                         if {[info exists done($dd)]} {
6689                             foreach b $arcout($dd) {
6690                                 if {[info exists arcend($b)]} {
6691                                     lappend tomark $arcend($b)
6692                                 }
6693                             }
6694                             if {[info exists tagloc($dd)]} {
6695                                 unset tagloc($dd)
6696                             }
6697                         } elseif {[info exists queued($dd)]} {
6698                             incr nc -1
6699                         }
6700                         set hastaggeddescendent($dd) 1
6701                     }
6702                 }
6703             }
6704             if {![info exists queued($d)]} {
6705                 lappend todo $d
6706                 set queued($d) 1
6707                 if {![info exists hastaggeddescendent($d)]} {
6708                     incr nc
6709                 }
6710             }
6711         }
6712     }
6713     set t2 [clock clicks -milliseconds]
6714     set loopix $i
6715     set tags {}
6716     foreach id [array names tagloc] {
6717         if {![info exists hastaggeddescendent($id)]} {
6718             foreach t $tagloc($id) {
6719                 if {[lsearch -exact $tags $t] < 0} {
6720                     lappend tags $t
6721                 }
6722             }
6723         }
6724     }
6725
6726     # remove tags that are ancestors of other tags
6727     for {set i 0} {$i < [llength $tags]} {incr i} {
6728         set a [lindex $tags $i]
6729         for {set j 0} {$j < $i} {incr j} {
6730             set b [lindex $tags $j]
6731             set r [anc_or_desc $a $b]
6732             if {$r == -1} {
6733                 set tags [lreplace $tags $j $j]
6734                 incr j -1
6735                 incr i -1
6736             } elseif {$r == 1} {
6737                 set tags [lreplace $tags $i $i]
6738                 incr i -1
6739                 break
6740             }
6741         }
6742     }
6743
6744     if {[array names growing] ne {}} {
6745         # graph isn't finished, need to check if any tag could get
6746         # eclipsed by another tag coming later.  Simply ignore any
6747         # tags that could later get eclipsed.
6748         set ctags {}
6749         foreach t $tags {
6750             if {[is_certain $origid $t]} {
6751                 lappend ctags $t
6752             }
6753         }
6754         if {$tags eq $ctags} {
6755             set cached_atags($origid) $tags
6756         } else {
6757             set tags $ctags
6758         }
6759     } else {
6760         set cached_atags($origid) $tags
6761     }
6762     set t3 [clock clicks -milliseconds]
6763     if {0 && $t3 - $t1 >= 100} {
6764         puts "iterating ancestors ($loopix/[llength $todo] nodes) took\
6765             [expr {$t2-$t1}]+[expr {$t3-$t2}]ms, $nc candidates left"
6766     }
6767     return $tags
6768 }
6769
6770 # Return the list of IDs that have heads that are descendents of id,
6771 # including id itself if it has a head.
6772 proc descheads {id} {
6773     global arcnos arcstart arcids archeads idheads cached_dheads
6774     global allparents
6775
6776     if {![info exists allparents($id)]} {
6777         return {}
6778     }
6779     set aret {}
6780     if {[llength $arcnos($id)] == 1 && [llength $allparents($id)] == 1} {
6781         # part-way along an arc; check it first
6782         set a [lindex $arcnos($id) 0]
6783         if {$archeads($a) ne {}} {
6784             validate_archeads $a
6785             set i [lsearch -exact $arcids($a) $id]
6786             foreach t $archeads($a) {
6787                 set j [lsearch -exact $arcids($a) $t]
6788                 if {$j > $i} break
6789                 lappend aret $t
6790             }
6791         }
6792         set id $arcstart($a)
6793     }
6794     set origid $id
6795     set todo [list $id]
6796     set seen($id) 1
6797     set ret {}
6798     for {set i 0} {$i < [llength $todo]} {incr i} {
6799         set id [lindex $todo $i]
6800         if {[info exists cached_dheads($id)]} {
6801             set ret [concat $ret $cached_dheads($id)]
6802         } else {
6803             if {[info exists idheads($id)]} {
6804                 lappend ret $id
6805             }
6806             foreach a $arcnos($id) {
6807                 if {$archeads($a) ne {}} {
6808                     validate_archeads $a
6809                     if {$archeads($a) ne {}} {
6810                         set ret [concat $ret $archeads($a)]
6811                     }
6812                 }
6813                 set d $arcstart($a)
6814                 if {![info exists seen($d)]} {
6815                     lappend todo $d
6816                     set seen($d) 1
6817                 }
6818             }
6819         }
6820     }
6821     set ret [lsort -unique $ret]
6822     set cached_dheads($origid) $ret
6823     return [concat $ret $aret]
6824 }
6825
6826 proc addedtag {id} {
6827     global arcnos arcout cached_dtags cached_atags
6828
6829     if {![info exists arcnos($id)]} return
6830     if {![info exists arcout($id)]} {
6831         recalcarc [lindex $arcnos($id) 0]
6832     }
6833     catch {unset cached_dtags}
6834     catch {unset cached_atags}
6835 }
6836
6837 proc addedhead {hid head} {
6838     global arcnos arcout cached_dheads
6839
6840     if {![info exists arcnos($hid)]} return
6841     if {![info exists arcout($hid)]} {
6842         recalcarc [lindex $arcnos($hid) 0]
6843     }
6844     catch {unset cached_dheads}
6845 }
6846
6847 proc removedhead {hid head} {
6848     global cached_dheads
6849
6850     catch {unset cached_dheads}
6851 }
6852
6853 proc movedhead {hid head} {
6854     global arcnos arcout cached_dheads
6855
6856     if {![info exists arcnos($hid)]} return
6857     if {![info exists arcout($hid)]} {
6858         recalcarc [lindex $arcnos($hid) 0]
6859     }
6860     catch {unset cached_dheads}
6861 }
6862
6863 proc changedrefs {} {
6864     global cached_dheads cached_dtags cached_atags
6865     global arctags archeads arcnos arcout idheads idtags
6866
6867     foreach id [concat [array names idheads] [array names idtags]] {
6868         if {[info exists arcnos($id)] && ![info exists arcout($id)]} {
6869             set a [lindex $arcnos($id) 0]
6870             if {![info exists donearc($a)]} {
6871                 recalcarc $a
6872                 set donearc($a) 1
6873             }
6874         }
6875     }
6876     catch {unset cached_dtags}
6877     catch {unset cached_atags}
6878     catch {unset cached_dheads}
6879 }
6880
6881 proc rereadrefs {} {
6882     global idtags idheads idotherrefs mainhead
6883
6884     set refids [concat [array names idtags] \
6885                     [array names idheads] [array names idotherrefs]]
6886     foreach id $refids {
6887         if {![info exists ref($id)]} {
6888             set ref($id) [listrefs $id]
6889         }
6890     }
6891     set oldmainhead $mainhead
6892     readrefs
6893     changedrefs
6894     set refids [lsort -unique [concat $refids [array names idtags] \
6895                         [array names idheads] [array names idotherrefs]]]
6896     foreach id $refids {
6897         set v [listrefs $id]
6898         if {![info exists ref($id)] || $ref($id) != $v ||
6899             ($id eq $oldmainhead && $id ne $mainhead) ||
6900             ($id eq $mainhead && $id ne $oldmainhead)} {
6901             redrawtags $id
6902         }
6903     }
6904 }
6905
6906 proc listrefs {id} {
6907     global idtags idheads idotherrefs
6908
6909     set x {}
6910     if {[info exists idtags($id)]} {
6911         set x $idtags($id)
6912     }
6913     set y {}
6914     if {[info exists idheads($id)]} {
6915         set y $idheads($id)
6916     }
6917     set z {}
6918     if {[info exists idotherrefs($id)]} {
6919         set z $idotherrefs($id)
6920     }
6921     return [list $x $y $z]
6922 }
6923
6924 proc showtag {tag isnew} {
6925     global ctext tagcontents tagids linknum tagobjid
6926
6927     if {$isnew} {
6928         addtohistory [list showtag $tag 0]
6929     }
6930     $ctext conf -state normal
6931     clear_ctext
6932     set linknum 0
6933     if {![info exists tagcontents($tag)]} {
6934         catch {
6935             set tagcontents($tag) [exec git cat-file tag $tagobjid($tag)]
6936         }
6937     }
6938     if {[info exists tagcontents($tag)]} {
6939         set text $tagcontents($tag)
6940     } else {
6941         set text "Tag: $tag\nId:  $tagids($tag)"
6942     }
6943     appendwithlinks $text {}
6944     $ctext conf -state disabled
6945     init_flist {}
6946 }
6947
6948 proc doquit {} {
6949     global stopped
6950     set stopped 100
6951     savestuff .
6952     destroy .
6953 }
6954
6955 proc doprefs {} {
6956     global maxwidth maxgraphpct diffopts
6957     global oldprefs prefstop showneartags showlocalchanges
6958     global bgcolor fgcolor ctext diffcolors selectbgcolor
6959     global uifont tabstop
6960
6961     set top .gitkprefs
6962     set prefstop $top
6963     if {[winfo exists $top]} {
6964         raise $top
6965         return
6966     }
6967     foreach v {maxwidth maxgraphpct diffopts showneartags showlocalchanges} {
6968         set oldprefs($v) [set $v]
6969     }
6970     toplevel $top
6971     wm title $top "Gitk preferences"
6972     label $top.ldisp -text "Commit list display options"
6973     $top.ldisp configure -font $uifont
6974     grid $top.ldisp - -sticky w -pady 10
6975     label $top.spacer -text " "
6976     label $top.maxwidthl -text "Maximum graph width (lines)" \
6977         -font optionfont
6978     spinbox $top.maxwidth -from 0 -to 100 -width 4 -textvariable maxwidth
6979     grid $top.spacer $top.maxwidthl $top.maxwidth -sticky w
6980     label $top.maxpctl -text "Maximum graph width (% of pane)" \
6981         -font optionfont
6982     spinbox $top.maxpct -from 1 -to 100 -width 4 -textvariable maxgraphpct
6983     grid x $top.maxpctl $top.maxpct -sticky w
6984     frame $top.showlocal
6985     label $top.showlocal.l -text "Show local changes" -font optionfont
6986     checkbutton $top.showlocal.b -variable showlocalchanges
6987     pack $top.showlocal.b $top.showlocal.l -side left
6988     grid x $top.showlocal -sticky w
6989
6990     label $top.ddisp -text "Diff display options"
6991     $top.ddisp configure -font $uifont
6992     grid $top.ddisp - -sticky w -pady 10
6993     label $top.diffoptl -text "Options for diff program" \
6994         -font optionfont
6995     entry $top.diffopt -width 20 -textvariable diffopts
6996     grid x $top.diffoptl $top.diffopt -sticky w
6997     frame $top.ntag
6998     label $top.ntag.l -text "Display nearby tags" -font optionfont
6999     checkbutton $top.ntag.b -variable showneartags
7000     pack $top.ntag.b $top.ntag.l -side left
7001     grid x $top.ntag -sticky w
7002     label $top.tabstopl -text "tabstop" -font optionfont
7003     spinbox $top.tabstop -from 1 -to 20 -width 4 -textvariable tabstop
7004     grid x $top.tabstopl $top.tabstop -sticky w
7005
7006     label $top.cdisp -text "Colors: press to choose"
7007     $top.cdisp configure -font $uifont
7008     grid $top.cdisp - -sticky w -pady 10
7009     label $top.bg -padx 40 -relief sunk -background $bgcolor
7010     button $top.bgbut -text "Background" -font optionfont \
7011         -command [list choosecolor bgcolor 0 $top.bg background setbg]
7012     grid x $top.bgbut $top.bg -sticky w
7013     label $top.fg -padx 40 -relief sunk -background $fgcolor
7014     button $top.fgbut -text "Foreground" -font optionfont \
7015         -command [list choosecolor fgcolor 0 $top.fg foreground setfg]
7016     grid x $top.fgbut $top.fg -sticky w
7017     label $top.diffold -padx 40 -relief sunk -background [lindex $diffcolors 0]
7018     button $top.diffoldbut -text "Diff: old lines" -font optionfont \
7019         -command [list choosecolor diffcolors 0 $top.diffold "diff old lines" \
7020                       [list $ctext tag conf d0 -foreground]]
7021     grid x $top.diffoldbut $top.diffold -sticky w
7022     label $top.diffnew -padx 40 -relief sunk -background [lindex $diffcolors 1]
7023     button $top.diffnewbut -text "Diff: new lines" -font optionfont \
7024         -command [list choosecolor diffcolors 1 $top.diffnew "diff new lines" \
7025                       [list $ctext tag conf d1 -foreground]]
7026     grid x $top.diffnewbut $top.diffnew -sticky w
7027     label $top.hunksep -padx 40 -relief sunk -background [lindex $diffcolors 2]
7028     button $top.hunksepbut -text "Diff: hunk header" -font optionfont \
7029         -command [list choosecolor diffcolors 2 $top.hunksep \
7030                       "diff hunk header" \
7031                       [list $ctext tag conf hunksep -foreground]]
7032     grid x $top.hunksepbut $top.hunksep -sticky w
7033     label $top.selbgsep -padx 40 -relief sunk -background $selectbgcolor
7034     button $top.selbgbut -text "Select bg" -font optionfont \
7035         -command [list choosecolor selectbgcolor 0 $top.selbgsep background setselbg]
7036     grid x $top.selbgbut $top.selbgsep -sticky w
7037
7038     frame $top.buts
7039     button $top.buts.ok -text "OK" -command prefsok -default active
7040     $top.buts.ok configure -font $uifont
7041     button $top.buts.can -text "Cancel" -command prefscan -default normal
7042     $top.buts.can configure -font $uifont
7043     grid $top.buts.ok $top.buts.can
7044     grid columnconfigure $top.buts 0 -weight 1 -uniform a
7045     grid columnconfigure $top.buts 1 -weight 1 -uniform a
7046     grid $top.buts - - -pady 10 -sticky ew
7047     bind $top <Visibility> "focus $top.buts.ok"
7048 }
7049
7050 proc choosecolor {v vi w x cmd} {
7051     global $v
7052
7053     set c [tk_chooseColor -initialcolor [lindex [set $v] $vi] \
7054                -title "Gitk: choose color for $x"]
7055     if {$c eq {}} return
7056     $w conf -background $c
7057     lset $v $vi $c
7058     eval $cmd $c
7059 }
7060
7061 proc setselbg {c} {
7062     global bglist cflist
7063     foreach w $bglist {
7064         $w configure -selectbackground $c
7065     }
7066     $cflist tag configure highlight \
7067         -background [$cflist cget -selectbackground]
7068     allcanvs itemconf secsel -fill $c
7069 }
7070
7071 proc setbg {c} {
7072     global bglist
7073
7074     foreach w $bglist {
7075         $w conf -background $c
7076     }
7077 }
7078
7079 proc setfg {c} {
7080     global fglist canv
7081
7082     foreach w $fglist {
7083         $w conf -foreground $c
7084     }
7085     allcanvs itemconf text -fill $c
7086     $canv itemconf circle -outline $c
7087 }
7088
7089 proc prefscan {} {
7090     global maxwidth maxgraphpct diffopts
7091     global oldprefs prefstop showneartags showlocalchanges
7092
7093     foreach v {maxwidth maxgraphpct diffopts showneartags showlocalchanges} {
7094         set $v $oldprefs($v)
7095     }
7096     catch {destroy $prefstop}
7097     unset prefstop
7098 }
7099
7100 proc prefsok {} {
7101     global maxwidth maxgraphpct
7102     global oldprefs prefstop showneartags showlocalchanges
7103     global charspc ctext tabstop
7104
7105     catch {destroy $prefstop}
7106     unset prefstop
7107     $ctext configure -tabs "[expr {$tabstop * $charspc}]"
7108     if {$showlocalchanges != $oldprefs(showlocalchanges)} {
7109         if {$showlocalchanges} {
7110             doshowlocalchanges
7111         } else {
7112             dohidelocalchanges
7113         }
7114     }
7115     if {$maxwidth != $oldprefs(maxwidth)
7116         || $maxgraphpct != $oldprefs(maxgraphpct)} {
7117         redisplay
7118     } elseif {$showneartags != $oldprefs(showneartags)} {
7119         reselectline
7120     }
7121 }
7122
7123 proc formatdate {d} {
7124     if {$d ne {}} {
7125         set d [clock format $d -format "%Y-%m-%d %H:%M:%S"]
7126     }
7127     return $d
7128 }
7129
7130 # This list of encoding names and aliases is distilled from
7131 # http://www.iana.org/assignments/character-sets.
7132 # Not all of them are supported by Tcl.
7133 set encoding_aliases {
7134     { ANSI_X3.4-1968 iso-ir-6 ANSI_X3.4-1986 ISO_646.irv:1991 ASCII
7135       ISO646-US US-ASCII us IBM367 cp367 csASCII }
7136     { ISO-10646-UTF-1 csISO10646UTF1 }
7137     { ISO_646.basic:1983 ref csISO646basic1983 }
7138     { INVARIANT csINVARIANT }
7139     { ISO_646.irv:1983 iso-ir-2 irv csISO2IntlRefVersion }
7140     { BS_4730 iso-ir-4 ISO646-GB gb uk csISO4UnitedKingdom }
7141     { NATS-SEFI iso-ir-8-1 csNATSSEFI }
7142     { NATS-SEFI-ADD iso-ir-8-2 csNATSSEFIADD }
7143     { NATS-DANO iso-ir-9-1 csNATSDANO }
7144     { NATS-DANO-ADD iso-ir-9-2 csNATSDANOADD }
7145     { SEN_850200_B iso-ir-10 FI ISO646-FI ISO646-SE se csISO10Swedish }
7146     { SEN_850200_C iso-ir-11 ISO646-SE2 se2 csISO11SwedishForNames }
7147     { KS_C_5601-1987 iso-ir-149 KS_C_5601-1989 KSC_5601 korean csKSC56011987 }
7148     { ISO-2022-KR csISO2022KR }
7149     { EUC-KR csEUCKR }
7150     { ISO-2022-JP csISO2022JP }
7151     { ISO-2022-JP-2 csISO2022JP2 }
7152     { JIS_C6220-1969-jp JIS_C6220-1969 iso-ir-13 katakana x0201-7
7153       csISO13JISC6220jp }
7154     { JIS_C6220-1969-ro iso-ir-14 jp ISO646-JP csISO14JISC6220ro }
7155     { IT iso-ir-15 ISO646-IT csISO15Italian }
7156     { PT iso-ir-16 ISO646-PT csISO16Portuguese }
7157     { ES iso-ir-17 ISO646-ES csISO17Spanish }
7158     { greek7-old iso-ir-18 csISO18Greek7Old }
7159     { latin-greek iso-ir-19 csISO19LatinGreek }
7160     { DIN_66003 iso-ir-21 de ISO646-DE csISO21German }
7161     { NF_Z_62-010_(1973) iso-ir-25 ISO646-FR1 csISO25French }
7162     { Latin-greek-1 iso-ir-27 csISO27LatinGreek1 }
7163     { ISO_5427 iso-ir-37 csISO5427Cyrillic }
7164     { JIS_C6226-1978 iso-ir-42 csISO42JISC62261978 }
7165     { BS_viewdata iso-ir-47 csISO47BSViewdata }
7166     { INIS iso-ir-49 csISO49INIS }
7167     { INIS-8 iso-ir-50 csISO50INIS8 }
7168     { INIS-cyrillic iso-ir-51 csISO51INISCyrillic }
7169     { ISO_5427:1981 iso-ir-54 ISO5427Cyrillic1981 }
7170     { ISO_5428:1980 iso-ir-55 csISO5428Greek }
7171     { GB_1988-80 iso-ir-57 cn ISO646-CN csISO57GB1988 }
7172     { GB_2312-80 iso-ir-58 chinese csISO58GB231280 }
7173     { NS_4551-1 iso-ir-60 ISO646-NO no csISO60DanishNorwegian
7174       csISO60Norwegian1 }
7175     { NS_4551-2 ISO646-NO2 iso-ir-61 no2 csISO61Norwegian2 }
7176     { NF_Z_62-010 iso-ir-69 ISO646-FR fr csISO69French }
7177     { videotex-suppl iso-ir-70 csISO70VideotexSupp1 }
7178     { PT2 iso-ir-84 ISO646-PT2 csISO84Portuguese2 }
7179     { ES2 iso-ir-85 ISO646-ES2 csISO85Spanish2 }
7180     { MSZ_7795.3 iso-ir-86 ISO646-HU hu csISO86Hungarian }
7181     { JIS_C6226-1983 iso-ir-87 x0208 JIS_X0208-1983 csISO87JISX0208 }
7182     { greek7 iso-ir-88 csISO88Greek7 }
7183     { ASMO_449 ISO_9036 arabic7 iso-ir-89 csISO89ASMO449 }
7184     { iso-ir-90 csISO90 }
7185     { JIS_C6229-1984-a iso-ir-91 jp-ocr-a csISO91JISC62291984a }
7186     { JIS_C6229-1984-b iso-ir-92 ISO646-JP-OCR-B jp-ocr-b
7187       csISO92JISC62991984b }
7188     { JIS_C6229-1984-b-add iso-ir-93 jp-ocr-b-add csISO93JIS62291984badd }
7189     { JIS_C6229-1984-hand iso-ir-94 jp-ocr-hand csISO94JIS62291984hand }
7190     { JIS_C6229-1984-hand-add iso-ir-95 jp-ocr-hand-add
7191       csISO95JIS62291984handadd }
7192     { JIS_C6229-1984-kana iso-ir-96 csISO96JISC62291984kana }
7193     { ISO_2033-1983 iso-ir-98 e13b csISO2033 }
7194     { ANSI_X3.110-1983 iso-ir-99 CSA_T500-1983 NAPLPS csISO99NAPLPS }
7195     { ISO_8859-1:1987 iso-ir-100 ISO_8859-1 ISO-8859-1 latin1 l1 IBM819
7196       CP819 csISOLatin1 }
7197     { ISO_8859-2:1987 iso-ir-101 ISO_8859-2 ISO-8859-2 latin2 l2 csISOLatin2 }
7198     { T.61-7bit iso-ir-102 csISO102T617bit }
7199     { T.61-8bit T.61 iso-ir-103 csISO103T618bit }
7200     { ISO_8859-3:1988 iso-ir-109 ISO_8859-3 ISO-8859-3 latin3 l3 csISOLatin3 }
7201     { ISO_8859-4:1988 iso-ir-110 ISO_8859-4 ISO-8859-4 latin4 l4 csISOLatin4 }
7202     { ECMA-cyrillic iso-ir-111 KOI8-E csISO111ECMACyrillic }
7203     { CSA_Z243.4-1985-1 iso-ir-121 ISO646-CA csa7-1 ca csISO121Canadian1 }
7204     { CSA_Z243.4-1985-2 iso-ir-122 ISO646-CA2 csa7-2 csISO122Canadian2 }
7205     { CSA_Z243.4-1985-gr iso-ir-123 csISO123CSAZ24341985gr }
7206     { ISO_8859-6:1987 iso-ir-127 ISO_8859-6 ISO-8859-6 ECMA-114 ASMO-708
7207       arabic csISOLatinArabic }
7208     { ISO_8859-6-E csISO88596E ISO-8859-6-E }
7209     { ISO_8859-6-I csISO88596I ISO-8859-6-I }
7210     { ISO_8859-7:1987 iso-ir-126 ISO_8859-7 ISO-8859-7 ELOT_928 ECMA-118
7211       greek greek8 csISOLatinGreek }
7212     { T.101-G2 iso-ir-128 csISO128T101G2 }
7213     { ISO_8859-8:1988 iso-ir-138 ISO_8859-8 ISO-8859-8 hebrew
7214       csISOLatinHebrew }
7215     { ISO_8859-8-E csISO88598E ISO-8859-8-E }
7216     { ISO_8859-8-I csISO88598I ISO-8859-8-I }
7217     { CSN_369103 iso-ir-139 csISO139CSN369103 }
7218     { JUS_I.B1.002 iso-ir-141 ISO646-YU js yu csISO141JUSIB1002 }
7219     { ISO_6937-2-add iso-ir-142 csISOTextComm }
7220     { IEC_P27-1 iso-ir-143 csISO143IECP271 }
7221     { ISO_8859-5:1988 iso-ir-144 ISO_8859-5 ISO-8859-5 cyrillic
7222       csISOLatinCyrillic }
7223     { JUS_I.B1.003-serb iso-ir-146 serbian csISO146Serbian }
7224     { JUS_I.B1.003-mac macedonian iso-ir-147 csISO147Macedonian }
7225     { ISO_8859-9:1989 iso-ir-148 ISO_8859-9 ISO-8859-9 latin5 l5 csISOLatin5 }
7226     { greek-ccitt iso-ir-150 csISO150 csISO150GreekCCITT }
7227     { NC_NC00-10:81 cuba iso-ir-151 ISO646-CU csISO151Cuba }
7228     { ISO_6937-2-25 iso-ir-152 csISO6937Add }
7229     { GOST_19768-74 ST_SEV_358-88 iso-ir-153 csISO153GOST1976874 }
7230     { ISO_8859-supp iso-ir-154 latin1-2-5 csISO8859Supp }
7231     { ISO_10367-box iso-ir-155 csISO10367Box }
7232     { ISO-8859-10 iso-ir-157 l6 ISO_8859-10:1992 csISOLatin6 latin6 }
7233     { latin-lap lap iso-ir-158 csISO158Lap }
7234     { JIS_X0212-1990 x0212 iso-ir-159 csISO159JISX02121990 }
7235     { DS_2089 DS2089 ISO646-DK dk csISO646Danish }
7236     { us-dk csUSDK }
7237     { dk-us csDKUS }
7238     { JIS_X0201 X0201 csHalfWidthKatakana }
7239     { KSC5636 ISO646-KR csKSC5636 }
7240     { ISO-10646-UCS-2 csUnicode }
7241     { ISO-10646-UCS-4 csUCS4 }
7242     { DEC-MCS dec csDECMCS }
7243     { hp-roman8 roman8 r8 csHPRoman8 }
7244     { macintosh mac csMacintosh }
7245     { IBM037 cp037 ebcdic-cp-us ebcdic-cp-ca ebcdic-cp-wt ebcdic-cp-nl
7246       csIBM037 }
7247     { IBM038 EBCDIC-INT cp038 csIBM038 }
7248     { IBM273 CP273 csIBM273 }
7249     { IBM274 EBCDIC-BE CP274 csIBM274 }
7250     { IBM275 EBCDIC-BR cp275 csIBM275 }
7251     { IBM277 EBCDIC-CP-DK EBCDIC-CP-NO csIBM277 }
7252     { IBM278 CP278 ebcdic-cp-fi ebcdic-cp-se csIBM278 }
7253     { IBM280 CP280 ebcdic-cp-it csIBM280 }
7254     { IBM281 EBCDIC-JP-E cp281 csIBM281 }
7255     { IBM284 CP284 ebcdic-cp-es csIBM284 }
7256     { IBM285 CP285 ebcdic-cp-gb csIBM285 }
7257     { IBM290 cp290 EBCDIC-JP-kana csIBM290 }
7258     { IBM297 cp297 ebcdic-cp-fr csIBM297 }
7259     { IBM420 cp420 ebcdic-cp-ar1 csIBM420 }
7260     { IBM423 cp423 ebcdic-cp-gr csIBM423 }
7261     { IBM424 cp424 ebcdic-cp-he csIBM424 }
7262     { IBM437 cp437 437 csPC8CodePage437 }
7263     { IBM500 CP500 ebcdic-cp-be ebcdic-cp-ch csIBM500 }
7264     { IBM775 cp775 csPC775Baltic }
7265     { IBM850 cp850 850 csPC850Multilingual }
7266     { IBM851 cp851 851 csIBM851 }
7267     { IBM852 cp852 852 csPCp852 }
7268     { IBM855 cp855 855 csIBM855 }
7269     { IBM857 cp857 857 csIBM857 }
7270     { IBM860 cp860 860 csIBM860 }
7271     { IBM861 cp861 861 cp-is csIBM861 }
7272     { IBM862 cp862 862 csPC862LatinHebrew }
7273     { IBM863 cp863 863 csIBM863 }
7274     { IBM864 cp864 csIBM864 }
7275     { IBM865 cp865 865 csIBM865 }
7276     { IBM866 cp866 866 csIBM866 }
7277     { IBM868 CP868 cp-ar csIBM868 }
7278     { IBM869 cp869 869 cp-gr csIBM869 }
7279     { IBM870 CP870 ebcdic-cp-roece ebcdic-cp-yu csIBM870 }
7280     { IBM871 CP871 ebcdic-cp-is csIBM871 }
7281     { IBM880 cp880 EBCDIC-Cyrillic csIBM880 }
7282     { IBM891 cp891 csIBM891 }
7283     { IBM903 cp903 csIBM903 }
7284     { IBM904 cp904 904 csIBBM904 }
7285     { IBM905 CP905 ebcdic-cp-tr csIBM905 }
7286     { IBM918 CP918 ebcdic-cp-ar2 csIBM918 }
7287     { IBM1026 CP1026 csIBM1026 }
7288     { EBCDIC-AT-DE csIBMEBCDICATDE }
7289     { EBCDIC-AT-DE-A csEBCDICATDEA }
7290     { EBCDIC-CA-FR csEBCDICCAFR }
7291     { EBCDIC-DK-NO csEBCDICDKNO }
7292     { EBCDIC-DK-NO-A csEBCDICDKNOA }
7293     { EBCDIC-FI-SE csEBCDICFISE }
7294     { EBCDIC-FI-SE-A csEBCDICFISEA }
7295     { EBCDIC-FR csEBCDICFR }
7296     { EBCDIC-IT csEBCDICIT }
7297     { EBCDIC-PT csEBCDICPT }
7298     { EBCDIC-ES csEBCDICES }
7299     { EBCDIC-ES-A csEBCDICESA }
7300     { EBCDIC-ES-S csEBCDICESS }
7301     { EBCDIC-UK csEBCDICUK }
7302     { EBCDIC-US csEBCDICUS }
7303     { UNKNOWN-8BIT csUnknown8BiT }
7304     { MNEMONIC csMnemonic }
7305     { MNEM csMnem }
7306     { VISCII csVISCII }
7307     { VIQR csVIQR }
7308     { KOI8-R csKOI8R }
7309     { IBM00858 CCSID00858 CP00858 PC-Multilingual-850+euro }
7310     { IBM00924 CCSID00924 CP00924 ebcdic-Latin9--euro }
7311     { IBM01140 CCSID01140 CP01140 ebcdic-us-37+euro }
7312     { IBM01141 CCSID01141 CP01141 ebcdic-de-273+euro }
7313     { IBM01142 CCSID01142 CP01142 ebcdic-dk-277+euro ebcdic-no-277+euro }
7314     { IBM01143 CCSID01143 CP01143 ebcdic-fi-278+euro ebcdic-se-278+euro }
7315     { IBM01144 CCSID01144 CP01144 ebcdic-it-280+euro }
7316     { IBM01145 CCSID01145 CP01145 ebcdic-es-284+euro }
7317     { IBM01146 CCSID01146 CP01146 ebcdic-gb-285+euro }
7318     { IBM01147 CCSID01147 CP01147 ebcdic-fr-297+euro }
7319     { IBM01148 CCSID01148 CP01148 ebcdic-international-500+euro }
7320     { IBM01149 CCSID01149 CP01149 ebcdic-is-871+euro }
7321     { IBM1047 IBM-1047 }
7322     { PTCP154 csPTCP154 PT154 CP154 Cyrillic-Asian }
7323     { Amiga-1251 Ami1251 Amiga1251 Ami-1251 }
7324     { UNICODE-1-1 csUnicode11 }
7325     { CESU-8 csCESU-8 }
7326     { BOCU-1 csBOCU-1 }
7327     { UNICODE-1-1-UTF-7 csUnicode11UTF7 }
7328     { ISO-8859-14 iso-ir-199 ISO_8859-14:1998 ISO_8859-14 latin8 iso-celtic
7329       l8 }
7330     { ISO-8859-15 ISO_8859-15 Latin-9 }
7331     { ISO-8859-16 iso-ir-226 ISO_8859-16:2001 ISO_8859-16 latin10 l10 }
7332     { GBK CP936 MS936 windows-936 }
7333     { JIS_Encoding csJISEncoding }
7334     { Shift_JIS MS_Kanji csShiftJIS }
7335     { Extended_UNIX_Code_Packed_Format_for_Japanese csEUCPkdFmtJapanese
7336       EUC-JP }
7337     { Extended_UNIX_Code_Fixed_Width_for_Japanese csEUCFixWidJapanese }
7338     { ISO-10646-UCS-Basic csUnicodeASCII }
7339     { ISO-10646-Unicode-Latin1 csUnicodeLatin1 ISO-10646 }
7340     { ISO-Unicode-IBM-1261 csUnicodeIBM1261 }
7341     { ISO-Unicode-IBM-1268 csUnicodeIBM1268 }
7342     { ISO-Unicode-IBM-1276 csUnicodeIBM1276 }
7343     { ISO-Unicode-IBM-1264 csUnicodeIBM1264 }
7344     { ISO-Unicode-IBM-1265 csUnicodeIBM1265 }
7345     { ISO-8859-1-Windows-3.0-Latin-1 csWindows30Latin1 }
7346     { ISO-8859-1-Windows-3.1-Latin-1 csWindows31Latin1 }
7347     { ISO-8859-2-Windows-Latin-2 csWindows31Latin2 }
7348     { ISO-8859-9-Windows-Latin-5 csWindows31Latin5 }
7349     { Adobe-Standard-Encoding csAdobeStandardEncoding }
7350     { Ventura-US csVenturaUS }
7351     { Ventura-International csVenturaInternational }
7352     { PC8-Danish-Norwegian csPC8DanishNorwegian }
7353     { PC8-Turkish csPC8Turkish }
7354     { IBM-Symbols csIBMSymbols }
7355     { IBM-Thai csIBMThai }
7356     { HP-Legal csHPLegal }
7357     { HP-Pi-font csHPPiFont }
7358     { HP-Math8 csHPMath8 }
7359     { Adobe-Symbol-Encoding csHPPSMath }
7360     { HP-DeskTop csHPDesktop }
7361     { Ventura-Math csVenturaMath }
7362     { Microsoft-Publishing csMicrosoftPublishing }
7363     { Windows-31J csWindows31J }
7364     { GB2312 csGB2312 }
7365     { Big5 csBig5 }
7366 }
7367
7368 proc tcl_encoding {enc} {
7369     global encoding_aliases
7370     set names [encoding names]
7371     set lcnames [string tolower $names]
7372     set enc [string tolower $enc]
7373     set i [lsearch -exact $lcnames $enc]
7374     if {$i < 0} {
7375         # look for "isonnn" instead of "iso-nnn" or "iso_nnn"
7376         if {[regsub {^iso[-_]} $enc iso encx]} {
7377             set i [lsearch -exact $lcnames $encx]
7378         }
7379     }
7380     if {$i < 0} {
7381         foreach l $encoding_aliases {
7382             set ll [string tolower $l]
7383             if {[lsearch -exact $ll $enc] < 0} continue
7384             # look through the aliases for one that tcl knows about
7385             foreach e $ll {
7386                 set i [lsearch -exact $lcnames $e]
7387                 if {$i < 0} {
7388                     if {[regsub {^iso[-_]} $e iso ex]} {
7389                         set i [lsearch -exact $lcnames $ex]
7390                     }
7391                 }
7392                 if {$i >= 0} break
7393             }
7394             break
7395         }
7396     }
7397     if {$i >= 0} {
7398         return [lindex $names $i]
7399     }
7400     return {}
7401 }
7402
7403 # defaults...
7404 set datemode 0
7405 set diffopts "-U 5 -p"
7406 set wrcomcmd "git diff-tree --stdin -p --pretty"
7407
7408 set gitencoding {}
7409 catch {
7410     set gitencoding [exec git config --get i18n.commitencoding]
7411 }
7412 if {$gitencoding == ""} {
7413     set gitencoding "utf-8"
7414 }
7415 set tclencoding [tcl_encoding $gitencoding]
7416 if {$tclencoding == {}} {
7417     puts stderr "Warning: encoding $gitencoding is not supported by Tcl/Tk"
7418 }
7419
7420 set mainfont {Helvetica 9}
7421 set textfont {Courier 9}
7422 set uifont {Helvetica 9 bold}
7423 set tabstop 8
7424 set findmergefiles 0
7425 set maxgraphpct 50
7426 set maxwidth 16
7427 set revlistorder 0
7428 set fastdate 0
7429 set uparrowlen 7
7430 set downarrowlen 7
7431 set mingaplen 30
7432 set cmitmode "patch"
7433 set wrapcomment "none"
7434 set showneartags 1
7435 set maxrefs 20
7436 set maxlinelen 200
7437 set showlocalchanges 1
7438
7439 set colors {green red blue magenta darkgrey brown orange}
7440 set bgcolor white
7441 set fgcolor black
7442 set diffcolors {red "#00a000" blue}
7443 set selectbgcolor gray85
7444
7445 catch {source ~/.gitk}
7446
7447 font create optionfont -family sans-serif -size -12
7448
7449 set revtreeargs {}
7450 foreach arg $argv {
7451     switch -regexp -- $arg {
7452         "^$" { }
7453         "^-d" { set datemode 1 }
7454         default {
7455             lappend revtreeargs $arg
7456         }
7457     }
7458 }
7459
7460 # check that we can find a .git directory somewhere...
7461 set gitdir [gitdir]
7462 if {![file isdirectory $gitdir]} {
7463     show_error {} . "Cannot find the git directory \"$gitdir\"."
7464     exit 1
7465 }
7466
7467 set cmdline_files {}
7468 set i [lsearch -exact $revtreeargs "--"]
7469 if {$i >= 0} {
7470     set cmdline_files [lrange $revtreeargs [expr {$i + 1}] end]
7471     set revtreeargs [lrange $revtreeargs 0 [expr {$i - 1}]]
7472 } elseif {$revtreeargs ne {}} {
7473     if {[catch {
7474         set f [eval exec git rev-parse --no-revs --no-flags $revtreeargs]
7475         set cmdline_files [split $f "\n"]
7476         set n [llength $cmdline_files]
7477         set revtreeargs [lrange $revtreeargs 0 end-$n]
7478     } err]} {
7479         # unfortunately we get both stdout and stderr in $err,
7480         # so look for "fatal:".
7481         set i [string first "fatal:" $err]
7482         if {$i > 0} {
7483             set err [string range $err [expr {$i + 6}] end]
7484         }
7485         show_error {} . "Bad arguments to gitk:\n$err"
7486         exit 1
7487     }
7488 }
7489
7490 set nullid "0000000000000000000000000000000000000000"
7491
7492 set runq {}
7493 set history {}
7494 set historyindex 0
7495 set fh_serial 0
7496 set nhl_names {}
7497 set highlight_paths {}
7498 set searchdirn -forwards
7499 set boldrows {}
7500 set boldnamerows {}
7501 set diffelide {0 0}
7502 set markingmatches 0
7503
7504 set optim_delay 16
7505
7506 set nextviewnum 1
7507 set curview 0
7508 set selectedview 0
7509 set selectedhlview None
7510 set viewfiles(0) {}
7511 set viewperm(0) 0
7512 set viewargs(0) {}
7513
7514 set cmdlineok 0
7515 set stopped 0
7516 set stuffsaved 0
7517 set patchnum 0
7518 set lookingforhead 0
7519 set localrow -1
7520 set lserial 0
7521 setcoords
7522 makewindow
7523 wm title . "[file tail $argv0]: [file tail [pwd]]"
7524 readrefs
7525
7526 if {$cmdline_files ne {} || $revtreeargs ne {}} {
7527     # create a view for the files/dirs specified on the command line
7528     set curview 1
7529     set selectedview 1
7530     set nextviewnum 2
7531     set viewname(1) "Command line"
7532     set viewfiles(1) $cmdline_files
7533     set viewargs(1) $revtreeargs
7534     set viewperm(1) 0
7535     addviewmenu 1
7536     .bar.view entryconf Edit* -state normal
7537     .bar.view entryconf Delete* -state normal
7538 }
7539
7540 if {[info exists permviews]} {
7541     foreach v $permviews {
7542         set n $nextviewnum
7543         incr nextviewnum
7544         set viewname($n) [lindex $v 0]
7545         set viewfiles($n) [lindex $v 1]
7546         set viewargs($n) [lindex $v 2]
7547         set viewperm($n) 1
7548         addviewmenu $n
7549     }
7550 }
7551 getcommits