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