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