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