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