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