Git fork
at reftables-rust 624 lines 15 kB view raw
1# git-gui revision chooser 2# Copyright (C) 2006, 2007 Shawn Pearce 3 4class choose_rev { 5 6image create photo ::choose_rev::img_find -data {R0lGODlhEAAQAIYAAPwCBCQmJDw+PBQSFAQCBMza3NTm5MTW1HyChOT29Ozq7MTq7Kze5Kzm7Oz6/NTy9Iza5GzGzKzS1Nzy9Nz29Kzq9HTGzHTK1Lza3AwKDLzu9JTi7HTW5GTCzITO1Mzq7Hza5FTK1ESyvHzKzKzW3DQyNDyqtDw6PIzW5HzGzAT+/Dw+RKyurNTOzMTGxMS+tJSGdATCxHRydLSqpLymnLSijBweHERCRNze3Pz69PTy9Oze1OTSxOTGrMSqlLy+vPTu5OzSvMymjNTGvNS+tMy2pMyunMSefAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAACH5BAEAAAAALAAAAAAQABAAAAe4gACCAAECA4OIiAIEBQYHBAKJgwIICQoLDA0IkZIECQ4PCxARCwSSAxITFA8VEBYXGBmJAQYLGhUbHB0eH7KIGRIMEBAgISIjJKaIJQQLFxERIialkieUGigpKRoIBCqJKyyLBwvJAioEyoICLS4v6QQwMQQyLuqLli8zNDU2BCf1lN3AkUPHDh49fAQAAEnGD1MCCALZEaSHkIUMBQS8wWMIkSJGhBzBmFEGgRsBUqpMiSgdAD+BAAAh/mhDcmVhdGVkIGJ5IEJNUFRvR0lGIFBybyB2ZXJzaW9uIDIuNQ0KqSBEZXZlbENvciAxOTk3LDE5OTguIEFsbCByaWdodHMgcmVzZXJ2ZWQuDQpodHRwOi8vd3d3LmRldmVsY29yLmNvbQA7} 7 8field w ; # our megawidget path 9field w_list ; # list of currently filtered specs 10field w_filter ; # filter entry for $w_list 11 12field c_expr {}; # current revision expression 13field filter ""; # current filter string 14field revtype head; # type of revision chosen 15field cur_specs [list]; # list of specs for $revtype 16field spec_head ; # list of all head specs 17field spec_trck ; # list of all tracking branch specs 18field spec_tag ; # list of all tag specs 19field tip_data ; # array of tip commit info by refname 20field log_last ; # array of reflog date by refname 21 22field tooltip_wm {} ; # Current tooltip toplevel, if open 23field tooltip_t {} ; # Text widget in $tooltip_wm 24field tooltip_timer {} ; # Current timer event for our tooltip 25 26proc new {path {title {}}} { 27 return [_new $path 0 $title] 28} 29 30proc new_unmerged {path {title {}}} { 31 return [_new $path 1 $title] 32} 33 34constructor _new {path unmerged_only title} { 35 global current_branch is_detached 36 37 if {![info exists ::all_remotes]} { 38 load_all_remotes 39 } 40 41 set w $path 42 43 if {$title ne {}} { 44 ttk::labelframe $w -text $title 45 } else { 46 ttk::frame $w 47 } 48 bind $w <Destroy> [cb _delete %W] 49 50 if {$is_detached} { 51 ttk::radiobutton $w.detachedhead_r \ 52 -text [mc "This Detached Checkout"] \ 53 -value HEAD \ 54 -variable @revtype 55 grid $w.detachedhead_r -sticky we -padx {0 5} -columnspan 2 56 } 57 58 ttk::radiobutton $w.expr_r \ 59 -text [mc "Revision Expression:"] \ 60 -value expr \ 61 -variable @revtype 62 ttk::entry $w.expr_t \ 63 -width 50 \ 64 -textvariable @c_expr \ 65 -validate key \ 66 -validatecommand [cb _validate %d %S] 67 grid $w.expr_r $w.expr_t -sticky we -padx {0 5} 68 69 ttk::frame $w.types 70 ttk::radiobutton $w.types.head_r \ 71 -text [mc "Local Branch"] \ 72 -value head \ 73 -variable @revtype 74 pack $w.types.head_r -side left 75 ttk::radiobutton $w.types.trck_r \ 76 -text [mc "Tracking Branch"] \ 77 -value trck \ 78 -variable @revtype 79 pack $w.types.trck_r -side left 80 ttk::radiobutton $w.types.tag_r \ 81 -text [mc "Tag"] \ 82 -value tag \ 83 -variable @revtype 84 pack $w.types.tag_r -side left 85 set w_filter $w.types.filter 86 ttk::entry $w_filter \ 87 -width 12 \ 88 -textvariable @filter \ 89 -validate key \ 90 -validatecommand [cb _filter %P] 91 pack $w_filter -side right 92 pack [ttk::label $w.types.filter_icon \ 93 -image ::choose_rev::img_find \ 94 ] -side right 95 grid $w.types -sticky we -padx {0 5} -columnspan 2 96 97 ttk::frame $w.list -style SListbox.TFrame -padding 2 98 set w_list $w.list.l 99 listbox $w_list \ 100 -font font_diff \ 101 -width 50 \ 102 -height 10 \ 103 -selectmode browse \ 104 -exportselection false \ 105 -xscrollcommand [cb _sb_set $w.list.sbx h] \ 106 -yscrollcommand [cb _sb_set $w.list.sby v] 107 $w_list configure -relief flat -highlightthickness 0 -borderwidth 0 108 pack $w_list -fill both -expand 1 109 grid $w.list -sticky nswe -padx {20 5} -columnspan 2 110 bind $w_list <Any-Motion> [cb _show_tooltip @%x,%y] 111 bind $w_list <Any-Enter> [cb _hide_tooltip] 112 bind $w_list <Any-Leave> [cb _hide_tooltip] 113 bind $w_list <Destroy> [cb _hide_tooltip] 114 115 grid columnconfigure $w 1 -weight 1 116 if {$is_detached} { 117 grid rowconfigure $w 3 -weight 1 118 } else { 119 grid rowconfigure $w 2 -weight 1 120 } 121 122 trace add variable @revtype write [cb _select] 123 bind $w_filter <Key-Return> [list focus $w_list]\;break 124 bind $w_filter <Key-Down> [list focus $w_list] 125 126 set fmt list 127 append fmt { %(refname)} 128 append fmt { [list} 129 append fmt { %(objecttype)} 130 append fmt { %(objectname)} 131 append fmt { [concat %(taggername) %(authorname)]} 132 append fmt { [reformat_date [concat %(taggerdate) %(authordate)]]} 133 append fmt { %(subject)} 134 append fmt {] [list} 135 append fmt { %(*objecttype)} 136 append fmt { %(*objectname)} 137 append fmt { %(*authorname)} 138 append fmt { [reformat_date %(*authordate)]} 139 append fmt { %(*subject)} 140 append fmt {]} 141 set all_refn [list] 142 set fr_fd [git_read [list for-each-ref \ 143 --tcl \ 144 --sort=-taggerdate \ 145 --format=$fmt \ 146 refs/heads \ 147 refs/remotes \ 148 refs/tags \ 149 ]] 150 fconfigure $fr_fd -encoding utf-8 151 while {[gets $fr_fd line] > 0} { 152 set line [eval $line] 153 if {[lindex $line 1 0] eq {tag}} { 154 if {[lindex $line 2 0] eq {commit}} { 155 set sha1 [lindex $line 2 1] 156 } else { 157 continue 158 } 159 } elseif {[lindex $line 1 0] eq {commit}} { 160 set sha1 [lindex $line 1 1] 161 } else { 162 continue 163 } 164 set refn [lindex $line 0] 165 set tip_data($refn) [lrange $line 1 end] 166 lappend cmt_refn($sha1) $refn 167 lappend all_refn $refn 168 } 169 close $fr_fd 170 171 if {$unmerged_only} { 172 set fr_fd [git_read [list rev-list --all ^$::HEAD]] 173 while {[gets $fr_fd sha1] > 0} { 174 if {[catch {set rlst $cmt_refn($sha1)}]} continue 175 foreach refn $rlst { 176 set inc($refn) 1 177 } 178 } 179 close $fr_fd 180 } else { 181 foreach refn $all_refn { 182 set inc($refn) 1 183 } 184 } 185 186 set spec_head [list] 187 foreach name [load_all_heads] { 188 set refn refs/heads/$name 189 if {[info exists inc($refn)]} { 190 lappend spec_head [list $name $refn] 191 } 192 } 193 194 set spec_trck [list] 195 foreach spec [all_tracking_branches] { 196 set refn [lindex $spec 0] 197 if {[info exists inc($refn)]} { 198 regsub ^refs/(heads|remotes)/ $refn {} name 199 lappend spec_trck [concat $name $spec] 200 } 201 } 202 203 set spec_tag [list] 204 foreach name [load_all_tags] { 205 set refn refs/tags/$name 206 if {[info exists inc($refn)]} { 207 lappend spec_tag [list $name $refn] 208 } 209 } 210 211 if {$is_detached} { set revtype HEAD 212 } elseif {[llength $spec_head] > 0} { set revtype head 213 } elseif {[llength $spec_trck] > 0} { set revtype trck 214 } elseif {[llength $spec_tag ] > 0} { set revtype tag 215 } else { set revtype expr 216 } 217 218 if {$revtype eq {head} && $current_branch ne {}} { 219 set i 0 220 foreach spec $spec_head { 221 if {[lindex $spec 0] eq $current_branch} { 222 $w_list selection clear 0 end 223 $w_list selection set $i 224 break 225 } 226 incr i 227 } 228 } 229 230 return $this 231} 232 233method none {text} { 234 if {![winfo exists $w.none_r]} { 235 ttk::radiobutton $w.none_r \ 236 -value none \ 237 -variable @revtype 238 grid $w.none_r -sticky we -padx {0 5} -columnspan 2 239 } 240 $w.none_r configure -text $text 241} 242 243method get {} { 244 switch -- $revtype { 245 head - 246 trck - 247 tag { 248 set i [$w_list curselection] 249 if {$i ne {}} { 250 return [lindex $cur_specs $i 0] 251 } else { 252 return {} 253 } 254 } 255 256 HEAD { return HEAD } 257 expr { return $c_expr } 258 none { return {} } 259 default { error "unknown type of revision" } 260 } 261} 262 263method pick_tracking_branch {} { 264 set revtype trck 265} 266 267method focus_filter {} { 268 if {[$w_filter cget -state] eq {normal}} { 269 focus $w_filter 270 } 271} 272 273method bind_listbox {event script} { 274 bind $w_list $event $script 275} 276 277method get_local_branch {} { 278 if {$revtype eq {head}} { 279 return [_expr $this] 280 } else { 281 return {} 282 } 283} 284 285method get_tracking_branch {} { 286 set i [$w_list curselection] 287 if {$i eq {} || $revtype ne {trck}} { 288 return {} 289 } 290 return [lrange [lindex $cur_specs $i] 1 end] 291} 292 293method get_commit {} { 294 set e [_expr $this] 295 if {$e eq {}} { 296 return {} 297 } 298 return [git rev-parse --verify "$e^0"] 299} 300 301method commit_or_die {} { 302 if {[catch {set new [get_commit $this]} err]} { 303 304 # Cleanup the not-so-friendly error from rev-parse. 305 # 306 regsub {^fatal:\s*} $err {} err 307 if {$err eq {Needed a single revision}} { 308 set err {} 309 } 310 311 set top [winfo toplevel $w] 312 set msg [strcat [mc "Invalid revision: %s" [get $this]] "\n\n$err"] 313 tk_messageBox \ 314 -icon error \ 315 -type ok \ 316 -title [wm title $top] \ 317 -parent $top \ 318 -message $msg 319 error $msg 320 } 321 return $new 322} 323 324method _expr {} { 325 switch -- $revtype { 326 head - 327 trck - 328 tag { 329 set i [$w_list curselection] 330 if {$i ne {}} { 331 return [lindex $cur_specs $i 1] 332 } else { 333 error [mc "No revision selected."] 334 } 335 } 336 337 expr { 338 if {$c_expr ne {}} { 339 return $c_expr 340 } else { 341 error [mc "Revision expression is empty."] 342 } 343 } 344 HEAD { return HEAD } 345 none { return {} } 346 default { error "unknown type of revision" } 347 } 348} 349 350method _validate {d S} { 351 if {$d == 1} { 352 if {[regexp {\s} $S]} { 353 return 0 354 } 355 if {[string length $S] > 0} { 356 set revtype expr 357 } 358 } 359 return 1 360} 361 362method _filter {P} { 363 if {[regexp {\s} $P]} { 364 return 0 365 } 366 _rebuild $this $P 367 return 1 368} 369 370method _select {args} { 371 _rebuild $this $filter 372 focus_filter $this 373} 374 375method _rebuild {pat} { 376 set ste normal 377 switch -- $revtype { 378 head { set new $spec_head } 379 trck { set new $spec_trck } 380 tag { set new $spec_tag } 381 expr - 382 HEAD - 383 none { 384 set new [list] 385 set ste disabled 386 } 387 } 388 389 if {[$w_list cget -state] eq {disabled}} { 390 $w_list configure -state normal 391 } 392 $w_list delete 0 end 393 394 if {$pat ne {}} { 395 set pat *${pat}* 396 } 397 set cur_specs [list] 398 foreach spec $new { 399 set txt [lindex $spec 0] 400 if {$pat eq {} || [string match $pat $txt]} { 401 lappend cur_specs $spec 402 $w_list insert end $txt 403 } 404 } 405 if {$cur_specs ne {}} { 406 $w_list selection clear 0 end 407 $w_list selection set 0 408 } 409 410 if {[$w_filter cget -state] ne $ste} { 411 $w_list configure -state $ste 412 $w_filter configure -state $ste 413 } 414} 415 416method _delete {current} { 417 if {$current eq $w} { 418 delete_this 419 } 420} 421 422method _sb_set {sb orient first last} { 423 set old_focus [focus -lastfor $w] 424 425 if {$first == 0 && $last == 1} { 426 if {[winfo exists $sb]} { 427 destroy $sb 428 if {$old_focus ne {}} { 429 update 430 focus $old_focus 431 } 432 } 433 return 434 } 435 436 if {![winfo exists $sb]} { 437 if {$orient eq {h}} { 438 ttk::scrollbar $sb -orient h -command [list $w_list xview] 439 pack $sb -fill x -side bottom -before $w_list 440 } else { 441 ttk::scrollbar $sb -orient v -command [list $w_list yview] 442 pack $sb -fill y -side right -before $w_list 443 } 444 if {$old_focus ne {}} { 445 update 446 focus $old_focus 447 } 448 } 449 450 catch {$sb set $first $last} 451} 452 453method _show_tooltip {pos} { 454 if {$tooltip_wm ne {}} { 455 _open_tooltip $this 456 } elseif {$tooltip_timer eq {}} { 457 set tooltip_timer [after 1000 [cb _open_tooltip]] 458 } 459} 460 461method _open_tooltip {} { 462 global remote_url 463 464 set tooltip_timer {} 465 set pos_x [winfo pointerx $w_list] 466 set pos_y [winfo pointery $w_list] 467 if {[winfo containing $pos_x $pos_y] ne $w_list} { 468 _hide_tooltip $this 469 return 470 } 471 472 set pos @[join [list \ 473 [expr {$pos_x - [winfo rootx $w_list]}] \ 474 [expr {$pos_y - [winfo rooty $w_list]}]] ,] 475 set lno [$w_list index $pos] 476 if {$lno eq {}} { 477 _hide_tooltip $this 478 return 479 } 480 481 set spec [lindex $cur_specs $lno] 482 set refn [lindex $spec 1] 483 if {$refn eq {}} { 484 _hide_tooltip $this 485 return 486 } 487 488 if {$tooltip_wm eq {}} { 489 set tooltip_wm [toplevel $w_list.tooltip -borderwidth 1] 490 catch {wm attributes $tooltip_wm -type tooltip} 491 wm overrideredirect $tooltip_wm 1 492 wm transient $tooltip_wm [winfo toplevel $w_list] 493 set tooltip_t $tooltip_wm.label 494 text $tooltip_t \ 495 -takefocus 0 \ 496 -highlightthickness 0 \ 497 -relief flat \ 498 -borderwidth 0 \ 499 -wrap none \ 500 -background lightyellow \ 501 -foreground black 502 $tooltip_t tag conf section_header -font font_uibold 503 bind $tooltip_wm <Escape> [cb _hide_tooltip] 504 pack $tooltip_t 505 } else { 506 $tooltip_t conf -state normal 507 $tooltip_t delete 0.0 end 508 } 509 510 set data $tip_data($refn) 511 if {[lindex $data 0 0] eq {tag}} { 512 set tag [lindex $data 0] 513 if {[lindex $data 1 0] eq {commit}} { 514 set cmit [lindex $data 1] 515 } else { 516 set cmit {} 517 } 518 } elseif {[lindex $data 0 0] eq {commit}} { 519 set tag {} 520 set cmit [lindex $data 0] 521 } 522 523 $tooltip_t insert end [lindex $spec 0] 524 set last [_reflog_last $this [lindex $spec 1]] 525 if {$last ne {}} { 526 $tooltip_t insert end "\n" 527 $tooltip_t insert end [mc "Updated"] 528 $tooltip_t insert end " $last" 529 } 530 $tooltip_t insert end "\n" 531 532 if {$tag ne {}} { 533 $tooltip_t insert end "\n" 534 $tooltip_t insert end [mc "Tag"] section_header 535 $tooltip_t insert end " [lindex $tag 1]\n" 536 $tooltip_t insert end [lindex $tag 2] 537 $tooltip_t insert end " ([lindex $tag 3])\n" 538 $tooltip_t insert end [lindex $tag 4] 539 $tooltip_t insert end "\n" 540 } 541 542 if {$cmit ne {}} { 543 $tooltip_t insert end "\n" 544 $tooltip_t insert end [mc "Commit@@noun"] section_header 545 $tooltip_t insert end " [lindex $cmit 1]\n" 546 $tooltip_t insert end [lindex $cmit 2] 547 $tooltip_t insert end " ([lindex $cmit 3])\n" 548 $tooltip_t insert end [lindex $cmit 4] 549 } 550 551 if {[llength $spec] > 2} { 552 $tooltip_t insert end "\n" 553 $tooltip_t insert end [mc "Remote"] section_header 554 $tooltip_t insert end " [lindex $spec 2]\n" 555 $tooltip_t insert end [mc "URL"] 556 $tooltip_t insert end " $remote_url([lindex $spec 2])\n" 557 $tooltip_t insert end [mc "Branch"] 558 $tooltip_t insert end " [lindex $spec 3]" 559 } 560 561 $tooltip_t conf -state disabled 562 _position_tooltip $this 563} 564 565method _reflog_last {name} { 566 if {[info exists reflog_last($name)]} { 567 return reflog_last($name) 568 } 569 570 set last {} 571 if {[catch {set last [file mtime [gitdir $name]]}] 572 && ![catch {set g [safe_open_file [gitdir logs $name] r]}]} { 573 fconfigure $g -encoding iso8859-1 574 while {[gets $g line] >= 0} { 575 if {[regexp {> ([1-9][0-9]*) } $line line when]} { 576 set last $when 577 } 578 } 579 close $g 580 } 581 582 if {$last ne {}} { 583 set last [format_date $last] 584 } 585 set reflog_last($name) $last 586 return $last 587} 588 589method _position_tooltip {} { 590 set max_h [lindex [split [$tooltip_t index end] .] 0] 591 set max_w 0 592 for {set i 1} {$i <= $max_h} {incr i} { 593 set c [lindex [split [$tooltip_t index "$i.0 lineend"] .] 1] 594 if {$c > $max_w} {set max_w $c} 595 } 596 $tooltip_t conf -width $max_w -height $max_h 597 598 set req_w [winfo reqwidth $tooltip_t] 599 set req_h [winfo reqheight $tooltip_t] 600 set pos_x [expr {[winfo pointerx .] + 5}] 601 set pos_y [expr {[winfo pointery .] + 10}] 602 603 set g "${req_w}x${req_h}" 604 if {[tk windowingsystem] eq "win32" || $pos_x >= 0} {append g +} 605 append g $pos_x 606 if {[tk windowingsystem] eq "win32" || $pos_y >= 0} {append g +} 607 append g $pos_y 608 609 wm geometry $tooltip_wm $g 610 raise $tooltip_wm 611} 612 613method _hide_tooltip {} { 614 if {$tooltip_wm ne {}} { 615 destroy $tooltip_wm 616 set tooltip_wm {} 617 } 618 if {$tooltip_timer ne {}} { 619 after cancel $tooltip_timer 620 set tooltip_timer {} 621 } 622} 623 624}