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