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