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