Skip to content

Commit ee66e08

Browse files
committed
gitk: Make updates go faster
This goes back to the method of doing updates where we translate the revisions we're given to SHA1 ids and then remove the ones we've asked for before or that we've already come across. This speeds up updates enormously in most cases since it means git log doesn't have to traverse large parts of the tree. We used to do this, but it had bugs, and commit 468bcae (gitk: Don't filter view arguments through git rev-parse) went to the slower method to avoid the bugs. In order to do this properly, we have to parse the command line and understand all the flag arguments. So this adds a parser that checks all the flag arguments. If there are any we don't know about, we disable the optimization and just pass the whole lot to git log (except for -d/--date-order, which we remove from the list). With this we can then use git rev-parse on the non-flag arguments to work out exactly what SHA1 ids are included and excluded in the list, which then enables us to ask for just the new ones when updating. One wrinkle is that we have to turn symmetric diff arguments (of the form a...b) back into symmetric diff form so that --left-right still works, as git rev parse turns a...b into a b ^merge_base(a,b). This also updates a couple of copyright notices. Signed-off-by: Paul Mackerras <paulus@samba.org>
1 parent 3ed31a8 commit ee66e08

File tree

1 file changed

+210
-15
lines changed

1 file changed

+210
-15
lines changed

gitk

Lines changed: 210 additions & 15 deletions
Original file line numberDiff line numberDiff line change
@@ -2,7 +2,7 @@
22
# Tcl ignores the next line -*- tcl -*- \
33
exec wish "$0" -- "$@"
44

5-
# Copyright (C) 2005-2006 Paul Mackerras. All rights reserved.
5+
# Copyright © 2005-2008 Paul Mackerras. All rights reserved.
66
# This program is free software; it may be used, copied, modified
77
# and distributed under the terms of the GNU General Public Licence,
88
# either version 2, or (at your option) any later version.
@@ -117,38 +117,190 @@ proc unmerged_files {files} {
117117
}
118118

119119
proc parseviewargs {n arglist} {
120-
global viewargs vdatemode vmergeonly
120+
global vdatemode vmergeonly vflags vdflags vrevs vfiltered vorigargs
121121

122122
set vdatemode($n) 0
123123
set vmergeonly($n) 0
124-
set glargs {}
125-
foreach arg $viewargs($n) {
124+
set glflags {}
125+
set diffargs {}
126+
set nextisval 0
127+
set revargs {}
128+
set origargs $arglist
129+
set allknown 1
130+
set filtered 0
131+
set i -1
132+
foreach arg $arglist {
133+
incr i
134+
if {$nextisval} {
135+
lappend glflags $arg
136+
set nextisval 0
137+
continue
138+
}
126139
switch -glob -- $arg {
127140
"-d" -
128141
"--date-order" {
129142
set vdatemode($n) 1
143+
# remove from origargs in case we hit an unknown option
144+
set origargs [lreplace $origargs $i $i]
145+
incr i -1
146+
}
147+
# These request or affect diff output, which we don't want.
148+
# Some could be used to set our defaults for diff display.
149+
"-[puabwcrRBMC]" -
150+
"--no-renames" - "--full-index" - "--binary" - "--abbrev=*" -
151+
"--find-copies-harder" - "-l*" - "--ext-diff" - "--no-ext-diff" -
152+
"--src-prefix=*" - "--dst-prefix=*" - "--no-prefix" -
153+
"-O*" - "--text" - "--full-diff" - "--ignore-space-at-eol" -
154+
"--ignore-space-change" - "-U*" - "--unified=*" {
155+
lappend diffargs $arg
156+
}
157+
# These cause our parsing of git log's output to fail, or else
158+
# they're options we want to set ourselves, so ignore them.
159+
"--raw" - "--patch-with-raw" - "--patch-with-stat" -
160+
"--name-only" - "--name-status" - "--color" - "--color-words" -
161+
"--log-size" - "--pretty=*" - "--decorate" - "--abbrev-commit" -
162+
"--cc" - "-z" - "--header" - "--parents" - "--boundary" -
163+
"--no-color" - "-g" - "--walk-reflogs" - "--no-walk" -
164+
"--timestamp" - "relative-date" - "--date=*" - "--stdin" -
165+
"--objects" - "--objects-edge" - "--reverse" {
166+
}
167+
# These are harmless, and some are even useful
168+
"--stat=*" - "--numstat" - "--shortstat" - "--summary" -
169+
"--check" - "--exit-code" - "--quiet" - "--topo-order" -
170+
"--full-history" - "--dense" - "--sparse" -
171+
"--follow" - "--left-right" - "--encoding=*" {
172+
lappend glflags $arg
173+
}
174+
# These mean that we get a subset of the commits
175+
"--diff-filter=*" - "--no-merges" - "--unpacked" -
176+
"--max-count=*" - "--skip=*" - "--since=*" - "--after=*" -
177+
"--until=*" - "--before=*" - "--max-age=*" - "--min-age=*" -
178+
"--author=*" - "--committer=*" - "--grep=*" - "-[iE]" -
179+
"--remove-empty" - "--first-parent" - "--cherry-pick" -
180+
"-S*" - "--pickaxe-all" - "--pickaxe-regex" - {
181+
set filtered 1
182+
lappend glflags $arg
183+
}
184+
# This appears to be the only one that has a value as a
185+
# separate word following it
186+
"-n" {
187+
set filtered 1
188+
set nextisval 1
189+
lappend glflags $arg
190+
}
191+
"--not" {
192+
set notflag [expr {!$notflag}]
193+
lappend revargs $arg
194+
}
195+
"--all" {
196+
lappend revargs $arg
130197
}
131198
"--merge" {
132199
set vmergeonly($n) 1
133-
lappend glargs $arg
200+
# git rev-parse doesn't understand --merge
201+
lappend revargs --gitk-symmetric-diff-marker MERGE_HEAD...HEAD
134202
}
203+
# Other flag arguments including -<n>
204+
"-*" {
205+
if {[string is digit -strict [string range $arg 1 end]]} {
206+
set filtered 1
207+
} else {
208+
# a flag argument that we don't recognize;
209+
# that means we can't optimize
210+
set allknown 0
211+
}
212+
lappend glflags $arg
213+
}
214+
# Non-flag arguments specify commits or ranges of commits
135215
default {
136-
lappend glargs $arg
216+
if {[string match "*...*" $arg]} {
217+
lappend revargs --gitk-symmetric-diff-marker
218+
}
219+
lappend revargs $arg
220+
}
221+
}
222+
}
223+
set vdflags($n) $diffargs
224+
set vflags($n) $glflags
225+
set vrevs($n) $revargs
226+
set vfiltered($n) $filtered
227+
set vorigargs($n) $origargs
228+
return $allknown
229+
}
230+
231+
proc parseviewrevs {view revs} {
232+
global vposids vnegids
233+
234+
if {$revs eq {}} {
235+
set revs HEAD
236+
}
237+
if {[catch {set ids [eval exec git rev-parse $revs]} err]} {
238+
# we get stdout followed by stderr in $err
239+
# for an unknown rev, git rev-parse echoes it and then errors out
240+
set errlines [split $err "\n"]
241+
set badrev {}
242+
for {set l 0} {$l < [llength $errlines]} {incr l} {
243+
set line [lindex $errlines $l]
244+
if {!([string length $line] == 40 && [string is xdigit $line])} {
245+
if {[string match "fatal:*" $line]} {
246+
if {[string match "fatal: ambiguous argument*" $line]
247+
&& $badrev ne {}} {
248+
if {[llength $badrev] == 1} {
249+
set err "unknown revision $badrev"
250+
} else {
251+
set err "unknown revisions: [join $badrev ", "]"
252+
}
253+
} else {
254+
set err [join [lrange $errlines $l end] "\n"]
255+
}
256+
break
257+
}
258+
lappend badrev $line
259+
}
260+
}
261+
error_popup "Error parsing revisions: $err"
262+
return {}
263+
}
264+
set ret {}
265+
set pos {}
266+
set neg {}
267+
set sdm 0
268+
foreach id [split $ids "\n"] {
269+
if {$id eq "--gitk-symmetric-diff-marker"} {
270+
set sdm 4
271+
} elseif {[string match "^*" $id]} {
272+
if {$sdm != 1} {
273+
lappend ret $id
274+
if {$sdm == 3} {
275+
set sdm 0
276+
}
137277
}
278+
lappend neg [string range $id 1 end]
279+
} else {
280+
if {$sdm != 2} {
281+
lappend ret $id
282+
} else {
283+
lset ret end [lindex $ret end]...$id
284+
}
285+
lappend pos $id
138286
}
287+
incr sdm -1
139288
}
140-
return $glargs
289+
set vposids($view) $pos
290+
set vnegids($view) $neg
291+
return $ret
141292
}
142293

143294
# Start off a git log process and arrange to read its output
144295
proc start_rev_list {view} {
145296
global startmsecs commitidx viewcomplete
146297
global commfd leftover tclencoding
147-
global viewargs viewargscmd vactualargs viewfiles vfilelimit
298+
global viewargs viewargscmd viewfiles vfilelimit
148299
global showlocalchanges commitinterest mainheadid
149300
global progressdirn progresscoords proglastnc curview
150301
global viewactive loginstance viewinstances vmergeonly
151302
global pending_select mainheadid
303+
global vcanopt vflags vrevs vorigargs
152304

153305
set startmsecs [clock clicks -milliseconds]
154306
set commitidx($view) 0
@@ -167,8 +319,7 @@ proc start_rev_list {view} {
167319
}
168320
set args [concat $args [split $str "\n"]]
169321
}
170-
set args [parseviewargs $view $args]
171-
set vactualargs($view) $args
322+
set vcanopt($view) [parseviewargs $view $args]
172323

173324
set files $viewfiles($view)
174325
if {$vmergeonly($view)} {
@@ -187,6 +338,16 @@ proc start_rev_list {view} {
187338
}
188339
set vfilelimit($view) $files
189340

341+
if {$vcanopt($view)} {
342+
set revs [parseviewrevs $view $vrevs($view)]
343+
if {$revs eq {}} {
344+
return 0
345+
}
346+
set args [concat $vflags($view) $revs]
347+
} else {
348+
set args $vorigargs($view)
349+
}
350+
190351
if {[catch {
191352
set fd [open [concat | git log --no-color -z --pretty=raw --parents \
192353
--boundary $args "--" $files] r]
@@ -248,11 +409,12 @@ proc getcommits {} {
248409
}
249410

250411
proc updatecommits {} {
251-
global curview vactualargs vfilelimit viewinstances
412+
global curview vcanopt vorigargs vfilelimit viewinstances
252413
global viewactive viewcomplete loginstance tclencoding mainheadid
253414
global startmsecs commfd showneartags showlocalchanges leftover
254415
global mainheadid pending_select
255416
global isworktree
417+
global varcid vposids vnegids vflags vrevs
256418

257419
set isworktree [expr {[exec git rev-parse --is-inside-work-tree] == "true"}]
258420
set oldmainid $mainheadid
@@ -266,13 +428,46 @@ proc updatecommits {} {
266428
}
267429
}
268430
set view $curview
431+
if {$vcanopt($view)} {
432+
set oldpos $vposids($view)
433+
set oldneg $vnegids($view)
434+
set revs [parseviewrevs $view $vrevs($view)]
435+
if {$revs eq {}} {
436+
return
437+
}
438+
# note: getting the delta when negative refs change is hard,
439+
# and could require multiple git log invocations, so in that
440+
# case we ask git log for all the commits (not just the delta)
441+
if {$oldneg eq $vnegids($view)} {
442+
set newrevs {}
443+
set npos 0
444+
# take out positive refs that we asked for before or
445+
# that we have already seen
446+
foreach rev $revs {
447+
if {[string length $rev] == 40} {
448+
if {[lsearch -exact $oldpos $rev] < 0
449+
&& ![info exists varcid($view,$rev)]} {
450+
lappend newrevs $rev
451+
incr npos
452+
}
453+
} else {
454+
lappend $newrevs $rev
455+
}
456+
}
457+
if {$npos == 0} return
458+
set revs $newrevs
459+
set vposids($view) [lsort -unique [concat $oldpos $vposids($view)]]
460+
}
461+
set args [concat $vflags($view) $revs --not $oldpos]
462+
} else {
463+
set args $vorigargs($view)
464+
}
269465
if {[catch {
270466
set fd [open [concat | git log --no-color -z --pretty=raw --parents \
271-
--boundary $vactualargs($view) --not [seeds $view] \
272-
"--" $vfilelimit($view)] r]
467+
--boundary $args "--" $vfilelimit($view)] r]
273468
} err]} {
274469
error_popup "Error executing git log: $err"
275-
exit 1
470+
return
276471
}
277472
if {$viewactive($view) == 0} {
278473
set startmsecs [clock clicks -milliseconds]
@@ -2217,7 +2412,7 @@ proc about {} {
22172412
message $w.m -text [mc "
22182413
Gitk - a commit viewer for git
22192414
2220-
Copyright © 2005-2006 Paul Mackerras
2415+
Copyright © 2005-2008 Paul Mackerras
22212416
22222417
Use and redistribute under the terms of the GNU General Public License"] \
22232418
-justify center -aspect 400 -border 2 -bg white -relief groove

0 commit comments

Comments
 (0)