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