Files
git/gitk-git/gitk
Junio C Hamano a0d74d160f Merge git://git.kernel.org/pub/scm/gitk/gitk
* git://git.kernel.org/pub/scm/gitk/gitk:
  gitk: Allow safely calling nukefile from a run queue handler
2008-08-16 23:21:07 -07:00

9997 lines
268 KiB
Tcl

#!/bin/sh
# Tcl ignores the next line -*- tcl -*- \
exec wish "$0" -- "$@"
# Copyright © 2005-2008 Paul Mackerras. All rights reserved.
# This program is free software; it may be used, copied, modified
# and distributed under the terms of the GNU General Public Licence,
# either version 2, or (at your option) any later version.
proc gitdir {} {
global env
if {[info exists env(GIT_DIR)]} {
return $env(GIT_DIR)
} else {
return [exec git rev-parse --git-dir]
}
}
# A simple scheduler for compute-intensive stuff.
# The aim is to make sure that event handlers for GUI actions can
# run at least every 50-100 ms. Unfortunately fileevent handlers are
# run before X event handlers, so reading from a fast source can
# make the GUI completely unresponsive.
proc run args {
global isonrunq runq currunq
set script $args
if {[info exists isonrunq($script)]} return
if {$runq eq {} && ![info exists currunq]} {
after idle dorunq
}
lappend runq [list {} $script]
set isonrunq($script) 1
}
proc filerun {fd script} {
fileevent $fd readable [list filereadable $fd $script]
}
proc filereadable {fd script} {
global runq currunq
fileevent $fd readable {}
if {$runq eq {} && ![info exists currunq]} {
after idle dorunq
}
lappend runq [list $fd $script]
}
proc nukefile {fd} {
global runq
for {set i 0} {$i < [llength $runq]} {} {
if {[lindex $runq $i 0] eq $fd} {
set runq [lreplace $runq $i $i]
} else {
incr i
}
}
}
proc dorunq {} {
global isonrunq runq currunq
set tstart [clock clicks -milliseconds]
set t0 $tstart
while {[llength $runq] > 0} {
set fd [lindex $runq 0 0]
set script [lindex $runq 0 1]
set currunq [lindex $runq 0]
set runq [lrange $runq 1 end]
set repeat [eval $script]
unset currunq
set t1 [clock clicks -milliseconds]
set t [expr {$t1 - $t0}]
if {$repeat ne {} && $repeat} {
if {$fd eq {} || $repeat == 2} {
# script returns 1 if it wants to be readded
# file readers return 2 if they could do more straight away
lappend runq [list $fd $script]
} else {
fileevent $fd readable [list filereadable $fd $script]
}
} elseif {$fd eq {}} {
unset isonrunq($script)
}
set t0 $t1
if {$t1 - $tstart >= 80} break
}
if {$runq ne {}} {
after idle dorunq
}
}
proc reg_instance {fd} {
global commfd leftover loginstance
set i [incr loginstance]
set commfd($i) $fd
set leftover($i) {}
return $i
}
proc unmerged_files {files} {
global nr_unmerged
# find the list of unmerged files
set mlist {}
set nr_unmerged 0
if {[catch {
set fd [open "| git ls-files -u" r]
} err]} {
show_error {} . "[mc "Couldn't get list of unmerged files:"] $err"
exit 1
}
while {[gets $fd line] >= 0} {
set i [string first "\t" $line]
if {$i < 0} continue
set fname [string range $line [expr {$i+1}] end]
if {[lsearch -exact $mlist $fname] >= 0} continue
incr nr_unmerged
if {$files eq {} || [path_filter $files $fname]} {
lappend mlist $fname
}
}
catch {close $fd}
return $mlist
}
proc parseviewargs {n arglist} {
global vdatemode vmergeonly vflags vdflags vrevs vfiltered vorigargs
set vdatemode($n) 0
set vmergeonly($n) 0
set glflags {}
set diffargs {}
set nextisval 0
set revargs {}
set origargs $arglist
set allknown 1
set filtered 0
set i -1
foreach arg $arglist {
incr i
if {$nextisval} {
lappend glflags $arg
set nextisval 0
continue
}
switch -glob -- $arg {
"-d" -
"--date-order" {
set vdatemode($n) 1
# remove from origargs in case we hit an unknown option
set origargs [lreplace $origargs $i $i]
incr i -1
}
# These request or affect diff output, which we don't want.
# Some could be used to set our defaults for diff display.
"-[puabwcrRBMC]" -
"--no-renames" - "--full-index" - "--binary" - "--abbrev=*" -
"--find-copies-harder" - "-l*" - "--ext-diff" - "--no-ext-diff" -
"--src-prefix=*" - "--dst-prefix=*" - "--no-prefix" -
"-O*" - "--text" - "--full-diff" - "--ignore-space-at-eol" -
"--ignore-space-change" - "-U*" - "--unified=*" {
lappend diffargs $arg
}
# These cause our parsing of git log's output to fail, or else
# they're options we want to set ourselves, so ignore them.
"--raw" - "--patch-with-raw" - "--patch-with-stat" -
"--name-only" - "--name-status" - "--color" - "--color-words" -
"--log-size" - "--pretty=*" - "--decorate" - "--abbrev-commit" -
"--cc" - "-z" - "--header" - "--parents" - "--boundary" -
"--no-color" - "-g" - "--walk-reflogs" - "--no-walk" -
"--timestamp" - "relative-date" - "--date=*" - "--stdin" -
"--objects" - "--objects-edge" - "--reverse" {
}
# These are harmless, and some are even useful
"--stat=*" - "--numstat" - "--shortstat" - "--summary" -
"--check" - "--exit-code" - "--quiet" - "--topo-order" -
"--full-history" - "--dense" - "--sparse" -
"--follow" - "--left-right" - "--encoding=*" {
lappend glflags $arg
}
# These mean that we get a subset of the commits
"--diff-filter=*" - "--no-merges" - "--unpacked" -
"--max-count=*" - "--skip=*" - "--since=*" - "--after=*" -
"--until=*" - "--before=*" - "--max-age=*" - "--min-age=*" -
"--author=*" - "--committer=*" - "--grep=*" - "-[iE]" -
"--remove-empty" - "--first-parent" - "--cherry-pick" -
"-S*" - "--pickaxe-all" - "--pickaxe-regex" - {
set filtered 1
lappend glflags $arg
}
# This appears to be the only one that has a value as a
# separate word following it
"-n" {
set filtered 1
set nextisval 1
lappend glflags $arg
}
"--not" {
set notflag [expr {!$notflag}]
lappend revargs $arg
}
"--all" {
lappend revargs $arg
}
"--merge" {
set vmergeonly($n) 1
# git rev-parse doesn't understand --merge
lappend revargs --gitk-symmetric-diff-marker MERGE_HEAD...HEAD
}
# Other flag arguments including -<n>
"-*" {
if {[string is digit -strict [string range $arg 1 end]]} {
set filtered 1
} else {
# a flag argument that we don't recognize;
# that means we can't optimize
set allknown 0
}
lappend glflags $arg
}
# Non-flag arguments specify commits or ranges of commits
default {
if {[string match "*...*" $arg]} {
lappend revargs --gitk-symmetric-diff-marker
}
lappend revargs $arg
}
}
}
set vdflags($n) $diffargs
set vflags($n) $glflags
set vrevs($n) $revargs
set vfiltered($n) $filtered
set vorigargs($n) $origargs
return $allknown
}
proc parseviewrevs {view revs} {
global vposids vnegids
if {$revs eq {}} {
set revs HEAD
}
if {[catch {set ids [eval exec git rev-parse $revs]} err]} {
# we get stdout followed by stderr in $err
# for an unknown rev, git rev-parse echoes it and then errors out
set errlines [split $err "\n"]
set badrev {}
for {set l 0} {$l < [llength $errlines]} {incr l} {
set line [lindex $errlines $l]
if {!([string length $line] == 40 && [string is xdigit $line])} {
if {[string match "fatal:*" $line]} {
if {[string match "fatal: ambiguous argument*" $line]
&& $badrev ne {}} {
if {[llength $badrev] == 1} {
set err "unknown revision $badrev"
} else {
set err "unknown revisions: [join $badrev ", "]"
}
} else {
set err [join [lrange $errlines $l end] "\n"]
}
break
}
lappend badrev $line
}
}
error_popup "Error parsing revisions: $err"
return {}
}
set ret {}
set pos {}
set neg {}
set sdm 0
foreach id [split $ids "\n"] {
if {$id eq "--gitk-symmetric-diff-marker"} {
set sdm 4
} elseif {[string match "^*" $id]} {
if {$sdm != 1} {
lappend ret $id
if {$sdm == 3} {
set sdm 0
}
}
lappend neg [string range $id 1 end]
} else {
if {$sdm != 2} {
lappend ret $id
} else {
lset ret end [lindex $ret end]...$id
}
lappend pos $id
}
incr sdm -1
}
set vposids($view) $pos
set vnegids($view) $neg
return $ret
}
# Start off a git log process and arrange to read its output
proc start_rev_list {view} {
global startmsecs commitidx viewcomplete curview
global tclencoding
global viewargs viewargscmd viewfiles vfilelimit
global showlocalchanges commitinterest
global viewactive viewinstances vmergeonly
global mainheadid
global vcanopt vflags vrevs vorigargs
set startmsecs [clock clicks -milliseconds]
set commitidx($view) 0
# these are set this way for the error exits
set viewcomplete($view) 1
set viewactive($view) 0
varcinit $view
set args $viewargs($view)
if {$viewargscmd($view) ne {}} {
if {[catch {
set str [exec sh -c $viewargscmd($view)]
} err]} {
error_popup "Error executing --argscmd command: $err"
return 0
}
set args [concat $args [split $str "\n"]]
}
set vcanopt($view) [parseviewargs $view $args]
set files $viewfiles($view)
if {$vmergeonly($view)} {
set files [unmerged_files $files]
if {$files eq {}} {
global nr_unmerged
if {$nr_unmerged == 0} {
error_popup [mc "No files selected: --merge specified but\
no files are unmerged."]
} else {
error_popup [mc "No files selected: --merge specified but\
no unmerged files are within file limit."]
}
return 0
}
}
set vfilelimit($view) $files
if {$vcanopt($view)} {
set revs [parseviewrevs $view $vrevs($view)]
if {$revs eq {}} {
return 0
}
set args [concat $vflags($view) $revs]
} else {
set args $vorigargs($view)
}
if {[catch {
set fd [open [concat | git log --no-color -z --pretty=raw --parents \
--boundary $args "--" $files] r]
} err]} {
error_popup "[mc "Error executing git log:"] $err"
return 0
}
set i [reg_instance $fd]
set viewinstances($view) [list $i]
if {$showlocalchanges && $mainheadid ne {}} {
lappend commitinterest($mainheadid) {dodiffindex}
}
fconfigure $fd -blocking 0 -translation lf -eofchar {}
if {$tclencoding != {}} {
fconfigure $fd -encoding $tclencoding
}
filerun $fd [list getcommitlines $fd $i $view 0]
nowbusy $view [mc "Reading"]
set viewcomplete($view) 0
set viewactive($view) 1
return 1
}
proc stop_instance {inst} {
global commfd leftover
set fd $commfd($inst)
catch {
set pid [pid $fd]
if {$::tcl_platform(platform) eq {windows}} {
exec kill -f $pid
} else {
exec kill $pid
}
}
catch {close $fd}
nukefile $fd
unset commfd($inst)
unset leftover($inst)
}
proc stop_backends {} {
global commfd
foreach inst [array names commfd] {
stop_instance $inst
}
}
proc stop_rev_list {view} {
global viewinstances
foreach inst $viewinstances($view) {
stop_instance $inst
}
set viewinstances($view) {}
}
proc reset_pending_select {selid} {
global pending_select mainheadid
if {$selid ne {}} {
set pending_select $selid
} else {
set pending_select $mainheadid
}
}
proc getcommits {selid} {
global canv curview need_redisplay viewactive
initlayout
if {[start_rev_list $curview]} {
reset_pending_select $selid
show_status [mc "Reading commits..."]
set need_redisplay 1
} else {
show_status [mc "No commits selected"]
}
}
proc updatecommits {} {
global curview vcanopt vorigargs vfilelimit viewinstances
global viewactive viewcomplete tclencoding
global startmsecs showneartags showlocalchanges
global mainheadid pending_select
global isworktree
global varcid vposids vnegids vflags vrevs
set isworktree [expr {[exec git rev-parse --is-inside-work-tree] == "true"}]
set oldmainid $mainheadid
rereadrefs
if {$showlocalchanges} {
if {$mainheadid ne $oldmainid} {
dohidelocalchanges
}
if {[commitinview $mainheadid $curview]} {
dodiffindex
}
}
set view $curview
if {$vcanopt($view)} {
set oldpos $vposids($view)
set oldneg $vnegids($view)
set revs [parseviewrevs $view $vrevs($view)]
if {$revs eq {}} {
return
}
# note: getting the delta when negative refs change is hard,
# and could require multiple git log invocations, so in that
# case we ask git log for all the commits (not just the delta)
if {$oldneg eq $vnegids($view)} {
set newrevs {}
set npos 0
# take out positive refs that we asked for before or
# that we have already seen
foreach rev $revs {
if {[string length $rev] == 40} {
if {[lsearch -exact $oldpos $rev] < 0
&& ![info exists varcid($view,$rev)]} {
lappend newrevs $rev
incr npos
}
} else {
lappend $newrevs $rev
}
}
if {$npos == 0} return
set revs $newrevs
set vposids($view) [lsort -unique [concat $oldpos $vposids($view)]]
}
set args [concat $vflags($view) $revs --not $oldpos]
} else {
set args $vorigargs($view)
}
if {[catch {
set fd [open [concat | git log --no-color -z --pretty=raw --parents \
--boundary $args "--" $vfilelimit($view)] r]
} err]} {
error_popup "Error executing git log: $err"
return
}
if {$viewactive($view) == 0} {
set startmsecs [clock clicks -milliseconds]
}
set i [reg_instance $fd]
lappend viewinstances($view) $i
fconfigure $fd -blocking 0 -translation lf -eofchar {}
if {$tclencoding != {}} {
fconfigure $fd -encoding $tclencoding
}
filerun $fd [list getcommitlines $fd $i $view 1]
incr viewactive($view)
set viewcomplete($view) 0
reset_pending_select {}
nowbusy $view "Reading"
if {$showneartags} {
getallcommits
}
}
proc reloadcommits {} {
global curview viewcomplete selectedline currentid thickerline
global showneartags treediffs commitinterest cached_commitrow
global targetid
set selid {}
if {$selectedline ne {}} {
set selid $currentid
}
if {!$viewcomplete($curview)} {
stop_rev_list $curview
}
resetvarcs $curview
set selectedline {}
catch {unset currentid}
catch {unset thickerline}
catch {unset treediffs}
readrefs
changedrefs
if {$showneartags} {
getallcommits
}
clear_display
catch {unset commitinterest}
catch {unset cached_commitrow}
catch {unset targetid}
setcanvscroll
getcommits $selid
return 0
}
# This makes a string representation of a positive integer which
# sorts as a string in numerical order
proc strrep {n} {
if {$n < 16} {
return [format "%x" $n]
} elseif {$n < 256} {
return [format "x%.2x" $n]
} elseif {$n < 65536} {
return [format "y%.4x" $n]
}
return [format "z%.8x" $n]
}
# Procedures used in reordering commits from git log (without
# --topo-order) into the order for display.
proc varcinit {view} {
global varcstart vupptr vdownptr vleftptr vbackptr varctok varcrow
global vtokmod varcmod vrowmod varcix vlastins
set varcstart($view) {{}}
set vupptr($view) {0}
set vdownptr($view) {0}
set vleftptr($view) {0}
set vbackptr($view) {0}
set varctok($view) {{}}
set varcrow($view) {{}}
set vtokmod($view) {}
set varcmod($view) 0
set vrowmod($view) 0
set varcix($view) {{}}
set vlastins($view) {0}
}
proc resetvarcs {view} {
global varcid varccommits parents children vseedcount ordertok
foreach vid [array names varcid $view,*] {
unset varcid($vid)
unset children($vid)
unset parents($vid)
}
# some commits might have children but haven't been seen yet
foreach vid [array names children $view,*] {
unset children($vid)
}
foreach va [array names varccommits $view,*] {
unset varccommits($va)
}
foreach vd [array names vseedcount $view,*] {
unset vseedcount($vd)
}
catch {unset ordertok}
}
# returns a list of the commits with no children
proc seeds {v} {
global vdownptr vleftptr varcstart
set ret {}
set a [lindex $vdownptr($v) 0]
while {$a != 0} {
lappend ret [lindex $varcstart($v) $a]
set a [lindex $vleftptr($v) $a]
}
return $ret
}
proc newvarc {view id} {
global varcid varctok parents children vdatemode
global vupptr vdownptr vleftptr vbackptr varcrow varcix varcstart
global commitdata commitinfo vseedcount varccommits vlastins
set a [llength $varctok($view)]
set vid $view,$id
if {[llength $children($vid)] == 0 || $vdatemode($view)} {
if {![info exists commitinfo($id)]} {
parsecommit $id $commitdata($id) 1
}
set cdate [lindex $commitinfo($id) 4]
if {![string is integer -strict $cdate]} {
set cdate 0
}
if {![info exists vseedcount($view,$cdate)]} {
set vseedcount($view,$cdate) -1
}
set c [incr vseedcount($view,$cdate)]
set cdate [expr {$cdate ^ 0xffffffff}]
set tok "s[strrep $cdate][strrep $c]"
} else {
set tok {}
}
set ka 0
if {[llength $children($vid)] > 0} {
set kid [lindex $children($vid) end]
set k $varcid($view,$kid)
if {[string compare [lindex $varctok($view) $k] $tok] > 0} {
set ki $kid
set ka $k
set tok [lindex $varctok($view) $k]
}
}
if {$ka != 0} {
set i [lsearch -exact $parents($view,$ki) $id]
set j [expr {[llength $parents($view,$ki)] - 1 - $i}]
append tok [strrep $j]
}
set c [lindex $vlastins($view) $ka]
if {$c == 0 || [string compare $tok [lindex $varctok($view) $c]] < 0} {
set c $ka
set b [lindex $vdownptr($view) $ka]
} else {
set b [lindex $vleftptr($view) $c]
}
while {$b != 0 && [string compare $tok [lindex $varctok($view) $b]] >= 0} {
set c $b
set b [lindex $vleftptr($view) $c]
}
if {$c == $ka} {
lset vdownptr($view) $ka $a
lappend vbackptr($view) 0
} else {
lset vleftptr($view) $c $a
lappend vbackptr($view) $c
}
lset vlastins($view) $ka $a
lappend vupptr($view) $ka
lappend vleftptr($view) $b
if {$b != 0} {
lset vbackptr($view) $b $a
}
lappend varctok($view) $tok
lappend varcstart($view) $id
lappend vdownptr($view) 0
lappend varcrow($view) {}
lappend varcix($view) {}
set varccommits($view,$a) {}
lappend vlastins($view) 0
return $a
}
proc splitvarc {p v} {
global varcid varcstart varccommits varctok
global vupptr vdownptr vleftptr vbackptr varcix varcrow vlastins
set oa $varcid($v,$p)
set ac $varccommits($v,$oa)
set i [lsearch -exact $varccommits($v,$oa) $p]
if {$i <= 0} return
set na [llength $varctok($v)]
# "%" sorts before "0"...
set tok "[lindex $varctok($v) $oa]%[strrep $i]"
lappend varctok($v) $tok
lappend varcrow($v) {}
lappend varcix($v) {}
set varccommits($v,$oa) [lrange $ac 0 [expr {$i - 1}]]
set varccommits($v,$na) [lrange $ac $i end]
lappend varcstart($v) $p
foreach id $varccommits($v,$na) {
set varcid($v,$id) $na
}
lappend vdownptr($v) [lindex $vdownptr($v) $oa]
lappend vlastins($v) [lindex $vlastins($v) $oa]
lset vdownptr($v) $oa $na
lset vlastins($v) $oa 0
lappend vupptr($v) $oa
lappend vleftptr($v) 0
lappend vbackptr($v) 0
for {set b [lindex $vdownptr($v) $na]} {$b != 0} {set b [lindex $vleftptr($v) $b]} {
lset vupptr($v) $b $na
}
}
proc renumbervarc {a v} {
global parents children varctok varcstart varccommits
global vupptr vdownptr vleftptr vbackptr vlastins varcid vtokmod vdatemode
set t1 [clock clicks -milliseconds]
set todo {}
set isrelated($a) 1
set kidchanged($a) 1
set ntot 0
while {$a != 0} {
if {[info exists isrelated($a)]} {
lappend todo $a
set id [lindex $varccommits($v,$a) end]
foreach p $parents($v,$id) {
if {[info exists varcid($v,$p)]} {
set isrelated($varcid($v,$p)) 1
}
}
}
incr ntot
set b [lindex $vdownptr($v) $a]
if {$b == 0} {
while {$a != 0} {
set b [lindex $vleftptr($v) $a]
if {$b != 0} break
set a [lindex $vupptr($v) $a]
}
}
set a $b
}
foreach a $todo {
if {![info exists kidchanged($a)]} continue
set id [lindex $varcstart($v) $a]
if {[llength $children($v,$id)] > 1} {
set children($v,$id) [lsort -command [list vtokcmp $v] \
$children($v,$id)]
}
set oldtok [lindex $varctok($v) $a]
if {!$vdatemode($v)} {
set tok {}
} else {
set tok $oldtok
}
set ka 0
set kid [last_real_child $v,$id]
if {$kid ne {}} {
set k $varcid($v,$kid)
if {[string compare [lindex $varctok($v) $k] $tok] > 0} {
set ki $kid
set ka $k
set tok [lindex $varctok($v) $k]
}
}
if {$ka != 0} {
set i [lsearch -exact $parents($v,$ki) $id]
set j [expr {[llength $parents($v,$ki)] - 1 - $i}]
append tok [strrep $j]
}
if {$tok eq $oldtok} {
continue
}
set id [lindex $varccommits($v,$a) end]
foreach p $parents($v,$id) {
if {[info exists varcid($v,$p)]} {
set kidchanged($varcid($v,$p)) 1
} else {
set sortkids($p) 1
}
}
lset varctok($v) $a $tok
set b [lindex $vupptr($v) $a]
if {$b != $ka} {
if {[string compare [lindex $varctok($v) $ka] $vtokmod($v)] < 0} {
modify_arc $v $ka
}
if {[string compare [lindex $varctok($v) $b] $vtokmod($v)] < 0} {
modify_arc $v $b
}
set c [lindex $vbackptr($v) $a]
set d [lindex $vleftptr($v) $a]
if {$c == 0} {
lset vdownptr($v) $b $d
} else {
lset vleftptr($v) $c $d
}
if {$d != 0} {
lset vbackptr($v) $d $c
}
if {[lindex $vlastins($v) $b] == $a} {
lset vlastins($v) $b $c
}
lset vupptr($v) $a $ka
set c [lindex $vlastins($v) $ka]
if {$c == 0 || \
[string compare $tok [lindex $varctok($v) $c]] < 0} {
set c $ka
set b [lindex $vdownptr($v) $ka]
} else {
set b [lindex $vleftptr($v) $c]
}
while {$b != 0 && \
[string compare $tok [lindex $varctok($v) $b]] >= 0} {
set c $b
set b [lindex $vleftptr($v) $c]
}
if {$c == $ka} {
lset vdownptr($v) $ka $a
lset vbackptr($v) $a 0
} else {
lset vleftptr($v) $c $a
lset vbackptr($v) $a $c
}
lset vleftptr($v) $a $b
if {$b != 0} {
lset vbackptr($v) $b $a
}
lset vlastins($v) $ka $a
}
}
foreach id [array names sortkids] {
if {[llength $children($v,$id)] > 1} {
set children($v,$id) [lsort -command [list vtokcmp $v] \
$children($v,$id)]
}
}
set t2 [clock clicks -milliseconds]
#puts "renumbervarc did [llength $todo] of $ntot arcs in [expr {$t2-$t1}]ms"
}
# Fix up the graph after we have found out that in view $v,
# $p (a commit that we have already seen) is actually the parent
# of the last commit in arc $a.
proc fix_reversal {p a v} {
global varcid varcstart varctok vupptr
set pa $varcid($v,$p)
if {$p ne [lindex $varcstart($v) $pa]} {
splitvarc $p $v
set pa $varcid($v,$p)
}
# seeds always need to be renumbered
if {[lindex $vupptr($v) $pa] == 0 ||
[string compare [lindex $varctok($v) $a] \
[lindex $varctok($v) $pa]] > 0} {
renumbervarc $pa $v
}
}
proc insertrow {id p v} {
global cmitlisted children parents varcid varctok vtokmod
global varccommits ordertok commitidx numcommits curview
global targetid targetrow
readcommit $id
set vid $v,$id
set cmitlisted($vid) 1
set children($vid) {}
set parents($vid) [list $p]
set a [newvarc $v $id]
set varcid($vid) $a
if {[string compare [lindex $varctok($v) $a] $vtokmod($v)] < 0} {
modify_arc $v $a
}
lappend varccommits($v,$a) $id
set vp $v,$p
if {[llength [lappend children($vp) $id]] > 1} {
set children($vp) [lsort -command [list vtokcmp $v] $children($vp)]
catch {unset ordertok}
}
fix_reversal $p $a $v
incr commitidx($v)
if {$v == $curview} {
set numcommits $commitidx($v)
setcanvscroll
if {[info exists targetid]} {
if {![comes_before $targetid $p]} {
incr targetrow
}
}
}
}
proc insertfakerow {id p} {
global varcid varccommits parents children cmitlisted
global commitidx varctok vtokmod targetid targetrow curview numcommits
set v $curview
set a $varcid($v,$p)
set i [lsearch -exact $varccommits($v,$a) $p]
if {$i < 0} {
puts "oops: insertfakerow can't find [shortids $p] on arc $a"
return
}
set children($v,$id) {}
set parents($v,$id) [list $p]
set varcid($v,$id) $a
lappend children($v,$p) $id
set cmitlisted($v,$id) 1
set numcommits [incr commitidx($v)]
# note we deliberately don't update varcstart($v) even if $i == 0
set varccommits($v,$a) [linsert $varccommits($v,$a) $i $id]
modify_arc $v $a $i
if {[info exists targetid]} {
if {![comes_before $targetid $p]} {
incr targetrow
}
}
setcanvscroll
drawvisible
}
proc removefakerow {id} {
global varcid varccommits parents children commitidx
global varctok vtokmod cmitlisted currentid selectedline
global targetid curview numcommits
set v $curview
if {[llength $parents($v,$id)] != 1} {
puts "oops: removefakerow [shortids $id] has [llength $parents($v,$id)] parents"
return
}
set p [lindex $parents($v,$id) 0]
set a $varcid($v,$id)
set i [lsearch -exact $varccommits($v,$a) $id]
if {$i < 0} {
puts "oops: removefakerow can't find [shortids $id] on arc $a"
return
}
unset varcid($v,$id)
set varccommits($v,$a) [lreplace $varccommits($v,$a) $i $i]
unset parents($v,$id)
unset children($v,$id)
unset cmitlisted($v,$id)
set numcommits [incr commitidx($v) -1]
set j [lsearch -exact $children($v,$p) $id]
if {$j >= 0} {
set children($v,$p) [lreplace $children($v,$p) $j $j]
}
modify_arc $v $a $i
if {[info exist currentid] && $id eq $currentid} {
unset currentid
set selectedline {}
}
if {[info exists targetid] && $targetid eq $id} {
set targetid $p
}
setcanvscroll
drawvisible
}
proc first_real_child {vp} {
global children nullid nullid2
foreach id $children($vp) {
if {$id ne $nullid && $id ne $nullid2} {
return $id
}
}
return {}
}
proc last_real_child {vp} {
global children nullid nullid2
set kids $children($vp)
for {set i [llength $kids]} {[incr i -1] >= 0} {} {
set id [lindex $kids $i]
if {$id ne $nullid && $id ne $nullid2} {
return $id
}
}
return {}
}
proc vtokcmp {v a b} {
global varctok varcid
return [string compare [lindex $varctok($v) $varcid($v,$a)] \
[lindex $varctok($v) $varcid($v,$b)]]
}
# This assumes that if lim is not given, the caller has checked that
# arc a's token is less than $vtokmod($v)
proc modify_arc {v a {lim {}}} {
global varctok vtokmod varcmod varcrow vupptr curview vrowmod varccommits
if {$lim ne {}} {
set c [string compare [lindex $varctok($v) $a] $vtokmod($v)]
if {$c > 0} return
if {$c == 0} {
set r [lindex $varcrow($v) $a]
if {$r ne {} && $vrowmod($v) <= $r + $lim} return
}
}
set vtokmod($v) [lindex $varctok($v) $a]
set varcmod($v) $a
if {$v == $curview} {
while {$a != 0 && [lindex $varcrow($v) $a] eq {}} {
set a [lindex $vupptr($v) $a]
set lim {}
}
set r 0
if {$a != 0} {
if {$lim eq {}} {
set lim [llength $varccommits($v,$a)]
}
set r [expr {[lindex $varcrow($v) $a] + $lim}]
}
set vrowmod($v) $r
undolayout $r
}
}
proc update_arcrows {v} {
global vtokmod varcmod vrowmod varcrow commitidx currentid selectedline
global varcid vrownum varcorder varcix varccommits
global vupptr vdownptr vleftptr varctok
global displayorder parentlist curview cached_commitrow
if {$vrowmod($v) == $commitidx($v)} return
if {$v == $curview} {
if {[llength $displayorder] > $vrowmod($v)} {
set displayorder [lrange $displayorder 0 [expr {$vrowmod($v) - 1}]]
set parentlist [lrange $parentlist 0 [expr {$vrowmod($v) - 1}]]
}
catch {unset cached_commitrow}
}
set narctot [expr {[llength $varctok($v)] - 1}]
set a $varcmod($v)
while {$a != 0 && [lindex $varcix($v) $a] eq {}} {
# go up the tree until we find something that has a row number,
# or we get to a seed
set a [lindex $vupptr($v) $a]
}
if {$a == 0} {
set a [lindex $vdownptr($v) 0]
if {$a == 0} return
set vrownum($v) {0}
set varcorder($v) [list $a]
lset varcix($v) $a 0
lset varcrow($v) $a 0
set arcn 0
set row 0
} else {
set arcn [lindex $varcix($v) $a]
if {[llength $vrownum($v)] > $arcn + 1} {
set vrownum($v) [lrange $vrownum($v) 0 $arcn]
set varcorder($v) [lrange $varcorder($v) 0 $arcn]
}
set row [lindex $varcrow($v) $a]
}
while {1} {
set p $a
incr row [llength $varccommits($v,$a)]
# go down if possible
set b [lindex $vdownptr($v) $a]
if {$b == 0} {
# if not, go left, or go up until we can go left
while {$a != 0} {
set b [lindex $vleftptr($v) $a]
if {$b != 0} break
set a [lindex $vupptr($v) $a]
}
if {$a == 0} break
}
set a $b
incr arcn
lappend vrownum($v) $row
lappend varcorder($v) $a
lset varcix($v) $a $arcn
lset varcrow($v) $a $row
}
set vtokmod($v) [lindex $varctok($v) $p]
set varcmod($v) $p
set vrowmod($v) $row
if {[info exists currentid]} {
set selectedline [rowofcommit $currentid]
}
}
# Test whether view $v contains commit $id
proc commitinview {id v} {
global varcid
return [info exists varcid($v,$id)]
}
# Return the row number for commit $id in the current view
proc rowofcommit {id} {
global varcid varccommits varcrow curview cached_commitrow
global varctok vtokmod
set v $curview
if {![info exists varcid($v,$id)]} {
puts "oops rowofcommit no arc for [shortids $id]"
return {}
}
set a $varcid($v,$id)
if {[string compare [lindex $varctok($v) $a] $vtokmod($v)] >= 0} {
update_arcrows $v
}
if {[info exists cached_commitrow($id)]} {
return $cached_commitrow($id)
}
set i [lsearch -exact $varccommits($v,$a) $id]
if {$i < 0} {
puts "oops didn't find commit [shortids $id] in arc $a"
return {}
}
incr i [lindex $varcrow($v) $a]
set cached_commitrow($id) $i
return $i
}
# Returns 1 if a is on an earlier row than b, otherwise 0
proc comes_before {a b} {
global varcid varctok curview
set v $curview
if {$a eq $b || ![info exists varcid($v,$a)] || \
![info exists varcid($v,$b)]} {
return 0
}
if {$varcid($v,$a) != $varcid($v,$b)} {
return [expr {[string compare [lindex $varctok($v) $varcid($v,$a)] \
[lindex $varctok($v) $varcid($v,$b)]] < 0}]
}
return [expr {[rowofcommit $a] < [rowofcommit $b]}]
}
proc bsearch {l elt} {
if {[llength $l] == 0 || $elt <= [lindex $l 0]} {
return 0
}
set lo 0
set hi [llength $l]
while {$hi - $lo > 1} {
set mid [expr {int(($lo + $hi) / 2)}]
set t [lindex $l $mid]
if {$elt < $t} {
set hi $mid
} elseif {$elt > $t} {
set lo $mid
} else {
return $mid
}
}
return $lo
}
# Make sure rows $start..$end-1 are valid in displayorder and parentlist
proc make_disporder {start end} {
global vrownum curview commitidx displayorder parentlist
global varccommits varcorder parents vrowmod varcrow
global d_valid_start d_valid_end
if {$end > $vrowmod($curview)} {
update_arcrows $curview
}
set ai [bsearch $vrownum($curview) $start]
set start [lindex $vrownum($curview) $ai]
set narc [llength $vrownum($curview)]
for {set r $start} {$ai < $narc && $r < $end} {incr ai} {
set a [lindex $varcorder($curview) $ai]
set l [llength $displayorder]
set al [llength $varccommits($curview,$a)]
if {$l < $r + $al} {
if {$l < $r} {
set pad [ntimes [expr {$r - $l}] {}]
set displayorder [concat $displayorder $pad]
set parentlist [concat $parentlist $pad]
} elseif {$l > $r} {
set displayorder [lrange $displayorder 0 [expr {$r - 1}]]
set parentlist [lrange $parentlist 0 [expr {$r - 1}]]
}
foreach id $varccommits($curview,$a) {
lappend displayorder $id
lappend parentlist $parents($curview,$id)
}
} elseif {[lindex $displayorder [expr {$r + $al - 1}]] eq {}} {
set i $r
foreach id $varccommits($curview,$a) {
lset displayorder $i $id
lset parentlist $i $parents($curview,$id)
incr i
}
}
incr r $al
}
}
proc commitonrow {row} {
global displayorder
set id [lindex $displayorder $row]
if {$id eq {}} {
make_disporder $row [expr {$row + 1}]
set id [lindex $displayorder $row]
}
return $id
}
proc closevarcs {v} {
global varctok varccommits varcid parents children
global cmitlisted commitidx commitinterest vtokmod
set missing_parents 0
set scripts {}
set narcs [llength $varctok($v)]
for {set a 1} {$a < $narcs} {incr a} {
set id [lindex $varccommits($v,$a) end]
foreach p $parents($v,$id) {
if {[info exists varcid($v,$p)]} continue
# add p as a new commit
incr missing_parents
set cmitlisted($v,$p) 0
set parents($v,$p) {}
if {[llength $children($v,$p)] == 1 &&
[llength $parents($v,$id)] == 1} {
set b $a
} else {
set b [newvarc $v $p]
}
set varcid($v,$p) $b
if {[string compare [lindex $varctok($v) $b] $vtokmod($v)] < 0} {
modify_arc $v $b
}
lappend varccommits($v,$b) $p
incr commitidx($v)
if {[info exists commitinterest($p)]} {
foreach script $commitinterest($p) {
lappend scripts [string map [list "%I" $p] $script]
}
unset commitinterest($id)
}
}
}
if {$missing_parents > 0} {
foreach s $scripts {
eval $s
}
}
}
# Use $rwid as a substitute for $id, i.e. reparent $id's children to $rwid
# Assumes we already have an arc for $rwid.
proc rewrite_commit {v id rwid} {
global children parents varcid varctok vtokmod varccommits
foreach ch $children($v,$id) {
# make $rwid be $ch's parent in place of $id
set i [lsearch -exact $parents($v,$ch) $id]
if {$i < 0} {
puts "oops rewrite_commit didn't find $id in parent list for $ch"
}
set parents($v,$ch) [lreplace $parents($v,$ch) $i $i $rwid]
# add $ch to $rwid's children and sort the list if necessary
if {[llength [lappend children($v,$rwid) $ch]] > 1} {
set children($v,$rwid) [lsort -command [list vtokcmp $v] \
$children($v,$rwid)]
}
# fix the graph after joining $id to $rwid
set a $varcid($v,$ch)
fix_reversal $rwid $a $v
# parentlist is wrong for the last element of arc $a
# even if displayorder is right, hence the 3rd arg here
modify_arc $v $a [expr {[llength $varccommits($v,$a)] - 1}]
}
}
proc getcommitlines {fd inst view updating} {
global cmitlisted commitinterest leftover
global commitidx commitdata vdatemode
global parents children curview hlview
global idpending ordertok
global varccommits varcid varctok vtokmod vfilelimit
set stuff [read $fd 500000]
# git log doesn't terminate the last commit with a null...
if {$stuff == {} && $leftover($inst) ne {} && [eof $fd]} {
set stuff "\0"
}
if {$stuff == {}} {
if {![eof $fd]} {
return 1
}
global commfd viewcomplete viewactive viewname
global viewinstances
unset commfd($inst)
set i [lsearch -exact $viewinstances($view) $inst]
if {$i >= 0} {
set viewinstances($view) [lreplace $viewinstances($view) $i $i]
}
# set it blocking so we wait for the process to terminate
fconfigure $fd -blocking 1
if {[catch {close $fd} err]} {
set fv {}
if {$view != $curview} {
set fv " for the \"$viewname($view)\" view"
}
if {[string range $err 0 4] == "usage"} {
set err "Gitk: error reading commits$fv:\
bad arguments to git log."
if {$viewname($view) eq "Command line"} {
append err \
" (Note: arguments to gitk are passed to git log\
to allow selection of commits to be displayed.)"
}
} else {
set err "Error reading commits$fv: $err"
}
error_popup $err
}
if {[incr viewactive($view) -1] <= 0} {
set viewcomplete($view) 1
# Check if we have seen any ids listed as parents that haven't
# appeared in the list
closevarcs $view
notbusy $view
}
if {$view == $curview} {
run chewcommits
}
return 0
}
set start 0
set gotsome 0
set scripts {}
while 1 {
set i [string first "\0" $stuff $start]
if {$i < 0} {
append leftover($inst) [string range $stuff $start end]
break
}
if {$start == 0} {
set cmit $leftover($inst)
append cmit [string range $stuff 0 [expr {$i - 1}]]
set leftover($inst) {}
} else {
set cmit [string range $stuff $start [expr {$i - 1}]]
}
set start [expr {$i + 1}]
set j [string first "\n" $cmit]
set ok 0
set listed 1
if {$j >= 0 && [string match "commit *" $cmit]} {
set ids [string range $cmit 7 [expr {$j - 1}]]
if {[string match {[-^<>]*} $ids]} {
switch -- [string index $ids 0] {
"-" {set listed 0}
"^" {set listed 2}
"<" {set listed 3}
">" {set listed 4}
}
set ids [string range $ids 1 end]
}
set ok 1
foreach id $ids {
if {[string length $id] != 40} {
set ok 0
break
}
}
}
if {!$ok} {
set shortcmit $cmit
if {[string length $shortcmit] > 80} {
set shortcmit "[string range $shortcmit 0 80]..."
}
error_popup "[mc "Can't parse git log output:"] {$shortcmit}"
exit 1
}
set id [lindex $ids 0]
set vid $view,$id
if {!$listed && $updating && ![info exists varcid($vid)] &&
$vfilelimit($view) ne {}} {
# git log doesn't rewrite parents for unlisted commits
# when doing path limiting, so work around that here
# by working out the rewritten parent with git rev-list
# and if we already know about it, using the rewritten
# parent as a substitute parent for $id's children.
if {![catch {
set rwid [exec git rev-list --first-parent --max-count=1 \
$id -- $vfilelimit($view)]
}]} {
if {$rwid ne {} && [info exists varcid($view,$rwid)]} {
# use $rwid in place of $id
rewrite_commit $view $id $rwid
continue
}
}
}
set a 0
if {[info exists varcid($vid)]} {
if {$cmitlisted($vid) || !$listed} continue
set a $varcid($vid)
}
if {$listed} {
set olds [lrange $ids 1 end]
} else {
set olds {}
}
set commitdata($id) [string range $cmit [expr {$j + 1}] end]
set cmitlisted($vid) $listed
set parents($vid) $olds
if {![info exists children($vid)]} {
set children($vid) {}
} elseif {$a == 0 && [llength $children($vid)] == 1} {
set k [lindex $children($vid) 0]
if {[llength $parents($view,$k)] == 1 &&
(!$vdatemode($view) ||
$varcid($view,$k) == [llength $varctok($view)] - 1)} {
set a $varcid($view,$k)
}
}
if {$a == 0} {
# new arc
set a [newvarc $view $id]
}
if {[string compare [lindex $varctok($view) $a] $vtokmod($view)] < 0} {
modify_arc $view $a
}
if {![info exists varcid($vid)]} {
set varcid($vid) $a
lappend varccommits($view,$a) $id
incr commitidx($view)
}
set i 0
foreach p $olds {
if {$i == 0 || [lsearch -exact $olds $p] >= $i} {
set vp $view,$p
if {[llength [lappend children($vp) $id]] > 1 &&
[vtokcmp $view [lindex $children($vp) end-1] $id] > 0} {
set children($vp) [lsort -command [list vtokcmp $view] \
$children($vp)]
catch {unset ordertok}
}
if {[info exists varcid($view,$p)]} {
fix_reversal $p $a $view
}
}
incr i
}
if {[info exists commitinterest($id)]} {
foreach script $commitinterest($id) {
lappend scripts [string map [list "%I" $id] $script]
}
unset commitinterest($id)
}
set gotsome 1
}
if {$gotsome} {
global numcommits hlview
if {$view == $curview} {
set numcommits $commitidx($view)
run chewcommits
}
if {[info exists hlview] && $view == $hlview} {
# we never actually get here...
run vhighlightmore
}
foreach s $scripts {
eval $s
}
}
return 2
}
proc chewcommits {} {
global curview hlview viewcomplete
global pending_select
layoutmore
if {$viewcomplete($curview)} {
global commitidx varctok
global numcommits startmsecs
if {[info exists pending_select]} {
update
reset_pending_select {}
if {[commitinview $pending_select $curview]} {
selectline [rowofcommit $pending_select] 1
} else {
set row [first_real_row]
selectline $row 1
}
}
if {$commitidx($curview) > 0} {
#set ms [expr {[clock clicks -milliseconds] - $startmsecs}]
#puts "overall $ms ms for $numcommits commits"
#puts "[llength $varctok($view)] arcs, $commitidx($view) commits"
} else {
show_status [mc "No commits selected"]
}
notbusy layout
}
return 0
}
proc readcommit {id} {
if {[catch {set contents [exec git cat-file commit $id]}]} return
parsecommit $id $contents 0
}
proc parsecommit {id contents listed} {
global commitinfo cdate
set inhdr 1
set comment {}
set headline {}
set auname {}
set audate {}
set comname {}
set comdate {}
set hdrend [string first "\n\n" $contents]
if {$hdrend < 0} {
# should never happen...
set hdrend [string length $contents]
}
set header [string range $contents 0 [expr {$hdrend - 1}]]
set comment [string range $contents [expr {$hdrend + 2}] end]
foreach line [split $header "\n"] {
set tag [lindex $line 0]
if {$tag == "author"} {
set audate [lindex $line end-1]
set auname [lrange $line 1 end-2]
} elseif {$tag == "committer"} {
set comdate [lindex $line end-1]
set comname [lrange $line 1 end-2]
}
}
set headline {}
# take the first non-blank line of the comment as the headline
set headline [string trimleft $comment]
set i [string first "\n" $headline]
if {$i >= 0} {
set headline [string range $headline 0 $i]
}
set headline [string trimright $headline]
set i [string first "\r" $headline]
if {$i >= 0} {
set headline [string trimright [string range $headline 0 $i]]
}
if {!$listed} {
# git log indents the comment by 4 spaces;
# if we got this via git cat-file, add the indentation
set newcomment {}
foreach line [split $comment "\n"] {
append newcomment " "
append newcomment $line
append newcomment "\n"
}
set comment $newcomment
}
if {$comdate != {}} {
set cdate($id) $comdate
}
set commitinfo($id) [list $headline $auname $audate \
$comname $comdate $comment]
}
proc getcommit {id} {
global commitdata commitinfo
if {[info exists commitdata($id)]} {
parsecommit $id $commitdata($id) 1
} else {
readcommit $id
if {![info exists commitinfo($id)]} {
set commitinfo($id) [list [mc "No commit information available"]]
}
}
return 1
}
proc readrefs {} {
global tagids idtags headids idheads tagobjid
global otherrefids idotherrefs mainhead mainheadid
foreach v {tagids idtags headids idheads otherrefids idotherrefs} {
catch {unset $v}
}
set refd [open [list | git show-ref -d] r]
while {[gets $refd line] >= 0} {
if {[string index $line 40] ne " "} continue
set id [string range $line 0 39]
set ref [string range $line 41 end]
if {![string match "refs/*" $ref]} continue
set name [string range $ref 5 end]
if {[string match "remotes/*" $name]} {
if {![string match "*/HEAD" $name]} {
set headids($name) $id
lappend idheads($id) $name
}
} elseif {[string match "heads/*