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