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