git-gui / git-gui.shon commit Merge branch 'master' of git://repo.or.cz/git-gui (2b93bfa)
   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 head {}
1609        set path {}
1610        set is_path 0
1611        foreach a $argv {
1612                if {$is_path || [file exists $_prefix$a]} {
1613                        if {$path ne {}} usage
1614                        set path $_prefix$a
1615                        break
1616                } elseif {$a eq {--}} {
1617                        if {$path ne {}} {
1618                                if {$head ne {}} usage
1619                                set head $path
1620                                set path {}
1621                        }
1622                        set is_path 1
1623                } elseif {$head eq {}} {
1624                        if {$head ne {}} usage
1625                        set head $a
1626                } else {
1627                        usage
1628                }
1629        }
1630        unset is_path
1631
1632        if {$head eq {}} {
1633                set current_branch [git symbolic-ref HEAD]
1634                regsub ^refs/((heads|tags|remotes)/)? \
1635                        $current_branch {} current_branch
1636        } else {
1637                set current_branch $head
1638        }
1639
1640        if {$path eq {}} usage
1641        blame::new $head $path
1642        return
1643}
1644citool -
1645gui {
1646        if {[llength $argv] != 0} {
1647                puts -nonewline stderr "usage: $argv0"
1648                if {$subcommand ne {gui} && [appname] ne "git-$subcommand"} {
1649                        puts -nonewline stderr " $subcommand"
1650                }
1651                puts stderr {}
1652                exit 1
1653        }
1654        # fall through to setup UI for commits
1655}
1656default {
1657        puts stderr "usage: $argv0 \[{blame|browser|citool}\]"
1658        exit 1
1659}
1660}
1661
1662# -- Branch Control
1663#
1664frame .branch \
1665        -borderwidth 1 \
1666        -relief sunken
1667label .branch.l1 \
1668        -text {Current Branch:} \
1669        -anchor w \
1670        -justify left
1671label .branch.cb \
1672        -textvariable current_branch \
1673        -anchor w \
1674        -justify left
1675pack .branch.l1 -side left
1676pack .branch.cb -side left -fill x
1677pack .branch -side top -fill x
1678
1679# -- Main Window Layout
1680#
1681panedwindow .vpane -orient vertical
1682panedwindow .vpane.files -orient horizontal
1683.vpane add .vpane.files -sticky nsew -height 100 -width 200
1684pack .vpane -anchor n -side top -fill both -expand 1
1685
1686# -- Index File List
1687#
1688frame .vpane.files.index -height 100 -width 200
1689label .vpane.files.index.title -text {Staged Changes (Will Be Committed)} \
1690        -background green
1691text $ui_index -background white -borderwidth 0 \
1692        -width 20 -height 10 \
1693        -wrap none \
1694        -cursor $cursor_ptr \
1695        -xscrollcommand {.vpane.files.index.sx set} \
1696        -yscrollcommand {.vpane.files.index.sy set} \
1697        -state disabled
1698scrollbar .vpane.files.index.sx -orient h -command [list $ui_index xview]
1699scrollbar .vpane.files.index.sy -orient v -command [list $ui_index yview]
1700pack .vpane.files.index.title -side top -fill x
1701pack .vpane.files.index.sx -side bottom -fill x
1702pack .vpane.files.index.sy -side right -fill y
1703pack $ui_index -side left -fill both -expand 1
1704.vpane.files add .vpane.files.index -sticky nsew
1705
1706# -- Working Directory File List
1707#
1708frame .vpane.files.workdir -height 100 -width 200
1709label .vpane.files.workdir.title -text {Unstaged Changes (Will Not Be Committed)} \
1710        -background red
1711text $ui_workdir -background white -borderwidth 0 \
1712        -width 20 -height 10 \
1713        -wrap none \
1714        -cursor $cursor_ptr \
1715        -xscrollcommand {.vpane.files.workdir.sx set} \
1716        -yscrollcommand {.vpane.files.workdir.sy set} \
1717        -state disabled
1718scrollbar .vpane.files.workdir.sx -orient h -command [list $ui_workdir xview]
1719scrollbar .vpane.files.workdir.sy -orient v -command [list $ui_workdir yview]
1720pack .vpane.files.workdir.title -side top -fill x
1721pack .vpane.files.workdir.sx -side bottom -fill x
1722pack .vpane.files.workdir.sy -side right -fill y
1723pack $ui_workdir -side left -fill both -expand 1
1724.vpane.files add .vpane.files.workdir -sticky nsew
1725
1726foreach i [list $ui_index $ui_workdir] {
1727        $i tag conf in_diff -font font_uibold
1728        $i tag conf in_sel \
1729                -background [$i cget -foreground] \
1730                -foreground [$i cget -background]
1731}
1732unset i
1733
1734# -- Diff and Commit Area
1735#
1736frame .vpane.lower -height 300 -width 400
1737frame .vpane.lower.commarea
1738frame .vpane.lower.diff -relief sunken -borderwidth 1
1739pack .vpane.lower.commarea -side top -fill x
1740pack .vpane.lower.diff -side bottom -fill both -expand 1
1741.vpane add .vpane.lower -sticky nsew
1742
1743# -- Commit Area Buttons
1744#
1745frame .vpane.lower.commarea.buttons
1746label .vpane.lower.commarea.buttons.l -text {} \
1747        -anchor w \
1748        -justify left
1749pack .vpane.lower.commarea.buttons.l -side top -fill x
1750pack .vpane.lower.commarea.buttons -side left -fill y
1751
1752button .vpane.lower.commarea.buttons.rescan -text {Rescan} \
1753        -command do_rescan
1754pack .vpane.lower.commarea.buttons.rescan -side top -fill x
1755lappend disable_on_lock \
1756        {.vpane.lower.commarea.buttons.rescan conf -state}
1757
1758button .vpane.lower.commarea.buttons.incall -text {Add Existing} \
1759        -command do_add_all
1760pack .vpane.lower.commarea.buttons.incall -side top -fill x
1761lappend disable_on_lock \
1762        {.vpane.lower.commarea.buttons.incall conf -state}
1763
1764button .vpane.lower.commarea.buttons.signoff -text {Sign Off} \
1765        -command do_signoff
1766pack .vpane.lower.commarea.buttons.signoff -side top -fill x
1767
1768button .vpane.lower.commarea.buttons.commit -text {Commit} \
1769        -command do_commit
1770pack .vpane.lower.commarea.buttons.commit -side top -fill x
1771lappend disable_on_lock \
1772        {.vpane.lower.commarea.buttons.commit conf -state}
1773
1774# -- Commit Message Buffer
1775#
1776frame .vpane.lower.commarea.buffer
1777frame .vpane.lower.commarea.buffer.header
1778set ui_comm .vpane.lower.commarea.buffer.t
1779set ui_coml .vpane.lower.commarea.buffer.header.l
1780radiobutton .vpane.lower.commarea.buffer.header.new \
1781        -text {New Commit} \
1782        -command do_select_commit_type \
1783        -variable selected_commit_type \
1784        -value new
1785lappend disable_on_lock \
1786        [list .vpane.lower.commarea.buffer.header.new conf -state]
1787radiobutton .vpane.lower.commarea.buffer.header.amend \
1788        -text {Amend Last Commit} \
1789        -command do_select_commit_type \
1790        -variable selected_commit_type \
1791        -value amend
1792lappend disable_on_lock \
1793        [list .vpane.lower.commarea.buffer.header.amend conf -state]
1794label $ui_coml \
1795        -anchor w \
1796        -justify left
1797proc trace_commit_type {varname args} {
1798        global ui_coml commit_type
1799        switch -glob -- $commit_type {
1800        initial       {set txt {Initial Commit Message:}}
1801        amend         {set txt {Amended Commit Message:}}
1802        amend-initial {set txt {Amended Initial Commit Message:}}
1803        amend-merge   {set txt {Amended Merge Commit Message:}}
1804        merge         {set txt {Merge Commit Message:}}
1805        *             {set txt {Commit Message:}}
1806        }
1807        $ui_coml conf -text $txt
1808}
1809trace add variable commit_type write trace_commit_type
1810pack $ui_coml -side left -fill x
1811pack .vpane.lower.commarea.buffer.header.amend -side right
1812pack .vpane.lower.commarea.buffer.header.new -side right
1813
1814text $ui_comm -background white -borderwidth 1 \
1815        -undo true \
1816        -maxundo 20 \
1817        -autoseparators true \
1818        -relief sunken \
1819        -width 75 -height 9 -wrap none \
1820        -font font_diff \
1821        -yscrollcommand {.vpane.lower.commarea.buffer.sby set}
1822scrollbar .vpane.lower.commarea.buffer.sby \
1823        -command [list $ui_comm yview]
1824pack .vpane.lower.commarea.buffer.header -side top -fill x
1825pack .vpane.lower.commarea.buffer.sby -side right -fill y
1826pack $ui_comm -side left -fill y
1827pack .vpane.lower.commarea.buffer -side left -fill y
1828
1829# -- Commit Message Buffer Context Menu
1830#
1831set ctxm .vpane.lower.commarea.buffer.ctxm
1832menu $ctxm -tearoff 0
1833$ctxm add command \
1834        -label {Cut} \
1835        -command {tk_textCut $ui_comm}
1836$ctxm add command \
1837        -label {Copy} \
1838        -command {tk_textCopy $ui_comm}
1839$ctxm add command \
1840        -label {Paste} \
1841        -command {tk_textPaste $ui_comm}
1842$ctxm add command \
1843        -label {Delete} \
1844        -command {$ui_comm delete sel.first sel.last}
1845$ctxm add separator
1846$ctxm add command \
1847        -label {Select All} \
1848        -command {focus $ui_comm;$ui_comm tag add sel 0.0 end}
1849$ctxm add command \
1850        -label {Copy All} \
1851        -command {
1852                $ui_comm tag add sel 0.0 end
1853                tk_textCopy $ui_comm
1854                $ui_comm tag remove sel 0.0 end
1855        }
1856$ctxm add separator
1857$ctxm add command \
1858        -label {Sign Off} \
1859        -command do_signoff
1860bind_button3 $ui_comm "tk_popup $ctxm %X %Y"
1861
1862# -- Diff Header
1863#
1864proc trace_current_diff_path {varname args} {
1865        global current_diff_path diff_actions file_states
1866        if {$current_diff_path eq {}} {
1867                set s {}
1868                set f {}
1869                set p {}
1870                set o disabled
1871        } else {
1872                set p $current_diff_path
1873                set s [mapdesc [lindex $file_states($p) 0] $p]
1874                set f {File:}
1875                set p [escape_path $p]
1876                set o normal
1877        }
1878
1879        .vpane.lower.diff.header.status configure -text $s
1880        .vpane.lower.diff.header.file configure -text $f
1881        .vpane.lower.diff.header.path configure -text $p
1882        foreach w $diff_actions {
1883                uplevel #0 $w $o
1884        }
1885}
1886trace add variable current_diff_path write trace_current_diff_path
1887
1888frame .vpane.lower.diff.header -background orange
1889label .vpane.lower.diff.header.status \
1890        -background orange \
1891        -width $max_status_desc \
1892        -anchor w \
1893        -justify left
1894label .vpane.lower.diff.header.file \
1895        -background orange \
1896        -anchor w \
1897        -justify left
1898label .vpane.lower.diff.header.path \
1899        -background orange \
1900        -anchor w \
1901        -justify left
1902pack .vpane.lower.diff.header.status -side left
1903pack .vpane.lower.diff.header.file -side left
1904pack .vpane.lower.diff.header.path -fill x
1905set ctxm .vpane.lower.diff.header.ctxm
1906menu $ctxm -tearoff 0
1907$ctxm add command \
1908        -label {Copy} \
1909        -command {
1910                clipboard clear
1911                clipboard append \
1912                        -format STRING \
1913                        -type STRING \
1914                        -- $current_diff_path
1915        }
1916lappend diff_actions [list $ctxm entryconf [$ctxm index last] -state]
1917bind_button3 .vpane.lower.diff.header.path "tk_popup $ctxm %X %Y"
1918
1919# -- Diff Body
1920#
1921frame .vpane.lower.diff.body
1922set ui_diff .vpane.lower.diff.body.t
1923text $ui_diff -background white -borderwidth 0 \
1924        -width 80 -height 15 -wrap none \
1925        -font font_diff \
1926        -xscrollcommand {.vpane.lower.diff.body.sbx set} \
1927        -yscrollcommand {.vpane.lower.diff.body.sby set} \
1928        -state disabled
1929scrollbar .vpane.lower.diff.body.sbx -orient horizontal \
1930        -command [list $ui_diff xview]
1931scrollbar .vpane.lower.diff.body.sby -orient vertical \
1932        -command [list $ui_diff yview]
1933pack .vpane.lower.diff.body.sbx -side bottom -fill x
1934pack .vpane.lower.diff.body.sby -side right -fill y
1935pack $ui_diff -side left -fill both -expand 1
1936pack .vpane.lower.diff.header -side top -fill x
1937pack .vpane.lower.diff.body -side bottom -fill both -expand 1
1938
1939$ui_diff tag conf d_cr -elide true
1940$ui_diff tag conf d_@ -foreground blue -font font_diffbold
1941$ui_diff tag conf d_+ -foreground {#00a000}
1942$ui_diff tag conf d_- -foreground red
1943
1944$ui_diff tag conf d_++ -foreground {#00a000}
1945$ui_diff tag conf d_-- -foreground red
1946$ui_diff tag conf d_+s \
1947        -foreground {#00a000} \
1948        -background {#e2effa}
1949$ui_diff tag conf d_-s \
1950        -foreground red \
1951        -background {#e2effa}
1952$ui_diff tag conf d_s+ \
1953        -foreground {#00a000} \
1954        -background ivory1
1955$ui_diff tag conf d_s- \
1956        -foreground red \
1957        -background ivory1
1958
1959$ui_diff tag conf d<<<<<<< \
1960        -foreground orange \
1961        -font font_diffbold
1962$ui_diff tag conf d======= \
1963        -foreground orange \
1964        -font font_diffbold
1965$ui_diff tag conf d>>>>>>> \
1966        -foreground orange \
1967        -font font_diffbold
1968
1969$ui_diff tag raise sel
1970
1971# -- Diff Body Context Menu
1972#
1973set ctxm .vpane.lower.diff.body.ctxm
1974menu $ctxm -tearoff 0
1975$ctxm add command \
1976        -label {Refresh} \
1977        -command reshow_diff
1978lappend diff_actions [list $ctxm entryconf [$ctxm index last] -state]
1979$ctxm add command \
1980        -label {Copy} \
1981        -command {tk_textCopy $ui_diff}
1982lappend diff_actions [list $ctxm entryconf [$ctxm index last] -state]
1983$ctxm add command \
1984        -label {Select All} \
1985        -command {focus $ui_diff;$ui_diff tag add sel 0.0 end}
1986lappend diff_actions [list $ctxm entryconf [$ctxm index last] -state]
1987$ctxm add command \
1988        -label {Copy All} \
1989        -command {
1990                $ui_diff tag add sel 0.0 end
1991                tk_textCopy $ui_diff
1992                $ui_diff tag remove sel 0.0 end
1993        }
1994lappend diff_actions [list $ctxm entryconf [$ctxm index last] -state]
1995$ctxm add separator
1996$ctxm add command \
1997        -label {Apply/Reverse Hunk} \
1998        -command {apply_hunk $cursorX $cursorY}
1999set ui_diff_applyhunk [$ctxm index last]
2000lappend diff_actions [list $ctxm entryconf $ui_diff_applyhunk -state]
2001$ctxm add separator
2002$ctxm add command \
2003        -label {Decrease Font Size} \
2004        -command {incr_font_size font_diff -1}
2005lappend diff_actions [list $ctxm entryconf [$ctxm index last] -state]
2006$ctxm add command \
2007        -label {Increase Font Size} \
2008        -command {incr_font_size font_diff 1}
2009lappend diff_actions [list $ctxm entryconf [$ctxm index last] -state]
2010$ctxm add separator
2011$ctxm add command \
2012        -label {Show Less Context} \
2013        -command {if {$repo_config(gui.diffcontext) >= 2} {
2014                incr repo_config(gui.diffcontext) -1
2015                reshow_diff
2016        }}
2017lappend diff_actions [list $ctxm entryconf [$ctxm index last] -state]
2018$ctxm add command \
2019        -label {Show More Context} \
2020        -command {
2021                incr repo_config(gui.diffcontext)
2022                reshow_diff
2023        }
2024lappend diff_actions [list $ctxm entryconf [$ctxm index last] -state]
2025$ctxm add separator
2026$ctxm add command -label {Options...} \
2027        -command do_options
2028bind_button3 $ui_diff "
2029        set cursorX %x
2030        set cursorY %y
2031        if {\$ui_index eq \$current_diff_side} {
2032                $ctxm entryconf $ui_diff_applyhunk -label {Unstage Hunk From Commit}
2033        } else {
2034                $ctxm entryconf $ui_diff_applyhunk -label {Stage Hunk For Commit}
2035        }
2036        tk_popup $ctxm %X %Y
2037"
2038unset ui_diff_applyhunk
2039
2040# -- Status Bar
2041#
2042label .status -textvariable ui_status_value \
2043        -anchor w \
2044        -justify left \
2045        -borderwidth 1 \
2046        -relief sunken
2047pack .status -anchor w -side bottom -fill x
2048
2049# -- Load geometry
2050#
2051catch {
2052set gm $repo_config(gui.geometry)
2053wm geometry . [lindex $gm 0]
2054.vpane sash place 0 \
2055        [lindex [.vpane sash coord 0] 0] \
2056        [lindex $gm 1]
2057.vpane.files sash place 0 \
2058        [lindex $gm 2] \
2059        [lindex [.vpane.files sash coord 0] 1]
2060unset gm
2061}
2062
2063# -- Key Bindings
2064#
2065bind $ui_comm <$M1B-Key-Return> {do_commit;break}
2066bind $ui_comm <$M1B-Key-i> {do_add_all;break}
2067bind $ui_comm <$M1B-Key-I> {do_add_all;break}
2068bind $ui_comm <$M1B-Key-x> {tk_textCut %W;break}
2069bind $ui_comm <$M1B-Key-X> {tk_textCut %W;break}
2070bind $ui_comm <$M1B-Key-c> {tk_textCopy %W;break}
2071bind $ui_comm <$M1B-Key-C> {tk_textCopy %W;break}
2072bind $ui_comm <$M1B-Key-v> {tk_textPaste %W; %W see insert; break}
2073bind $ui_comm <$M1B-Key-V> {tk_textPaste %W; %W see insert; break}
2074bind $ui_comm <$M1B-Key-a> {%W tag add sel 0.0 end;break}
2075bind $ui_comm <$M1B-Key-A> {%W tag add sel 0.0 end;break}
2076
2077bind $ui_diff <$M1B-Key-x> {tk_textCopy %W;break}
2078bind $ui_diff <$M1B-Key-X> {tk_textCopy %W;break}
2079bind $ui_diff <$M1B-Key-c> {tk_textCopy %W;break}
2080bind $ui_diff <$M1B-Key-C> {tk_textCopy %W;break}
2081bind $ui_diff <$M1B-Key-v> {break}
2082bind $ui_diff <$M1B-Key-V> {break}
2083bind $ui_diff <$M1B-Key-a> {%W tag add sel 0.0 end;break}
2084bind $ui_diff <$M1B-Key-A> {%W tag add sel 0.0 end;break}
2085bind $ui_diff <Key-Up>     {catch {%W yview scroll -1 units};break}
2086bind $ui_diff <Key-Down>   {catch {%W yview scroll  1 units};break}
2087bind $ui_diff <Key-Left>   {catch {%W xview scroll -1 units};break}
2088bind $ui_diff <Key-Right>  {catch {%W xview scroll  1 units};break}
2089bind $ui_diff <Key-k>         {catch {%W yview scroll -1 units};break}
2090bind $ui_diff <Key-j>         {catch {%W yview scroll  1 units};break}
2091bind $ui_diff <Key-h>         {catch {%W xview scroll -1 units};break}
2092bind $ui_diff <Key-l>         {catch {%W xview scroll  1 units};break}
2093bind $ui_diff <Control-Key-b> {catch {%W yview scroll -1 pages};break}
2094bind $ui_diff <Control-Key-f> {catch {%W yview scroll  1 pages};break}
2095bind $ui_diff <Button-1>   {focus %W}
2096
2097if {[is_enabled branch]} {
2098        bind . <$M1B-Key-n> do_create_branch
2099        bind . <$M1B-Key-N> do_create_branch
2100}
2101
2102bind all <Key-F5> do_rescan
2103bind all <$M1B-Key-r> do_rescan
2104bind all <$M1B-Key-R> do_rescan
2105bind .   <$M1B-Key-s> do_signoff
2106bind .   <$M1B-Key-S> do_signoff
2107bind .   <$M1B-Key-i> do_add_all
2108bind .   <$M1B-Key-I> do_add_all
2109bind .   <$M1B-Key-Return> do_commit
2110foreach i [list $ui_index $ui_workdir] {
2111        bind $i <Button-1>       "toggle_or_diff         $i %x %y; break"
2112        bind $i <$M1B-Button-1>  "add_one_to_selection   $i %x %y; break"
2113        bind $i <Shift-Button-1> "add_range_to_selection $i %x %y; break"
2114}
2115unset i
2116
2117set file_lists($ui_index) [list]
2118set file_lists($ui_workdir) [list]
2119
2120wm title . "[appname] ([reponame]) [file normalize [file dirname [gitdir]]]"
2121focus -force $ui_comm
2122
2123# -- Warn the user about environmental problems.  Cygwin's Tcl
2124#    does *not* pass its env array onto any processes it spawns.
2125#    This means that git processes get none of our environment.
2126#
2127if {[is_Cygwin]} {
2128        set ignored_env 0
2129        set suggest_user {}
2130        set msg "Possible environment issues exist.
2131
2132The following environment variables are probably
2133going to be ignored by any Git subprocess run
2134by [appname]:
2135
2136"
2137        foreach name [array names env] {
2138                switch -regexp -- $name {
2139                {^GIT_INDEX_FILE$} -
2140                {^GIT_OBJECT_DIRECTORY$} -
2141                {^GIT_ALTERNATE_OBJECT_DIRECTORIES$} -
2142                {^GIT_DIFF_OPTS$} -
2143                {^GIT_EXTERNAL_DIFF$} -
2144                {^GIT_PAGER$} -
2145                {^GIT_TRACE$} -
2146                {^GIT_CONFIG$} -
2147                {^GIT_CONFIG_LOCAL$} -
2148                {^GIT_(AUTHOR|COMMITTER)_DATE$} {
2149                        append msg " - $name\n"
2150                        incr ignored_env
2151                }
2152                {^GIT_(AUTHOR|COMMITTER)_(NAME|EMAIL)$} {
2153                        append msg " - $name\n"
2154                        incr ignored_env
2155                        set suggest_user $name
2156                }
2157                }
2158        }
2159        if {$ignored_env > 0} {
2160                append msg "
2161This is due to a known issue with the
2162Tcl binary distributed by Cygwin."
2163
2164                if {$suggest_user ne {}} {
2165                        append msg "
2166
2167A good replacement for $suggest_user
2168is placing values for the user.name and
2169user.email settings into your personal
2170~/.gitconfig file.
2171"
2172                }
2173                warn_popup $msg
2174        }
2175        unset ignored_env msg suggest_user name
2176}
2177
2178# -- Only initialize complex UI if we are going to stay running.
2179#
2180if {[is_enabled transport]} {
2181        load_all_remotes
2182        load_all_heads
2183
2184        populate_branch_menu
2185        populate_fetch_menu
2186        populate_push_menu
2187}
2188
2189# -- Only suggest a gc run if we are going to stay running.
2190#
2191if {[is_enabled multicommit]} {
2192        set object_limit 2000
2193        if {[is_Windows]} {set object_limit 200}
2194        regexp {^([0-9]+) objects,} [git count-objects] _junk objects_current
2195        if {$objects_current >= $object_limit} {
2196                if {[ask_popup \
2197                        "This repository currently has $objects_current loose objects.
2198
2199To maintain optimal performance it is strongly recommended that you compress the database when more than $object_limit loose objects exist.
2200
2201Compress the database now?"] eq yes} {
2202                        do_gc
2203                }
2204        }
2205        unset object_limit _junk objects_current
2206}
2207
2208lock_index begin-read
2209after 1 do_rescan