git-gui / git-gui.shon commit Merge branch 'sp/cvsexport' (c0e9892)
   1#!/bin/sh
   2# Tcl ignores the next line -*- tcl -*- \
   3exec wish "$0" -- "$@"
   4
   5set appvers {@@GITGUI_VERSION@@}
   6set copyright {
   7Copyright © 2006, 2007 Shawn Pearce, et. al.
   8
   9This program is free software; you can redistribute it and/or modify
  10it under the terms of the GNU General Public License as published by
  11the Free Software Foundation; either version 2 of the License, or
  12(at your option) any later version.
  13
  14This program is distributed in the hope that it will be useful,
  15but WITHOUT ANY WARRANTY; without even the implied warranty of
  16MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
  17GNU General Public License for more details.
  18
  19You should have received a copy of the GNU General Public License
  20along with this program; if not, write to the Free Software
  21Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA  02111-1307  USA}
  22
  23######################################################################
  24##
  25## configure our library
  26
  27set oguilib {@@GITGUI_LIBDIR@@}
  28if {[string match @@* $oguilib]} {
  29        set oguilib [file join [file dirname [file normalize $argv0]] lib]
  30}
  31set idx [file join $oguilib tclIndex]
  32catch {
  33        set fd [open $idx r]
  34        if {[gets $fd] eq {# Autogenerated by git-gui Makefile}} {
  35                set idx [list]
  36                while {[gets $fd n] >= 0} {
  37                        if {$n ne {} && ![string match #* $n]} {
  38                                lappend idx $n
  39                        }
  40                }
  41        } else {
  42                set idx {}
  43        }
  44        close $fd
  45}
  46if {$idx ne {}} {
  47        set loaded [list]
  48        foreach p $idx {
  49                if {[lsearch -exact $loaded $p] >= 0} continue
  50                puts $p
  51                source [file join $oguilib $p]
  52                lappend loaded $p
  53        }
  54        unset loaded p
  55} else {
  56        set auto_path [concat [list $oguilib] $auto_path]
  57}
  58unset -nocomplain fd idx
  59
  60if {![catch {set _verbose $env(GITGUI_VERBOSE)}]} {
  61        unset _verbose
  62        rename auto_load real__auto_load
  63        proc auto_load {name args} {
  64                puts stderr "auto_load $name"
  65                return [uplevel 1 real__auto_load $name $args]
  66        }
  67        rename source real__source
  68        proc source {name} {
  69                puts stderr "source    $name"
  70                uplevel 1 real__source $name
  71        }
  72}
  73
  74######################################################################
  75##
  76## read only globals
  77
  78set _appname [lindex [file split $argv0] end]
  79set _gitdir {}
  80set _gitexec {}
  81set _reponame {}
  82set _iscygwin {}
  83
  84proc appname {} {
  85        global _appname
  86        return $_appname
  87}
  88
  89proc gitdir {args} {
  90        global _gitdir
  91        if {$args eq {}} {
  92                return $_gitdir
  93        }
  94        return [eval [concat [list file join $_gitdir] $args]]
  95}
  96
  97proc gitexec {args} {
  98        global _gitexec
  99        if {$_gitexec eq {}} {
 100                if {[catch {set _gitexec [git --exec-path]} err]} {
 101                        error "Git not installed?\n\n$err"
 102                }
 103        }
 104        if {$args eq {}} {
 105                return $_gitexec
 106        }
 107        return [eval [concat [list file join $_gitexec] $args]]
 108}
 109
 110proc reponame {} {
 111        global _reponame
 112        return $_reponame
 113}
 114
 115proc is_MacOSX {} {
 116        global tcl_platform tk_library
 117        if {[tk windowingsystem] eq {aqua}} {
 118                return 1
 119        }
 120        return 0
 121}
 122
 123proc is_Windows {} {
 124        global tcl_platform
 125        if {$tcl_platform(platform) eq {windows}} {
 126                return 1
 127        }
 128        return 0
 129}
 130
 131proc is_Cygwin {} {
 132        global tcl_platform _iscygwin
 133        if {$_iscygwin eq {}} {
 134                if {$tcl_platform(platform) eq {windows}} {
 135                        if {[catch {set p [exec cygpath --windir]} err]} {
 136                                set _iscygwin 0
 137                        } else {
 138                                set _iscygwin 1
 139                        }
 140                } else {
 141                        set _iscygwin 0
 142                }
 143        }
 144        return $_iscygwin
 145}
 146
 147proc is_enabled {option} {
 148        global enabled_options
 149        if {[catch {set on $enabled_options($option)}]} {return 0}
 150        return $on
 151}
 152
 153proc enable_option {option} {
 154        global enabled_options
 155        set enabled_options($option) 1
 156}
 157
 158proc disable_option {option} {
 159        global enabled_options
 160        set enabled_options($option) 0
 161}
 162
 163######################################################################
 164##
 165## config
 166
 167proc is_many_config {name} {
 168        switch -glob -- $name {
 169        remote.*.fetch -
 170        remote.*.push
 171                {return 1}
 172        *
 173                {return 0}
 174        }
 175}
 176
 177proc is_config_true {name} {
 178        global repo_config
 179        if {[catch {set v $repo_config($name)}]} {
 180                return 0
 181        } elseif {$v eq {true} || $v eq {1} || $v eq {yes}} {
 182                return 1
 183        } else {
 184                return 0
 185        }
 186}
 187
 188proc load_config {include_global} {
 189        global repo_config global_config default_config
 190
 191        array unset global_config
 192        if {$include_global} {
 193                catch {
 194                        set fd_rc [open "| git config --global --list" r]
 195                        while {[gets $fd_rc line] >= 0} {
 196                                if {[regexp {^([^=]+)=(.*)$} $line line name value]} {
 197                                        if {[is_many_config $name]} {
 198                                                lappend global_config($name) $value
 199                                        } else {
 200                                                set global_config($name) $value
 201                                        }
 202                                }
 203                        }
 204                        close $fd_rc
 205                }
 206        }
 207
 208        array unset repo_config
 209        catch {
 210                set fd_rc [open "| git config --list" r]
 211                while {[gets $fd_rc line] >= 0} {
 212                        if {[regexp {^([^=]+)=(.*)$} $line line name value]} {
 213                                if {[is_many_config $name]} {
 214                                        lappend repo_config($name) $value
 215                                } else {
 216                                        set repo_config($name) $value
 217                                }
 218                        }
 219                }
 220                close $fd_rc
 221        }
 222
 223        foreach name [array names default_config] {
 224                if {[catch {set v $global_config($name)}]} {
 225                        set global_config($name) $default_config($name)
 226                }
 227                if {[catch {set v $repo_config($name)}]} {
 228                        set repo_config($name) $default_config($name)
 229                }
 230        }
 231}
 232
 233######################################################################
 234##
 235## handy utils
 236
 237proc git {args} {
 238        return [eval exec git $args]
 239}
 240
 241auto_load tk_optionMenu
 242rename tk_optionMenu real__tkOptionMenu
 243proc tk_optionMenu {w varName args} {
 244        set m [eval real__tkOptionMenu $w $varName $args]
 245        $m configure -font font_ui
 246        $w configure -font font_ui
 247        return $m
 248}
 249
 250######################################################################
 251##
 252## version check
 253
 254if {{--version} eq $argv || {version} eq $argv} {
 255        puts "git-gui version $appvers"
 256        exit
 257}
 258
 259set req_maj 1
 260set req_min 5
 261
 262if {[catch {set v [git --version]} err]} {
 263        catch {wm withdraw .}
 264        error_popup "Cannot determine Git version:
 265
 266$err
 267
 268[appname] requires Git $req_maj.$req_min or later."
 269        exit 1
 270}
 271if {[regexp {^git version (\d+)\.(\d+)} $v _junk act_maj act_min]} {
 272        if {$act_maj < $req_maj
 273                || ($act_maj == $req_maj && $act_min < $req_min)} {
 274                catch {wm withdraw .}
 275                error_popup "[appname] requires Git $req_maj.$req_min or later.
 276
 277You are using $v."
 278                exit 1
 279        }
 280} else {
 281        catch {wm withdraw .}
 282        error_popup "Cannot parse Git version string:\n\n$v"
 283        exit 1
 284}
 285unset -nocomplain v _junk act_maj act_min req_maj req_min
 286
 287######################################################################
 288##
 289## repository setup
 290
 291if {[catch {
 292                set _gitdir $env(GIT_DIR)
 293                set _prefix {}
 294                }]
 295        && [catch {
 296                set _gitdir [git rev-parse --git-dir]
 297                set _prefix [git rev-parse --show-prefix]
 298        } err]} {
 299        catch {wm withdraw .}
 300        error_popup "Cannot find the git directory:\n\n$err"
 301        exit 1
 302}
 303if {![file isdirectory $_gitdir] && [is_Cygwin]} {
 304        catch {set _gitdir [exec cygpath --unix $_gitdir]}
 305}
 306if {![file isdirectory $_gitdir]} {
 307        catch {wm withdraw .}
 308        error_popup "Git directory not found:\n\n$_gitdir"
 309        exit 1
 310}
 311if {[lindex [file split $_gitdir] end] ne {.git}} {
 312        catch {wm withdraw .}
 313        error_popup "Cannot use funny .git directory:\n\n$_gitdir"
 314        exit 1
 315}
 316if {[catch {cd [file dirname $_gitdir]} err]} {
 317        catch {wm withdraw .}
 318        error_popup "No working directory [file dirname $_gitdir]:\n\n$err"
 319        exit 1
 320}
 321set _reponame [lindex [file split \
 322        [file normalize [file dirname $_gitdir]]] \
 323        end]
 324
 325######################################################################
 326##
 327## global init
 328
 329set current_diff_path {}
 330set current_diff_side {}
 331set diff_actions [list]
 332set ui_status_value {Initializing...}
 333
 334set HEAD {}
 335set PARENT {}
 336set MERGE_HEAD [list]
 337set commit_type {}
 338set empty_tree {}
 339set current_branch {}
 340set current_diff_path {}
 341set selected_commit_type new
 342
 343######################################################################
 344##
 345## task management
 346
 347set rescan_active 0
 348set diff_active 0
 349set last_clicked {}
 350
 351set disable_on_lock [list]
 352set index_lock_type none
 353
 354proc lock_index {type} {
 355        global index_lock_type disable_on_lock
 356
 357        if {$index_lock_type eq {none}} {
 358                set index_lock_type $type
 359                foreach w $disable_on_lock {
 360                        uplevel #0 $w disabled
 361                }
 362                return 1
 363        } elseif {$index_lock_type eq "begin-$type"} {
 364                set index_lock_type $type
 365                return 1
 366        }
 367        return 0
 368}
 369
 370proc unlock_index {} {
 371        global index_lock_type disable_on_lock
 372
 373        set index_lock_type none
 374        foreach w $disable_on_lock {
 375                uplevel #0 $w normal
 376        }
 377}
 378
 379######################################################################
 380##
 381## status
 382
 383proc repository_state {ctvar hdvar mhvar} {
 384        global current_branch
 385        upvar $ctvar ct $hdvar hd $mhvar mh
 386
 387        set mh [list]
 388
 389        if {[catch {set current_branch [git symbolic-ref HEAD]}]} {
 390                set current_branch {}
 391        } else {
 392                regsub ^refs/((heads|tags|remotes)/)? \
 393                        $current_branch \
 394                        {} \
 395                        current_branch
 396        }
 397
 398        if {[catch {set hd [git rev-parse --verify HEAD]}]} {
 399                set hd {}
 400                set ct initial
 401                return
 402        }
 403
 404        set merge_head [gitdir MERGE_HEAD]
 405        if {[file exists $merge_head]} {
 406                set ct merge
 407                set fd_mh [open $merge_head r]
 408                while {[gets $fd_mh line] >= 0} {
 409                        lappend mh $line
 410                }
 411                close $fd_mh
 412                return
 413        }
 414
 415        set ct normal
 416}
 417
 418proc PARENT {} {
 419        global PARENT empty_tree
 420
 421        set p [lindex $PARENT 0]
 422        if {$p ne {}} {
 423                return $p
 424        }
 425        if {$empty_tree eq {}} {
 426                set empty_tree [git mktree << {}]
 427        }
 428        return $empty_tree
 429}
 430
 431proc rescan {after {honor_trustmtime 1}} {
 432        global HEAD PARENT MERGE_HEAD commit_type
 433        global ui_index ui_workdir ui_status_value ui_comm
 434        global rescan_active file_states
 435        global repo_config
 436
 437        if {$rescan_active > 0 || ![lock_index read]} return
 438
 439        repository_state newType newHEAD newMERGE_HEAD
 440        if {[string match amend* $commit_type]
 441                && $newType eq {normal}
 442                && $newHEAD eq $HEAD} {
 443        } else {
 444                set HEAD $newHEAD
 445                set PARENT $newHEAD
 446                set MERGE_HEAD $newMERGE_HEAD
 447                set commit_type $newType
 448        }
 449
 450        array unset file_states
 451
 452        if {![$ui_comm edit modified]
 453                || [string trim [$ui_comm get 0.0 end]] eq {}} {
 454                if {[load_message GITGUI_MSG]} {
 455                } elseif {[load_message MERGE_MSG]} {
 456                } elseif {[load_message SQUASH_MSG]} {
 457                }
 458                $ui_comm edit reset
 459                $ui_comm edit modified false
 460        }
 461
 462        if {[is_enabled branch]} {
 463                load_all_heads
 464                populate_branch_menu
 465        }
 466
 467        if {$honor_trustmtime && $repo_config(gui.trustmtime) eq {true}} {
 468                rescan_stage2 {} $after
 469        } else {
 470                set rescan_active 1
 471                set ui_status_value {Refreshing file status...}
 472                set cmd [list git update-index]
 473                lappend cmd -q
 474                lappend cmd --unmerged
 475                lappend cmd --ignore-missing
 476                lappend cmd --refresh
 477                set fd_rf [open "| $cmd" r]
 478                fconfigure $fd_rf -blocking 0 -translation binary
 479                fileevent $fd_rf readable \
 480                        [list rescan_stage2 $fd_rf $after]
 481        }
 482}
 483
 484proc rescan_stage2 {fd after} {
 485        global ui_status_value
 486        global rescan_active buf_rdi buf_rdf buf_rlo
 487
 488        if {$fd ne {}} {
 489                read $fd
 490                if {![eof $fd]} return
 491                close $fd
 492        }
 493
 494        set ls_others [list | git ls-files --others -z \
 495                --exclude-per-directory=.gitignore]
 496        set info_exclude [gitdir info exclude]
 497        if {[file readable $info_exclude]} {
 498                lappend ls_others "--exclude-from=$info_exclude"
 499        }
 500
 501        set buf_rdi {}
 502        set buf_rdf {}
 503        set buf_rlo {}
 504
 505        set rescan_active 3
 506        set ui_status_value {Scanning for modified files ...}
 507        set fd_di [open "| git diff-index --cached -z [PARENT]" r]
 508        set fd_df [open "| git diff-files -z" r]
 509        set fd_lo [open $ls_others r]
 510
 511        fconfigure $fd_di -blocking 0 -translation binary -encoding binary
 512        fconfigure $fd_df -blocking 0 -translation binary -encoding binary
 513        fconfigure $fd_lo -blocking 0 -translation binary -encoding binary
 514        fileevent $fd_di readable [list read_diff_index $fd_di $after]
 515        fileevent $fd_df readable [list read_diff_files $fd_df $after]
 516        fileevent $fd_lo readable [list read_ls_others $fd_lo $after]
 517}
 518
 519proc load_message {file} {
 520        global ui_comm
 521
 522        set f [gitdir $file]
 523        if {[file isfile $f]} {
 524                if {[catch {set fd [open $f r]}]} {
 525                        return 0
 526                }
 527                set content [string trim [read $fd]]
 528                close $fd
 529                regsub -all -line {[ \r\t]+$} $content {} content
 530                $ui_comm delete 0.0 end
 531                $ui_comm insert end $content
 532                return 1
 533        }
 534        return 0
 535}
 536
 537proc read_diff_index {fd after} {
 538        global buf_rdi
 539
 540        append buf_rdi [read $fd]
 541        set c 0
 542        set n [string length $buf_rdi]
 543        while {$c < $n} {
 544                set z1 [string first "\0" $buf_rdi $c]
 545                if {$z1 == -1} break
 546                incr z1
 547                set z2 [string first "\0" $buf_rdi $z1]
 548                if {$z2 == -1} break
 549
 550                incr c
 551                set i [split [string range $buf_rdi $c [expr {$z1 - 2}]] { }]
 552                set p [string range $buf_rdi $z1 [expr {$z2 - 1}]]
 553                merge_state \
 554                        [encoding convertfrom $p] \
 555                        [lindex $i 4]? \
 556                        [list [lindex $i 0] [lindex $i 2]] \
 557                        [list]
 558                set c $z2
 559                incr c
 560        }
 561        if {$c < $n} {
 562                set buf_rdi [string range $buf_rdi $c end]
 563        } else {
 564                set buf_rdi {}
 565        }
 566
 567        rescan_done $fd buf_rdi $after
 568}
 569
 570proc read_diff_files {fd after} {
 571        global buf_rdf
 572
 573        append buf_rdf [read $fd]
 574        set c 0
 575        set n [string length $buf_rdf]
 576        while {$c < $n} {
 577                set z1 [string first "\0" $buf_rdf $c]
 578                if {$z1 == -1} break
 579                incr z1
 580                set z2 [string first "\0" $buf_rdf $z1]
 581                if {$z2 == -1} break
 582
 583                incr c
 584                set i [split [string range $buf_rdf $c [expr {$z1 - 2}]] { }]
 585                set p [string range $buf_rdf $z1 [expr {$z2 - 1}]]
 586                merge_state \
 587                        [encoding convertfrom $p] \
 588                        ?[lindex $i 4] \
 589                        [list] \
 590                        [list [lindex $i 0] [lindex $i 2]]
 591                set c $z2
 592                incr c
 593        }
 594        if {$c < $n} {
 595                set buf_rdf [string range $buf_rdf $c end]
 596        } else {
 597                set buf_rdf {}
 598        }
 599
 600        rescan_done $fd buf_rdf $after
 601}
 602
 603proc read_ls_others {fd after} {
 604        global buf_rlo
 605
 606        append buf_rlo [read $fd]
 607        set pck [split $buf_rlo "\0"]
 608        set buf_rlo [lindex $pck end]
 609        foreach p [lrange $pck 0 end-1] {
 610                merge_state [encoding convertfrom $p] ?O
 611        }
 612        rescan_done $fd buf_rlo $after
 613}
 614
 615proc rescan_done {fd buf after} {
 616        global rescan_active current_diff_path
 617        global file_states repo_config
 618        upvar $buf to_clear
 619
 620        if {![eof $fd]} return
 621        set to_clear {}
 622        close $fd
 623        if {[incr rescan_active -1] > 0} return
 624
 625        prune_selection
 626        unlock_index
 627        display_all_files
 628        if {$current_diff_path ne {}} reshow_diff
 629        uplevel #0 $after
 630}
 631
 632proc prune_selection {} {
 633        global file_states selected_paths
 634
 635        foreach path [array names selected_paths] {
 636                if {[catch {set still_here $file_states($path)}]} {
 637                        unset selected_paths($path)
 638                }
 639        }
 640}
 641
 642######################################################################
 643##
 644## ui helpers
 645
 646proc mapicon {w state path} {
 647        global all_icons
 648
 649        if {[catch {set r $all_icons($state$w)}]} {
 650                puts "error: no icon for $w state={$state} $path"
 651                return file_plain
 652        }
 653        return $r
 654}
 655
 656proc mapdesc {state path} {
 657        global all_descs
 658
 659        if {[catch {set r $all_descs($state)}]} {
 660                puts "error: no desc for state={$state} $path"
 661                return $state
 662        }
 663        return $r
 664}
 665
 666proc escape_path {path} {
 667        regsub -all {\\} $path "\\\\" path
 668        regsub -all "\n" $path "\\n" path
 669        return $path
 670}
 671
 672proc short_path {path} {
 673        return [escape_path [lindex [file split $path] end]]
 674}
 675
 676set next_icon_id 0
 677set null_sha1 [string repeat 0 40]
 678
 679proc merge_state {path new_state {head_info {}} {index_info {}}} {
 680        global file_states next_icon_id null_sha1
 681
 682        set s0 [string index $new_state 0]
 683        set s1 [string index $new_state 1]
 684
 685        if {[catch {set info $file_states($path)}]} {
 686                set state __
 687                set icon n[incr next_icon_id]
 688        } else {
 689                set state [lindex $info 0]
 690                set icon [lindex $info 1]
 691                if {$head_info eq {}}  {set head_info  [lindex $info 2]}
 692                if {$index_info eq {}} {set index_info [lindex $info 3]}
 693        }
 694
 695        if     {$s0 eq {?}} {set s0 [string index $state 0]} \
 696        elseif {$s0 eq {_}} {set s0 _}
 697
 698        if     {$s1 eq {?}} {set s1 [string index $state 1]} \
 699        elseif {$s1 eq {_}} {set s1 _}
 700
 701        if {$s0 eq {A} && $s1 eq {_} && $head_info eq {}} {
 702                set head_info [list 0 $null_sha1]
 703        } elseif {$s0 ne {_} && [string index $state 0] eq {_}
 704                && $head_info eq {}} {
 705                set head_info $index_info
 706        }
 707
 708        set file_states($path) [list $s0$s1 $icon \
 709                $head_info $index_info \
 710                ]
 711        return $state
 712}
 713
 714proc display_file_helper {w path icon_name old_m new_m} {
 715        global file_lists
 716
 717        if {$new_m eq {_}} {
 718                set lno [lsearch -sorted -exact $file_lists($w) $path]
 719                if {$lno >= 0} {
 720                        set file_lists($w) [lreplace $file_lists($w) $lno $lno]
 721                        incr lno
 722                        $w conf -state normal
 723                        $w delete $lno.0 [expr {$lno + 1}].0
 724                        $w conf -state disabled
 725                }
 726        } elseif {$old_m eq {_} && $new_m ne {_}} {
 727                lappend file_lists($w) $path
 728                set file_lists($w) [lsort -unique $file_lists($w)]
 729                set lno [lsearch -sorted -exact $file_lists($w) $path]
 730                incr lno
 731                $w conf -state normal
 732                $w image create $lno.0 \
 733                        -align center -padx 5 -pady 1 \
 734                        -name $icon_name \
 735                        -image [mapicon $w $new_m $path]
 736                $w insert $lno.1 "[escape_path $path]\n"
 737                $w conf -state disabled
 738        } elseif {$old_m ne $new_m} {
 739                $w conf -state normal
 740                $w image conf $icon_name -image [mapicon $w $new_m $path]
 741                $w conf -state disabled
 742        }
 743}
 744
 745proc display_file {path state} {
 746        global file_states selected_paths
 747        global ui_index ui_workdir
 748
 749        set old_m [merge_state $path $state]
 750        set s $file_states($path)
 751        set new_m [lindex $s 0]
 752        set icon_name [lindex $s 1]
 753
 754        set o [string index $old_m 0]
 755        set n [string index $new_m 0]
 756        if {$o eq {U}} {
 757                set o _
 758        }
 759        if {$n eq {U}} {
 760                set n _
 761        }
 762        display_file_helper     $ui_index $path $icon_name $o $n
 763
 764        if {[string index $old_m 0] eq {U}} {
 765                set o U
 766        } else {
 767                set o [string index $old_m 1]
 768        }
 769        if {[string index $new_m 0] eq {U}} {
 770                set n U
 771        } else {
 772                set n [string index $new_m 1]
 773        }
 774        display_file_helper     $ui_workdir $path $icon_name $o $n
 775
 776        if {$new_m eq {__}} {
 777                unset file_states($path)
 778                catch {unset selected_paths($path)}
 779        }
 780}
 781
 782proc display_all_files_helper {w path icon_name m} {
 783        global file_lists
 784
 785        lappend file_lists($w) $path
 786        set lno [expr {[lindex [split [$w index end] .] 0] - 1}]
 787        $w image create end \
 788                -align center -padx 5 -pady 1 \
 789                -name $icon_name \
 790                -image [mapicon $w $m $path]
 791        $w insert end "[escape_path $path]\n"
 792}
 793
 794proc display_all_files {} {
 795        global ui_index ui_workdir
 796        global file_states file_lists
 797        global last_clicked
 798
 799        $ui_index conf -state normal
 800        $ui_workdir conf -state normal
 801
 802        $ui_index delete 0.0 end
 803        $ui_workdir delete 0.0 end
 804        set last_clicked {}
 805
 806        set file_lists($ui_index) [list]
 807        set file_lists($ui_workdir) [list]
 808
 809        foreach path [lsort [array names file_states]] {
 810                set s $file_states($path)
 811                set m [lindex $s 0]
 812                set icon_name [lindex $s 1]
 813
 814                set s [string index $m 0]
 815                if {$s ne {U} && $s ne {_}} {
 816                        display_all_files_helper $ui_index $path \
 817                                $icon_name $s
 818                }
 819
 820                if {[string index $m 0] eq {U}} {
 821                        set s U
 822                } else {
 823                        set s [string index $m 1]
 824                }
 825                if {$s ne {_}} {
 826                        display_all_files_helper $ui_workdir $path \
 827                                $icon_name $s
 828                }
 829        }
 830
 831        $ui_index conf -state disabled
 832        $ui_workdir conf -state disabled
 833}
 834
 835######################################################################
 836##
 837## icons
 838
 839set filemask {
 840#define mask_width 14
 841#define mask_height 15
 842static unsigned char mask_bits[] = {
 843   0xfe, 0x1f, 0xfe, 0x1f, 0xfe, 0x1f, 0xfe, 0x1f, 0xfe, 0x1f, 0xfe, 0x1f,
 844   0xfe, 0x1f, 0xfe, 0x1f, 0xfe, 0x1f, 0xfe, 0x1f, 0xfe, 0x1f, 0xfe, 0x1f,
 845   0xfe, 0x1f, 0xfe, 0x1f, 0xfe, 0x1f};
 846}
 847
 848image create bitmap file_plain -background white -foreground black -data {
 849#define plain_width 14
 850#define plain_height 15
 851static unsigned char plain_bits[] = {
 852   0xfe, 0x01, 0x02, 0x03, 0x02, 0x05, 0x02, 0x09, 0x02, 0x1f, 0x02, 0x10,
 853   0x02, 0x10, 0x02, 0x10, 0x02, 0x10, 0x02, 0x10, 0x02, 0x10, 0x02, 0x10,
 854   0x02, 0x10, 0x02, 0x10, 0xfe, 0x1f};
 855} -maskdata $filemask
 856
 857image create bitmap file_mod -background white -foreground blue -data {
 858#define mod_width 14
 859#define mod_height 15
 860static unsigned char mod_bits[] = {
 861   0xfe, 0x01, 0x02, 0x03, 0x7a, 0x05, 0x02, 0x09, 0x7a, 0x1f, 0x02, 0x10,
 862   0xfa, 0x17, 0x02, 0x10, 0xfa, 0x17, 0x02, 0x10, 0xfa, 0x17, 0x02, 0x10,
 863   0xfa, 0x17, 0x02, 0x10, 0xfe, 0x1f};
 864} -maskdata $filemask
 865
 866image create bitmap file_fulltick -background white -foreground "#007000" -data {
 867#define file_fulltick_width 14
 868#define file_fulltick_height 15
 869static unsigned char file_fulltick_bits[] = {
 870   0xfe, 0x01, 0x02, 0x1a, 0x02, 0x0c, 0x02, 0x0c, 0x02, 0x16, 0x02, 0x16,
 871   0x02, 0x13, 0x00, 0x13, 0x86, 0x11, 0x8c, 0x11, 0xd8, 0x10, 0xf2, 0x10,
 872   0x62, 0x10, 0x02, 0x10, 0xfe, 0x1f};
 873} -maskdata $filemask
 874
 875image create bitmap file_parttick -background white -foreground "#005050" -data {
 876#define parttick_width 14
 877#define parttick_height 15
 878static unsigned char parttick_bits[] = {
 879   0xfe, 0x01, 0x02, 0x03, 0x7a, 0x05, 0x02, 0x09, 0x7a, 0x1f, 0x02, 0x10,
 880   0x7a, 0x14, 0x02, 0x16, 0x02, 0x13, 0x8a, 0x11, 0xda, 0x10, 0x72, 0x10,
 881   0x22, 0x10, 0x02, 0x10, 0xfe, 0x1f};
 882} -maskdata $filemask
 883
 884image create bitmap file_question -background white -foreground black -data {
 885#define file_question_width 14
 886#define file_question_height 15
 887static unsigned char file_question_bits[] = {
 888   0xfe, 0x01, 0x02, 0x02, 0xe2, 0x04, 0xf2, 0x09, 0x1a, 0x1b, 0x0a, 0x13,
 889   0x82, 0x11, 0xc2, 0x10, 0x62, 0x10, 0x62, 0x10, 0x02, 0x10, 0x62, 0x10,
 890   0x62, 0x10, 0x02, 0x10, 0xfe, 0x1f};
 891} -maskdata $filemask
 892
 893image create bitmap file_removed -background white -foreground red -data {
 894#define file_removed_width 14
 895#define file_removed_height 15
 896static unsigned char file_removed_bits[] = {
 897   0xfe, 0x01, 0x02, 0x03, 0x02, 0x05, 0x02, 0x09, 0x02, 0x1f, 0x02, 0x10,
 898   0x1a, 0x16, 0x32, 0x13, 0xe2, 0x11, 0xc2, 0x10, 0xe2, 0x11, 0x32, 0x13,
 899   0x1a, 0x16, 0x02, 0x10, 0xfe, 0x1f};
 900} -maskdata $filemask
 901
 902image create bitmap file_merge -background white -foreground blue -data {
 903#define file_merge_width 14
 904#define file_merge_height 15
 905static unsigned char file_merge_bits[] = {
 906   0xfe, 0x01, 0x02, 0x03, 0x62, 0x05, 0x62, 0x09, 0x62, 0x1f, 0x62, 0x10,
 907   0xfa, 0x11, 0xf2, 0x10, 0x62, 0x10, 0x02, 0x10, 0xfa, 0x17, 0x02, 0x10,
 908   0xfa, 0x17, 0x02, 0x10, 0xfe, 0x1f};
 909} -maskdata $filemask
 910
 911set file_dir_data {
 912#define file_width 18
 913#define file_height 18
 914static unsigned char file_bits[] = {
 915  0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0xf8, 0x03, 0x00,
 916  0x0c, 0x03, 0x00, 0x04, 0xfe, 0x00, 0x06, 0x80, 0x00, 0xff, 0x9f, 0x00,
 917  0x03, 0x98, 0x00, 0x02, 0x90, 0x00, 0x06, 0xb0, 0x00, 0x04, 0xa0, 0x00,
 918  0x0c, 0xe0, 0x00, 0x08, 0xc0, 0x00, 0xf8, 0xff, 0x00, 0x00, 0x00, 0x00,
 919  0x00, 0x00, 0x00, 0x00, 0x00, 0x00};
 920}
 921image create bitmap file_dir -background white -foreground blue \
 922        -data $file_dir_data -maskdata $file_dir_data
 923unset file_dir_data
 924
 925set file_uplevel_data {
 926#define up_width 15
 927#define up_height 15
 928static unsigned char up_bits[] = {
 929  0x80, 0x00, 0xc0, 0x01, 0xe0, 0x03, 0xf0, 0x07, 0xf8, 0x0f, 0xfc, 0x1f,
 930  0xfe, 0x3f, 0xc0, 0x01, 0xc0, 0x01, 0xc0, 0x01, 0xc0, 0x01, 0xc0, 0x01,
 931  0xc0, 0x01, 0xc0, 0x01, 0x00, 0x00};
 932}
 933image create bitmap file_uplevel -background white -foreground red \
 934        -data $file_uplevel_data -maskdata $file_uplevel_data
 935unset file_uplevel_data
 936
 937set ui_index .vpane.files.index.list
 938set ui_workdir .vpane.files.workdir.list
 939
 940set all_icons(_$ui_index)   file_plain
 941set all_icons(A$ui_index)   file_fulltick
 942set all_icons(M$ui_index)   file_fulltick
 943set all_icons(D$ui_index)   file_removed
 944set all_icons(U$ui_index)   file_merge
 945
 946set all_icons(_$ui_workdir) file_plain
 947set all_icons(M$ui_workdir) file_mod
 948set all_icons(D$ui_workdir) file_question
 949set all_icons(U$ui_workdir) file_merge
 950set all_icons(O$ui_workdir) file_plain
 951
 952set max_status_desc 0
 953foreach i {
 954                {__ "Unmodified"}
 955
 956                {_M "Modified, not staged"}
 957                {M_ "Staged for commit"}
 958                {MM "Portions staged for commit"}
 959                {MD "Staged for commit, missing"}
 960
 961                {_O "Untracked, not staged"}
 962                {A_ "Staged for commit"}
 963                {AM "Portions staged for commit"}
 964                {AD "Staged for commit, missing"}
 965
 966                {_D "Missing"}
 967                {D_ "Staged for removal"}
 968                {DO "Staged for removal, still present"}
 969
 970                {U_ "Requires merge resolution"}
 971                {UU "Requires merge resolution"}
 972                {UM "Requires merge resolution"}
 973                {UD "Requires merge resolution"}
 974        } {
 975        if {$max_status_desc < [string length [lindex $i 1]]} {
 976                set max_status_desc [string length [lindex $i 1]]
 977        }
 978        set all_descs([lindex $i 0]) [lindex $i 1]
 979}
 980unset i
 981
 982######################################################################
 983##
 984## util
 985
 986proc bind_button3 {w cmd} {
 987        bind $w <Any-Button-3> $cmd
 988        if {[is_MacOSX]} {
 989                bind $w <Control-Button-1> $cmd
 990        }
 991}
 992
 993proc scrollbar2many {list mode args} {
 994        foreach w $list {eval $w $mode $args}
 995}
 996
 997proc many2scrollbar {list mode sb top bottom} {
 998        $sb set $top $bottom
 999        foreach w $list {$w $mode moveto $top}
1000}
1001
1002proc incr_font_size {font {amt 1}} {
1003        set sz [font configure $font -size]
1004        incr sz $amt
1005        font configure $font -size $sz
1006        font configure ${font}bold -size $sz
1007}
1008
1009######################################################################
1010##
1011## ui commands
1012
1013set starting_gitk_msg {Starting gitk... please wait...}
1014
1015proc do_gitk {revs} {
1016        global env ui_status_value starting_gitk_msg
1017
1018        # -- Always start gitk through whatever we were loaded with.  This
1019        #    lets us bypass using shell process on Windows systems.
1020        #
1021        set cmd [list [info nameofexecutable]]
1022        lappend cmd [gitexec gitk]
1023        if {$revs ne {}} {
1024                append cmd { }
1025                append cmd $revs
1026        }
1027
1028        if {[catch {eval exec $cmd &} err]} {
1029                error_popup "Failed to start gitk:\n\n$err"
1030        } else {
1031                set ui_status_value $starting_gitk_msg
1032                after 10000 {
1033                        if {$ui_status_value eq $starting_gitk_msg} {
1034                                set ui_status_value {Ready.}
1035                        }
1036                }
1037        }
1038}
1039
1040set is_quitting 0
1041
1042proc do_quit {} {
1043        global ui_comm is_quitting repo_config commit_type
1044
1045        if {$is_quitting} return
1046        set is_quitting 1
1047
1048        if {[winfo exists $ui_comm]} {
1049                # -- Stash our current commit buffer.
1050                #
1051                set save [gitdir GITGUI_MSG]
1052                set msg [string trim [$ui_comm get 0.0 end]]
1053                regsub -all -line {[ \r\t]+$} $msg {} msg
1054                if {(![string match amend* $commit_type]
1055                        || [$ui_comm edit modified])
1056                        && $msg ne {}} {
1057                        catch {
1058                                set fd [open $save w]
1059                                puts -nonewline $fd $msg
1060                                close $fd
1061                        }
1062                } else {
1063                        catch {file delete $save}
1064                }
1065
1066                # -- Stash our current window geometry into this repository.
1067                #
1068                set cfg_geometry [list]
1069                lappend cfg_geometry [wm geometry .]
1070                lappend cfg_geometry [lindex [.vpane sash coord 0] 1]
1071                lappend cfg_geometry [lindex [.vpane.files sash coord 0] 0]
1072                if {[catch {set rc_geometry $repo_config(gui.geometry)}]} {
1073                        set rc_geometry {}
1074                }
1075                if {$cfg_geometry ne $rc_geometry} {
1076                        catch {git config gui.geometry $cfg_geometry}
1077                }
1078        }
1079
1080        destroy .
1081}
1082
1083proc do_rescan {} {
1084        rescan {set ui_status_value {Ready.}}
1085}
1086
1087proc do_commit {} {
1088        commit_tree
1089}
1090
1091proc toggle_or_diff {w x y} {
1092        global file_states file_lists current_diff_path ui_index ui_workdir
1093        global last_clicked selected_paths
1094
1095        set pos [split [$w index @$x,$y] .]
1096        set lno [lindex $pos 0]
1097        set col [lindex $pos 1]
1098        set path [lindex $file_lists($w) [expr {$lno - 1}]]
1099        if {$path eq {}} {
1100                set last_clicked {}
1101                return
1102        }
1103
1104        set last_clicked [list $w $lno]
1105        array unset selected_paths
1106        $ui_index tag remove in_sel 0.0 end
1107        $ui_workdir tag remove in_sel 0.0 end
1108
1109        if {$col == 0} {
1110                if {$current_diff_path eq $path} {
1111                        set after {reshow_diff;}
1112                } else {
1113                        set after {}
1114                }
1115                if {$w eq $ui_index} {
1116                        update_indexinfo \
1117                                "Unstaging [short_path $path] from commit" \
1118                                [list $path] \
1119                                [concat $after {set ui_status_value {Ready.}}]
1120                } elseif {$w eq $ui_workdir} {
1121                        update_index \
1122                                "Adding [short_path $path]" \
1123                                [list $path] \
1124                                [concat $after {set ui_status_value {Ready.}}]
1125                }
1126        } else {
1127                show_diff $path $w $lno
1128        }
1129}
1130
1131proc add_one_to_selection {w x y} {
1132        global file_lists last_clicked selected_paths
1133
1134        set lno [lindex [split [$w index @$x,$y] .] 0]
1135        set path [lindex $file_lists($w) [expr {$lno - 1}]]
1136        if {$path eq {}} {
1137                set last_clicked {}
1138                return
1139        }
1140
1141        if {$last_clicked ne {}
1142                && [lindex $last_clicked 0] ne $w} {
1143                array unset selected_paths
1144                [lindex $last_clicked 0] tag remove in_sel 0.0 end
1145        }
1146
1147        set last_clicked [list $w $lno]
1148        if {[catch {set in_sel $selected_paths($path)}]} {
1149                set in_sel 0
1150        }
1151        if {$in_sel} {
1152                unset selected_paths($path)
1153                $w tag remove in_sel $lno.0 [expr {$lno + 1}].0
1154        } else {
1155                set selected_paths($path) 1
1156                $w tag add in_sel $lno.0 [expr {$lno + 1}].0
1157        }
1158}
1159
1160proc add_range_to_selection {w x y} {
1161        global file_lists last_clicked selected_paths
1162
1163        if {[lindex $last_clicked 0] ne $w} {
1164                toggle_or_diff $w $x $y
1165                return
1166        }
1167
1168        set lno [lindex [split [$w index @$x,$y] .] 0]
1169        set lc [lindex $last_clicked 1]
1170        if {$lc < $lno} {
1171                set begin $lc
1172                set end $lno
1173        } else {
1174                set begin $lno
1175                set end $lc
1176        }
1177
1178        foreach path [lrange $file_lists($w) \
1179                [expr {$begin - 1}] \
1180                [expr {$end - 1}]] {
1181                set selected_paths($path) 1
1182        }
1183        $w tag add in_sel $begin.0 [expr {$end + 1}].0
1184}
1185
1186######################################################################
1187##
1188## config defaults
1189
1190set cursor_ptr arrow
1191font create font_diff -family Courier -size 10
1192font create font_ui
1193catch {
1194        label .dummy
1195        eval font configure font_ui [font actual [.dummy cget -font]]
1196        destroy .dummy
1197}
1198
1199font create font_uibold
1200font create font_diffbold
1201
1202foreach class {Button Checkbutton Entry Label
1203                Labelframe Listbox Menu Message
1204                Radiobutton Text} {
1205        option add *$class.font font_ui
1206}
1207unset class
1208
1209if {[is_Windows]} {
1210        set M1B Control
1211        set M1T Ctrl
1212} elseif {[is_MacOSX]} {
1213        set M1B M1
1214        set M1T Cmd
1215} else {
1216        set M1B M1
1217        set M1T M1
1218}
1219
1220proc apply_config {} {
1221        global repo_config font_descs
1222
1223        foreach option $font_descs {
1224                set name [lindex $option 0]
1225                set font [lindex $option 1]
1226                if {[catch {
1227                        foreach {cn cv} $repo_config(gui.$name) {
1228                                font configure $font $cn $cv
1229                        }
1230                        } err]} {
1231                        error_popup "Invalid font specified in gui.$name:\n\n$err"
1232                }
1233                foreach {cn cv} [font configure $font] {
1234                        font configure ${font}bold $cn $cv
1235                }
1236                font configure ${font}bold -weight bold
1237        }
1238}
1239
1240set default_config(merge.summary) false
1241set default_config(merge.verbosity) 2
1242set default_config(user.name) {}
1243set default_config(user.email) {}
1244
1245set default_config(gui.trustmtime) false
1246set default_config(gui.diffcontext) 5
1247set default_config(gui.newbranchtemplate) {}
1248set default_config(gui.fontui) [font configure font_ui]
1249set default_config(gui.fontdiff) [font configure font_diff]
1250set font_descs {
1251        {fontui   font_ui   {Main Font}}
1252        {fontdiff font_diff {Diff/Console Font}}
1253}
1254load_config 0
1255apply_config
1256
1257######################################################################
1258##
1259## feature option selection
1260
1261if {[regexp {^git-(.+)$} [appname] _junk subcommand]} {
1262        unset _junk
1263} else {
1264        set subcommand gui
1265}
1266if {$subcommand eq {gui.sh}} {
1267        set subcommand gui
1268}
1269if {$subcommand eq {gui} && [llength $argv] > 0} {
1270        set subcommand [lindex $argv 0]
1271        set argv [lrange $argv 1 end]
1272}
1273
1274enable_option multicommit
1275enable_option branch
1276enable_option transport
1277
1278switch -- $subcommand {
1279browser -
1280blame {
1281        disable_option multicommit
1282        disable_option branch
1283        disable_option transport
1284}
1285citool {
1286        enable_option singlecommit
1287
1288        disable_option multicommit
1289        disable_option branch
1290        disable_option transport
1291}
1292}
1293
1294######################################################################
1295##
1296## ui construction
1297
1298set ui_comm {}
1299
1300# -- Menu Bar
1301#
1302menu .mbar -tearoff 0
1303.mbar add cascade -label Repository -menu .mbar.repository
1304.mbar add cascade -label Edit -menu .mbar.edit
1305if {[is_enabled branch]} {
1306        .mbar add cascade -label Branch -menu .mbar.branch
1307}
1308if {[is_enabled multicommit] || [is_enabled singlecommit]} {
1309        .mbar add cascade -label Commit -menu .mbar.commit
1310}
1311if {[is_enabled transport]} {
1312        .mbar add cascade -label Merge -menu .mbar.merge
1313        .mbar add cascade -label Fetch -menu .mbar.fetch
1314        .mbar add cascade -label Push -menu .mbar.push
1315}
1316. configure -menu .mbar
1317
1318# -- Repository Menu
1319#
1320menu .mbar.repository
1321
1322.mbar.repository add command \
1323        -label {Browse Current Branch} \
1324        -command {browser::new $current_branch}
1325trace add variable current_branch write ".mbar.repository entryconf [.mbar.repository index last] -label \"Browse \$current_branch\" ;#"
1326.mbar.repository add separator
1327
1328.mbar.repository add command \
1329        -label {Visualize Current Branch} \
1330        -command {do_gitk $current_branch}
1331trace add variable current_branch write ".mbar.repository entryconf [.mbar.repository index last] -label \"Visualize \$current_branch\" ;#"
1332.mbar.repository add command \
1333        -label {Visualize All Branches} \
1334        -command {do_gitk --all}
1335.mbar.repository add separator
1336
1337if {[is_enabled multicommit]} {
1338        .mbar.repository add command -label {Database Statistics} \
1339                -command do_stats
1340
1341        .mbar.repository add command -label {Compress Database} \
1342                -command do_gc
1343
1344        .mbar.repository add command -label {Verify Database} \
1345                -command do_fsck_objects
1346
1347        .mbar.repository add separator
1348
1349        if {[is_Cygwin]} {
1350                .mbar.repository add command \
1351                        -label {Create Desktop Icon} \
1352                        -command do_cygwin_shortcut
1353        } elseif {[is_Windows]} {
1354                .mbar.repository add command \
1355                        -label {Create Desktop Icon} \
1356                        -command do_windows_shortcut
1357        } elseif {[is_MacOSX]} {
1358                .mbar.repository add command \
1359                        -label {Create Desktop Icon} \
1360                        -command do_macosx_app
1361        }
1362}
1363
1364.mbar.repository add command -label Quit \
1365        -command do_quit \
1366        -accelerator $M1T-Q
1367
1368# -- Edit Menu
1369#
1370menu .mbar.edit
1371.mbar.edit add command -label Undo \
1372        -command {catch {[focus] edit undo}} \
1373        -accelerator $M1T-Z
1374.mbar.edit add command -label Redo \
1375        -command {catch {[focus] edit redo}} \
1376        -accelerator $M1T-Y
1377.mbar.edit add separator
1378.mbar.edit add command -label Cut \
1379        -command {catch {tk_textCut [focus]}} \
1380        -accelerator $M1T-X
1381.mbar.edit add command -label Copy \
1382        -command {catch {tk_textCopy [focus]}} \
1383        -accelerator $M1T-C
1384.mbar.edit add command -label Paste \
1385        -command {catch {tk_textPaste [focus]; [focus] see insert}} \
1386        -accelerator $M1T-V
1387.mbar.edit add command -label Delete \
1388        -command {catch {[focus] delete sel.first sel.last}} \
1389        -accelerator Del
1390.mbar.edit add separator
1391.mbar.edit add command -label {Select All} \
1392        -command {catch {[focus] tag add sel 0.0 end}} \
1393        -accelerator $M1T-A
1394
1395# -- Branch Menu
1396#
1397if {[is_enabled branch]} {
1398        menu .mbar.branch
1399
1400        .mbar.branch add command -label {Create...} \
1401                -command do_create_branch \
1402                -accelerator $M1T-N
1403        lappend disable_on_lock [list .mbar.branch entryconf \
1404                [.mbar.branch index last] -state]
1405
1406        .mbar.branch add command -label {Delete...} \
1407                -command do_delete_branch
1408        lappend disable_on_lock [list .mbar.branch entryconf \
1409                [.mbar.branch index last] -state]
1410
1411        .mbar.branch add command -label {Reset...} \
1412                -command merge::reset_hard
1413        lappend disable_on_lock [list .mbar.branch entryconf \
1414                [.mbar.branch index last] -state]
1415}
1416
1417# -- Commit Menu
1418#
1419if {[is_enabled multicommit] || [is_enabled singlecommit]} {
1420        menu .mbar.commit
1421
1422        .mbar.commit add radiobutton \
1423                -label {New Commit} \
1424                -command do_select_commit_type \
1425                -variable selected_commit_type \
1426                -value new
1427        lappend disable_on_lock \
1428                [list .mbar.commit entryconf [.mbar.commit index last] -state]
1429
1430        .mbar.commit add radiobutton \
1431                -label {Amend Last Commit} \
1432                -command do_select_commit_type \
1433                -variable selected_commit_type \
1434                -value amend
1435        lappend disable_on_lock \
1436                [list .mbar.commit entryconf [.mbar.commit index last] -state]
1437
1438        .mbar.commit add separator
1439
1440        .mbar.commit add command -label Rescan \
1441                -command do_rescan \
1442                -accelerator F5
1443        lappend disable_on_lock \
1444                [list .mbar.commit entryconf [.mbar.commit index last] -state]
1445
1446        .mbar.commit add command -label {Add To Commit} \
1447                -command do_add_selection
1448        lappend disable_on_lock \
1449                [list .mbar.commit entryconf [.mbar.commit index last] -state]
1450
1451        .mbar.commit add command -label {Add Existing To Commit} \
1452                -command do_add_all \
1453                -accelerator $M1T-I
1454        lappend disable_on_lock \
1455                [list .mbar.commit entryconf [.mbar.commit index last] -state]
1456
1457        .mbar.commit add command -label {Unstage From Commit} \
1458                -command do_unstage_selection
1459        lappend disable_on_lock \
1460                [list .mbar.commit entryconf [.mbar.commit index last] -state]
1461
1462        .mbar.commit add command -label {Revert Changes} \
1463                -command do_revert_selection
1464        lappend disable_on_lock \
1465                [list .mbar.commit entryconf [.mbar.commit index last] -state]
1466
1467        .mbar.commit add separator
1468
1469        .mbar.commit add command -label {Sign Off} \
1470                -command do_signoff \
1471                -accelerator $M1T-S
1472
1473        .mbar.commit add command -label Commit \
1474                -command do_commit \
1475                -accelerator $M1T-Return
1476        lappend disable_on_lock \
1477                [list .mbar.commit entryconf [.mbar.commit index last] -state]
1478}
1479
1480# -- Merge Menu
1481#
1482if {[is_enabled branch]} {
1483        menu .mbar.merge
1484        .mbar.merge add command -label {Local Merge...} \
1485                -command merge::dialog
1486        lappend disable_on_lock \
1487                [list .mbar.merge entryconf [.mbar.merge index last] -state]
1488        .mbar.merge add command -label {Abort Merge...} \
1489                -command merge::reset_hard
1490        lappend disable_on_lock \
1491                [list .mbar.merge entryconf [.mbar.merge index last] -state]
1492
1493}
1494
1495# -- Transport Menu
1496#
1497if {[is_enabled transport]} {
1498        menu .mbar.fetch
1499
1500        menu .mbar.push
1501        .mbar.push add command -label {Push...} \
1502                -command do_push_anywhere
1503}
1504
1505if {[is_MacOSX]} {
1506        # -- Apple Menu (Mac OS X only)
1507        #
1508        .mbar add cascade -label Apple -menu .mbar.apple
1509        menu .mbar.apple
1510
1511        .mbar.apple add command -label "About [appname]" \
1512                -command do_about
1513        .mbar.apple add command -label "Options..." \
1514                -command do_options
1515} else {
1516        # -- Edit Menu
1517        #
1518        .mbar.edit add separator
1519        .mbar.edit add command -label {Options...} \
1520                -command do_options
1521
1522        # -- Tools Menu
1523        #
1524        if {[file exists /usr/local/miga/lib/gui-miga]
1525                && [file exists .pvcsrc]} {
1526        proc do_miga {} {
1527                global ui_status_value
1528                if {![lock_index update]} return
1529                set cmd [list sh --login -c "/usr/local/miga/lib/gui-miga \"[pwd]\""]
1530                set miga_fd [open "|$cmd" r]
1531                fconfigure $miga_fd -blocking 0
1532                fileevent $miga_fd readable [list miga_done $miga_fd]
1533                set ui_status_value {Running miga...}
1534        }
1535        proc miga_done {fd} {
1536                read $fd 512
1537                if {[eof $fd]} {
1538                        close $fd
1539                        unlock_index
1540                        rescan [list set ui_status_value {Ready.}]
1541                }
1542        }
1543        .mbar add cascade -label Tools -menu .mbar.tools
1544        menu .mbar.tools
1545        .mbar.tools add command -label "Migrate" \
1546                -command do_miga
1547        lappend disable_on_lock \
1548                [list .mbar.tools entryconf [.mbar.tools index last] -state]
1549        }
1550}
1551
1552# -- Help Menu
1553#
1554.mbar add cascade -label Help -menu .mbar.help
1555menu .mbar.help
1556
1557if {![is_MacOSX]} {
1558        .mbar.help add command -label "About [appname]" \
1559                -command do_about
1560}
1561
1562set browser {}
1563catch {set browser $repo_config(instaweb.browser)}
1564set doc_path [file dirname [gitexec]]
1565set doc_path [file join $doc_path Documentation index.html]
1566
1567if {[is_Cygwin]} {
1568        set doc_path [exec cygpath --mixed $doc_path]
1569}
1570
1571if {$browser eq {}} {
1572        if {[is_MacOSX]} {
1573                set browser open
1574        } elseif {[is_Cygwin]} {
1575                set program_files [file dirname [exec cygpath --windir]]
1576                set program_files [file join $program_files {Program Files}]
1577                set firefox [file join $program_files {Mozilla Firefox} firefox.exe]
1578                set ie [file join $program_files {Internet Explorer} IEXPLORE.EXE]
1579                if {[file exists $firefox]} {
1580                        set browser $firefox
1581                } elseif {[file exists $ie]} {
1582                        set browser $ie
1583                }
1584                unset program_files firefox ie
1585        }
1586}
1587
1588if {[file isfile $doc_path]} {
1589        set doc_url "file:$doc_path"
1590} else {
1591        set doc_url {http://www.kernel.org/pub/software/scm/git/docs/}
1592}
1593
1594if {$browser ne {}} {
1595        .mbar.help add command -label {Online Documentation} \
1596                -command [list exec $browser $doc_url &]
1597}
1598unset browser doc_path doc_url
1599
1600# -- Standard bindings
1601#
1602bind .   <Destroy> do_quit
1603bind all <$M1B-Key-q> do_quit
1604bind all <$M1B-Key-Q> do_quit
1605bind all <$M1B-Key-w> {destroy [winfo toplevel %W]}
1606bind all <$M1B-Key-W> {destroy [winfo toplevel %W]}
1607
1608set subcommand_args {}
1609proc usage {} {
1610        puts stderr "usage: $::argv0 $::subcommand $::subcommand_args"
1611        exit 1
1612}
1613
1614# -- Not a normal commit type invocation?  Do that instead!
1615#
1616switch -- $subcommand {
1617browser {
1618        set subcommand_args {rev?}
1619        switch [llength $argv] {
1620        0 {
1621                set current_branch [git symbolic-ref HEAD]
1622                regsub ^refs/((heads|tags|remotes)/)? \
1623                        $current_branch {} current_branch
1624        }
1625        1 {
1626                set current_branch [lindex $argv 0]
1627        }
1628        default usage
1629        }
1630        browser::new $current_branch
1631        return
1632}
1633blame {
1634        set subcommand_args {rev? path?}
1635        set head {}
1636        set path {}
1637        set is_path 0
1638        foreach a $argv {
1639                if {$is_path || [file exists $_prefix$a]} {
1640                        if {$path ne {}} usage
1641                        set path $_prefix$a
1642                        break
1643                } elseif {$a eq {--}} {
1644                        if {$path ne {}} {
1645                                if {$head ne {}} usage
1646                                set head $path
1647                                set path {}
1648                        }
1649                        set is_path 1
1650                } elseif {$head eq {}} {
1651                        if {$head ne {}} usage
1652                        set head $a
1653                } else {
1654                        usage
1655                }
1656        }
1657        unset is_path
1658
1659        if {$head eq {}} {
1660                set current_branch [git symbolic-ref HEAD]
1661                regsub ^refs/((heads|tags|remotes)/)? \
1662                        $current_branch {} current_branch
1663        } else {
1664                set current_branch $head
1665        }
1666
1667        if {$path eq {}} usage
1668        blame::new $head $path
1669        return
1670}
1671citool -
1672gui {
1673        if {[llength $argv] != 0} {
1674                puts -nonewline stderr "usage: $argv0"
1675                if {$subcommand ne {gui} && [appname] ne "git-$subcommand"} {
1676                        puts -nonewline stderr " $subcommand"
1677                }
1678                puts stderr {}
1679                exit 1
1680        }
1681        # fall through to setup UI for commits
1682}
1683default {
1684        puts stderr "usage: $argv0 \[{blame|browser|citool}\]"
1685        exit 1
1686}
1687}
1688
1689# -- Branch Control
1690#
1691frame .branch \
1692        -borderwidth 1 \
1693        -relief sunken
1694label .branch.l1 \
1695        -text {Current Branch:} \
1696        -anchor w \
1697        -justify left
1698label .branch.cb \
1699        -textvariable current_branch \
1700        -anchor w \
1701        -justify left
1702pack .branch.l1 -side left
1703pack .branch.cb -side left -fill x
1704pack .branch -side top -fill x
1705
1706# -- Main Window Layout
1707#
1708panedwindow .vpane -orient vertical
1709panedwindow .vpane.files -orient horizontal
1710.vpane add .vpane.files -sticky nsew -height 100 -width 200
1711pack .vpane -anchor n -side top -fill both -expand 1
1712
1713# -- Index File List
1714#
1715frame .vpane.files.index -height 100 -width 200
1716label .vpane.files.index.title -text {Staged Changes (Will Be Committed)} \
1717        -background green
1718text $ui_index -background white -borderwidth 0 \
1719        -width 20 -height 10 \
1720        -wrap none \
1721        -cursor $cursor_ptr \
1722        -xscrollcommand {.vpane.files.index.sx set} \
1723        -yscrollcommand {.vpane.files.index.sy set} \
1724        -state disabled
1725scrollbar .vpane.files.index.sx -orient h -command [list $ui_index xview]
1726scrollbar .vpane.files.index.sy -orient v -command [list $ui_index yview]
1727pack .vpane.files.index.title -side top -fill x
1728pack .vpane.files.index.sx -side bottom -fill x
1729pack .vpane.files.index.sy -side right -fill y
1730pack $ui_index -side left -fill both -expand 1
1731.vpane.files add .vpane.files.index -sticky nsew
1732
1733# -- Working Directory File List
1734#
1735frame .vpane.files.workdir -height 100 -width 200
1736label .vpane.files.workdir.title -text {Unstaged Changes (Will Not Be Committed)} \
1737        -background red
1738text $ui_workdir -background white -borderwidth 0 \
1739        -width 20 -height 10 \
1740        -wrap none \
1741        -cursor $cursor_ptr \
1742        -xscrollcommand {.vpane.files.workdir.sx set} \
1743        -yscrollcommand {.vpane.files.workdir.sy set} \
1744        -state disabled
1745scrollbar .vpane.files.workdir.sx -orient h -command [list $ui_workdir xview]
1746scrollbar .vpane.files.workdir.sy -orient v -command [list $ui_workdir yview]
1747pack .vpane.files.workdir.title -side top -fill x
1748pack .vpane.files.workdir.sx -side bottom -fill x
1749pack .vpane.files.workdir.sy -side right -fill y
1750pack $ui_workdir -side left -fill both -expand 1
1751.vpane.files add .vpane.files.workdir -sticky nsew
1752
1753foreach i [list $ui_index $ui_workdir] {
1754        $i tag conf in_diff -font font_uibold
1755        $i tag conf in_sel \
1756                -background [$i cget -foreground] \
1757                -foreground [$i cget -background]
1758}
1759unset i
1760
1761# -- Diff and Commit Area
1762#
1763frame .vpane.lower -height 300 -width 400
1764frame .vpane.lower.commarea
1765frame .vpane.lower.diff -relief sunken -borderwidth 1
1766pack .vpane.lower.commarea -side top -fill x
1767pack .vpane.lower.diff -side bottom -fill both -expand 1
1768.vpane add .vpane.lower -sticky nsew
1769
1770# -- Commit Area Buttons
1771#
1772frame .vpane.lower.commarea.buttons
1773label .vpane.lower.commarea.buttons.l -text {} \
1774        -anchor w \
1775        -justify left
1776pack .vpane.lower.commarea.buttons.l -side top -fill x
1777pack .vpane.lower.commarea.buttons -side left -fill y
1778
1779button .vpane.lower.commarea.buttons.rescan -text {Rescan} \
1780        -command do_rescan
1781pack .vpane.lower.commarea.buttons.rescan -side top -fill x
1782lappend disable_on_lock \
1783        {.vpane.lower.commarea.buttons.rescan conf -state}
1784
1785button .vpane.lower.commarea.buttons.incall -text {Add Existing} \
1786        -command do_add_all
1787pack .vpane.lower.commarea.buttons.incall -side top -fill x
1788lappend disable_on_lock \
1789        {.vpane.lower.commarea.buttons.incall conf -state}
1790
1791button .vpane.lower.commarea.buttons.signoff -text {Sign Off} \
1792        -command do_signoff
1793pack .vpane.lower.commarea.buttons.signoff -side top -fill x
1794
1795button .vpane.lower.commarea.buttons.commit -text {Commit} \
1796        -command do_commit
1797pack .vpane.lower.commarea.buttons.commit -side top -fill x
1798lappend disable_on_lock \
1799        {.vpane.lower.commarea.buttons.commit conf -state}
1800
1801# -- Commit Message Buffer
1802#
1803frame .vpane.lower.commarea.buffer
1804frame .vpane.lower.commarea.buffer.header
1805set ui_comm .vpane.lower.commarea.buffer.t
1806set ui_coml .vpane.lower.commarea.buffer.header.l
1807radiobutton .vpane.lower.commarea.buffer.header.new \
1808        -text {New Commit} \
1809        -command do_select_commit_type \
1810        -variable selected_commit_type \
1811        -value new
1812lappend disable_on_lock \
1813        [list .vpane.lower.commarea.buffer.header.new conf -state]
1814radiobutton .vpane.lower.commarea.buffer.header.amend \
1815        -text {Amend Last Commit} \
1816        -command do_select_commit_type \
1817        -variable selected_commit_type \
1818        -value amend
1819lappend disable_on_lock \
1820        [list .vpane.lower.commarea.buffer.header.amend conf -state]
1821label $ui_coml \
1822        -anchor w \
1823        -justify left
1824proc trace_commit_type {varname args} {
1825        global ui_coml commit_type
1826        switch -glob -- $commit_type {
1827        initial       {set txt {Initial Commit Message:}}
1828        amend         {set txt {Amended Commit Message:}}
1829        amend-initial {set txt {Amended Initial Commit Message:}}
1830        amend-merge   {set txt {Amended Merge Commit Message:}}
1831        merge         {set txt {Merge Commit Message:}}
1832        *             {set txt {Commit Message:}}
1833        }
1834        $ui_coml conf -text $txt
1835}
1836trace add variable commit_type write trace_commit_type
1837pack $ui_coml -side left -fill x
1838pack .vpane.lower.commarea.buffer.header.amend -side right
1839pack .vpane.lower.commarea.buffer.header.new -side right
1840
1841text $ui_comm -background white -borderwidth 1 \
1842        -undo true \
1843        -maxundo 20 \
1844        -autoseparators true \
1845        -relief sunken \
1846        -width 75 -height 9 -wrap none \
1847        -font font_diff \
1848        -yscrollcommand {.vpane.lower.commarea.buffer.sby set}
1849scrollbar .vpane.lower.commarea.buffer.sby \
1850        -command [list $ui_comm yview]
1851pack .vpane.lower.commarea.buffer.header -side top -fill x
1852pack .vpane.lower.commarea.buffer.sby -side right -fill y
1853pack $ui_comm -side left -fill y
1854pack .vpane.lower.commarea.buffer -side left -fill y
1855
1856# -- Commit Message Buffer Context Menu
1857#
1858set ctxm .vpane.lower.commarea.buffer.ctxm
1859menu $ctxm -tearoff 0
1860$ctxm add command \
1861        -label {Cut} \
1862        -command {tk_textCut $ui_comm}
1863$ctxm add command \
1864        -label {Copy} \
1865        -command {tk_textCopy $ui_comm}
1866$ctxm add command \
1867        -label {Paste} \
1868        -command {tk_textPaste $ui_comm}
1869$ctxm add command \
1870        -label {Delete} \
1871        -command {$ui_comm delete sel.first sel.last}
1872$ctxm add separator
1873$ctxm add command \
1874        -label {Select All} \
1875        -command {focus $ui_comm;$ui_comm tag add sel 0.0 end}
1876$ctxm add command \
1877        -label {Copy All} \
1878        -command {
1879                $ui_comm tag add sel 0.0 end
1880                tk_textCopy $ui_comm
1881                $ui_comm tag remove sel 0.0 end
1882        }
1883$ctxm add separator
1884$ctxm add command \
1885        -label {Sign Off} \
1886        -command do_signoff
1887bind_button3 $ui_comm "tk_popup $ctxm %X %Y"
1888
1889# -- Diff Header
1890#
1891proc trace_current_diff_path {varname args} {
1892        global current_diff_path diff_actions file_states
1893        if {$current_diff_path eq {}} {
1894                set s {}
1895                set f {}
1896                set p {}
1897                set o disabled
1898        } else {
1899                set p $current_diff_path
1900                set s [mapdesc [lindex $file_states($p) 0] $p]
1901                set f {File:}
1902                set p [escape_path $p]
1903                set o normal
1904        }
1905
1906        .vpane.lower.diff.header.status configure -text $s
1907        .vpane.lower.diff.header.file configure -text $f
1908        .vpane.lower.diff.header.path configure -text $p
1909        foreach w $diff_actions {
1910                uplevel #0 $w $o
1911        }
1912}
1913trace add variable current_diff_path write trace_current_diff_path
1914
1915frame .vpane.lower.diff.header -background orange
1916label .vpane.lower.diff.header.status \
1917        -background orange \
1918        -width $max_status_desc \
1919        -anchor w \
1920        -justify left
1921label .vpane.lower.diff.header.file \
1922        -background orange \
1923        -anchor w \
1924        -justify left
1925label .vpane.lower.diff.header.path \
1926        -background orange \
1927        -anchor w \
1928        -justify left
1929pack .vpane.lower.diff.header.status -side left
1930pack .vpane.lower.diff.header.file -side left
1931pack .vpane.lower.diff.header.path -fill x
1932set ctxm .vpane.lower.diff.header.ctxm
1933menu $ctxm -tearoff 0
1934$ctxm add command \
1935        -label {Copy} \
1936        -command {
1937                clipboard clear
1938                clipboard append \
1939                        -format STRING \
1940                        -type STRING \
1941                        -- $current_diff_path
1942        }
1943lappend diff_actions [list $ctxm entryconf [$ctxm index last] -state]
1944bind_button3 .vpane.lower.diff.header.path "tk_popup $ctxm %X %Y"
1945
1946# -- Diff Body
1947#
1948frame .vpane.lower.diff.body
1949set ui_diff .vpane.lower.diff.body.t
1950text $ui_diff -background white -borderwidth 0 \
1951        -width 80 -height 15 -wrap none \
1952        -font font_diff \
1953        -xscrollcommand {.vpane.lower.diff.body.sbx set} \
1954        -yscrollcommand {.vpane.lower.diff.body.sby set} \
1955        -state disabled
1956scrollbar .vpane.lower.diff.body.sbx -orient horizontal \
1957        -command [list $ui_diff xview]
1958scrollbar .vpane.lower.diff.body.sby -orient vertical \
1959        -command [list $ui_diff yview]
1960pack .vpane.lower.diff.body.sbx -side bottom -fill x
1961pack .vpane.lower.diff.body.sby -side right -fill y
1962pack $ui_diff -side left -fill both -expand 1
1963pack .vpane.lower.diff.header -side top -fill x
1964pack .vpane.lower.diff.body -side bottom -fill both -expand 1
1965
1966$ui_diff tag conf d_cr -elide true
1967$ui_diff tag conf d_@ -foreground blue -font font_diffbold
1968$ui_diff tag conf d_+ -foreground {#00a000}
1969$ui_diff tag conf d_- -foreground red
1970
1971$ui_diff tag conf d_++ -foreground {#00a000}
1972$ui_diff tag conf d_-- -foreground red
1973$ui_diff tag conf d_+s \
1974        -foreground {#00a000} \
1975        -background {#e2effa}
1976$ui_diff tag conf d_-s \
1977        -foreground red \
1978        -background {#e2effa}
1979$ui_diff tag conf d_s+ \
1980        -foreground {#00a000} \
1981        -background ivory1
1982$ui_diff tag conf d_s- \
1983        -foreground red \
1984        -background ivory1
1985
1986$ui_diff tag conf d<<<<<<< \
1987        -foreground orange \
1988        -font font_diffbold
1989$ui_diff tag conf d======= \
1990        -foreground orange \
1991        -font font_diffbold
1992$ui_diff tag conf d>>>>>>> \
1993        -foreground orange \
1994        -font font_diffbold
1995
1996$ui_diff tag raise sel
1997
1998# -- Diff Body Context Menu
1999#
2000set ctxm .vpane.lower.diff.body.ctxm
2001menu $ctxm -tearoff 0
2002$ctxm add command \
2003        -label {Refresh} \
2004        -command reshow_diff
2005lappend diff_actions [list $ctxm entryconf [$ctxm index last] -state]
2006$ctxm add command \
2007        -label {Copy} \
2008        -command {tk_textCopy $ui_diff}
2009lappend diff_actions [list $ctxm entryconf [$ctxm index last] -state]
2010$ctxm add command \
2011        -label {Select All} \
2012        -command {focus $ui_diff;$ui_diff tag add sel 0.0 end}
2013lappend diff_actions [list $ctxm entryconf [$ctxm index last] -state]
2014$ctxm add command \
2015        -label {Copy All} \
2016        -command {
2017                $ui_diff tag add sel 0.0 end
2018                tk_textCopy $ui_diff
2019                $ui_diff tag remove sel 0.0 end
2020        }
2021lappend diff_actions [list $ctxm entryconf [$ctxm index last] -state]
2022$ctxm add separator
2023$ctxm add command \
2024        -label {Apply/Reverse Hunk} \
2025        -command {apply_hunk $cursorX $cursorY}
2026set ui_diff_applyhunk [$ctxm index last]
2027lappend diff_actions [list $ctxm entryconf $ui_diff_applyhunk -state]
2028$ctxm add separator
2029$ctxm add command \
2030        -label {Decrease Font Size} \
2031        -command {incr_font_size font_diff -1}
2032lappend diff_actions [list $ctxm entryconf [$ctxm index last] -state]
2033$ctxm add command \
2034        -label {Increase Font Size} \
2035        -command {incr_font_size font_diff 1}
2036lappend diff_actions [list $ctxm entryconf [$ctxm index last] -state]
2037$ctxm add separator
2038$ctxm add command \
2039        -label {Show Less Context} \
2040        -command {if {$repo_config(gui.diffcontext) >= 2} {
2041                incr repo_config(gui.diffcontext) -1
2042                reshow_diff
2043        }}
2044lappend diff_actions [list $ctxm entryconf [$ctxm index last] -state]
2045$ctxm add command \
2046        -label {Show More Context} \
2047        -command {
2048                incr repo_config(gui.diffcontext)
2049                reshow_diff
2050        }
2051lappend diff_actions [list $ctxm entryconf [$ctxm index last] -state]
2052$ctxm add separator
2053$ctxm add command -label {Options...} \
2054        -command do_options
2055bind_button3 $ui_diff "
2056        set cursorX %x
2057        set cursorY %y
2058        if {\$ui_index eq \$current_diff_side} {
2059                $ctxm entryconf $ui_diff_applyhunk -label {Unstage Hunk From Commit}
2060        } else {
2061                $ctxm entryconf $ui_diff_applyhunk -label {Stage Hunk For Commit}
2062        }
2063        tk_popup $ctxm %X %Y
2064"
2065unset ui_diff_applyhunk
2066
2067# -- Status Bar
2068#
2069label .status -textvariable ui_status_value \
2070        -anchor w \
2071        -justify left \
2072        -borderwidth 1 \
2073        -relief sunken
2074pack .status -anchor w -side bottom -fill x
2075
2076# -- Load geometry
2077#
2078catch {
2079set gm $repo_config(gui.geometry)
2080wm geometry . [lindex $gm 0]
2081.vpane sash place 0 \
2082        [lindex [.vpane sash coord 0] 0] \
2083        [lindex $gm 1]
2084.vpane.files sash place 0 \
2085        [lindex $gm 2] \
2086        [lindex [.vpane.files sash coord 0] 1]
2087unset gm
2088}
2089
2090# -- Key Bindings
2091#
2092bind $ui_comm <$M1B-Key-Return> {do_commit;break}
2093bind $ui_comm <$M1B-Key-i> {do_add_all;break}
2094bind $ui_comm <$M1B-Key-I> {do_add_all;break}
2095bind $ui_comm <$M1B-Key-x> {tk_textCut %W;break}
2096bind $ui_comm <$M1B-Key-X> {tk_textCut %W;break}
2097bind $ui_comm <$M1B-Key-c> {tk_textCopy %W;break}
2098bind $ui_comm <$M1B-Key-C> {tk_textCopy %W;break}
2099bind $ui_comm <$M1B-Key-v> {tk_textPaste %W; %W see insert; break}
2100bind $ui_comm <$M1B-Key-V> {tk_textPaste %W; %W see insert; break}
2101bind $ui_comm <$M1B-Key-a> {%W tag add sel 0.0 end;break}
2102bind $ui_comm <$M1B-Key-A> {%W tag add sel 0.0 end;break}
2103
2104bind $ui_diff <$M1B-Key-x> {tk_textCopy %W;break}
2105bind $ui_diff <$M1B-Key-X> {tk_textCopy %W;break}
2106bind $ui_diff <$M1B-Key-c> {tk_textCopy %W;break}
2107bind $ui_diff <$M1B-Key-C> {tk_textCopy %W;break}
2108bind $ui_diff <$M1B-Key-v> {break}
2109bind $ui_diff <$M1B-Key-V> {break}
2110bind $ui_diff <$M1B-Key-a> {%W tag add sel 0.0 end;break}
2111bind $ui_diff <$M1B-Key-A> {%W tag add sel 0.0 end;break}
2112bind $ui_diff <Key-Up>     {catch {%W yview scroll -1 units};break}
2113bind $ui_diff <Key-Down>   {catch {%W yview scroll  1 units};break}
2114bind $ui_diff <Key-Left>   {catch {%W xview scroll -1 units};break}
2115bind $ui_diff <Key-Right>  {catch {%W xview scroll  1 units};break}
2116bind $ui_diff <Key-k>         {catch {%W yview scroll -1 units};break}
2117bind $ui_diff <Key-j>         {catch {%W yview scroll  1 units};break}
2118bind $ui_diff <Key-h>         {catch {%W xview scroll -1 units};break}
2119bind $ui_diff <Key-l>         {catch {%W xview scroll  1 units};break}
2120bind $ui_diff <Control-Key-b> {catch {%W yview scroll -1 pages};break}
2121bind $ui_diff <Control-Key-f> {catch {%W yview scroll  1 pages};break}
2122bind $ui_diff <Button-1>   {focus %W}
2123
2124if {[is_enabled branch]} {
2125        bind . <$M1B-Key-n> do_create_branch
2126        bind . <$M1B-Key-N> do_create_branch
2127}
2128
2129bind all <Key-F5> do_rescan
2130bind all <$M1B-Key-r> do_rescan
2131bind all <$M1B-Key-R> do_rescan
2132bind .   <$M1B-Key-s> do_signoff
2133bind .   <$M1B-Key-S> do_signoff
2134bind .   <$M1B-Key-i> do_add_all
2135bind .   <$M1B-Key-I> do_add_all
2136bind .   <$M1B-Key-Return> do_commit
2137foreach i [list $ui_index $ui_workdir] {
2138        bind $i <Button-1>       "toggle_or_diff         $i %x %y; break"
2139        bind $i <$M1B-Button-1>  "add_one_to_selection   $i %x %y; break"
2140        bind $i <Shift-Button-1> "add_range_to_selection $i %x %y; break"
2141}
2142unset i
2143
2144set file_lists($ui_index) [list]
2145set file_lists($ui_workdir) [list]
2146
2147wm title . "[appname] ([reponame]) [file normalize [file dirname [gitdir]]]"
2148focus -force $ui_comm
2149
2150# -- Warn the user about environmental problems.  Cygwin's Tcl
2151#    does *not* pass its env array onto any processes it spawns.
2152#    This means that git processes get none of our environment.
2153#
2154if {[is_Cygwin]} {
2155        set ignored_env 0
2156        set suggest_user {}
2157        set msg "Possible environment issues exist.
2158
2159The following environment variables are probably
2160going to be ignored by any Git subprocess run
2161by [appname]:
2162
2163"
2164        foreach name [array names env] {
2165                switch -regexp -- $name {
2166                {^GIT_INDEX_FILE$} -
2167                {^GIT_OBJECT_DIRECTORY$} -
2168                {^GIT_ALTERNATE_OBJECT_DIRECTORIES$} -
2169                {^GIT_DIFF_OPTS$} -
2170                {^GIT_EXTERNAL_DIFF$} -
2171                {^GIT_PAGER$} -
2172                {^GIT_TRACE$} -
2173                {^GIT_CONFIG$} -
2174                {^GIT_CONFIG_LOCAL$} -
2175                {^GIT_(AUTHOR|COMMITTER)_DATE$} {
2176                        append msg " - $name\n"
2177                        incr ignored_env
2178                }
2179                {^GIT_(AUTHOR|COMMITTER)_(NAME|EMAIL)$} {
2180                        append msg " - $name\n"
2181                        incr ignored_env
2182                        set suggest_user $name
2183                }
2184                }
2185        }
2186        if {$ignored_env > 0} {
2187                append msg "
2188This is due to a known issue with the
2189Tcl binary distributed by Cygwin."
2190
2191                if {$suggest_user ne {}} {
2192                        append msg "
2193
2194A good replacement for $suggest_user
2195is placing values for the user.name and
2196user.email settings into your personal
2197~/.gitconfig file.
2198"
2199                }
2200                warn_popup $msg
2201        }
2202        unset ignored_env msg suggest_user name
2203}
2204
2205# -- Only initialize complex UI if we are going to stay running.
2206#
2207if {[is_enabled transport]} {
2208        load_all_remotes
2209        load_all_heads
2210
2211        populate_branch_menu
2212        populate_fetch_menu
2213        populate_push_menu
2214}
2215
2216# -- Only suggest a gc run if we are going to stay running.
2217#
2218if {[is_enabled multicommit]} {
2219        set object_limit 2000
2220        if {[is_Windows]} {set object_limit 200}
2221        regexp {^([0-9]+) objects,} [git count-objects] _junk objects_current
2222        if {$objects_current >= $object_limit} {
2223                if {[ask_popup \
2224                        "This repository currently has $objects_current loose objects.
2225
2226To maintain optimal performance it is strongly recommended that you compress the database when more than $object_limit loose objects exist.
2227
2228Compress the database now?"] eq yes} {
2229                        do_gc
2230                }
2231        }
2232        unset object_limit _junk objects_current
2233}
2234
2235lock_index begin-read
2236after 1 do_rescan