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