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