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