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