git-gui / git-gui.shon commit Clarify documentation of fast-import's D subcommand (512e44b)
   1#!/bin/sh
   2# Tcl ignores the next line -*- tcl -*- \
   3 if test "z$*" = zversion \
   4 || test "z$*" = z--version; \
   5 then \
   6        echo 'git-gui version @@GITGUI_VERSION@@'; \
   7        exit; \
   8 fi; \
   9 exec wish "$0" -- "$@"
  10
  11set appvers {@@GITGUI_VERSION@@}
  12set copyright {
  13Copyright © 2006, 2007 Shawn Pearce, et. al.
  14
  15This program is free software; you can redistribute it and/or modify
  16it under the terms of the GNU General Public License as published by
  17the Free Software Foundation; either version 2 of the License, or
  18(at your option) any later version.
  19
  20This program is distributed in the hope that it will be useful,
  21but WITHOUT ANY WARRANTY; without even the implied warranty of
  22MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
  23GNU General Public License for more details.
  24
  25You should have received a copy of the GNU General Public License
  26along with this program; if not, write to the Free Software
  27Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA  02111-1307  USA}
  28
  29######################################################################
  30##
  31## Tcl/Tk sanity check
  32
  33if {[catch {package require Tcl 8.4} err]
  34 || [catch {package require Tk  8.4} err]
  35} {
  36        catch {wm withdraw .}
  37        tk_messageBox \
  38                -icon error \
  39                -type ok \
  40                -title "git-gui: fatal error" \
  41                -message $err
  42        exit 1
  43}
  44
  45######################################################################
  46##
  47## configure our library
  48
  49set oguilib {@@GITGUI_LIBDIR@@}
  50set oguirel {@@GITGUI_RELATIVE@@}
  51if {$oguirel eq {1}} {
  52        set oguilib [file dirname [file dirname [file normalize $argv0]]]
  53        set oguilib [file join $oguilib share git-gui lib]
  54} elseif {[string match @@* $oguirel]} {
  55        set oguilib [file join [file dirname [file normalize $argv0]] lib]
  56}
  57set idx [file join $oguilib tclIndex]
  58catch {
  59        set fd [open $idx r]
  60        if {[gets $fd] eq {# Autogenerated by git-gui Makefile}} {
  61                set idx [list]
  62                while {[gets $fd n] >= 0} {
  63                        if {$n ne {} && ![string match #* $n]} {
  64                                lappend idx $n
  65                        }
  66                }
  67        } else {
  68                set idx {}
  69        }
  70        close $fd
  71}
  72if {$idx ne {}} {
  73        set loaded [list]
  74        foreach p $idx {
  75                if {[lsearch -exact $loaded $p] >= 0} continue
  76                puts $p
  77                source [file join $oguilib $p]
  78                lappend loaded $p
  79        }
  80        unset loaded p
  81} else {
  82        set auto_path [concat [list $oguilib] $auto_path]
  83}
  84unset -nocomplain oguilib oguirel idx fd
  85
  86if {![catch {set _verbose $env(GITGUI_VERBOSE)}]} {
  87        unset _verbose
  88        rename auto_load real__auto_load
  89        proc auto_load {name args} {
  90                puts stderr "auto_load $name"
  91                return [uplevel 1 real__auto_load $name $args]
  92        }
  93        rename source real__source
  94        proc source {name} {
  95                puts stderr "source    $name"
  96                uplevel 1 real__source $name
  97        }
  98}
  99
 100######################################################################
 101##
 102## read only globals
 103
 104set _appname [lindex [file split $argv0] end]
 105set _gitdir {}
 106set _gitexec {}
 107set _reponame {}
 108set _iscygwin {}
 109
 110proc appname {} {
 111        global _appname
 112        return $_appname
 113}
 114
 115proc gitdir {args} {
 116        global _gitdir
 117        if {$args eq {}} {
 118                return $_gitdir
 119        }
 120        return [eval [concat [list file join $_gitdir] $args]]
 121}
 122
 123proc gitexec {args} {
 124        global _gitexec
 125        if {$_gitexec eq {}} {
 126                if {[catch {set _gitexec [git --exec-path]} err]} {
 127                        error "Git not installed?\n\n$err"
 128                }
 129        }
 130        if {$args eq {}} {
 131                return $_gitexec
 132        }
 133        return [eval [concat [list file join $_gitexec] $args]]
 134}
 135
 136proc reponame {} {
 137        global _reponame
 138        return $_reponame
 139}
 140
 141proc is_MacOSX {} {
 142        global tcl_platform tk_library
 143        if {[tk windowingsystem] eq {aqua}} {
 144                return 1
 145        }
 146        return 0
 147}
 148
 149proc is_Windows {} {
 150        global tcl_platform
 151        if {$tcl_platform(platform) eq {windows}} {
 152                return 1
 153        }
 154        return 0
 155}
 156
 157proc is_Cygwin {} {
 158        global tcl_platform _iscygwin
 159        if {$_iscygwin eq {}} {
 160                if {$tcl_platform(platform) eq {windows}} {
 161                        if {[catch {set p [exec cygpath --windir]} err]} {
 162                                set _iscygwin 0
 163                        } else {
 164                                set _iscygwin 1
 165                        }
 166                } else {
 167                        set _iscygwin 0
 168                }
 169        }
 170        return $_iscygwin
 171}
 172
 173proc is_enabled {option} {
 174        global enabled_options
 175        if {[catch {set on $enabled_options($option)}]} {return 0}
 176        return $on
 177}
 178
 179proc enable_option {option} {
 180        global enabled_options
 181        set enabled_options($option) 1
 182}
 183
 184proc disable_option {option} {
 185        global enabled_options
 186        set enabled_options($option) 0
 187}
 188
 189######################################################################
 190##
 191## config
 192
 193proc is_many_config {name} {
 194        switch -glob -- $name {
 195        remote.*.fetch -
 196        remote.*.push
 197                {return 1}
 198        *
 199                {return 0}
 200        }
 201}
 202
 203proc is_config_true {name} {
 204        global repo_config
 205        if {[catch {set v $repo_config($name)}]} {
 206                return 0
 207        } elseif {$v eq {true} || $v eq {1} || $v eq {yes}} {
 208                return 1
 209        } else {
 210                return 0
 211        }
 212}
 213
 214proc load_config {include_global} {
 215        global repo_config global_config default_config
 216
 217        array unset global_config
 218        if {$include_global} {
 219                catch {
 220                        set fd_rc [open "| git config --global --list" r]
 221                        while {[gets $fd_rc line] >= 0} {
 222                                if {[regexp {^([^=]+)=(.*)$} $line line name value]} {
 223                                        if {[is_many_config $name]} {
 224                                                lappend global_config($name) $value
 225                                        } else {
 226                                                set global_config($name) $value
 227                                        }
 228                                }
 229                        }
 230                        close $fd_rc
 231                }
 232        }
 233
 234        array unset repo_config
 235        catch {
 236                set fd_rc [open "| git config --list" r]
 237                while {[gets $fd_rc line] >= 0} {
 238                        if {[regexp {^([^=]+)=(.*)$} $line line name value]} {
 239                                if {[is_many_config $name]} {
 240                                        lappend repo_config($name) $value
 241                                } else {
 242                                        set repo_config($name) $value
 243                                }
 244                        }
 245                }
 246                close $fd_rc
 247        }
 248
 249        foreach name [array names default_config] {
 250                if {[catch {set v $global_config($name)}]} {
 251                        set global_config($name) $default_config($name)
 252                }
 253                if {[catch {set v $repo_config($name)}]} {
 254                        set repo_config($name) $default_config($name)
 255                }
 256        }
 257}
 258
 259######################################################################
 260##
 261## handy utils
 262
 263proc git {args} {
 264        return [eval exec git $args]
 265}
 266
 267auto_load tk_optionMenu
 268rename tk_optionMenu real__tkOptionMenu
 269proc tk_optionMenu {w varName args} {
 270        set m [eval real__tkOptionMenu $w $varName $args]
 271        $m configure -font font_ui
 272        $w configure -font font_ui
 273        return $m
 274}
 275
 276######################################################################
 277##
 278## version check
 279
 280set req_maj 1
 281set req_min 5
 282
 283if {[catch {set v [git --version]} err]} {
 284        catch {wm withdraw .}
 285        error_popup "Cannot determine Git version:
 286
 287$err
 288
 289[appname] requires Git $req_maj.$req_min or later."
 290        exit 1
 291}
 292if {[regexp {^git version (\d+)\.(\d+)} $v _junk act_maj act_min]} {
 293        if {$act_maj < $req_maj
 294                || ($act_maj == $req_maj && $act_min < $req_min)} {
 295                catch {wm withdraw .}
 296                error_popup "[appname] requires Git $req_maj.$req_min or later.
 297
 298You are using $v."
 299                exit 1
 300        }
 301} else {
 302        catch {wm withdraw .}
 303        error_popup "Cannot parse Git version string:\n\n$v"
 304        exit 1
 305}
 306unset -nocomplain v _junk act_maj act_min req_maj req_min
 307
 308######################################################################
 309##
 310## repository setup
 311
 312if {[catch {
 313                set _gitdir $env(GIT_DIR)
 314                set _prefix {}
 315                }]
 316        && [catch {
 317                set _gitdir [git rev-parse --git-dir]
 318                set _prefix [git rev-parse --show-prefix]
 319        } err]} {
 320        catch {wm withdraw .}
 321        error_popup "Cannot find the git directory:\n\n$err"
 322        exit 1
 323}
 324if {![file isdirectory $_gitdir] && [is_Cygwin]} {
 325        catch {set _gitdir [exec cygpath --unix $_gitdir]}
 326}
 327if {![file isdirectory $_gitdir]} {
 328        catch {wm withdraw .}
 329        error_popup "Git directory not found:\n\n$_gitdir"
 330        exit 1
 331}
 332if {[lindex [file split $_gitdir] end] ne {.git}} {
 333        catch {wm withdraw .}
 334        error_popup "Cannot use funny .git directory:\n\n$_gitdir"
 335        exit 1
 336}
 337if {[catch {cd [file dirname $_gitdir]} err]} {
 338        catch {wm withdraw .}
 339        error_popup "No working directory [file dirname $_gitdir]:\n\n$err"
 340        exit 1
 341}
 342set _reponame [lindex [file split \
 343        [file normalize [file dirname $_gitdir]]] \
 344        end]
 345
 346######################################################################
 347##
 348## global init
 349
 350set current_diff_path {}
 351set current_diff_side {}
 352set diff_actions [list]
 353set ui_status_value {Initializing...}
 354
 355set HEAD {}
 356set PARENT {}
 357set MERGE_HEAD [list]
 358set commit_type {}
 359set empty_tree {}
 360set current_branch {}
 361set current_diff_path {}
 362set selected_commit_type new
 363
 364######################################################################
 365##
 366## task management
 367
 368set rescan_active 0
 369set diff_active 0
 370set last_clicked {}
 371
 372set disable_on_lock [list]
 373set index_lock_type none
 374
 375proc lock_index {type} {
 376        global index_lock_type disable_on_lock
 377
 378        if {$index_lock_type eq {none}} {
 379                set index_lock_type $type
 380                foreach w $disable_on_lock {
 381                        uplevel #0 $w disabled
 382                }
 383                return 1
 384        } elseif {$index_lock_type eq "begin-$type"} {
 385                set index_lock_type $type
 386                return 1
 387        }
 388        return 0
 389}
 390
 391proc unlock_index {} {
 392        global index_lock_type disable_on_lock
 393
 394        set index_lock_type none
 395        foreach w $disable_on_lock {
 396                uplevel #0 $w normal
 397        }
 398}
 399
 400######################################################################
 401##
 402## status
 403
 404proc repository_state {ctvar hdvar mhvar} {
 405        global current_branch
 406        upvar $ctvar ct $hdvar hd $mhvar mh
 407
 408        set mh [list]
 409
 410        if {[catch {set current_branch [git symbolic-ref HEAD]}]} {
 411                set current_branch {}
 412        } else {
 413                regsub ^refs/((heads|tags|remotes)/)? \
 414                        $current_branch \
 415                        {} \
 416                        current_branch
 417        }
 418
 419        if {[catch {set hd [git rev-parse --verify HEAD]}]} {
 420                set hd {}
 421                set ct initial
 422                return
 423        }
 424
 425        set merge_head [gitdir MERGE_HEAD]
 426        if {[file exists $merge_head]} {
 427                set ct merge
 428                set fd_mh [open $merge_head r]
 429                while {[gets $fd_mh line] >= 0} {
 430                        lappend mh $line
 431                }
 432                close $fd_mh
 433                return
 434        }
 435
 436        set ct normal
 437}
 438
 439proc PARENT {} {
 440        global PARENT empty_tree
 441
 442        set p [lindex $PARENT 0]
 443        if {$p ne {}} {
 444                return $p
 445        }
 446        if {$empty_tree eq {}} {
 447                set empty_tree [git mktree << {}]
 448        }
 449        return $empty_tree
 450}
 451
 452proc rescan {after {honor_trustmtime 1}} {
 453        global HEAD PARENT MERGE_HEAD commit_type
 454        global ui_index ui_workdir ui_status_value ui_comm
 455        global rescan_active file_states
 456        global repo_config
 457
 458        if {$rescan_active > 0 || ![lock_index read]} return
 459
 460        repository_state newType newHEAD newMERGE_HEAD
 461        if {[string match amend* $commit_type]
 462                && $newType eq {normal}
 463                && $newHEAD eq $HEAD} {
 464        } else {
 465                set HEAD $newHEAD
 466                set PARENT $newHEAD
 467                set MERGE_HEAD $newMERGE_HEAD
 468                set commit_type $newType
 469        }
 470
 471        array unset file_states
 472
 473        if {![$ui_comm edit modified]
 474                || [string trim [$ui_comm get 0.0 end]] eq {}} {
 475                if {[string match amend* $commit_type]} {
 476                } elseif {[load_message GITGUI_MSG]} {
 477                } elseif {[load_message MERGE_MSG]} {
 478                } elseif {[load_message SQUASH_MSG]} {
 479                }
 480                $ui_comm edit reset
 481                $ui_comm edit modified false
 482        }
 483
 484        if {[is_enabled branch]} {
 485                load_all_heads
 486                populate_branch_menu
 487        }
 488
 489        if {$honor_trustmtime && $repo_config(gui.trustmtime) eq {true}} {
 490                rescan_stage2 {} $after
 491        } else {
 492                set rescan_active 1
 493                set ui_status_value {Refreshing file status...}
 494                set cmd [list git update-index]
 495                lappend cmd -q
 496                lappend cmd --unmerged
 497                lappend cmd --ignore-missing
 498                lappend cmd --refresh
 499                set fd_rf [open "| $cmd" r]
 500                fconfigure $fd_rf -blocking 0 -translation binary
 501                fileevent $fd_rf readable \
 502                        [list rescan_stage2 $fd_rf $after]
 503        }
 504}
 505
 506proc rescan_stage2 {fd after} {
 507        global ui_status_value
 508        global rescan_active buf_rdi buf_rdf buf_rlo
 509
 510        if {$fd ne {}} {
 511                read $fd
 512                if {![eof $fd]} return
 513                close $fd
 514        }
 515
 516        set ls_others [list | git ls-files --others -z \
 517                --exclude-per-directory=.gitignore]
 518        set info_exclude [gitdir info exclude]
 519        if {[file readable $info_exclude]} {
 520                lappend ls_others "--exclude-from=$info_exclude"
 521        }
 522
 523        set buf_rdi {}
 524        set buf_rdf {}
 525        set buf_rlo {}
 526
 527        set rescan_active 3
 528        set ui_status_value {Scanning for modified files ...}
 529        set fd_di [open "| git diff-index --cached -z [PARENT]" r]
 530        set fd_df [open "| git diff-files -z" r]
 531        set fd_lo [open $ls_others r]
 532
 533        fconfigure $fd_di -blocking 0 -translation binary -encoding binary
 534        fconfigure $fd_df -blocking 0 -translation binary -encoding binary
 535        fconfigure $fd_lo -blocking 0 -translation binary -encoding binary
 536        fileevent $fd_di readable [list read_diff_index $fd_di $after]
 537        fileevent $fd_df readable [list read_diff_files $fd_df $after]
 538        fileevent $fd_lo readable [list read_ls_others $fd_lo $after]
 539}
 540
 541proc load_message {file} {
 542        global ui_comm
 543
 544        set f [gitdir $file]
 545        if {[file isfile $f]} {
 546                if {[catch {set fd [open $f r]}]} {
 547                        return 0
 548                }
 549                set content [string trim [read $fd]]
 550                close $fd
 551                regsub -all -line {[ \r\t]+$} $content {} content
 552                $ui_comm delete 0.0 end
 553                $ui_comm insert end $content
 554                return 1
 555        }
 556        return 0
 557}
 558
 559proc read_diff_index {fd after} {
 560        global buf_rdi
 561
 562        append buf_rdi [read $fd]
 563        set c 0
 564        set n [string length $buf_rdi]
 565        while {$c < $n} {
 566                set z1 [string first "\0" $buf_rdi $c]
 567                if {$z1 == -1} break
 568                incr z1
 569                set z2 [string first "\0" $buf_rdi $z1]
 570                if {$z2 == -1} break
 571
 572                incr c
 573                set i [split [string range $buf_rdi $c [expr {$z1 - 2}]] { }]
 574                set p [string range $buf_rdi $z1 [expr {$z2 - 1}]]
 575                merge_state \
 576                        [encoding convertfrom $p] \
 577                        [lindex $i 4]? \
 578                        [list [lindex $i 0] [lindex $i 2]] \
 579                        [list]
 580                set c $z2
 581                incr c
 582        }
 583        if {$c < $n} {
 584                set buf_rdi [string range $buf_rdi $c end]
 585        } else {
 586                set buf_rdi {}
 587        }
 588
 589        rescan_done $fd buf_rdi $after
 590}
 591
 592proc read_diff_files {fd after} {
 593        global buf_rdf
 594
 595        append buf_rdf [read $fd]
 596        set c 0
 597        set n [string length $buf_rdf]
 598        while {$c < $n} {
 599                set z1 [string first "\0" $buf_rdf $c]
 600                if {$z1 == -1} break
 601                incr z1
 602                set z2 [string first "\0" $buf_rdf $z1]
 603                if {$z2 == -1} break
 604
 605                incr c
 606                set i [split [string range $buf_rdf $c [expr {$z1 - 2}]] { }]
 607                set p [string range $buf_rdf $z1 [expr {$z2 - 1}]]
 608                merge_state \
 609                        [encoding convertfrom $p] \
 610                        ?[lindex $i 4] \
 611                        [list] \
 612                        [list [lindex $i 0] [lindex $i 2]]
 613                set c $z2
 614                incr c
 615        }
 616        if {$c < $n} {
 617                set buf_rdf [string range $buf_rdf $c end]
 618        } else {
 619                set buf_rdf {}
 620        }
 621
 622        rescan_done $fd buf_rdf $after
 623}
 624
 625proc read_ls_others {fd after} {
 626        global buf_rlo
 627
 628        append buf_rlo [read $fd]
 629        set pck [split $buf_rlo "\0"]
 630        set buf_rlo [lindex $pck end]
 631        foreach p [lrange $pck 0 end-1] {
 632                merge_state [encoding convertfrom $p] ?O
 633        }
 634        rescan_done $fd buf_rlo $after
 635}
 636
 637proc rescan_done {fd buf after} {
 638        global rescan_active current_diff_path
 639        global file_states repo_config
 640        upvar $buf to_clear
 641
 642        if {![eof $fd]} return
 643        set to_clear {}
 644        close $fd
 645        if {[incr rescan_active -1] > 0} return
 646
 647        prune_selection
 648        unlock_index
 649        display_all_files
 650        if {$current_diff_path ne {}} reshow_diff
 651        uplevel #0 $after
 652}
 653
 654proc prune_selection {} {
 655        global file_states selected_paths
 656
 657        foreach path [array names selected_paths] {
 658                if {[catch {set still_here $file_states($path)}]} {
 659                        unset selected_paths($path)
 660                }
 661        }
 662}
 663
 664######################################################################
 665##
 666## ui helpers
 667
 668proc mapicon {w state path} {
 669        global all_icons
 670
 671        if {[catch {set r $all_icons($state$w)}]} {
 672                puts "error: no icon for $w state={$state} $path"
 673                return file_plain
 674        }
 675        return $r
 676}
 677
 678proc mapdesc {state path} {
 679        global all_descs
 680
 681        if {[catch {set r $all_descs($state)}]} {
 682                puts "error: no desc for state={$state} $path"
 683                return $state
 684        }
 685        return $r
 686}
 687
 688proc escape_path {path} {
 689        regsub -all {\\} $path "\\\\" path
 690        regsub -all "\n" $path "\\n" path
 691        return $path
 692}
 693
 694proc short_path {path} {
 695        return [escape_path [lindex [file split $path] end]]
 696}
 697
 698set next_icon_id 0
 699set null_sha1 [string repeat 0 40]
 700
 701proc merge_state {path new_state {head_info {}} {index_info {}}} {
 702        global file_states next_icon_id null_sha1
 703
 704        set s0 [string index $new_state 0]
 705        set s1 [string index $new_state 1]
 706
 707        if {[catch {set info $file_states($path)}]} {
 708                set state __
 709                set icon n[incr next_icon_id]
 710        } else {
 711                set state [lindex $info 0]
 712                set icon [lindex $info 1]
 713                if {$head_info eq {}}  {set head_info  [lindex $info 2]}
 714                if {$index_info eq {}} {set index_info [lindex $info 3]}
 715        }
 716
 717        if     {$s0 eq {?}} {set s0 [string index $state 0]} \
 718        elseif {$s0 eq {_}} {set s0 _}
 719
 720        if     {$s1 eq {?}} {set s1 [string index $state 1]} \
 721        elseif {$s1 eq {_}} {set s1 _}
 722
 723        if {$s0 eq {A} && $s1 eq {_} && $head_info eq {}} {
 724                set head_info [list 0 $null_sha1]
 725        } elseif {$s0 ne {_} && [string index $state 0] eq {_}
 726                && $head_info eq {}} {
 727                set head_info $index_info
 728        }
 729
 730        set file_states($path) [list $s0$s1 $icon \
 731                $head_info $index_info \
 732                ]
 733        return $state
 734}
 735
 736proc display_file_helper {w path icon_name old_m new_m} {
 737        global file_lists
 738
 739        if {$new_m eq {_}} {
 740                set lno [lsearch -sorted -exact $file_lists($w) $path]
 741                if {$lno >= 0} {
 742                        set file_lists($w) [lreplace $file_lists($w) $lno $lno]
 743                        incr lno
 744                        $w conf -state normal
 745                        $w delete $lno.0 [expr {$lno + 1}].0
 746                        $w conf -state disabled
 747                }
 748        } elseif {$old_m eq {_} && $new_m ne {_}} {
 749                lappend file_lists($w) $path
 750                set file_lists($w) [lsort -unique $file_lists($w)]
 751                set lno [lsearch -sorted -exact $file_lists($w) $path]
 752                incr lno
 753                $w conf -state normal
 754                $w image create $lno.0 \
 755                        -align center -padx 5 -pady 1 \
 756                        -name $icon_name \
 757                        -image [mapicon $w $new_m $path]
 758                $w insert $lno.1 "[escape_path $path]\n"
 759                $w conf -state disabled
 760        } elseif {$old_m ne $new_m} {
 761                $w conf -state normal
 762                $w image conf $icon_name -image [mapicon $w $new_m $path]
 763                $w conf -state disabled
 764        }
 765}
 766
 767proc display_file {path state} {
 768        global file_states selected_paths
 769        global ui_index ui_workdir
 770
 771        set old_m [merge_state $path $state]
 772        set s $file_states($path)
 773        set new_m [lindex $s 0]
 774        set icon_name [lindex $s 1]
 775
 776        set o [string index $old_m 0]
 777        set n [string index $new_m 0]
 778        if {$o eq {U}} {
 779                set o _
 780        }
 781        if {$n eq {U}} {
 782                set n _
 783        }
 784        display_file_helper     $ui_index $path $icon_name $o $n
 785
 786        if {[string index $old_m 0] eq {U}} {
 787                set o U
 788        } else {
 789                set o [string index $old_m 1]
 790        }
 791        if {[string index $new_m 0] eq {U}} {
 792                set n U
 793        } else {
 794                set n [string index $new_m 1]
 795        }
 796        display_file_helper     $ui_workdir $path $icon_name $o $n
 797
 798        if {$new_m eq {__}} {
 799                unset file_states($path)
 800                catch {unset selected_paths($path)}
 801        }
 802}
 803
 804proc display_all_files_helper {w path icon_name m} {
 805        global file_lists
 806
 807        lappend file_lists($w) $path
 808        set lno [expr {[lindex [split [$w index end] .] 0] - 1}]
 809        $w image create end \
 810                -align center -padx 5 -pady 1 \
 811                -name $icon_name \
 812                -image [mapicon $w $m $path]
 813        $w insert end "[escape_path $path]\n"
 814}
 815
 816proc display_all_files {} {
 817        global ui_index ui_workdir
 818        global file_states file_lists
 819        global last_clicked
 820
 821        $ui_index conf -state normal
 822        $ui_workdir conf -state normal
 823
 824        $ui_index delete 0.0 end
 825        $ui_workdir delete 0.0 end
 826        set last_clicked {}
 827
 828        set file_lists($ui_index) [list]
 829        set file_lists($ui_workdir) [list]
 830
 831        foreach path [lsort [array names file_states]] {
 832                set s $file_states($path)
 833                set m [lindex $s 0]
 834                set icon_name [lindex $s 1]
 835
 836                set s [string index $m 0]
 837                if {$s ne {U} && $s ne {_}} {
 838                        display_all_files_helper $ui_index $path \
 839                                $icon_name $s
 840                }
 841
 842                if {[string index $m 0] eq {U}} {
 843                        set s U
 844                } else {
 845                        set s [string index $m 1]
 846                }
 847                if {$s ne {_}} {
 848                        display_all_files_helper $ui_workdir $path \
 849                                $icon_name $s
 850                }
 851        }
 852
 853        $ui_index conf -state disabled
 854        $ui_workdir conf -state disabled
 855}
 856
 857######################################################################
 858##
 859## icons
 860
 861set filemask {
 862#define mask_width 14
 863#define mask_height 15
 864static unsigned char mask_bits[] = {
 865   0xfe, 0x1f, 0xfe, 0x1f, 0xfe, 0x1f, 0xfe, 0x1f, 0xfe, 0x1f, 0xfe, 0x1f,
 866   0xfe, 0x1f, 0xfe, 0x1f, 0xfe, 0x1f, 0xfe, 0x1f, 0xfe, 0x1f, 0xfe, 0x1f,
 867   0xfe, 0x1f, 0xfe, 0x1f, 0xfe, 0x1f};
 868}
 869
 870image create bitmap file_plain -background white -foreground black -data {
 871#define plain_width 14
 872#define plain_height 15
 873static unsigned char plain_bits[] = {
 874   0xfe, 0x01, 0x02, 0x03, 0x02, 0x05, 0x02, 0x09, 0x02, 0x1f, 0x02, 0x10,
 875   0x02, 0x10, 0x02, 0x10, 0x02, 0x10, 0x02, 0x10, 0x02, 0x10, 0x02, 0x10,
 876   0x02, 0x10, 0x02, 0x10, 0xfe, 0x1f};
 877} -maskdata $filemask
 878
 879image create bitmap file_mod -background white -foreground blue -data {
 880#define mod_width 14
 881#define mod_height 15
 882static unsigned char mod_bits[] = {
 883   0xfe, 0x01, 0x02, 0x03, 0x7a, 0x05, 0x02, 0x09, 0x7a, 0x1f, 0x02, 0x10,
 884   0xfa, 0x17, 0x02, 0x10, 0xfa, 0x17, 0x02, 0x10, 0xfa, 0x17, 0x02, 0x10,
 885   0xfa, 0x17, 0x02, 0x10, 0xfe, 0x1f};
 886} -maskdata $filemask
 887
 888image create bitmap file_fulltick -background white -foreground "#007000" -data {
 889#define file_fulltick_width 14
 890#define file_fulltick_height 15
 891static unsigned char file_fulltick_bits[] = {
 892   0xfe, 0x01, 0x02, 0x1a, 0x02, 0x0c, 0x02, 0x0c, 0x02, 0x16, 0x02, 0x16,
 893   0x02, 0x13, 0x00, 0x13, 0x86, 0x11, 0x8c, 0x11, 0xd8, 0x10, 0xf2, 0x10,
 894   0x62, 0x10, 0x02, 0x10, 0xfe, 0x1f};
 895} -maskdata $filemask
 896
 897image create bitmap file_parttick -background white -foreground "#005050" -data {
 898#define parttick_width 14
 899#define parttick_height 15
 900static unsigned char parttick_bits[] = {
 901   0xfe, 0x01, 0x02, 0x03, 0x7a, 0x05, 0x02, 0x09, 0x7a, 0x1f, 0x02, 0x10,
 902   0x7a, 0x14, 0x02, 0x16, 0x02, 0x13, 0x8a, 0x11, 0xda, 0x10, 0x72, 0x10,
 903   0x22, 0x10, 0x02, 0x10, 0xfe, 0x1f};
 904} -maskdata $filemask
 905
 906image create bitmap file_question -background white -foreground black -data {
 907#define file_question_width 14
 908#define file_question_height 15
 909static unsigned char file_question_bits[] = {
 910   0xfe, 0x01, 0x02, 0x02, 0xe2, 0x04, 0xf2, 0x09, 0x1a, 0x1b, 0x0a, 0x13,
 911   0x82, 0x11, 0xc2, 0x10, 0x62, 0x10, 0x62, 0x10, 0x02, 0x10, 0x62, 0x10,
 912   0x62, 0x10, 0x02, 0x10, 0xfe, 0x1f};
 913} -maskdata $filemask
 914
 915image create bitmap file_removed -background white -foreground red -data {
 916#define file_removed_width 14
 917#define file_removed_height 15
 918static unsigned char file_removed_bits[] = {
 919   0xfe, 0x01, 0x02, 0x03, 0x02, 0x05, 0x02, 0x09, 0x02, 0x1f, 0x02, 0x10,
 920   0x1a, 0x16, 0x32, 0x13, 0xe2, 0x11, 0xc2, 0x10, 0xe2, 0x11, 0x32, 0x13,
 921   0x1a, 0x16, 0x02, 0x10, 0xfe, 0x1f};
 922} -maskdata $filemask
 923
 924image create bitmap file_merge -background white -foreground blue -data {
 925#define file_merge_width 14
 926#define file_merge_height 15
 927static unsigned char file_merge_bits[] = {
 928   0xfe, 0x01, 0x02, 0x03, 0x62, 0x05, 0x62, 0x09, 0x62, 0x1f, 0x62, 0x10,
 929   0xfa, 0x11, 0xf2, 0x10, 0x62, 0x10, 0x02, 0x10, 0xfa, 0x17, 0x02, 0x10,
 930   0xfa, 0x17, 0x02, 0x10, 0xfe, 0x1f};
 931} -maskdata $filemask
 932
 933set file_dir_data {
 934#define file_width 18
 935#define file_height 18
 936static unsigned char file_bits[] = {
 937  0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0xf8, 0x03, 0x00,
 938  0x0c, 0x03, 0x00, 0x04, 0xfe, 0x00, 0x06, 0x80, 0x00, 0xff, 0x9f, 0x00,
 939  0x03, 0x98, 0x00, 0x02, 0x90, 0x00, 0x06, 0xb0, 0x00, 0x04, 0xa0, 0x00,
 940  0x0c, 0xe0, 0x00, 0x08, 0xc0, 0x00, 0xf8, 0xff, 0x00, 0x00, 0x00, 0x00,
 941  0x00, 0x00, 0x00, 0x00, 0x00, 0x00};
 942}
 943image create bitmap file_dir -background white -foreground blue \
 944        -data $file_dir_data -maskdata $file_dir_data
 945unset file_dir_data
 946
 947set file_uplevel_data {
 948#define up_width 15
 949#define up_height 15
 950static unsigned char up_bits[] = {
 951  0x80, 0x00, 0xc0, 0x01, 0xe0, 0x03, 0xf0, 0x07, 0xf8, 0x0f, 0xfc, 0x1f,
 952  0xfe, 0x3f, 0xc0, 0x01, 0xc0, 0x01, 0xc0, 0x01, 0xc0, 0x01, 0xc0, 0x01,
 953  0xc0, 0x01, 0xc0, 0x01, 0x00, 0x00};
 954}
 955image create bitmap file_uplevel -background white -foreground red \
 956        -data $file_uplevel_data -maskdata $file_uplevel_data
 957unset file_uplevel_data
 958
 959set ui_index .vpane.files.index.list
 960set ui_workdir .vpane.files.workdir.list
 961
 962set all_icons(_$ui_index)   file_plain
 963set all_icons(A$ui_index)   file_fulltick
 964set all_icons(M$ui_index)   file_fulltick
 965set all_icons(D$ui_index)   file_removed
 966set all_icons(U$ui_index)   file_merge
 967
 968set all_icons(_$ui_workdir) file_plain
 969set all_icons(M$ui_workdir) file_mod
 970set all_icons(D$ui_workdir) file_question
 971set all_icons(U$ui_workdir) file_merge
 972set all_icons(O$ui_workdir) file_plain
 973
 974set max_status_desc 0
 975foreach i {
 976                {__ "Unmodified"}
 977
 978                {_M "Modified, not staged"}
 979                {M_ "Staged for commit"}
 980                {MM "Portions staged for commit"}
 981                {MD "Staged for commit, missing"}
 982
 983                {_O "Untracked, not staged"}
 984                {A_ "Staged for commit"}
 985                {AM "Portions staged for commit"}
 986                {AD "Staged for commit, missing"}
 987
 988                {_D "Missing"}
 989                {D_ "Staged for removal"}
 990                {DO "Staged for removal, still present"}
 991
 992                {U_ "Requires merge resolution"}
 993                {UU "Requires merge resolution"}
 994                {UM "Requires merge resolution"}
 995                {UD "Requires merge resolution"}
 996        } {
 997        if {$max_status_desc < [string length [lindex $i 1]]} {
 998                set max_status_desc [string length [lindex $i 1]]
 999        }
1000        set all_descs([lindex $i 0]) [lindex $i 1]
1001}
1002unset i
1003
1004######################################################################
1005##
1006## util
1007
1008proc bind_button3 {w cmd} {
1009        bind $w <Any-Button-3> $cmd
1010        if {[is_MacOSX]} {
1011                bind $w <Control-Button-1> $cmd
1012        }
1013}
1014
1015proc scrollbar2many {list mode args} {
1016        foreach w $list {eval $w $mode $args}
1017}
1018
1019proc many2scrollbar {list mode sb top bottom} {
1020        $sb set $top $bottom
1021        foreach w $list {$w $mode moveto $top}
1022}
1023
1024proc incr_font_size {font {amt 1}} {
1025        set sz [font configure $font -size]
1026        incr sz $amt
1027        font configure $font -size $sz
1028        font configure ${font}bold -size $sz
1029        font configure ${font}italic -size $sz
1030}
1031
1032######################################################################
1033##
1034## ui commands
1035
1036set starting_gitk_msg {Starting gitk... please wait...}
1037
1038proc do_gitk {revs} {
1039        global env ui_status_value starting_gitk_msg
1040
1041        # -- Always start gitk through whatever we were loaded with.  This
1042        #    lets us bypass using shell process on Windows systems.
1043        #
1044        set cmd [list [info nameofexecutable]]
1045        lappend cmd [gitexec gitk]
1046        if {$revs ne {}} {
1047                append cmd { }
1048                append cmd $revs
1049        }
1050
1051        if {[catch {eval exec $cmd &} err]} {
1052                error_popup "Failed to start gitk:\n\n$err"
1053        } else {
1054                set ui_status_value $starting_gitk_msg
1055                after 10000 {
1056                        if {$ui_status_value eq $starting_gitk_msg} {
1057                                set ui_status_value {Ready.}
1058                        }
1059                }
1060        }
1061}
1062
1063set is_quitting 0
1064
1065proc do_quit {} {
1066        global ui_comm is_quitting repo_config commit_type
1067
1068        if {$is_quitting} return
1069        set is_quitting 1
1070
1071        if {[winfo exists $ui_comm]} {
1072                # -- Stash our current commit buffer.
1073                #
1074                set save [gitdir GITGUI_MSG]
1075                set msg [string trim [$ui_comm get 0.0 end]]
1076                regsub -all -line {[ \r\t]+$} $msg {} msg
1077                if {(![string match amend* $commit_type]
1078                        || [$ui_comm edit modified])
1079                        && $msg ne {}} {
1080                        catch {
1081                                set fd [open $save w]
1082                                puts -nonewline $fd $msg
1083                                close $fd
1084                        }
1085                } else {
1086                        catch {file delete $save}
1087                }
1088
1089                # -- Stash our current window geometry into this repository.
1090                #
1091                set cfg_geometry [list]
1092                lappend cfg_geometry [wm geometry .]
1093                lappend cfg_geometry [lindex [.vpane sash coord 0] 1]
1094                lappend cfg_geometry [lindex [.vpane.files sash coord 0] 0]
1095                if {[catch {set rc_geometry $repo_config(gui.geometry)}]} {
1096                        set rc_geometry {}
1097                }
1098                if {$cfg_geometry ne $rc_geometry} {
1099                        catch {git config gui.geometry $cfg_geometry}
1100                }
1101        }
1102
1103        destroy .
1104}
1105
1106proc do_rescan {} {
1107        rescan {set ui_status_value {Ready.}}
1108}
1109
1110proc do_commit {} {
1111        commit_tree
1112}
1113
1114proc toggle_or_diff {w x y} {
1115        global file_states file_lists current_diff_path ui_index ui_workdir
1116        global last_clicked selected_paths
1117
1118        set pos [split [$w index @$x,$y] .]
1119        set lno [lindex $pos 0]
1120        set col [lindex $pos 1]
1121        set path [lindex $file_lists($w) [expr {$lno - 1}]]
1122        if {$path eq {}} {
1123                set last_clicked {}
1124                return
1125        }
1126
1127        set last_clicked [list $w $lno]
1128        array unset selected_paths
1129        $ui_index tag remove in_sel 0.0 end
1130        $ui_workdir tag remove in_sel 0.0 end
1131
1132        if {$col == 0} {
1133                if {$current_diff_path eq $path} {
1134                        set after {reshow_diff;}
1135                } else {
1136                        set after {}
1137                }
1138                if {$w eq $ui_index} {
1139                        update_indexinfo \
1140                                "Unstaging [short_path $path] from commit" \
1141                                [list $path] \
1142                                [concat $after {set ui_status_value {Ready.}}]
1143                } elseif {$w eq $ui_workdir} {
1144                        update_index \
1145                                "Adding [short_path $path]" \
1146                                [list $path] \
1147                                [concat $after {set ui_status_value {Ready.}}]
1148                }
1149        } else {
1150                show_diff $path $w $lno
1151        }
1152}
1153
1154proc add_one_to_selection {w x y} {
1155        global file_lists last_clicked selected_paths
1156
1157        set lno [lindex [split [$w index @$x,$y] .] 0]
1158        set path [lindex $file_lists($w) [expr {$lno - 1}]]
1159        if {$path eq {}} {
1160                set last_clicked {}
1161                return
1162        }
1163
1164        if {$last_clicked ne {}
1165                && [lindex $last_clicked 0] ne $w} {
1166                array unset selected_paths
1167                [lindex $last_clicked 0] tag remove in_sel 0.0 end
1168        }
1169
1170        set last_clicked [list $w $lno]
1171        if {[catch {set in_sel $selected_paths($path)}]} {
1172                set in_sel 0
1173        }
1174        if {$in_sel} {
1175                unset selected_paths($path)
1176                $w tag remove in_sel $lno.0 [expr {$lno + 1}].0
1177        } else {
1178                set selected_paths($path) 1
1179                $w tag add in_sel $lno.0 [expr {$lno + 1}].0
1180        }
1181}
1182
1183proc add_range_to_selection {w x y} {
1184        global file_lists last_clicked selected_paths
1185
1186        if {[lindex $last_clicked 0] ne $w} {
1187                toggle_or_diff $w $x $y
1188                return
1189        }
1190
1191        set lno [lindex [split [$w index @$x,$y] .] 0]
1192        set lc [lindex $last_clicked 1]
1193        if {$lc < $lno} {
1194                set begin $lc
1195                set end $lno
1196        } else {
1197                set begin $lno
1198                set end $lc
1199        }
1200
1201        foreach path [lrange $file_lists($w) \
1202                [expr {$begin - 1}] \
1203                [expr {$end - 1}]] {
1204                set selected_paths($path) 1
1205        }
1206        $w tag add in_sel $begin.0 [expr {$end + 1}].0
1207}
1208
1209######################################################################
1210##
1211## config defaults
1212
1213set cursor_ptr arrow
1214font create font_diff -family Courier -size 10
1215font create font_ui
1216catch {
1217        label .dummy
1218        eval font configure font_ui [font actual [.dummy cget -font]]
1219        destroy .dummy
1220}
1221
1222font create font_uiitalic
1223font create font_uibold
1224font create font_diffbold
1225font create font_diffitalic
1226
1227foreach class {Button Checkbutton Entry Label
1228                Labelframe Listbox Menu Message
1229                Radiobutton Spinbox Text} {
1230        option add *$class.font font_ui
1231}
1232unset class
1233
1234if {[is_MacOSX]} {
1235        set M1B M1
1236        set M1T Cmd
1237} else {
1238        set M1B Control
1239        set M1T Ctrl
1240}
1241
1242proc apply_config {} {
1243        global repo_config font_descs
1244
1245        foreach option $font_descs {
1246                set name [lindex $option 0]
1247                set font [lindex $option 1]
1248                if {[catch {
1249                        foreach {cn cv} $repo_config(gui.$name) {
1250                                font configure $font $cn $cv
1251                        }
1252                        } err]} {
1253                        error_popup "Invalid font specified in gui.$name:\n\n$err"
1254                }
1255                foreach {cn cv} [font configure $font] {
1256                        font configure ${font}bold $cn $cv
1257                        font configure ${font}italic $cn $cv
1258                }
1259                font configure ${font}bold -weight bold
1260                font configure ${font}italic -slant italic
1261        }
1262}
1263
1264set default_config(merge.summary) false
1265set default_config(merge.verbosity) 2
1266set default_config(user.name) {}
1267set default_config(user.email) {}
1268
1269set default_config(gui.trustmtime) false
1270set default_config(gui.diffcontext) 5
1271set default_config(gui.newbranchtemplate) {}
1272set default_config(gui.fontui) [font configure font_ui]
1273set default_config(gui.fontdiff) [font configure font_diff]
1274set font_descs {
1275        {fontui   font_ui   {Main Font}}
1276        {fontdiff font_diff {Diff/Console Font}}
1277}
1278load_config 0
1279apply_config
1280
1281######################################################################
1282##
1283## feature option selection
1284
1285if {[regexp {^git-(.+)$} [appname] _junk subcommand]} {
1286        unset _junk
1287} else {
1288        set subcommand gui
1289}
1290if {$subcommand eq {gui.sh}} {
1291        set subcommand gui
1292}
1293if {$subcommand eq {gui} && [llength $argv] > 0} {
1294        set subcommand [lindex $argv 0]
1295        set argv [lrange $argv 1 end]
1296}
1297
1298enable_option multicommit
1299enable_option branch
1300enable_option transport
1301
1302switch -- $subcommand {
1303browser -
1304blame {
1305        disable_option multicommit
1306        disable_option branch
1307        disable_option transport
1308}
1309citool {
1310        enable_option singlecommit
1311
1312        disable_option multicommit
1313        disable_option branch
1314        disable_option transport
1315}
1316}
1317
1318######################################################################
1319##
1320## ui construction
1321
1322set ui_comm {}
1323
1324# -- Menu Bar
1325#
1326menu .mbar -tearoff 0
1327.mbar add cascade -label Repository -menu .mbar.repository
1328.mbar add cascade -label Edit -menu .mbar.edit
1329if {[is_enabled branch]} {
1330        .mbar add cascade -label Branch -menu .mbar.branch
1331}
1332if {[is_enabled multicommit] || [is_enabled singlecommit]} {
1333        .mbar add cascade -label Commit -menu .mbar.commit
1334}
1335if {[is_enabled transport]} {
1336        .mbar add cascade -label Merge -menu .mbar.merge
1337        .mbar add cascade -label Fetch -menu .mbar.fetch
1338        .mbar add cascade -label Push -menu .mbar.push
1339}
1340. configure -menu .mbar
1341
1342# -- Repository Menu
1343#
1344menu .mbar.repository
1345
1346.mbar.repository add command \
1347        -label {Browse Current Branch} \
1348        -command {browser::new $current_branch}
1349trace add variable current_branch write ".mbar.repository entryconf [.mbar.repository index last] -label \"Browse \$current_branch\" ;#"
1350.mbar.repository add separator
1351
1352.mbar.repository add command \
1353        -label {Visualize Current Branch} \
1354        -command {do_gitk $current_branch}
1355trace add variable current_branch write ".mbar.repository entryconf [.mbar.repository index last] -label \"Visualize \$current_branch\" ;#"
1356.mbar.repository add command \
1357        -label {Visualize All Branches} \
1358        -command {do_gitk --all}
1359.mbar.repository add separator
1360
1361if {[is_enabled multicommit]} {
1362        .mbar.repository add command -label {Database Statistics} \
1363                -command do_stats
1364
1365        .mbar.repository add command -label {Compress Database} \
1366                -command do_gc
1367
1368        .mbar.repository add command -label {Verify Database} \
1369                -command do_fsck_objects
1370
1371        .mbar.repository add separator
1372
1373        if {[is_Cygwin]} {
1374                .mbar.repository add command \
1375                        -label {Create Desktop Icon} \
1376                        -command do_cygwin_shortcut
1377        } elseif {[is_Windows]} {
1378                .mbar.repository add command \
1379                        -label {Create Desktop Icon} \
1380                        -command do_windows_shortcut
1381        } elseif {[is_MacOSX]} {
1382                .mbar.repository add command \
1383                        -label {Create Desktop Icon} \
1384                        -command do_macosx_app
1385        }
1386}
1387
1388.mbar.repository add command -label Quit \
1389        -command do_quit \
1390        -accelerator $M1T-Q
1391
1392# -- Edit Menu
1393#
1394menu .mbar.edit
1395.mbar.edit add command -label Undo \
1396        -command {catch {[focus] edit undo}} \
1397        -accelerator $M1T-Z
1398.mbar.edit add command -label Redo \
1399        -command {catch {[focus] edit redo}} \
1400        -accelerator $M1T-Y
1401.mbar.edit add separator
1402.mbar.edit add command -label Cut \
1403        -command {catch {tk_textCut [focus]}} \
1404        -accelerator $M1T-X
1405.mbar.edit add command -label Copy \
1406        -command {catch {tk_textCopy [focus]}} \
1407        -accelerator $M1T-C
1408.mbar.edit add command -label Paste \
1409        -command {catch {tk_textPaste [focus]; [focus] see insert}} \
1410        -accelerator $M1T-V
1411.mbar.edit add command -label Delete \
1412        -command {catch {[focus] delete sel.first sel.last}} \
1413        -accelerator Del
1414.mbar.edit add separator
1415.mbar.edit add command -label {Select All} \
1416        -command {catch {[focus] tag add sel 0.0 end}} \
1417        -accelerator $M1T-A
1418
1419# -- Branch Menu
1420#
1421if {[is_enabled branch]} {
1422        menu .mbar.branch
1423
1424        .mbar.branch add command -label {Create...} \
1425                -command do_create_branch \
1426                -accelerator $M1T-N
1427        lappend disable_on_lock [list .mbar.branch entryconf \
1428                [.mbar.branch index last] -state]
1429
1430        .mbar.branch add command -label {Delete...} \
1431                -command do_delete_branch
1432        lappend disable_on_lock [list .mbar.branch entryconf \
1433                [.mbar.branch index last] -state]
1434
1435        .mbar.branch add command -label {Reset...} \
1436                -command merge::reset_hard
1437        lappend disable_on_lock [list .mbar.branch entryconf \
1438                [.mbar.branch index last] -state]
1439}
1440
1441# -- Commit Menu
1442#
1443if {[is_enabled multicommit] || [is_enabled singlecommit]} {
1444        menu .mbar.commit
1445
1446        .mbar.commit add radiobutton \
1447                -label {New Commit} \
1448                -command do_select_commit_type \
1449                -variable selected_commit_type \
1450                -value new
1451        lappend disable_on_lock \
1452                [list .mbar.commit entryconf [.mbar.commit index last] -state]
1453
1454        .mbar.commit add radiobutton \
1455                -label {Amend Last Commit} \
1456                -command do_select_commit_type \
1457                -variable selected_commit_type \
1458                -value amend
1459        lappend disable_on_lock \
1460                [list .mbar.commit entryconf [.mbar.commit index last] -state]
1461
1462        .mbar.commit add separator
1463
1464        .mbar.commit add command -label Rescan \
1465                -command do_rescan \
1466                -accelerator F5
1467        lappend disable_on_lock \
1468                [list .mbar.commit entryconf [.mbar.commit index last] -state]
1469
1470        .mbar.commit add command -label {Add To Commit} \
1471                -command do_add_selection
1472        lappend disable_on_lock \
1473                [list .mbar.commit entryconf [.mbar.commit index last] -state]
1474
1475        .mbar.commit add command -label {Add Existing To Commit} \
1476                -command do_add_all \
1477                -accelerator $M1T-I
1478        lappend disable_on_lock \
1479                [list .mbar.commit entryconf [.mbar.commit index last] -state]
1480
1481        .mbar.commit add command -label {Unstage From Commit} \
1482                -command do_unstage_selection
1483        lappend disable_on_lock \
1484                [list .mbar.commit entryconf [.mbar.commit index last] -state]
1485
1486        .mbar.commit add command -label {Revert Changes} \
1487                -command do_revert_selection
1488        lappend disable_on_lock \
1489                [list .mbar.commit entryconf [.mbar.commit index last] -state]
1490
1491        .mbar.commit add separator
1492
1493        .mbar.commit add command -label {Sign Off} \
1494                -command do_signoff \
1495                -accelerator $M1T-S
1496
1497        .mbar.commit add command -label Commit \
1498                -command do_commit \
1499                -accelerator $M1T-Return
1500        lappend disable_on_lock \
1501                [list .mbar.commit entryconf [.mbar.commit index last] -state]
1502}
1503
1504# -- Merge Menu
1505#
1506if {[is_enabled branch]} {
1507        menu .mbar.merge
1508        .mbar.merge add command -label {Local Merge...} \
1509                -command merge::dialog
1510        lappend disable_on_lock \
1511                [list .mbar.merge entryconf [.mbar.merge index last] -state]
1512        .mbar.merge add command -label {Abort Merge...} \
1513                -command merge::reset_hard
1514        lappend disable_on_lock \
1515                [list .mbar.merge entryconf [.mbar.merge index last] -state]
1516
1517}
1518
1519# -- Transport Menu
1520#
1521if {[is_enabled transport]} {
1522        menu .mbar.fetch
1523
1524        menu .mbar.push
1525        .mbar.push add command -label {Push...} \
1526                -command do_push_anywhere
1527}
1528
1529if {[is_MacOSX]} {
1530        # -- Apple Menu (Mac OS X only)
1531        #
1532        .mbar add cascade -label Apple -menu .mbar.apple
1533        menu .mbar.apple
1534
1535        .mbar.apple add command -label "About [appname]" \
1536                -command do_about
1537        .mbar.apple add command -label "Options..." \
1538                -command do_options
1539} else {
1540        # -- Edit Menu
1541        #
1542        .mbar.edit add separator
1543        .mbar.edit add command -label {Options...} \
1544                -command do_options
1545
1546        # -- Tools Menu
1547        #
1548        if {[is_Cygwin] && [file exists /usr/local/miga/lib/gui-miga]} {
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