git-gui.shon commit git-gui: use "blame -w -C -C" for "where did it come from, originally?" (a840566)
   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.pruneduringfetch) false
1343set default_config(gui.trustmtime) false
1344set default_config(gui.diffcontext) 5
1345set default_config(gui.newbranchtemplate) {}
1346set default_config(gui.fontui) [font configure font_ui]
1347set default_config(gui.fontdiff) [font configure font_diff]
1348set font_descs {
1349        {fontui   font_ui   {Main Font}}
1350        {fontdiff font_diff {Diff/Console Font}}
1351}
1352load_config 0
1353apply_config
1354
1355######################################################################
1356##
1357## feature option selection
1358
1359if {[regexp {^git-(.+)$} [appname] _junk subcommand]} {
1360        unset _junk
1361} else {
1362        set subcommand gui
1363}
1364if {$subcommand eq {gui.sh}} {
1365        set subcommand gui
1366}
1367if {$subcommand eq {gui} && [llength $argv] > 0} {
1368        set subcommand [lindex $argv 0]
1369        set argv [lrange $argv 1 end]
1370}
1371
1372enable_option multicommit
1373enable_option branch
1374enable_option transport
1375
1376switch -- $subcommand {
1377browser -
1378blame {
1379        disable_option multicommit
1380        disable_option branch
1381        disable_option transport
1382}
1383citool {
1384        enable_option singlecommit
1385
1386        disable_option multicommit
1387        disable_option branch
1388        disable_option transport
1389}
1390}
1391
1392######################################################################
1393##
1394## ui construction
1395
1396set ui_comm {}
1397
1398# -- Menu Bar
1399#
1400menu .mbar -tearoff 0
1401.mbar add cascade -label Repository -menu .mbar.repository
1402.mbar add cascade -label Edit -menu .mbar.edit
1403if {[is_enabled branch]} {
1404        .mbar add cascade -label Branch -menu .mbar.branch
1405}
1406if {[is_enabled multicommit] || [is_enabled singlecommit]} {
1407        .mbar add cascade -label Commit -menu .mbar.commit
1408}
1409if {[is_enabled transport]} {
1410        .mbar add cascade -label Merge -menu .mbar.merge
1411        .mbar add cascade -label Fetch -menu .mbar.fetch
1412        .mbar add cascade -label Push -menu .mbar.push
1413}
1414. configure -menu .mbar
1415
1416# -- Repository Menu
1417#
1418menu .mbar.repository
1419
1420.mbar.repository add command \
1421        -label {Browse Current Branch} \
1422        -command {browser::new $current_branch}
1423trace add variable current_branch write ".mbar.repository entryconf [.mbar.repository index last] -label \"Browse \$current_branch\" ;#"
1424.mbar.repository add separator
1425
1426.mbar.repository add command \
1427        -label {Visualize Current Branch} \
1428        -command {do_gitk $current_branch}
1429trace add variable current_branch write ".mbar.repository entryconf [.mbar.repository index last] -label \"Visualize \$current_branch\" ;#"
1430.mbar.repository add command \
1431        -label {Visualize All Branches} \
1432        -command {do_gitk --all}
1433.mbar.repository add separator
1434
1435if {[is_enabled multicommit]} {
1436        .mbar.repository add command -label {Database Statistics} \
1437                -command do_stats
1438
1439        .mbar.repository add command -label {Compress Database} \
1440                -command do_gc
1441
1442        .mbar.repository add command -label {Verify Database} \
1443                -command do_fsck_objects
1444
1445        .mbar.repository add separator
1446
1447        if {[is_Cygwin]} {
1448                .mbar.repository add command \
1449                        -label {Create Desktop Icon} \
1450                        -command do_cygwin_shortcut
1451        } elseif {[is_Windows]} {
1452                .mbar.repository add command \
1453                        -label {Create Desktop Icon} \
1454                        -command do_windows_shortcut
1455        } elseif {[is_MacOSX]} {
1456                .mbar.repository add command \
1457                        -label {Create Desktop Icon} \
1458                        -command do_macosx_app
1459        }
1460}
1461
1462.mbar.repository add command -label Quit \
1463        -command do_quit \
1464        -accelerator $M1T-Q
1465
1466# -- Edit Menu
1467#
1468menu .mbar.edit
1469.mbar.edit add command -label Undo \
1470        -command {catch {[focus] edit undo}} \
1471        -accelerator $M1T-Z
1472.mbar.edit add command -label Redo \
1473        -command {catch {[focus] edit redo}} \
1474        -accelerator $M1T-Y
1475.mbar.edit add separator
1476.mbar.edit add command -label Cut \
1477        -command {catch {tk_textCut [focus]}} \
1478        -accelerator $M1T-X
1479.mbar.edit add command -label Copy \
1480        -command {catch {tk_textCopy [focus]}} \
1481        -accelerator $M1T-C
1482.mbar.edit add command -label Paste \
1483        -command {catch {tk_textPaste [focus]; [focus] see insert}} \
1484        -accelerator $M1T-V
1485.mbar.edit add command -label Delete \
1486        -command {catch {[focus] delete sel.first sel.last}} \
1487        -accelerator Del
1488.mbar.edit add separator
1489.mbar.edit add command -label {Select All} \
1490        -command {catch {[focus] tag add sel 0.0 end}} \
1491        -accelerator $M1T-A
1492
1493# -- Branch Menu
1494#
1495if {[is_enabled branch]} {
1496        menu .mbar.branch
1497
1498        .mbar.branch add command -label {Create...} \
1499                -command do_create_branch \
1500                -accelerator $M1T-N
1501        lappend disable_on_lock [list .mbar.branch entryconf \
1502                [.mbar.branch index last] -state]
1503
1504        .mbar.branch add command -label {Rename...} \
1505                -command branch_rename::dialog
1506        lappend disable_on_lock [list .mbar.branch entryconf \
1507                [.mbar.branch index last] -state]
1508
1509        .mbar.branch add command -label {Delete...} \
1510                -command do_delete_branch
1511        lappend disable_on_lock [list .mbar.branch entryconf \
1512                [.mbar.branch index last] -state]
1513
1514        .mbar.branch add command -label {Reset...} \
1515                -command merge::reset_hard
1516        lappend disable_on_lock [list .mbar.branch entryconf \
1517                [.mbar.branch index last] -state]
1518}
1519
1520# -- Commit Menu
1521#
1522if {[is_enabled multicommit] || [is_enabled singlecommit]} {
1523        menu .mbar.commit
1524
1525        .mbar.commit add radiobutton \
1526                -label {New Commit} \
1527                -command do_select_commit_type \
1528                -variable selected_commit_type \
1529                -value new
1530        lappend disable_on_lock \
1531                [list .mbar.commit entryconf [.mbar.commit index last] -state]
1532
1533        .mbar.commit add radiobutton \
1534                -label {Amend Last Commit} \
1535                -command do_select_commit_type \
1536                -variable selected_commit_type \
1537                -value amend
1538        lappend disable_on_lock \
1539                [list .mbar.commit entryconf [.mbar.commit index last] -state]
1540
1541        .mbar.commit add separator
1542
1543        .mbar.commit add command -label Rescan \
1544                -command do_rescan \
1545                -accelerator F5
1546        lappend disable_on_lock \
1547                [list .mbar.commit entryconf [.mbar.commit index last] -state]
1548
1549        .mbar.commit add command -label {Add To Commit} \
1550                -command do_add_selection
1551        lappend disable_on_lock \
1552                [list .mbar.commit entryconf [.mbar.commit index last] -state]
1553
1554        .mbar.commit add command -label {Add Existing To Commit} \
1555                -command do_add_all \
1556                -accelerator $M1T-I
1557        lappend disable_on_lock \
1558                [list .mbar.commit entryconf [.mbar.commit index last] -state]
1559
1560        .mbar.commit add command -label {Unstage From Commit} \
1561                -command do_unstage_selection
1562        lappend disable_on_lock \
1563                [list .mbar.commit entryconf [.mbar.commit index last] -state]
1564
1565        .mbar.commit add command -label {Revert Changes} \
1566                -command do_revert_selection
1567        lappend disable_on_lock \
1568                [list .mbar.commit entryconf [.mbar.commit index last] -state]
1569
1570        .mbar.commit add separator
1571
1572        .mbar.commit add command -label {Sign Off} \
1573                -command do_signoff \
1574                -accelerator $M1T-S
1575
1576        .mbar.commit add command -label Commit \
1577                -command do_commit \
1578                -accelerator $M1T-Return
1579        lappend disable_on_lock \
1580                [list .mbar.commit entryconf [.mbar.commit index last] -state]
1581}
1582
1583# -- Merge Menu
1584#
1585if {[is_enabled branch]} {
1586        menu .mbar.merge
1587        .mbar.merge add command -label {Local Merge...} \
1588                -command merge::dialog
1589        lappend disable_on_lock \
1590                [list .mbar.merge entryconf [.mbar.merge index last] -state]
1591        .mbar.merge add command -label {Abort Merge...} \
1592                -command merge::reset_hard
1593        lappend disable_on_lock \
1594                [list .mbar.merge entryconf [.mbar.merge index last] -state]
1595
1596}
1597
1598# -- Transport Menu
1599#
1600if {[is_enabled transport]} {
1601        menu .mbar.fetch
1602
1603        menu .mbar.push
1604        .mbar.push add command -label {Push...} \
1605                -command do_push_anywhere \
1606                -accelerator $M1T-P
1607        .mbar.push add command -label {Delete...} \
1608                -command remote_branch_delete::dialog
1609}
1610
1611if {[is_MacOSX]} {
1612        # -- Apple Menu (Mac OS X only)
1613        #
1614        .mbar add cascade -label Apple -menu .mbar.apple
1615        menu .mbar.apple
1616
1617        .mbar.apple add command -label "About [appname]" \
1618                -command do_about
1619        .mbar.apple add command -label "Options..." \
1620                -command do_options
1621} else {
1622        # -- Edit Menu
1623        #
1624        .mbar.edit add separator
1625        .mbar.edit add command -label {Options...} \
1626                -command do_options
1627
1628        # -- Tools Menu
1629        #
1630        if {[is_Cygwin] && [file exists /usr/local/miga/lib/gui-miga]} {
1631        proc do_miga {} {
1632                global ui_status_value
1633                if {![lock_index update]} return
1634                set cmd [list sh --login -c "/usr/local/miga/lib/gui-miga \"[pwd]\""]
1635                set miga_fd [open "|$cmd" r]
1636                fconfigure $miga_fd -blocking 0
1637                fileevent $miga_fd readable [list miga_done $miga_fd]
1638                set ui_status_value {Running miga...}
1639        }
1640        proc miga_done {fd} {
1641                read $fd 512
1642                if {[eof $fd]} {
1643                        close $fd
1644                        unlock_index
1645                        rescan [list set ui_status_value {Ready.}]
1646                }
1647        }
1648        .mbar add cascade -label Tools -menu .mbar.tools
1649        menu .mbar.tools
1650        .mbar.tools add command -label "Migrate" \
1651                -command do_miga
1652        lappend disable_on_lock \
1653                [list .mbar.tools entryconf [.mbar.tools index last] -state]
1654        }
1655}
1656
1657# -- Help Menu
1658#
1659.mbar add cascade -label Help -menu .mbar.help
1660menu .mbar.help
1661
1662if {![is_MacOSX]} {
1663        .mbar.help add command -label "About [appname]" \
1664                -command do_about
1665}
1666
1667set browser {}
1668catch {set browser $repo_config(instaweb.browser)}
1669set doc_path [file dirname [gitexec]]
1670set doc_path [file join $doc_path Documentation index.html]
1671
1672if {[is_Cygwin]} {
1673        set doc_path [exec cygpath --mixed $doc_path]
1674}
1675
1676if {$browser eq {}} {
1677        if {[is_MacOSX]} {
1678                set browser open
1679        } elseif {[is_Cygwin]} {
1680                set program_files [file dirname [exec cygpath --windir]]
1681                set program_files [file join $program_files {Program Files}]
1682                set firefox [file join $program_files {Mozilla Firefox} firefox.exe]
1683                set ie [file join $program_files {Internet Explorer} IEXPLORE.EXE]
1684                if {[file exists $firefox]} {
1685                        set browser $firefox
1686                } elseif {[file exists $ie]} {
1687                        set browser $ie
1688                }
1689                unset program_files firefox ie
1690        }
1691}
1692
1693if {[file isfile $doc_path]} {
1694        set doc_url "file:$doc_path"
1695} else {
1696        set doc_url {http://www.kernel.org/pub/software/scm/git/docs/}
1697}
1698
1699if {$browser ne {}} {
1700        .mbar.help add command -label {Online Documentation} \
1701                -command [list exec $browser $doc_url &]
1702}
1703unset browser doc_path doc_url
1704
1705# -- Standard bindings
1706#
1707wm protocol . WM_DELETE_WINDOW do_quit
1708bind all <$M1B-Key-q> do_quit
1709bind all <$M1B-Key-Q> do_quit
1710bind all <$M1B-Key-w> {destroy [winfo toplevel %W]}
1711bind all <$M1B-Key-W> {destroy [winfo toplevel %W]}
1712
1713set subcommand_args {}
1714proc usage {} {
1715        puts stderr "usage: $::argv0 $::subcommand $::subcommand_args"
1716        exit 1
1717}
1718
1719# -- Not a normal commit type invocation?  Do that instead!
1720#
1721switch -- $subcommand {
1722browser {
1723        set subcommand_args {rev?}
1724        switch [llength $argv] {
1725        0 { set current_branch [current-branch] }
1726        1 { set current_branch [lindex $argv 0] }
1727        default usage
1728        }
1729        browser::new $current_branch
1730        return
1731}
1732blame {
1733        set subcommand_args {rev? path?}
1734        set head {}
1735        set path {}
1736        set is_path 0
1737        foreach a $argv {
1738                if {$is_path || [file exists $_prefix$a]} {
1739                        if {$path ne {}} usage
1740                        set path $_prefix$a
1741                        break
1742                } elseif {$a eq {--}} {
1743                        if {$path ne {}} {
1744                                if {$head ne {}} usage
1745                                set head $path
1746                                set path {}
1747                        }
1748                        set is_path 1
1749                } elseif {$head eq {}} {
1750                        if {$head ne {}} usage
1751                        set head $a
1752                } else {
1753                        usage
1754                }
1755        }
1756        unset is_path
1757
1758        if {$head eq {}} {
1759                set current_branch [current-branch]
1760        } else {
1761                set current_branch $head
1762        }
1763
1764        if {$path eq {}} usage
1765        blame::new $head $path
1766        return
1767}
1768citool -
1769gui {
1770        if {[llength $argv] != 0} {
1771                puts -nonewline stderr "usage: $argv0"
1772                if {$subcommand ne {gui} && [appname] ne "git-$subcommand"} {
1773                        puts -nonewline stderr " $subcommand"
1774                }
1775                puts stderr {}
1776                exit 1
1777        }
1778        # fall through to setup UI for commits
1779}
1780default {
1781        puts stderr "usage: $argv0 \[{blame|browser|citool}\]"
1782        exit 1
1783}
1784}
1785
1786# -- Branch Control
1787#
1788frame .branch \
1789        -borderwidth 1 \
1790        -relief sunken
1791label .branch.l1 \
1792        -text {Current Branch:} \
1793        -anchor w \
1794        -justify left
1795label .branch.cb \
1796        -textvariable current_branch \
1797        -anchor w \
1798        -justify left
1799pack .branch.l1 -side left
1800pack .branch.cb -side left -fill x
1801pack .branch -side top -fill x
1802
1803# -- Main Window Layout
1804#
1805panedwindow .vpane -orient vertical
1806panedwindow .vpane.files -orient horizontal
1807.vpane add .vpane.files -sticky nsew -height 100 -width 200
1808pack .vpane -anchor n -side top -fill both -expand 1
1809
1810# -- Index File List
1811#
1812frame .vpane.files.index -height 100 -width 200
1813label .vpane.files.index.title -text {Staged Changes (Will Be Committed)} \
1814        -background lightgreen
1815text $ui_index -background white -borderwidth 0 \
1816        -width 20 -height 10 \
1817        -wrap none \
1818        -cursor $cursor_ptr \
1819        -xscrollcommand {.vpane.files.index.sx set} \
1820        -yscrollcommand {.vpane.files.index.sy set} \
1821        -state disabled
1822scrollbar .vpane.files.index.sx -orient h -command [list $ui_index xview]
1823scrollbar .vpane.files.index.sy -orient v -command [list $ui_index yview]
1824pack .vpane.files.index.title -side top -fill x
1825pack .vpane.files.index.sx -side bottom -fill x
1826pack .vpane.files.index.sy -side right -fill y
1827pack $ui_index -side left -fill both -expand 1
1828.vpane.files add .vpane.files.index -sticky nsew
1829
1830# -- Working Directory File List
1831#
1832frame .vpane.files.workdir -height 100 -width 200
1833label .vpane.files.workdir.title -text {Unstaged Changes (Will Not Be Committed)} \
1834        -background lightsalmon
1835text $ui_workdir -background white -borderwidth 0 \
1836        -width 20 -height 10 \
1837        -wrap none \
1838        -cursor $cursor_ptr \
1839        -xscrollcommand {.vpane.files.workdir.sx set} \
1840        -yscrollcommand {.vpane.files.workdir.sy set} \
1841        -state disabled
1842scrollbar .vpane.files.workdir.sx -orient h -command [list $ui_workdir xview]
1843scrollbar .vpane.files.workdir.sy -orient v -command [list $ui_workdir yview]
1844pack .vpane.files.workdir.title -side top -fill x
1845pack .vpane.files.workdir.sx -side bottom -fill x
1846pack .vpane.files.workdir.sy -side right -fill y
1847pack $ui_workdir -side left -fill both -expand 1
1848.vpane.files add .vpane.files.workdir -sticky nsew
1849
1850foreach i [list $ui_index $ui_workdir] {
1851        $i tag conf in_diff -background lightgray
1852        $i tag conf in_sel  -background lightgray
1853}
1854unset i
1855
1856# -- Diff and Commit Area
1857#
1858frame .vpane.lower -height 300 -width 400
1859frame .vpane.lower.commarea
1860frame .vpane.lower.diff -relief sunken -borderwidth 1
1861pack .vpane.lower.commarea -side top -fill x
1862pack .vpane.lower.diff -side bottom -fill both -expand 1
1863.vpane add .vpane.lower -sticky nsew
1864
1865# -- Commit Area Buttons
1866#
1867frame .vpane.lower.commarea.buttons
1868label .vpane.lower.commarea.buttons.l -text {} \
1869        -anchor w \
1870        -justify left
1871pack .vpane.lower.commarea.buttons.l -side top -fill x
1872pack .vpane.lower.commarea.buttons -side left -fill y
1873
1874button .vpane.lower.commarea.buttons.rescan -text {Rescan} \
1875        -command do_rescan
1876pack .vpane.lower.commarea.buttons.rescan -side top -fill x
1877lappend disable_on_lock \
1878        {.vpane.lower.commarea.buttons.rescan conf -state}
1879
1880button .vpane.lower.commarea.buttons.incall -text {Add Existing} \
1881        -command do_add_all
1882pack .vpane.lower.commarea.buttons.incall -side top -fill x
1883lappend disable_on_lock \
1884        {.vpane.lower.commarea.buttons.incall conf -state}
1885
1886button .vpane.lower.commarea.buttons.signoff -text {Sign Off} \
1887        -command do_signoff
1888pack .vpane.lower.commarea.buttons.signoff -side top -fill x
1889
1890button .vpane.lower.commarea.buttons.commit -text {Commit} \
1891        -command do_commit
1892pack .vpane.lower.commarea.buttons.commit -side top -fill x
1893lappend disable_on_lock \
1894        {.vpane.lower.commarea.buttons.commit conf -state}
1895
1896button .vpane.lower.commarea.buttons.push -text {Push} \
1897        -command do_push_anywhere
1898pack .vpane.lower.commarea.buttons.push -side top -fill x
1899
1900# -- Commit Message Buffer
1901#
1902frame .vpane.lower.commarea.buffer
1903frame .vpane.lower.commarea.buffer.header
1904set ui_comm .vpane.lower.commarea.buffer.t
1905set ui_coml .vpane.lower.commarea.buffer.header.l
1906radiobutton .vpane.lower.commarea.buffer.header.new \
1907        -text {New Commit} \
1908        -command do_select_commit_type \
1909        -variable selected_commit_type \
1910        -value new
1911lappend disable_on_lock \
1912        [list .vpane.lower.commarea.buffer.header.new conf -state]
1913radiobutton .vpane.lower.commarea.buffer.header.amend \
1914        -text {Amend Last Commit} \
1915        -command do_select_commit_type \
1916        -variable selected_commit_type \
1917        -value amend
1918lappend disable_on_lock \
1919        [list .vpane.lower.commarea.buffer.header.amend conf -state]
1920label $ui_coml \
1921        -anchor w \
1922        -justify left
1923proc trace_commit_type {varname args} {
1924        global ui_coml commit_type
1925        switch -glob -- $commit_type {
1926        initial       {set txt {Initial Commit Message:}}
1927        amend         {set txt {Amended Commit Message:}}
1928        amend-initial {set txt {Amended Initial Commit Message:}}
1929        amend-merge   {set txt {Amended Merge Commit Message:}}
1930        merge         {set txt {Merge Commit Message:}}
1931        *             {set txt {Commit Message:}}
1932        }
1933        $ui_coml conf -text $txt
1934}
1935trace add variable commit_type write trace_commit_type
1936pack $ui_coml -side left -fill x
1937pack .vpane.lower.commarea.buffer.header.amend -side right
1938pack .vpane.lower.commarea.buffer.header.new -side right
1939
1940text $ui_comm -background white -borderwidth 1 \
1941        -undo true \
1942        -maxundo 20 \
1943        -autoseparators true \
1944        -relief sunken \
1945        -width 75 -height 9 -wrap none \
1946        -font font_diff \
1947        -yscrollcommand {.vpane.lower.commarea.buffer.sby set}
1948scrollbar .vpane.lower.commarea.buffer.sby \
1949        -command [list $ui_comm yview]
1950pack .vpane.lower.commarea.buffer.header -side top -fill x
1951pack .vpane.lower.commarea.buffer.sby -side right -fill y
1952pack $ui_comm -side left -fill y
1953pack .vpane.lower.commarea.buffer -side left -fill y
1954
1955# -- Commit Message Buffer Context Menu
1956#
1957set ctxm .vpane.lower.commarea.buffer.ctxm
1958menu $ctxm -tearoff 0
1959$ctxm add command \
1960        -label {Cut} \
1961        -command {tk_textCut $ui_comm}
1962$ctxm add command \
1963        -label {Copy} \
1964        -command {tk_textCopy $ui_comm}
1965$ctxm add command \
1966        -label {Paste} \
1967        -command {tk_textPaste $ui_comm}
1968$ctxm add command \
1969        -label {Delete} \
1970        -command {$ui_comm delete sel.first sel.last}
1971$ctxm add separator
1972$ctxm add command \
1973        -label {Select All} \
1974        -command {focus $ui_comm;$ui_comm tag add sel 0.0 end}
1975$ctxm add command \
1976        -label {Copy All} \
1977        -command {
1978                $ui_comm tag add sel 0.0 end
1979                tk_textCopy $ui_comm
1980                $ui_comm tag remove sel 0.0 end
1981        }
1982$ctxm add separator
1983$ctxm add command \
1984        -label {Sign Off} \
1985        -command do_signoff
1986bind_button3 $ui_comm "tk_popup $ctxm %X %Y"
1987
1988# -- Diff Header
1989#
1990proc trace_current_diff_path {varname args} {
1991        global current_diff_path diff_actions file_states
1992        if {$current_diff_path eq {}} {
1993                set s {}
1994                set f {}
1995                set p {}
1996                set o disabled
1997        } else {
1998                set p $current_diff_path
1999                set s [mapdesc [lindex $file_states($p) 0] $p]
2000                set f {File:}
2001                set p [escape_path $p]
2002                set o normal
2003        }
2004
2005        .vpane.lower.diff.header.status configure -text $s
2006        .vpane.lower.diff.header.file configure -text $f
2007        .vpane.lower.diff.header.path configure -text $p
2008        foreach w $diff_actions {
2009                uplevel #0 $w $o
2010        }
2011}
2012trace add variable current_diff_path write trace_current_diff_path
2013
2014frame .vpane.lower.diff.header -background gold
2015label .vpane.lower.diff.header.status \
2016        -background gold \
2017        -width $max_status_desc \
2018        -anchor w \
2019        -justify left
2020label .vpane.lower.diff.header.file \
2021        -background gold \
2022        -anchor w \
2023        -justify left
2024label .vpane.lower.diff.header.path \
2025        -background gold \
2026        -anchor w \
2027        -justify left
2028pack .vpane.lower.diff.header.status -side left
2029pack .vpane.lower.diff.header.file -side left
2030pack .vpane.lower.diff.header.path -fill x
2031set ctxm .vpane.lower.diff.header.ctxm
2032menu $ctxm -tearoff 0
2033$ctxm add command \
2034        -label {Copy} \
2035        -command {
2036                clipboard clear
2037                clipboard append \
2038                        -format STRING \
2039                        -type STRING \
2040                        -- $current_diff_path
2041        }
2042lappend diff_actions [list $ctxm entryconf [$ctxm index last] -state]
2043bind_button3 .vpane.lower.diff.header.path "tk_popup $ctxm %X %Y"
2044
2045# -- Diff Body
2046#
2047frame .vpane.lower.diff.body
2048set ui_diff .vpane.lower.diff.body.t
2049text $ui_diff -background white -borderwidth 0 \
2050        -width 80 -height 15 -wrap none \
2051        -font font_diff \
2052        -xscrollcommand {.vpane.lower.diff.body.sbx set} \
2053        -yscrollcommand {.vpane.lower.diff.body.sby set} \
2054        -state disabled
2055scrollbar .vpane.lower.diff.body.sbx -orient horizontal \
2056        -command [list $ui_diff xview]
2057scrollbar .vpane.lower.diff.body.sby -orient vertical \
2058        -command [list $ui_diff yview]
2059pack .vpane.lower.diff.body.sbx -side bottom -fill x
2060pack .vpane.lower.diff.body.sby -side right -fill y
2061pack $ui_diff -side left -fill both -expand 1
2062pack .vpane.lower.diff.header -side top -fill x
2063pack .vpane.lower.diff.body -side bottom -fill both -expand 1
2064
2065$ui_diff tag conf d_cr -elide true
2066$ui_diff tag conf d_@ -foreground blue -font font_diffbold
2067$ui_diff tag conf d_+ -foreground {#00a000}
2068$ui_diff tag conf d_- -foreground red
2069
2070$ui_diff tag conf d_++ -foreground {#00a000}
2071$ui_diff tag conf d_-- -foreground red
2072$ui_diff tag conf d_+s \
2073        -foreground {#00a000} \
2074        -background {#e2effa}
2075$ui_diff tag conf d_-s \
2076        -foreground red \
2077        -background {#e2effa}
2078$ui_diff tag conf d_s+ \
2079        -foreground {#00a000} \
2080        -background ivory1
2081$ui_diff tag conf d_s- \
2082        -foreground red \
2083        -background ivory1
2084
2085$ui_diff tag conf d<<<<<<< \
2086        -foreground orange \
2087        -font font_diffbold
2088$ui_diff tag conf d======= \
2089        -foreground orange \
2090        -font font_diffbold
2091$ui_diff tag conf d>>>>>>> \
2092        -foreground orange \
2093        -font font_diffbold
2094
2095$ui_diff tag raise sel
2096
2097# -- Diff Body Context Menu
2098#
2099set ctxm .vpane.lower.diff.body.ctxm
2100menu $ctxm -tearoff 0
2101$ctxm add command \
2102        -label {Refresh} \
2103        -command reshow_diff
2104lappend diff_actions [list $ctxm entryconf [$ctxm index last] -state]
2105$ctxm add command \
2106        -label {Copy} \
2107        -command {tk_textCopy $ui_diff}
2108lappend diff_actions [list $ctxm entryconf [$ctxm index last] -state]
2109$ctxm add command \
2110        -label {Select All} \
2111        -command {focus $ui_diff;$ui_diff tag add sel 0.0 end}
2112lappend diff_actions [list $ctxm entryconf [$ctxm index last] -state]
2113$ctxm add command \
2114        -label {Copy All} \
2115        -command {
2116                $ui_diff tag add sel 0.0 end
2117                tk_textCopy $ui_diff
2118                $ui_diff tag remove sel 0.0 end
2119        }
2120lappend diff_actions [list $ctxm entryconf [$ctxm index last] -state]
2121$ctxm add separator
2122$ctxm add command \
2123        -label {Apply/Reverse Hunk} \
2124        -command {apply_hunk $cursorX $cursorY}
2125set ui_diff_applyhunk [$ctxm index last]
2126lappend diff_actions [list $ctxm entryconf $ui_diff_applyhunk -state]
2127$ctxm add separator
2128$ctxm add command \
2129        -label {Decrease Font Size} \
2130        -command {incr_font_size font_diff -1}
2131lappend diff_actions [list $ctxm entryconf [$ctxm index last] -state]
2132$ctxm add command \
2133        -label {Increase Font Size} \
2134        -command {incr_font_size font_diff 1}
2135lappend diff_actions [list $ctxm entryconf [$ctxm index last] -state]
2136$ctxm add separator
2137$ctxm add command \
2138        -label {Show Less Context} \
2139        -command {if {$repo_config(gui.diffcontext) >= 1} {
2140                incr repo_config(gui.diffcontext) -1
2141                reshow_diff
2142        }}
2143lappend diff_actions [list $ctxm entryconf [$ctxm index last] -state]
2144$ctxm add command \
2145        -label {Show More Context} \
2146        -command {if {$repo_config(gui.diffcontext) < 99} {
2147                incr repo_config(gui.diffcontext)
2148                reshow_diff
2149        }}
2150lappend diff_actions [list $ctxm entryconf [$ctxm index last] -state]
2151$ctxm add separator
2152$ctxm add command -label {Options...} \
2153        -command do_options
2154bind_button3 $ui_diff "
2155        set cursorX %x
2156        set cursorY %y
2157        if {\$ui_index eq \$current_diff_side} {
2158                $ctxm entryconf $ui_diff_applyhunk -label {Unstage Hunk From Commit}
2159        } else {
2160                $ctxm entryconf $ui_diff_applyhunk -label {Stage Hunk For Commit}
2161        }
2162        tk_popup $ctxm %X %Y
2163"
2164unset ui_diff_applyhunk
2165
2166# -- Status Bar
2167#
2168label .status -textvariable ui_status_value \
2169        -anchor w \
2170        -justify left \
2171        -borderwidth 1 \
2172        -relief sunken
2173pack .status -anchor w -side bottom -fill x
2174
2175# -- Load geometry
2176#
2177catch {
2178set gm $repo_config(gui.geometry)
2179wm geometry . [lindex $gm 0]
2180.vpane sash place 0 \
2181        [lindex [.vpane sash coord 0] 0] \
2182        [lindex $gm 1]
2183.vpane.files sash place 0 \
2184        [lindex $gm 2] \
2185        [lindex [.vpane.files sash coord 0] 1]
2186unset gm
2187}
2188
2189# -- Key Bindings
2190#
2191bind $ui_comm <$M1B-Key-Return> {do_commit;break}
2192bind $ui_comm <$M1B-Key-i> {do_add_all;break}
2193bind $ui_comm <$M1B-Key-I> {do_add_all;break}
2194bind $ui_comm <$M1B-Key-x> {tk_textCut %W;break}
2195bind $ui_comm <$M1B-Key-X> {tk_textCut %W;break}
2196bind $ui_comm <$M1B-Key-c> {tk_textCopy %W;break}
2197bind $ui_comm <$M1B-Key-C> {tk_textCopy %W;break}
2198bind $ui_comm <$M1B-Key-v> {tk_textPaste %W; %W see insert; break}
2199bind $ui_comm <$M1B-Key-V> {tk_textPaste %W; %W see insert; break}
2200bind $ui_comm <$M1B-Key-a> {%W tag add sel 0.0 end;break}
2201bind $ui_comm <$M1B-Key-A> {%W tag add sel 0.0 end;break}
2202
2203bind $ui_diff <$M1B-Key-x> {tk_textCopy %W;break}
2204bind $ui_diff <$M1B-Key-X> {tk_textCopy %W;break}
2205bind $ui_diff <$M1B-Key-c> {tk_textCopy %W;break}
2206bind $ui_diff <$M1B-Key-C> {tk_textCopy %W;break}
2207bind $ui_diff <$M1B-Key-v> {break}
2208bind $ui_diff <$M1B-Key-V> {break}
2209bind $ui_diff <$M1B-Key-a> {%W tag add sel 0.0 end;break}
2210bind $ui_diff <$M1B-Key-A> {%W tag add sel 0.0 end;break}
2211bind $ui_diff <Key-Up>     {catch {%W yview scroll -1 units};break}
2212bind $ui_diff <Key-Down>   {catch {%W yview scroll  1 units};break}
2213bind $ui_diff <Key-Left>   {catch {%W xview scroll -1 units};break}
2214bind $ui_diff <Key-Right>  {catch {%W xview scroll  1 units};break}
2215bind $ui_diff <Key-k>         {catch {%W yview scroll -1 units};break}
2216bind $ui_diff <Key-j>         {catch {%W yview scroll  1 units};break}
2217bind $ui_diff <Key-h>         {catch {%W xview scroll -1 units};break}
2218bind $ui_diff <Key-l>         {catch {%W xview scroll  1 units};break}
2219bind $ui_diff <Control-Key-b> {catch {%W yview scroll -1 pages};break}
2220bind $ui_diff <Control-Key-f> {catch {%W yview scroll  1 pages};break}
2221bind $ui_diff <Button-1>   {focus %W}
2222
2223if {[is_enabled branch]} {
2224        bind . <$M1B-Key-n> do_create_branch
2225        bind . <$M1B-Key-N> do_create_branch
2226}
2227if {[is_enabled transport]} {
2228        bind . <$M1B-Key-p> do_push_anywhere
2229        bind . <$M1B-Key-P> do_push_anywhere
2230}
2231
2232bind .   <Key-F5>     do_rescan
2233bind .   <$M1B-Key-r> do_rescan
2234bind .   <$M1B-Key-R> do_rescan
2235bind .   <$M1B-Key-s> do_signoff
2236bind .   <$M1B-Key-S> do_signoff
2237bind .   <$M1B-Key-i> do_add_all
2238bind .   <$M1B-Key-I> do_add_all
2239bind .   <$M1B-Key-Return> do_commit
2240foreach i [list $ui_index $ui_workdir] {
2241        bind $i <Button-1>       "toggle_or_diff         $i %x %y; break"
2242        bind $i <$M1B-Button-1>  "add_one_to_selection   $i %x %y; break"
2243        bind $i <Shift-Button-1> "add_range_to_selection $i %x %y; break"
2244}
2245unset i
2246
2247set file_lists($ui_index) [list]
2248set file_lists($ui_workdir) [list]
2249
2250wm title . "[appname] ([reponame]) [file normalize [file dirname [gitdir]]]"
2251focus -force $ui_comm
2252
2253# -- Warn the user about environmental problems.  Cygwin's Tcl
2254#    does *not* pass its env array onto any processes it spawns.
2255#    This means that git processes get none of our environment.
2256#
2257if {[is_Cygwin]} {
2258        set ignored_env 0
2259        set suggest_user {}
2260        set msg "Possible environment issues exist.
2261
2262The following environment variables are probably
2263going to be ignored by any Git subprocess run
2264by [appname]:
2265
2266"
2267        foreach name [array names env] {
2268                switch -regexp -- $name {
2269                {^GIT_INDEX_FILE$} -
2270                {^GIT_OBJECT_DIRECTORY$} -
2271                {^GIT_ALTERNATE_OBJECT_DIRECTORIES$} -
2272                {^GIT_DIFF_OPTS$} -
2273                {^GIT_EXTERNAL_DIFF$} -
2274                {^GIT_PAGER$} -
2275                {^GIT_TRACE$} -
2276                {^GIT_CONFIG$} -
2277                {^GIT_CONFIG_LOCAL$} -
2278                {^GIT_(AUTHOR|COMMITTER)_DATE$} {
2279                        append msg " - $name\n"
2280                        incr ignored_env
2281                }
2282                {^GIT_(AUTHOR|COMMITTER)_(NAME|EMAIL)$} {
2283                        append msg " - $name\n"
2284                        incr ignored_env
2285                        set suggest_user $name
2286                }
2287                }
2288        }
2289        if {$ignored_env > 0} {
2290                append msg "
2291This is due to a known issue with the
2292Tcl binary distributed by Cygwin."
2293
2294                if {$suggest_user ne {}} {
2295                        append msg "
2296
2297A good replacement for $suggest_user
2298is placing values for the user.name and
2299user.email settings into your personal
2300~/.gitconfig file.
2301"
2302                }
2303                warn_popup $msg
2304        }
2305        unset ignored_env msg suggest_user name
2306}
2307
2308# -- Only initialize complex UI if we are going to stay running.
2309#
2310if {[is_enabled transport]} {
2311        load_all_remotes
2312        load_all_heads
2313
2314        populate_branch_menu
2315        populate_fetch_menu
2316        populate_push_menu
2317}
2318
2319# -- Only suggest a gc run if we are going to stay running.
2320#
2321if {[is_enabled multicommit]} {
2322        set object_limit 2000
2323        if {[is_Windows]} {set object_limit 200}
2324        regexp {^([0-9]+) objects,} [git count-objects] _junk objects_current
2325        if {$objects_current >= $object_limit} {
2326                if {[ask_popup \
2327                        "This repository currently has $objects_current loose objects.
2328
2329To maintain optimal performance it is strongly recommended that you compress the database when more than $object_limit loose objects exist.
2330
2331Compress the database now?"] eq yes} {
2332                        do_gc
2333                }
2334        }
2335        unset object_limit _junk objects_current
2336}
2337
2338lock_index begin-read
2339after 1 do_rescan