84205b08bfa67977b08260f5114a9da116f0cae9
   1#!/bin/sh
   2# Tcl ignores the next line -*- tcl -*- \
   3exec wish "$0" -- "$@"
   4
   5set appvers {@@GIT_VERSION@@}
   6set copyright {
   7Copyright © 2006, 2007 Shawn Pearce, Paul Mackerras.
   8
   9This program is free software; you can redistribute it and/or modify
  10it under the terms of the GNU General Public License as published by
  11the Free Software Foundation; either version 2 of the License, or
  12(at your option) any later version.
  13
  14This program is distributed in the hope that it will be useful,
  15but WITHOUT ANY WARRANTY; without even the implied warranty of
  16MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
  17GNU General Public License for more details.
  18
  19You should have received a copy of the GNU General Public License
  20along with this program; if not, write to the Free Software
  21Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA  02111-1307  USA}
  22
  23######################################################################
  24##
  25## read only globals
  26
  27set _appname [lindex [file split $argv0] end]
  28set _gitdir {}
  29set _reponame {}
  30
  31proc appname {} {
  32        global _appname
  33        return $_appname
  34}
  35
  36proc gitdir {args} {
  37        global _gitdir
  38        if {$args eq {}} {
  39                return $_gitdir
  40        }
  41        return [eval [concat [list file join $_gitdir] $args]]
  42}
  43
  44proc reponame {} {
  45        global _reponame
  46        return $_reponame
  47}
  48
  49######################################################################
  50##
  51## config
  52
  53proc is_many_config {name} {
  54        switch -glob -- $name {
  55        remote.*.fetch -
  56        remote.*.push
  57                {return 1}
  58        *
  59                {return 0}
  60        }
  61}
  62
  63proc load_config {include_global} {
  64        global repo_config global_config default_config
  65
  66        array unset global_config
  67        if {$include_global} {
  68                catch {
  69                        set fd_rc [open "| git repo-config --global --list" r]
  70                        while {[gets $fd_rc line] >= 0} {
  71                                if {[regexp {^([^=]+)=(.*)$} $line line name value]} {
  72                                        if {[is_many_config $name]} {
  73                                                lappend global_config($name) $value
  74                                        } else {
  75                                                set global_config($name) $value
  76                                        }
  77                                }
  78                        }
  79                        close $fd_rc
  80                }
  81        }
  82
  83        array unset repo_config
  84        catch {
  85                set fd_rc [open "| git repo-config --list" r]
  86                while {[gets $fd_rc line] >= 0} {
  87                        if {[regexp {^([^=]+)=(.*)$} $line line name value]} {
  88                                if {[is_many_config $name]} {
  89                                        lappend repo_config($name) $value
  90                                } else {
  91                                        set repo_config($name) $value
  92                                }
  93                        }
  94                }
  95                close $fd_rc
  96        }
  97
  98        foreach name [array names default_config] {
  99                if {[catch {set v $global_config($name)}]} {
 100                        set global_config($name) $default_config($name)
 101                }
 102                if {[catch {set v $repo_config($name)}]} {
 103                        set repo_config($name) $default_config($name)
 104                }
 105        }
 106}
 107
 108proc save_config {} {
 109        global default_config font_descs
 110        global repo_config global_config
 111        global repo_config_new global_config_new
 112
 113        foreach option $font_descs {
 114                set name [lindex $option 0]
 115                set font [lindex $option 1]
 116                font configure $font \
 117                        -family $global_config_new(gui.$font^^family) \
 118                        -size $global_config_new(gui.$font^^size)
 119                font configure ${font}bold \
 120                        -family $global_config_new(gui.$font^^family) \
 121                        -size $global_config_new(gui.$font^^size)
 122                set global_config_new(gui.$name) [font configure $font]
 123                unset global_config_new(gui.$font^^family)
 124                unset global_config_new(gui.$font^^size)
 125        }
 126
 127        foreach name [array names default_config] {
 128                set value $global_config_new($name)
 129                if {$value ne $global_config($name)} {
 130                        if {$value eq $default_config($name)} {
 131                                catch {exec git repo-config --global --unset $name}
 132                        } else {
 133                                regsub -all "\[{}\]" $value {"} value
 134                                exec git repo-config --global $name $value
 135                        }
 136                        set global_config($name) $value
 137                        if {$value eq $repo_config($name)} {
 138                                catch {exec git repo-config --unset $name}
 139                                set repo_config($name) $value
 140                        }
 141                }
 142        }
 143
 144        foreach name [array names default_config] {
 145                set value $repo_config_new($name)
 146                if {$value ne $repo_config($name)} {
 147                        if {$value eq $global_config($name)} {
 148                                catch {exec git repo-config --unset $name}
 149                        } else {
 150                                regsub -all "\[{}\]" $value {"} value
 151                                exec git repo-config $name $value
 152                        }
 153                        set repo_config($name) $value
 154                }
 155        }
 156}
 157
 158proc error_popup {msg} {
 159        set title [appname]
 160        if {[reponame] ne {}} {
 161                append title " ([reponame])"
 162        }
 163        set cmd [list tk_messageBox \
 164                -icon error \
 165                -type ok \
 166                -title "$title: error" \
 167                -message $msg]
 168        if {[winfo ismapped .]} {
 169                lappend cmd -parent .
 170        }
 171        eval $cmd
 172}
 173
 174proc warn_popup {msg} {
 175        set title [appname]
 176        if {[reponame] ne {}} {
 177                append title " ([reponame])"
 178        }
 179        set cmd [list tk_messageBox \
 180                -icon warning \
 181                -type ok \
 182                -title "$title: warning" \
 183                -message $msg]
 184        if {[winfo ismapped .]} {
 185                lappend cmd -parent .
 186        }
 187        eval $cmd
 188}
 189
 190proc info_popup {msg} {
 191        set title [appname]
 192        if {[reponame] ne {}} {
 193                append title " ([reponame])"
 194        }
 195        tk_messageBox \
 196                -parent . \
 197                -icon info \
 198                -type ok \
 199                -title $title \
 200                -message $msg
 201}
 202
 203proc ask_popup {msg} {
 204        set title [appname]
 205        if {[reponame] ne {}} {
 206                append title " ([reponame])"
 207        }
 208        return [tk_messageBox \
 209                -parent . \
 210                -icon question \
 211                -type yesno \
 212                -title $title \
 213                -message $msg]
 214}
 215
 216######################################################################
 217##
 218## repository setup
 219
 220if {   [catch {set _gitdir $env(GIT_DIR)}]
 221        && [catch {set _gitdir [exec git rev-parse --git-dir]} err]} {
 222        catch {wm withdraw .}
 223        error_popup "Cannot find the git directory:\n\n$err"
 224        exit 1
 225}
 226if {![file isdirectory $_gitdir]} {
 227        catch {wm withdraw .}
 228        error_popup "Git directory not found:\n\n$_gitdir"
 229        exit 1
 230}
 231if {[lindex [file split $_gitdir] end] ne {.git}} {
 232        catch {wm withdraw .}
 233        error_popup "Cannot use funny .git directory:\n\n$gitdir"
 234        exit 1
 235}
 236if {[catch {cd [file dirname $_gitdir]} err]} {
 237        catch {wm withdraw .}
 238        error_popup "No working directory [file dirname $_gitdir]:\n\n$err"
 239        exit 1
 240}
 241set _reponame [lindex [file split \
 242        [file normalize [file dirname $_gitdir]]] \
 243        end]
 244
 245set single_commit 0
 246if {[appname] eq {git-citool}} {
 247        set single_commit 1
 248}
 249
 250######################################################################
 251##
 252## task management
 253
 254set rescan_active 0
 255set diff_active 0
 256set last_clicked {}
 257
 258set disable_on_lock [list]
 259set index_lock_type none
 260
 261proc lock_index {type} {
 262        global index_lock_type disable_on_lock
 263
 264        if {$index_lock_type eq {none}} {
 265                set index_lock_type $type
 266                foreach w $disable_on_lock {
 267                        uplevel #0 $w disabled
 268                }
 269                return 1
 270        } elseif {$index_lock_type eq "begin-$type"} {
 271                set index_lock_type $type
 272                return 1
 273        }
 274        return 0
 275}
 276
 277proc unlock_index {} {
 278        global index_lock_type disable_on_lock
 279
 280        set index_lock_type none
 281        foreach w $disable_on_lock {
 282                uplevel #0 $w normal
 283        }
 284}
 285
 286######################################################################
 287##
 288## status
 289
 290proc repository_state {ctvar hdvar mhvar} {
 291        global current_branch
 292        upvar $ctvar ct $hdvar hd $mhvar mh
 293
 294        set mh [list]
 295
 296        if {[catch {set current_branch [exec git symbolic-ref HEAD]}]} {
 297                set current_branch {}
 298        } else {
 299                regsub ^refs/((heads|tags|remotes)/)? \
 300                        $current_branch \
 301                        {} \
 302                        current_branch
 303        }
 304
 305        if {[catch {set hd [exec git rev-parse --verify HEAD]}]} {
 306                set hd {}
 307                set ct initial
 308                return
 309        }
 310
 311        set merge_head [gitdir MERGE_HEAD]
 312        if {[file exists $merge_head]} {
 313                set ct merge
 314                set fd_mh [open $merge_head r]
 315                while {[gets $fd_mh line] >= 0} {
 316                        lappend mh $line
 317                }
 318                close $fd_mh
 319                return
 320        }
 321
 322        set ct normal
 323}
 324
 325proc PARENT {} {
 326        global PARENT empty_tree
 327
 328        set p [lindex $PARENT 0]
 329        if {$p ne {}} {
 330                return $p
 331        }
 332        if {$empty_tree eq {}} {
 333                set empty_tree [exec git mktree << {}]
 334        }
 335        return $empty_tree
 336}
 337
 338proc rescan {after} {
 339        global HEAD PARENT MERGE_HEAD commit_type
 340        global ui_index ui_other ui_status_value ui_comm
 341        global rescan_active file_states
 342        global repo_config
 343
 344        if {$rescan_active > 0 || ![lock_index read]} return
 345
 346        repository_state newType newHEAD newMERGE_HEAD
 347        if {[string match amend* $commit_type]
 348                && $newType eq {normal}
 349                && $newHEAD eq $HEAD} {
 350        } else {
 351                set HEAD $newHEAD
 352                set PARENT $newHEAD
 353                set MERGE_HEAD $newMERGE_HEAD
 354                set commit_type $newType
 355        }
 356
 357        array unset file_states
 358
 359        if {![$ui_comm edit modified]
 360                || [string trim [$ui_comm get 0.0 end]] eq {}} {
 361                if {[load_message GITGUI_MSG]} {
 362                } elseif {[load_message MERGE_MSG]} {
 363                } elseif {[load_message SQUASH_MSG]} {
 364                }
 365                $ui_comm edit reset
 366                $ui_comm edit modified false
 367        }
 368
 369        if {$repo_config(gui.trustmtime) eq {true}} {
 370                rescan_stage2 {} $after
 371        } else {
 372                set rescan_active 1
 373                set ui_status_value {Refreshing file status...}
 374                set cmd [list git update-index]
 375                lappend cmd -q
 376                lappend cmd --unmerged
 377                lappend cmd --ignore-missing
 378                lappend cmd --refresh
 379                set fd_rf [open "| $cmd" r]
 380                fconfigure $fd_rf -blocking 0 -translation binary
 381                fileevent $fd_rf readable \
 382                        [list rescan_stage2 $fd_rf $after]
 383        }
 384}
 385
 386proc rescan_stage2 {fd after} {
 387        global ui_status_value
 388        global rescan_active buf_rdi buf_rdf buf_rlo
 389
 390        if {$fd ne {}} {
 391                read $fd
 392                if {![eof $fd]} return
 393                close $fd
 394        }
 395
 396        set ls_others [list | git ls-files --others -z \
 397                --exclude-per-directory=.gitignore]
 398        set info_exclude [gitdir info exclude]
 399        if {[file readable $info_exclude]} {
 400                lappend ls_others "--exclude-from=$info_exclude"
 401        }
 402
 403        set buf_rdi {}
 404        set buf_rdf {}
 405        set buf_rlo {}
 406
 407        set rescan_active 3
 408        set ui_status_value {Scanning for modified files ...}
 409        set fd_di [open "| git diff-index --cached -z [PARENT]" r]
 410        set fd_df [open "| git diff-files -z" r]
 411        set fd_lo [open $ls_others r]
 412
 413        fconfigure $fd_di -blocking 0 -translation binary
 414        fconfigure $fd_df -blocking 0 -translation binary
 415        fconfigure $fd_lo -blocking 0 -translation binary
 416        fileevent $fd_di readable [list read_diff_index $fd_di $after]
 417        fileevent $fd_df readable [list read_diff_files $fd_df $after]
 418        fileevent $fd_lo readable [list read_ls_others $fd_lo $after]
 419}
 420
 421proc load_message {file} {
 422        global ui_comm
 423
 424        set f [gitdir $file]
 425        if {[file isfile $f]} {
 426                if {[catch {set fd [open $f r]}]} {
 427                        return 0
 428                }
 429                set content [string trim [read $fd]]
 430                close $fd
 431                $ui_comm delete 0.0 end
 432                $ui_comm insert end $content
 433                return 1
 434        }
 435        return 0
 436}
 437
 438proc read_diff_index {fd after} {
 439        global buf_rdi
 440
 441        append buf_rdi [read $fd]
 442        set c 0
 443        set n [string length $buf_rdi]
 444        while {$c < $n} {
 445                set z1 [string first "\0" $buf_rdi $c]
 446                if {$z1 == -1} break
 447                incr z1
 448                set z2 [string first "\0" $buf_rdi $z1]
 449                if {$z2 == -1} break
 450
 451                incr c
 452                set i [split [string range $buf_rdi $c [expr {$z1 - 2}]] { }]
 453                merge_state \
 454                        [string range $buf_rdi $z1 [expr {$z2 - 1}]] \
 455                        [lindex $i 4]? \
 456                        [list [lindex $i 0] [lindex $i 2]] \
 457                        [list]
 458                set c $z2
 459                incr c
 460        }
 461        if {$c < $n} {
 462                set buf_rdi [string range $buf_rdi $c end]
 463        } else {
 464                set buf_rdi {}
 465        }
 466
 467        rescan_done $fd buf_rdi $after
 468}
 469
 470proc read_diff_files {fd after} {
 471        global buf_rdf
 472
 473        append buf_rdf [read $fd]
 474        set c 0
 475        set n [string length $buf_rdf]
 476        while {$c < $n} {
 477                set z1 [string first "\0" $buf_rdf $c]
 478                if {$z1 == -1} break
 479                incr z1
 480                set z2 [string first "\0" $buf_rdf $z1]
 481                if {$z2 == -1} break
 482
 483                incr c
 484                set i [split [string range $buf_rdf $c [expr {$z1 - 2}]] { }]
 485                merge_state \
 486                        [string range $buf_rdf $z1 [expr {$z2 - 1}]] \
 487                        ?[lindex $i 4] \
 488                        [list] \
 489                        [list [lindex $i 0] [lindex $i 2]]
 490                set c $z2
 491                incr c
 492        }
 493        if {$c < $n} {
 494                set buf_rdf [string range $buf_rdf $c end]
 495        } else {
 496                set buf_rdf {}
 497        }
 498
 499        rescan_done $fd buf_rdf $after
 500}
 501
 502proc read_ls_others {fd after} {
 503        global buf_rlo
 504
 505        append buf_rlo [read $fd]
 506        set pck [split $buf_rlo "\0"]
 507        set buf_rlo [lindex $pck end]
 508        foreach p [lrange $pck 0 end-1] {
 509                merge_state $p ?O
 510        }
 511        rescan_done $fd buf_rlo $after
 512}
 513
 514proc rescan_done {fd buf after} {
 515        global rescan_active
 516        global file_states repo_config
 517        upvar $buf to_clear
 518
 519        if {![eof $fd]} return
 520        set to_clear {}
 521        close $fd
 522        if {[incr rescan_active -1] > 0} return
 523
 524        prune_selection
 525        unlock_index
 526        display_all_files
 527
 528        if {$repo_config(gui.partialinclude) ne {true}} {
 529                set pathList [list]
 530                foreach path [array names file_states] {
 531                        switch -- [lindex $file_states($path) 0] {
 532                        A? -
 533                        M? {lappend pathList $path}
 534                        }
 535                }
 536                if {$pathList ne {}} {
 537                        update_index \
 538                                "Updating included files" \
 539                                $pathList \
 540                                [concat {reshow_diff;} $after]
 541                        return
 542                }
 543        }
 544
 545        reshow_diff
 546        uplevel #0 $after
 547}
 548
 549proc prune_selection {} {
 550        global file_states selected_paths
 551
 552        foreach path [array names selected_paths] {
 553                if {[catch {set still_here $file_states($path)}]} {
 554                        unset selected_paths($path)
 555                }
 556        }
 557}
 558
 559######################################################################
 560##
 561## diff
 562
 563proc clear_diff {} {
 564        global ui_diff current_diff ui_index ui_other
 565
 566        $ui_diff conf -state normal
 567        $ui_diff delete 0.0 end
 568        $ui_diff conf -state disabled
 569
 570        set current_diff {}
 571
 572        $ui_index tag remove in_diff 0.0 end
 573        $ui_other tag remove in_diff 0.0 end
 574}
 575
 576proc reshow_diff {} {
 577        global current_diff ui_status_value file_states
 578
 579        if {$current_diff eq {}
 580                || [catch {set s $file_states($current_diff)}]} {
 581                clear_diff
 582        } else {
 583                show_diff $current_diff
 584        }
 585}
 586
 587proc handle_empty_diff {} {
 588        global current_diff file_states file_lists
 589
 590        set path $current_diff
 591        set s $file_states($path)
 592        if {[lindex $s 0] ne {_M}} return
 593
 594        info_popup "No differences detected.
 595
 596[short_path $path] has no changes.
 597
 598The modification date of this file was updated
 599by another application and you currently have
 600the Trust File Modification Timestamps option
 601enabled, so Git did not automatically detect
 602that there are no content differences in this
 603file.
 604
 605This file will now be removed from the modified
 606files list, to prevent possible confusion.
 607"
 608        if {[catch {exec git update-index -- $path} err]} {
 609                error_popup "Failed to refresh index:\n\n$err"
 610        }
 611
 612        clear_diff
 613        set old_w [mapcol [lindex $file_states($path) 0] $path]
 614        set lno [lsearch -sorted $file_lists($old_w) $path]
 615        if {$lno >= 0} {
 616                set file_lists($old_w) \
 617                        [lreplace $file_lists($old_w) $lno $lno]
 618                incr lno
 619                $old_w conf -state normal
 620                $old_w delete $lno.0 [expr {$lno + 1}].0
 621                $old_w conf -state disabled
 622        }
 623}
 624
 625proc show_diff {path {w {}} {lno {}}} {
 626        global file_states file_lists
 627        global is_3way_diff diff_active repo_config
 628        global ui_diff current_diff ui_status_value
 629
 630        if {$diff_active || ![lock_index read]} return
 631
 632        clear_diff
 633        if {$w eq {} || $lno == {}} {
 634                foreach w [array names file_lists] {
 635                        set lno [lsearch -sorted $file_lists($w) $path]
 636                        if {$lno >= 0} {
 637                                incr lno
 638                                break
 639                        }
 640                }
 641        }
 642        if {$w ne {} && $lno >= 1} {
 643                $w tag add in_diff $lno.0 [expr {$lno + 1}].0
 644        }
 645
 646        set s $file_states($path)
 647        set m [lindex $s 0]
 648        set is_3way_diff 0
 649        set diff_active 1
 650        set current_diff $path
 651        set ui_status_value "Loading diff of [escape_path $path]..."
 652
 653        set cmd [list | git diff-index]
 654        lappend cmd --no-color
 655        if {$repo_config(gui.diffcontext) > 0} {
 656                lappend cmd "-U$repo_config(gui.diffcontext)"
 657        }
 658        lappend cmd -p
 659
 660        switch $m {
 661        MM {
 662                lappend cmd -c
 663        }
 664        _O {
 665                if {[catch {
 666                                set fd [open $path r]
 667                                set content [read $fd]
 668                                close $fd
 669                        } err ]} {
 670                        set diff_active 0
 671                        unlock_index
 672                        set ui_status_value "Unable to display [escape_path $path]"
 673                        error_popup "Error loading file:\n\n$err"
 674                        return
 675                }
 676                $ui_diff conf -state normal
 677                $ui_diff insert end $content
 678                $ui_diff conf -state disabled
 679                set diff_active 0
 680                unlock_index
 681                set ui_status_value {Ready.}
 682                return
 683        }
 684        }
 685
 686        lappend cmd [PARENT]
 687        lappend cmd --
 688        lappend cmd $path
 689
 690        if {[catch {set fd [open $cmd r]} err]} {
 691                set diff_active 0
 692                unlock_index
 693                set ui_status_value "Unable to display [escape_path $path]"
 694                error_popup "Error loading diff:\n\n$err"
 695                return
 696        }
 697
 698        fconfigure $fd -blocking 0 -translation auto
 699        fileevent $fd readable [list read_diff $fd]
 700}
 701
 702proc read_diff {fd} {
 703        global ui_diff ui_status_value is_3way_diff diff_active
 704        global repo_config
 705
 706        $ui_diff conf -state normal
 707        while {[gets $fd line] >= 0} {
 708                # -- Cleanup uninteresting diff header lines.
 709                #
 710                if {[string match {diff --git *}      $line]} continue
 711                if {[string match {diff --combined *} $line]} continue
 712                if {[string match {--- *}             $line]} continue
 713                if {[string match {+++ *}             $line]} continue
 714                if {$line eq {deleted file mode 120000}} {
 715                        set line "deleted symlink"
 716                }
 717
 718                # -- Automatically detect if this is a 3 way diff.
 719                #
 720                if {[string match {@@@ *} $line]} {set is_3way_diff 1}
 721
 722                # -- Reformat a 3 way diff, 'cause its too weird.
 723                #
 724                if {$is_3way_diff} {
 725                        set op [string range $line 0 1]
 726                        switch -- $op {
 727                        {@@} {set tags d_@}
 728                        {++} {set tags d_+ ; set op { +}}
 729                        {--} {set tags d_- ; set op { -}}
 730                        { +} {set tags d_++; set op {++}}
 731                        { -} {set tags d_--; set op {--}}
 732                        {+ } {set tags d_-+; set op {-+}}
 733                        {- } {set tags d_+-; set op {+-}}
 734                        default {set tags {}}
 735                        }
 736                        set line [string replace $line 0 1 $op]
 737                } else {
 738                        switch -- [string index $line 0] {
 739                        @ {set tags d_@}
 740                        + {set tags d_+}
 741                        - {set tags d_-}
 742                        default {set tags {}}
 743                        }
 744                }
 745                $ui_diff insert end $line $tags
 746                $ui_diff insert end "\n" $tags
 747        }
 748        $ui_diff conf -state disabled
 749
 750        if {[eof $fd]} {
 751                close $fd
 752                set diff_active 0
 753                unlock_index
 754                set ui_status_value {Ready.}
 755
 756                if {$repo_config(gui.trustmtime) eq {true}
 757                        && [$ui_diff index end] eq {2.0}} {
 758                        handle_empty_diff
 759                }
 760        }
 761}
 762
 763######################################################################
 764##
 765## commit
 766
 767proc load_last_commit {} {
 768        global HEAD PARENT MERGE_HEAD commit_type ui_comm
 769
 770        if {[llength $PARENT] == 0} {
 771                error_popup {There is nothing to amend.
 772
 773You are about to create the initial commit.
 774There is no commit before this to amend.
 775}
 776                return
 777        }
 778
 779        repository_state curType curHEAD curMERGE_HEAD
 780        if {$curType eq {merge}} {
 781                error_popup {Cannot amend while merging.
 782
 783You are currently in the middle of a merge that
 784has not been fully completed.  You cannot amend
 785the prior commit unless you first abort the
 786current merge activity.
 787}
 788                return
 789        }
 790
 791        set msg {}
 792        set parents [list]
 793        if {[catch {
 794                        set fd [open "| git cat-file commit $curHEAD" r]
 795                        while {[gets $fd line] > 0} {
 796                                if {[string match {parent *} $line]} {
 797                                        lappend parents [string range $line 7 end]
 798                                }
 799                        }
 800                        set msg [string trim [read $fd]]
 801                        close $fd
 802                } err]} {
 803                error_popup "Error loading commit data for amend:\n\n$err"
 804                return
 805        }
 806
 807        set HEAD $curHEAD
 808        set PARENT $parents
 809        set MERGE_HEAD [list]
 810        switch -- [llength $parents] {
 811        0       {set commit_type amend-initial}
 812        1       {set commit_type amend}
 813        default {set commit_type amend-merge}
 814        }
 815
 816        $ui_comm delete 0.0 end
 817        $ui_comm insert end $msg
 818        $ui_comm edit reset
 819        $ui_comm edit modified false
 820        rescan {set ui_status_value {Ready.}}
 821}
 822
 823proc create_new_commit {} {
 824        global commit_type ui_comm
 825
 826        set commit_type normal
 827        $ui_comm delete 0.0 end
 828        $ui_comm edit reset
 829        $ui_comm edit modified false
 830        rescan {set ui_status_value {Ready.}}
 831}
 832
 833set GIT_COMMITTER_IDENT {}
 834
 835proc committer_ident {} {
 836        global GIT_COMMITTER_IDENT
 837
 838        if {$GIT_COMMITTER_IDENT eq {}} {
 839                if {[catch {set me [exec git var GIT_COMMITTER_IDENT]} err]} {
 840                        error_popup "Unable to obtain your identity:\n\n$err"
 841                        return {}
 842                }
 843                if {![regexp {^(.*) [0-9]+ [-+0-9]+$} \
 844                        $me me GIT_COMMITTER_IDENT]} {
 845                        error_popup "Invalid GIT_COMMITTER_IDENT:\n\n$me"
 846                        return {}
 847                }
 848        }
 849
 850        return $GIT_COMMITTER_IDENT
 851}
 852
 853proc commit_tree {} {
 854        global HEAD commit_type file_states ui_comm repo_config
 855
 856        if {![lock_index update]} return
 857        if {[committer_ident] eq {}} return
 858
 859        # -- Our in memory state should match the repository.
 860        #
 861        repository_state curType curHEAD curMERGE_HEAD
 862        if {[string match amend* $commit_type]
 863                && $curType eq {normal}
 864                && $curHEAD eq $HEAD} {
 865        } elseif {$commit_type ne $curType || $HEAD ne $curHEAD} {
 866                info_popup {Last scanned state does not match repository state.
 867
 868Another Git program has modified this repository
 869since the last scan.  A rescan must be performed
 870before another commit can be created.
 871
 872The rescan will be automatically started now.
 873}
 874                unlock_index
 875                rescan {set ui_status_value {Ready.}}
 876                return
 877        }
 878
 879        # -- At least one file should differ in the index.
 880        #
 881        set files_ready 0
 882        foreach path [array names file_states] {
 883                switch -glob -- [lindex $file_states($path) 0] {
 884                _? {continue}
 885                A? -
 886                D? -
 887                M? {set files_ready 1; break}
 888                U? {
 889                        error_popup "Unmerged files cannot be committed.
 890
 891File [short_path $path] has merge conflicts.
 892You must resolve them and include the file before committing.
 893"
 894                        unlock_index
 895                        return
 896                }
 897                default {
 898                        error_popup "Unknown file state [lindex $s 0] detected.
 899
 900File [short_path $path] cannot be committed by this program.
 901"
 902                }
 903                }
 904        }
 905        if {!$files_ready} {
 906                error_popup {No included files to commit.
 907
 908You must include at least 1 file before you can commit.
 909}
 910                unlock_index
 911                return
 912        }
 913
 914        # -- A message is required.
 915        #
 916        set msg [string trim [$ui_comm get 1.0 end]]
 917        if {$msg eq {}} {
 918                error_popup {Please supply a commit message.
 919
 920A good commit message has the following format:
 921
 922- First line: Describe in one sentance what you did.
 923- Second line: Blank
 924- Remaining lines: Describe why this change is good.
 925}
 926                unlock_index
 927                return
 928        }
 929
 930        # -- Update included files if partialincludes are off.
 931        #
 932        if {$repo_config(gui.partialinclude) ne {true}} {
 933                set pathList [list]
 934                foreach path [array names file_states] {
 935                        switch -glob -- [lindex $file_states($path) 0] {
 936                        A? -
 937                        M? {lappend pathList $path}
 938                        }
 939                }
 940                if {$pathList ne {}} {
 941                        unlock_index
 942                        update_index \
 943                                "Updating included files" \
 944                                $pathList \
 945                                [concat {lock_index update;} \
 946                                        [list commit_prehook $curHEAD $msg]]
 947                        return
 948                }
 949        }
 950
 951        commit_prehook $curHEAD $msg
 952}
 953
 954proc commit_prehook {curHEAD msg} {
 955        global ui_status_value pch_error
 956
 957        set pchook [gitdir hooks pre-commit]
 958
 959        # On Cygwin [file executable] might lie so we need to ask
 960        # the shell if the hook is executable.  Yes that's annoying.
 961        #
 962        if {[is_Windows] && [file isfile $pchook]} {
 963                set pchook [list sh -c [concat \
 964                        "if test -x \"$pchook\";" \
 965                        "then exec \"$pchook\" 2>&1;" \
 966                        "fi"]]
 967        } elseif {[file executable $pchook]} {
 968                set pchook [list $pchook |& cat]
 969        } else {
 970                commit_writetree $curHEAD $msg
 971                return
 972        }
 973
 974        set ui_status_value {Calling pre-commit hook...}
 975        set pch_error {}
 976        set fd_ph [open "| $pchook" r]
 977        fconfigure $fd_ph -blocking 0 -translation binary
 978        fileevent $fd_ph readable \
 979                [list commit_prehook_wait $fd_ph $curHEAD $msg]
 980}
 981
 982proc commit_prehook_wait {fd_ph curHEAD msg} {
 983        global pch_error ui_status_value
 984
 985        append pch_error [read $fd_ph]
 986        fconfigure $fd_ph -blocking 1
 987        if {[eof $fd_ph]} {
 988                if {[catch {close $fd_ph}]} {
 989                        set ui_status_value {Commit declined by pre-commit hook.}
 990                        hook_failed_popup pre-commit $pch_error
 991                        unlock_index
 992                } else {
 993                        commit_writetree $curHEAD $msg
 994                }
 995                set pch_error {}
 996                return
 997        }
 998        fconfigure $fd_ph -blocking 0
 999}
1000
1001proc commit_writetree {curHEAD msg} {
1002        global ui_status_value
1003
1004        set ui_status_value {Committing changes...}
1005        set fd_wt [open "| git write-tree" r]
1006        fileevent $fd_wt readable \
1007                [list commit_committree $fd_wt $curHEAD $msg]
1008}
1009
1010proc commit_committree {fd_wt curHEAD msg} {
1011        global HEAD PARENT MERGE_HEAD commit_type
1012        global single_commit
1013        global ui_status_value ui_comm selected_commit_type
1014        global file_states selected_paths rescan_active
1015
1016        gets $fd_wt tree_id
1017        if {$tree_id eq {} || [catch {close $fd_wt} err]} {
1018                error_popup "write-tree failed:\n\n$err"
1019                set ui_status_value {Commit failed.}
1020                unlock_index
1021                return
1022        }
1023
1024        # -- Create the commit.
1025        #
1026        set cmd [list git commit-tree $tree_id]
1027        set parents [concat $PARENT $MERGE_HEAD]
1028        if {[llength $parents] > 0} {
1029                foreach p $parents {
1030                        lappend cmd -p $p
1031                }
1032        } else {
1033                # git commit-tree writes to stderr during initial commit.
1034                lappend cmd 2>/dev/null
1035        }
1036        lappend cmd << $msg
1037        if {[catch {set cmt_id [eval exec $cmd]} err]} {
1038                error_popup "commit-tree failed:\n\n$err"
1039                set ui_status_value {Commit failed.}
1040                unlock_index
1041                return
1042        }
1043
1044        # -- Update the HEAD ref.
1045        #
1046        set reflogm commit
1047        if {$commit_type ne {normal}} {
1048                append reflogm " ($commit_type)"
1049        }
1050        set i [string first "\n" $msg]
1051        if {$i >= 0} {
1052                append reflogm {: } [string range $msg 0 [expr {$i - 1}]]
1053        } else {
1054                append reflogm {: } $msg
1055        }
1056        set cmd [list git update-ref -m $reflogm HEAD $cmt_id $curHEAD]
1057        if {[catch {eval exec $cmd} err]} {
1058                error_popup "update-ref failed:\n\n$err"
1059                set ui_status_value {Commit failed.}
1060                unlock_index
1061                return
1062        }
1063
1064        # -- Cleanup after ourselves.
1065        #
1066        catch {file delete [gitdir MERGE_HEAD]}
1067        catch {file delete [gitdir MERGE_MSG]}
1068        catch {file delete [gitdir SQUASH_MSG]}
1069        catch {file delete [gitdir GITGUI_MSG]}
1070
1071        # -- Let rerere do its thing.
1072        #
1073        if {[file isdirectory [gitdir rr-cache]]} {
1074                catch {exec git rerere}
1075        }
1076
1077        # -- Run the post-commit hook.
1078        #
1079        set pchook [gitdir hooks post-commit]
1080        if {[is_Windows] && [file isfile $pchook]} {
1081                set pchook [list sh -c [concat \
1082                        "if test -x \"$pchook\";" \
1083                        "then exec \"$pchook\";" \
1084                        "fi"]]
1085        } elseif {![file executable $pchook]} {
1086                set pchook {}
1087        }
1088        if {$pchook ne {}} {
1089                catch {exec $pchook &}
1090        }
1091
1092        $ui_comm delete 0.0 end
1093        $ui_comm edit reset
1094        $ui_comm edit modified false
1095
1096        if {$single_commit} do_quit
1097
1098        # -- Update in memory status
1099        #
1100        set selected_commit_type new
1101        set commit_type normal
1102        set HEAD $cmt_id
1103        set PARENT $cmt_id
1104        set MERGE_HEAD [list]
1105
1106        foreach path [array names file_states] {
1107                set s $file_states($path)
1108                set m [lindex $s 0]
1109                switch -glob -- $m {
1110                _O -
1111                _M -
1112                _D {continue}
1113                __ -
1114                A_ -
1115                M_ -
1116                DD {
1117                        unset file_states($path)
1118                        catch {unset selected_paths($path)}
1119                }
1120                DO {
1121                        set file_states($path) [list _O [lindex $s 1] {} {}]
1122                }
1123                AM -
1124                AD -
1125                MM -
1126                MD -
1127                DM {
1128                        set file_states($path) [list \
1129                                _[string index $m 1] \
1130                                [lindex $s 1] \
1131                                [lindex $s 3] \
1132                                {}]
1133                }
1134                }
1135        }
1136
1137        display_all_files
1138        unlock_index
1139        reshow_diff
1140        set ui_status_value \
1141                "Changes committed as [string range $cmt_id 0 7]."
1142}
1143
1144######################################################################
1145##
1146## fetch pull push
1147
1148proc fetch_from {remote} {
1149        set w [new_console "fetch $remote" \
1150                "Fetching new changes from $remote"]
1151        set cmd [list git fetch]
1152        lappend cmd $remote
1153        console_exec $w $cmd
1154}
1155
1156proc pull_remote {remote branch} {
1157        global HEAD commit_type file_states repo_config
1158
1159        if {![lock_index update]} return
1160
1161        # -- Our in memory state should match the repository.
1162        #
1163        repository_state curType curHEAD curMERGE_HEAD
1164        if {$commit_type ne $curType || $HEAD ne $curHEAD} {
1165                info_popup {Last scanned state does not match repository state.
1166
1167Another Git program has modified this repository
1168since the last scan.  A rescan must be performed
1169before a pull operation can be started.
1170
1171The rescan will be automatically started now.
1172}
1173                unlock_index
1174                rescan {set ui_status_value {Ready.}}
1175                return
1176        }
1177
1178        # -- No differences should exist before a pull.
1179        #
1180        if {[array size file_states] != 0} {
1181                error_popup {Uncommitted but modified files are present.
1182
1183You should not perform a pull with unmodified
1184files in your working directory as Git will be
1185unable to recover from an incorrect merge.
1186
1187You should commit or revert all changes before
1188starting a pull operation.
1189}
1190                unlock_index
1191                return
1192        }
1193
1194        set w [new_console "pull $remote $branch" \
1195                "Pulling new changes from branch $branch in $remote"]
1196        set cmd [list git pull]
1197        if {$repo_config(gui.pullsummary) eq {false}} {
1198                lappend cmd --no-summary
1199        }
1200        lappend cmd $remote
1201        lappend cmd $branch
1202        console_exec $w $cmd [list post_pull_remote $remote $branch]
1203}
1204
1205proc post_pull_remote {remote branch success} {
1206        global HEAD PARENT MERGE_HEAD commit_type selected_commit_type
1207        global ui_status_value
1208
1209        unlock_index
1210        if {$success} {
1211                repository_state commit_type HEAD MERGE_HEAD
1212                set PARENT $HEAD
1213                set selected_commit_type new
1214                set ui_status_value "Pulling $branch from $remote complete."
1215        } else {
1216                rescan [list set ui_status_value \
1217                        "Conflicts detected while pulling $branch from $remote."]
1218        }
1219}
1220
1221proc push_to {remote} {
1222        set w [new_console "push $remote" \
1223                "Pushing changes to $remote"]
1224        set cmd [list git push]
1225        lappend cmd $remote
1226        console_exec $w $cmd
1227}
1228
1229######################################################################
1230##
1231## ui helpers
1232
1233proc mapcol {state path} {
1234        global all_cols ui_other
1235
1236        if {[catch {set r $all_cols($state)}]} {
1237                puts "error: no column for state={$state} $path"
1238                return $ui_other
1239        }
1240        return $r
1241}
1242
1243proc mapicon {state path} {
1244        global all_icons
1245
1246        if {[catch {set r $all_icons($state)}]} {
1247                puts "error: no icon for state={$state} $path"
1248                return file_plain
1249        }
1250        return $r
1251}
1252
1253proc mapdesc {state path} {
1254        global all_descs
1255
1256        if {[catch {set r $all_descs($state)}]} {
1257                puts "error: no desc for state={$state} $path"
1258                return $state
1259        }
1260        return $r
1261}
1262
1263proc escape_path {path} {
1264        regsub -all "\n" $path "\\n" path
1265        return $path
1266}
1267
1268proc short_path {path} {
1269        return [escape_path [lindex [file split $path] end]]
1270}
1271
1272set next_icon_id 0
1273set null_sha1 [string repeat 0 40]
1274
1275proc merge_state {path new_state {head_info {}} {index_info {}}} {
1276        global file_states next_icon_id null_sha1
1277
1278        set s0 [string index $new_state 0]
1279        set s1 [string index $new_state 1]
1280
1281        if {[catch {set info $file_states($path)}]} {
1282                set state __
1283                set icon n[incr next_icon_id]
1284        } else {
1285                set state [lindex $info 0]
1286                set icon [lindex $info 1]
1287                if {$head_info eq {}}  {set head_info  [lindex $info 2]}
1288                if {$index_info eq {}} {set index_info [lindex $info 3]}
1289        }
1290
1291        if     {$s0 eq {?}} {set s0 [string index $state 0]} \
1292        elseif {$s0 eq {_}} {set s0 _}
1293
1294        if     {$s1 eq {?}} {set s1 [string index $state 1]} \
1295        elseif {$s1 eq {_}} {set s1 _}
1296
1297        if {$s0 eq {A} && $s1 eq {_} && $head_info eq {}} {
1298                set head_info [list 0 $null_sha1]
1299        } elseif {$s0 ne {_} && [string index $state 0] eq {_}
1300                && $head_info eq {}} {
1301                set head_info $index_info
1302        }
1303
1304        set file_states($path) [list $s0$s1 $icon \
1305                $head_info $index_info \
1306                ]
1307        return $state
1308}
1309
1310proc display_file {path state} {
1311        global file_states file_lists selected_paths
1312
1313        set old_m [merge_state $path $state]
1314        set s $file_states($path)
1315        set new_m [lindex $s 0]
1316        set new_w [mapcol $new_m $path] 
1317        set old_w [mapcol $old_m $path]
1318        set new_icon [mapicon $new_m $path]
1319
1320        if {$new_m eq {__}} {
1321                set lno [lsearch -sorted $file_lists($old_w) $path]
1322                if {$lno >= 0} {
1323                        set file_lists($old_w) \
1324                                [lreplace $file_lists($old_w) $lno $lno]
1325                        incr lno
1326                        $old_w conf -state normal
1327                        $old_w delete $lno.0 [expr {$lno + 1}].0
1328                        $old_w conf -state disabled
1329                }
1330                unset file_states($path)
1331                catch {unset selected_paths($path)}
1332                return
1333        }
1334
1335        if {$new_w ne $old_w} {
1336                set lno [lsearch -sorted $file_lists($old_w) $path]
1337                if {$lno >= 0} {
1338                        set file_lists($old_w) \
1339                                [lreplace $file_lists($old_w) $lno $lno]
1340                        incr lno
1341                        $old_w conf -state normal
1342                        $old_w delete $lno.0 [expr {$lno + 1}].0
1343                        $old_w conf -state disabled
1344                }
1345
1346                lappend file_lists($new_w) $path
1347                set file_lists($new_w) [lsort $file_lists($new_w)]
1348                set lno [lsearch -sorted $file_lists($new_w) $path]
1349                incr lno
1350                $new_w conf -state normal
1351                $new_w image create $lno.0 \
1352                        -align center -padx 5 -pady 1 \
1353                        -name [lindex $s 1] \
1354                        -image $new_icon
1355                $new_w insert $lno.1 "[escape_path $path]\n"
1356                if {[catch {set in_sel $selected_paths($path)}]} {
1357                        set in_sel 0
1358                }
1359                if {$in_sel} {
1360                        $new_w tag add in_sel $lno.0 [expr {$lno + 1}].0
1361                }
1362                $new_w conf -state disabled
1363        } elseif {$new_icon ne [mapicon $old_m $path]} {
1364                $new_w conf -state normal
1365                $new_w image conf [lindex $s 1] -image $new_icon
1366                $new_w conf -state disabled
1367        }
1368}
1369
1370proc display_all_files {} {
1371        global ui_index ui_other
1372        global file_states file_lists
1373        global last_clicked selected_paths
1374
1375        $ui_index conf -state normal
1376        $ui_other conf -state normal
1377
1378        $ui_index delete 0.0 end
1379        $ui_other delete 0.0 end
1380        set last_clicked {}
1381
1382        set file_lists($ui_index) [list]
1383        set file_lists($ui_other) [list]
1384
1385        foreach path [lsort [array names file_states]] {
1386                set s $file_states($path)
1387                set m [lindex $s 0]
1388                set w [mapcol $m $path]
1389                lappend file_lists($w) $path
1390                set lno [expr {[lindex [split [$w index end] .] 0] - 1}]
1391                $w image create end \
1392                        -align center -padx 5 -pady 1 \
1393                        -name [lindex $s 1] \
1394                        -image [mapicon $m $path]
1395                $w insert end "[escape_path $path]\n"
1396                if {[catch {set in_sel $selected_paths($path)}]} {
1397                        set in_sel 0
1398                }
1399                if {$in_sel} {
1400                        $w tag add in_sel $lno.0 [expr {$lno + 1}].0
1401                }
1402        }
1403
1404        $ui_index conf -state disabled
1405        $ui_other conf -state disabled
1406}
1407
1408proc update_indexinfo {msg pathList after} {
1409        global update_index_cp ui_status_value
1410
1411        if {![lock_index update]} return
1412
1413        set update_index_cp 0
1414        set pathList [lsort $pathList]
1415        set totalCnt [llength $pathList]
1416        set batch [expr {int($totalCnt * .01) + 1}]
1417        if {$batch > 25} {set batch 25}
1418
1419        set ui_status_value [format \
1420                "$msg... %i/%i files (%.2f%%)" \
1421                $update_index_cp \
1422                $totalCnt \
1423                0.0]
1424        set fd [open "| git update-index -z --index-info" w]
1425        fconfigure $fd \
1426                -blocking 0 \
1427                -buffering full \
1428                -buffersize 512 \
1429                -translation binary
1430        fileevent $fd writable [list \
1431                write_update_indexinfo \
1432                $fd \
1433                $pathList \
1434                $totalCnt \
1435                $batch \
1436                $msg \
1437                $after \
1438                ]
1439}
1440
1441proc write_update_indexinfo {fd pathList totalCnt batch msg after} {
1442        global update_index_cp ui_status_value
1443        global file_states current_diff
1444
1445        if {$update_index_cp >= $totalCnt} {
1446                close $fd
1447                unlock_index
1448                uplevel #0 $after
1449                return
1450        }
1451
1452        for {set i $batch} \
1453                {$update_index_cp < $totalCnt && $i > 0} \
1454                {incr i -1} {
1455                set path [lindex $pathList $update_index_cp]
1456                incr update_index_cp
1457
1458                set s $file_states($path)
1459                switch -glob -- [lindex $s 0] {
1460                A? {set new _O}
1461                M? {set new _M}
1462                D_ {set new _D}
1463                D? {set new _?}
1464                ?? {continue}
1465                }
1466                set info [lindex $s 2]
1467                if {$info eq {}} continue
1468
1469                puts -nonewline $fd $info
1470                puts -nonewline $fd "\t"
1471                puts -nonewline $fd $path
1472                puts -nonewline $fd "\0"
1473                display_file $path $new
1474        }
1475
1476        set ui_status_value [format \
1477                "$msg... %i/%i files (%.2f%%)" \
1478                $update_index_cp \
1479                $totalCnt \
1480                [expr {100.0 * $update_index_cp / $totalCnt}]]
1481}
1482
1483proc update_index {msg pathList after} {
1484        global update_index_cp ui_status_value
1485
1486        if {![lock_index update]} return
1487
1488        set update_index_cp 0
1489        set pathList [lsort $pathList]
1490        set totalCnt [llength $pathList]
1491        set batch [expr {int($totalCnt * .01) + 1}]
1492        if {$batch > 25} {set batch 25}
1493
1494        set ui_status_value [format \
1495                "$msg... %i/%i files (%.2f%%)" \
1496                $update_index_cp \
1497                $totalCnt \
1498                0.0]
1499        set fd [open "| git update-index --add --remove -z --stdin" w]
1500        fconfigure $fd \
1501                -blocking 0 \
1502                -buffering full \
1503                -buffersize 512 \
1504                -translation binary
1505        fileevent $fd writable [list \
1506                write_update_index \
1507                $fd \
1508                $pathList \
1509                $totalCnt \
1510                $batch \
1511                $msg \
1512                $after \
1513                ]
1514}
1515
1516proc write_update_index {fd pathList totalCnt batch msg after} {
1517        global update_index_cp ui_status_value
1518        global file_states current_diff
1519
1520        if {$update_index_cp >= $totalCnt} {
1521                close $fd
1522                unlock_index
1523                uplevel #0 $after
1524                return
1525        }
1526
1527        for {set i $batch} \
1528                {$update_index_cp < $totalCnt && $i > 0} \
1529                {incr i -1} {
1530                set path [lindex $pathList $update_index_cp]
1531                incr update_index_cp
1532
1533                switch -glob -- [lindex $file_states($path) 0] {
1534                AD -
1535                MD -
1536                UD -
1537                _D {set new DD}
1538
1539                _M -
1540                MM -
1541                UM -
1542                U_ -
1543                M_ {set new M_}
1544
1545                _O -
1546                AM -
1547                A_ {set new A_}
1548
1549                ?? {continue}
1550                }
1551
1552                puts -nonewline $fd $path
1553                puts -nonewline $fd "\0"
1554                display_file $path $new
1555        }
1556
1557        set ui_status_value [format \
1558                "$msg... %i/%i files (%.2f%%)" \
1559                $update_index_cp \
1560                $totalCnt \
1561                [expr {100.0 * $update_index_cp / $totalCnt}]]
1562}
1563
1564proc checkout_index {msg pathList after} {
1565        global update_index_cp ui_status_value
1566
1567        if {![lock_index update]} return
1568
1569        set update_index_cp 0
1570        set pathList [lsort $pathList]
1571        set totalCnt [llength $pathList]
1572        set batch [expr {int($totalCnt * .01) + 1}]
1573        if {$batch > 25} {set batch 25}
1574
1575        set ui_status_value [format \
1576                "$msg... %i/%i files (%.2f%%)" \
1577                $update_index_cp \
1578                $totalCnt \
1579                0.0]
1580        set cmd [list git checkout-index]
1581        lappend cmd --index
1582        lappend cmd --quiet
1583        lappend cmd --force
1584        lappend cmd -z
1585        lappend cmd --stdin
1586        set fd [open "| $cmd " w]
1587        fconfigure $fd \
1588                -blocking 0 \
1589                -buffering full \
1590                -buffersize 512 \
1591                -translation binary
1592        fileevent $fd writable [list \
1593                write_checkout_index \
1594                $fd \
1595                $pathList \
1596                $totalCnt \
1597                $batch \
1598                $msg \
1599                $after \
1600                ]
1601}
1602
1603proc write_checkout_index {fd pathList totalCnt batch msg after} {
1604        global update_index_cp ui_status_value
1605        global file_states current_diff
1606
1607        if {$update_index_cp >= $totalCnt} {
1608                close $fd
1609                unlock_index
1610                uplevel #0 $after
1611                return
1612        }
1613
1614        for {set i $batch} \
1615                {$update_index_cp < $totalCnt && $i > 0} \
1616                {incr i -1} {
1617                set path [lindex $pathList $update_index_cp]
1618                incr update_index_cp
1619
1620                switch -glob -- [lindex $file_states($path) 0] {
1621                AM -
1622                AD {set new A_}
1623                MM -
1624                MD {set new M_}
1625                _M -
1626                _D {set new __}
1627                ?? {continue}
1628                }
1629
1630                puts -nonewline $fd $path
1631                puts -nonewline $fd "\0"
1632                display_file $path $new
1633        }
1634
1635        set ui_status_value [format \
1636                "$msg... %i/%i files (%.2f%%)" \
1637                $update_index_cp \
1638                $totalCnt \
1639                [expr {100.0 * $update_index_cp / $totalCnt}]]
1640}
1641
1642######################################################################
1643##
1644## branch management
1645
1646proc load_all_heads {} {
1647        global all_heads tracking_branches
1648
1649        set all_heads [list]
1650        set cmd [list git for-each-ref]
1651        lappend cmd --format=%(refname)
1652        lappend cmd refs/heads
1653        set fd [open "| $cmd" r]
1654        while {[gets $fd line] > 0} {
1655                if {![catch {set info $tracking_branches($line)}]} continue
1656                if {![regsub ^refs/heads/ $line {} name]} continue
1657                lappend all_heads $name
1658        }
1659        close $fd
1660
1661        set all_heads [lsort $all_heads]
1662}
1663
1664proc populate_branch_menu {m} {
1665        global all_heads disable_on_lock
1666
1667        $m add separator
1668        foreach b $all_heads {
1669                $m add radiobutton \
1670                        -label $b \
1671                        -command [list switch_branch $b] \
1672                        -variable current_branch \
1673                        -value $b \
1674                        -font font_ui
1675                lappend disable_on_lock \
1676                        [list $m entryconf [$m index last] -state]
1677        }
1678}
1679
1680proc do_create_branch {} {
1681        error "NOT IMPLEMENTED"
1682}
1683
1684proc do_delete_branch {} {
1685        error "NOT IMPLEMENTED"
1686}
1687
1688proc switch_branch {b} {
1689        global HEAD commit_type file_states current_branch
1690        global selected_commit_type ui_comm
1691
1692        if {![lock_index switch]} return
1693
1694        # -- Backup the selected branch (repository_state resets it)
1695        #
1696        set new_branch $current_branch
1697
1698        # -- Our in memory state should match the repository.
1699        #
1700        repository_state curType curHEAD curMERGE_HEAD
1701        if {[string match amend* $commit_type]
1702                && $curType eq {normal}
1703                && $curHEAD eq $HEAD} {
1704        } elseif {$commit_type ne $curType || $HEAD ne $curHEAD} {
1705                info_popup {Last scanned state does not match repository state.
1706
1707Another Git program has modified this repository
1708since the last scan.  A rescan must be performed
1709before the current branch can be changed.
1710
1711The rescan will be automatically started now.
1712}
1713                unlock_index
1714                rescan {set ui_status_value {Ready.}}
1715                return
1716        }
1717
1718        # -- Toss the message buffer if we are in amend mode.
1719        #
1720        if {[string match amend* $curType]} {
1721                $ui_comm delete 0.0 end
1722                $ui_comm edit reset
1723                $ui_comm edit modified false
1724        }
1725
1726        set selected_commit_type new
1727        set current_branch $new_branch
1728
1729        unlock_index
1730        error "NOT FINISHED"
1731}
1732
1733######################################################################
1734##
1735## remote management
1736
1737proc load_all_remotes {} {
1738        global repo_config
1739        global all_remotes tracking_branches
1740
1741        set all_remotes [list]
1742        array unset tracking_branches
1743
1744        set rm_dir [gitdir remotes]
1745        if {[file isdirectory $rm_dir]} {
1746                set all_remotes [glob \
1747                        -types f \
1748                        -tails \
1749                        -nocomplain \
1750                        -directory $rm_dir *]
1751
1752                foreach name $all_remotes {
1753                        catch {
1754                                set fd [open [file join $rm_dir $name] r]
1755                                while {[gets $fd line] >= 0} {
1756                                        if {![regexp {^Pull:[   ]*([^:]+):(.+)$} \
1757                                                $line line src dst]} continue
1758                                        if {![regexp ^refs/ $dst]} {
1759                                                set dst "refs/heads/$dst"
1760                                        }
1761                                        set tracking_branches($dst) [list $name $src]
1762                                }
1763                                close $fd
1764                        }
1765                }
1766        }
1767
1768        foreach line [array names repo_config remote.*.url] {
1769                if {![regexp ^remote\.(.*)\.url\$ $line line name]} continue
1770                lappend all_remotes $name
1771
1772                if {[catch {set fl $repo_config(remote.$name.fetch)}]} {
1773                        set fl {}
1774                }
1775                foreach line $fl {
1776                        if {![regexp {^([^:]+):(.+)$} $line line src dst]} continue
1777                        if {![regexp ^refs/ $dst]} {
1778                                set dst "refs/heads/$dst"
1779                        }
1780                        set tracking_branches($dst) [list $name $src]
1781                }
1782        }
1783
1784        set all_remotes [lsort -unique $all_remotes]
1785}
1786
1787proc populate_fetch_menu {m} {
1788        global all_remotes repo_config
1789
1790        foreach r $all_remotes {
1791                set enable 0
1792                if {![catch {set a $repo_config(remote.$r.url)}]} {
1793                        if {![catch {set a $repo_config(remote.$r.fetch)}]} {
1794                                set enable 1
1795                        }
1796                } else {
1797                        catch {
1798                                set fd [open [gitdir remotes $r] r]
1799                                while {[gets $fd n] >= 0} {
1800                                        if {[regexp {^Pull:[ \t]*([^:]+):} $n]} {
1801                                                set enable 1
1802                                                break
1803                                        }
1804                                }
1805                                close $fd
1806                        }
1807                }
1808
1809                if {$enable} {
1810                        $m add command \
1811                                -label "Fetch from $r..." \
1812                                -command [list fetch_from $r] \
1813                                -font font_ui
1814                }
1815        }
1816}
1817
1818proc populate_push_menu {m} {
1819        global all_remotes repo_config
1820
1821        foreach r $all_remotes {
1822                set enable 0
1823                if {![catch {set a $repo_config(remote.$r.url)}]} {
1824                        if {![catch {set a $repo_config(remote.$r.push)}]} {
1825                                set enable 1
1826                        }
1827                } else {
1828                        catch {
1829                                set fd [open [gitdir remotes $r] r]
1830                                while {[gets $fd n] >= 0} {
1831                                        if {[regexp {^Push:[ \t]*([^:]+):} $n]} {
1832                                                set enable 1
1833                                                break
1834                                        }
1835                                }
1836                                close $fd
1837                        }
1838                }
1839
1840                if {$enable} {
1841                        $m add command \
1842                                -label "Push to $r..." \
1843                                -command [list push_to $r] \
1844                                -font font_ui
1845                }
1846        }
1847}
1848
1849proc populate_pull_menu {m} {
1850        global repo_config all_remotes disable_on_lock
1851
1852        foreach remote $all_remotes {
1853                set rb_list [list]
1854                if {[array get repo_config remote.$remote.url] ne {}} {
1855                        if {[array get repo_config remote.$remote.fetch] ne {}} {
1856                                foreach line $repo_config(remote.$remote.fetch) {
1857                                        if {[regexp {^([^:]+):} $line line rb]} {
1858                                                lappend rb_list $rb
1859                                        }
1860                                }
1861                        }
1862                } else {
1863                        catch {
1864                                set fd [open [gitdir remotes $remote] r]
1865                                while {[gets $fd line] >= 0} {
1866                                        if {[regexp {^Pull:[ \t]*([^:]+):} $line line rb]} {
1867                                                lappend rb_list $rb
1868                                        }
1869                                }
1870                                close $fd
1871                        }
1872                }
1873
1874                foreach rb $rb_list {
1875                        regsub ^refs/heads/ $rb {} rb_short
1876                        $m add command \
1877                                -label "Branch $rb_short from $remote..." \
1878                                -command [list pull_remote $remote $rb] \
1879                                -font font_ui
1880                        lappend disable_on_lock \
1881                                [list $m entryconf [$m index last] -state]
1882                }
1883        }
1884}
1885
1886######################################################################
1887##
1888## icons
1889
1890set filemask {
1891#define mask_width 14
1892#define mask_height 15
1893static unsigned char mask_bits[] = {
1894   0xfe, 0x1f, 0xfe, 0x1f, 0xfe, 0x1f, 0xfe, 0x1f, 0xfe, 0x1f, 0xfe, 0x1f,
1895   0xfe, 0x1f, 0xfe, 0x1f, 0xfe, 0x1f, 0xfe, 0x1f, 0xfe, 0x1f, 0xfe, 0x1f,
1896   0xfe, 0x1f, 0xfe, 0x1f, 0xfe, 0x1f};
1897}
1898
1899image create bitmap file_plain -background white -foreground black -data {
1900#define plain_width 14
1901#define plain_height 15
1902static unsigned char plain_bits[] = {
1903   0xfe, 0x01, 0x02, 0x03, 0x02, 0x05, 0x02, 0x09, 0x02, 0x1f, 0x02, 0x10,
1904   0x02, 0x10, 0x02, 0x10, 0x02, 0x10, 0x02, 0x10, 0x02, 0x10, 0x02, 0x10,
1905   0x02, 0x10, 0x02, 0x10, 0xfe, 0x1f};
1906} -maskdata $filemask
1907
1908image create bitmap file_mod -background white -foreground blue -data {
1909#define mod_width 14
1910#define mod_height 15
1911static unsigned char mod_bits[] = {
1912   0xfe, 0x01, 0x02, 0x03, 0x7a, 0x05, 0x02, 0x09, 0x7a, 0x1f, 0x02, 0x10,
1913   0xfa, 0x17, 0x02, 0x10, 0xfa, 0x17, 0x02, 0x10, 0xfa, 0x17, 0x02, 0x10,
1914   0xfa, 0x17, 0x02, 0x10, 0xfe, 0x1f};
1915} -maskdata $filemask
1916
1917image create bitmap file_fulltick -background white -foreground "#007000" -data {
1918#define file_fulltick_width 14
1919#define file_fulltick_height 15
1920static unsigned char file_fulltick_bits[] = {
1921   0xfe, 0x01, 0x02, 0x1a, 0x02, 0x0c, 0x02, 0x0c, 0x02, 0x16, 0x02, 0x16,
1922   0x02, 0x13, 0x00, 0x13, 0x86, 0x11, 0x8c, 0x11, 0xd8, 0x10, 0xf2, 0x10,
1923   0x62, 0x10, 0x02, 0x10, 0xfe, 0x1f};
1924} -maskdata $filemask
1925
1926image create bitmap file_parttick -background white -foreground "#005050" -data {
1927#define parttick_width 14
1928#define parttick_height 15
1929static unsigned char parttick_bits[] = {
1930   0xfe, 0x01, 0x02, 0x03, 0x7a, 0x05, 0x02, 0x09, 0x7a, 0x1f, 0x02, 0x10,
1931   0x7a, 0x14, 0x02, 0x16, 0x02, 0x13, 0x8a, 0x11, 0xda, 0x10, 0x72, 0x10,
1932   0x22, 0x10, 0x02, 0x10, 0xfe, 0x1f};
1933} -maskdata $filemask
1934
1935image create bitmap file_question -background white -foreground black -data {
1936#define file_question_width 14
1937#define file_question_height 15
1938static unsigned char file_question_bits[] = {
1939   0xfe, 0x01, 0x02, 0x02, 0xe2, 0x04, 0xf2, 0x09, 0x1a, 0x1b, 0x0a, 0x13,
1940   0x82, 0x11, 0xc2, 0x10, 0x62, 0x10, 0x62, 0x10, 0x02, 0x10, 0x62, 0x10,
1941   0x62, 0x10, 0x02, 0x10, 0xfe, 0x1f};
1942} -maskdata $filemask
1943
1944image create bitmap file_removed -background white -foreground red -data {
1945#define file_removed_width 14
1946#define file_removed_height 15
1947static unsigned char file_removed_bits[] = {
1948   0xfe, 0x01, 0x02, 0x03, 0x02, 0x05, 0x02, 0x09, 0x02, 0x1f, 0x02, 0x10,
1949   0x1a, 0x16, 0x32, 0x13, 0xe2, 0x11, 0xc2, 0x10, 0xe2, 0x11, 0x32, 0x13,
1950   0x1a, 0x16, 0x02, 0x10, 0xfe, 0x1f};
1951} -maskdata $filemask
1952
1953image create bitmap file_merge -background white -foreground blue -data {
1954#define file_merge_width 14
1955#define file_merge_height 15
1956static unsigned char file_merge_bits[] = {
1957   0xfe, 0x01, 0x02, 0x03, 0x62, 0x05, 0x62, 0x09, 0x62, 0x1f, 0x62, 0x10,
1958   0xfa, 0x11, 0xf2, 0x10, 0x62, 0x10, 0x02, 0x10, 0xfa, 0x17, 0x02, 0x10,
1959   0xfa, 0x17, 0x02, 0x10, 0xfe, 0x1f};
1960} -maskdata $filemask
1961
1962set ui_index .vpane.files.index.list
1963set ui_other .vpane.files.other.list
1964set max_status_desc 0
1965foreach i {
1966                {__ i plain    "Unmodified"}
1967                {_M i mod      "Modified"}
1968                {M_ i fulltick "Added to commit"}
1969                {MM i parttick "Partially included"}
1970                {MD i question "Added (but gone)"}
1971
1972                {_O o plain    "Untracked"}
1973                {A_ o fulltick "Added by commit"}
1974                {AM o parttick "Partially added"}
1975                {AD o question "Added (but gone)"}
1976
1977                {_D i question "Missing"}
1978                {DD i removed  "Removed by commit"}
1979                {D_ i removed  "Removed by commit"}
1980                {DO i removed  "Removed (still exists)"}
1981                {DM i removed  "Removed (but modified)"}
1982
1983                {UD i merge    "Merge conflicts"}
1984                {UM i merge    "Merge conflicts"}
1985                {U_ i merge    "Merge conflicts"}
1986        } {
1987        if {$max_status_desc < [string length [lindex $i 3]]} {
1988                set max_status_desc [string length [lindex $i 3]]
1989        }
1990        if {[lindex $i 1] eq {i}} {
1991                set all_cols([lindex $i 0]) $ui_index
1992        } else {
1993                set all_cols([lindex $i 0]) $ui_other
1994        }
1995        set all_icons([lindex $i 0]) file_[lindex $i 2]
1996        set all_descs([lindex $i 0]) [lindex $i 3]
1997}
1998unset filemask i
1999
2000######################################################################
2001##
2002## util
2003
2004proc is_MacOSX {} {
2005        global tcl_platform tk_library
2006        if {[tk windowingsystem] eq {aqua}} {
2007                return 1
2008        }
2009        return 0
2010}
2011
2012proc is_Windows {} {
2013        global tcl_platform
2014        if {$tcl_platform(platform) eq {windows}} {
2015                return 1
2016        }
2017        return 0
2018}
2019
2020proc bind_button3 {w cmd} {
2021        bind $w <Any-Button-3> $cmd
2022        if {[is_MacOSX]} {
2023                bind $w <Control-Button-1> $cmd
2024        }
2025}
2026
2027proc incr_font_size {font {amt 1}} {
2028        set sz [font configure $font -size]
2029        incr sz $amt
2030        font configure $font -size $sz
2031        font configure ${font}bold -size $sz
2032}
2033
2034proc hook_failed_popup {hook msg} {
2035        set w .hookfail
2036        toplevel $w
2037
2038        frame $w.m
2039        label $w.m.l1 -text "$hook hook failed:" \
2040                -anchor w \
2041                -justify left \
2042                -font font_uibold
2043        text $w.m.t \
2044                -background white -borderwidth 1 \
2045                -relief sunken \
2046                -width 80 -height 10 \
2047                -font font_diff \
2048                -yscrollcommand [list $w.m.sby set]
2049        label $w.m.l2 \
2050                -text {You must correct the above errors before committing.} \
2051                -anchor w \
2052                -justify left \
2053                -font font_uibold
2054        scrollbar $w.m.sby -command [list $w.m.t yview]
2055        pack $w.m.l1 -side top -fill x
2056        pack $w.m.l2 -side bottom -fill x
2057        pack $w.m.sby -side right -fill y
2058        pack $w.m.t -side left -fill both -expand 1
2059        pack $w.m -side top -fill both -expand 1 -padx 5 -pady 10
2060
2061        $w.m.t insert 1.0 $msg
2062        $w.m.t conf -state disabled
2063
2064        button $w.ok -text OK \
2065                -width 15 \
2066                -font font_ui \
2067                -command "destroy $w"
2068        pack $w.ok -side bottom -anchor e -pady 10 -padx 10
2069
2070        bind $w <Visibility> "grab $w; focus $w"
2071        bind $w <Key-Return> "destroy $w"
2072        wm title $w "[appname] ([reponame]): error"
2073        tkwait window $w
2074}
2075
2076set next_console_id 0
2077
2078proc new_console {short_title long_title} {
2079        global next_console_id console_data
2080        set w .console[incr next_console_id]
2081        set console_data($w) [list $short_title $long_title]
2082        return [console_init $w]
2083}
2084
2085proc console_init {w} {
2086        global console_cr console_data M1B
2087
2088        set console_cr($w) 1.0
2089        toplevel $w
2090        frame $w.m
2091        label $w.m.l1 -text "[lindex $console_data($w) 1]:" \
2092                -anchor w \
2093                -justify left \
2094                -font font_uibold
2095        text $w.m.t \
2096                -background white -borderwidth 1 \
2097                -relief sunken \
2098                -width 80 -height 10 \
2099                -font font_diff \
2100                -state disabled \
2101                -yscrollcommand [list $w.m.sby set]
2102        label $w.m.s -text {Working... please wait...} \
2103                -anchor w \
2104                -justify left \
2105                -font font_uibold
2106        scrollbar $w.m.sby -command [list $w.m.t yview]
2107        pack $w.m.l1 -side top -fill x
2108        pack $w.m.s -side bottom -fill x
2109        pack $w.m.sby -side right -fill y
2110        pack $w.m.t -side left -fill both -expand 1
2111        pack $w.m -side top -fill both -expand 1 -padx 5 -pady 10
2112
2113        menu $w.ctxm -tearoff 0
2114        $w.ctxm add command -label "Copy" \
2115                -font font_ui \
2116                -command "tk_textCopy $w.m.t"
2117        $w.ctxm add command -label "Select All" \
2118                -font font_ui \
2119                -command "$w.m.t tag add sel 0.0 end"
2120        $w.ctxm add command -label "Copy All" \
2121                -font font_ui \
2122                -command "
2123                        $w.m.t tag add sel 0.0 end
2124                        tk_textCopy $w.m.t
2125                        $w.m.t tag remove sel 0.0 end
2126                "
2127
2128        button $w.ok -text {Close} \
2129                -font font_ui \
2130                -state disabled \
2131                -command "destroy $w"
2132        pack $w.ok -side bottom -anchor e -pady 10 -padx 10
2133
2134        bind_button3 $w.m.t "tk_popup $w.ctxm %X %Y"
2135        bind $w.m.t <$M1B-Key-a> "$w.m.t tag add sel 0.0 end;break"
2136        bind $w.m.t <$M1B-Key-A> "$w.m.t tag add sel 0.0 end;break"
2137        bind $w <Visibility> "focus $w"
2138        wm title $w "[appname] ([reponame]): [lindex $console_data($w) 0]"
2139        return $w
2140}
2141
2142proc console_exec {w cmd {after {}}} {
2143        # -- Windows tosses the enviroment when we exec our child.
2144        #    But most users need that so we have to relogin. :-(
2145        #
2146        if {[is_Windows]} {
2147                set cmd [list sh --login -c "cd \"[pwd]\" && [join $cmd { }]"]
2148        }
2149
2150        # -- Tcl won't let us redirect both stdout and stderr to
2151        #    the same pipe.  So pass it through cat...
2152        #
2153        set cmd [concat | $cmd |& cat]
2154
2155        set fd_f [open $cmd r]
2156        fconfigure $fd_f -blocking 0 -translation binary
2157        fileevent $fd_f readable [list console_read $w $fd_f $after]
2158}
2159
2160proc console_read {w fd after} {
2161        global console_cr console_data
2162
2163        set buf [read $fd]
2164        if {$buf ne {}} {
2165                if {![winfo exists $w]} {console_init $w}
2166                $w.m.t conf -state normal
2167                set c 0
2168                set n [string length $buf]
2169                while {$c < $n} {
2170                        set cr [string first "\r" $buf $c]
2171                        set lf [string first "\n" $buf $c]
2172                        if {$cr < 0} {set cr [expr {$n + 1}]}
2173                        if {$lf < 0} {set lf [expr {$n + 1}]}
2174
2175                        if {$lf < $cr} {
2176                                $w.m.t insert end [string range $buf $c $lf]
2177                                set console_cr($w) [$w.m.t index {end -1c}]
2178                                set c $lf
2179                                incr c
2180                        } else {
2181                                $w.m.t delete $console_cr($w) end
2182                                $w.m.t insert end "\n"
2183                                $w.m.t insert end [string range $buf $c $cr]
2184                                set c $cr
2185                                incr c
2186                        }
2187                }
2188                $w.m.t conf -state disabled
2189                $w.m.t see end
2190        }
2191
2192        fconfigure $fd -blocking 1
2193        if {[eof $fd]} {
2194                if {[catch {close $fd}]} {
2195                        if {![winfo exists $w]} {console_init $w}
2196                        $w.m.s conf -background red -text {Error: Command Failed}
2197                        $w.ok conf -state normal
2198                        set ok 0
2199                } elseif {[winfo exists $w]} {
2200                        $w.m.s conf -background green -text {Success}
2201                        $w.ok conf -state normal
2202                        set ok 1
2203                }
2204                array unset console_cr $w
2205                array unset console_data $w
2206                if {$after ne {}} {
2207                        uplevel #0 $after $ok
2208                }
2209                return
2210        }
2211        fconfigure $fd -blocking 0
2212}
2213
2214######################################################################
2215##
2216## ui commands
2217
2218set starting_gitk_msg {Please wait... Starting gitk...}
2219
2220proc do_gitk {revs} {
2221        global ui_status_value starting_gitk_msg
2222
2223        set cmd gitk
2224        if {$revs ne {}} {
2225                append cmd { }
2226                append cmd $revs
2227        }
2228        if {[is_Windows]} {
2229                set cmd "sh -c \"exec $cmd\""
2230        }
2231        append cmd { &}
2232
2233        if {[catch {eval exec $cmd} err]} {
2234                error_popup "Failed to start gitk:\n\n$err"
2235        } else {
2236                set ui_status_value $starting_gitk_msg
2237                after 10000 {
2238                        if {$ui_status_value eq $starting_gitk_msg} {
2239                                set ui_status_value {Ready.}
2240                        }
2241                }
2242        }
2243}
2244
2245proc do_gc {} {
2246        set w [new_console {gc} {Compressing the object database}]
2247        console_exec $w {git gc}
2248}
2249
2250proc do_fsck_objects {} {
2251        set w [new_console {fsck-objects} \
2252                {Verifying the object database with fsck-objects}]
2253        set cmd [list git fsck-objects]
2254        lappend cmd --full
2255        lappend cmd --cache
2256        lappend cmd --strict
2257        console_exec $w $cmd
2258}
2259
2260set is_quitting 0
2261
2262proc do_quit {} {
2263        global ui_comm is_quitting repo_config commit_type
2264
2265        if {$is_quitting} return
2266        set is_quitting 1
2267
2268        # -- Stash our current commit buffer.
2269        #
2270        set save [gitdir GITGUI_MSG]
2271        set msg [string trim [$ui_comm get 0.0 end]]
2272        if {![string match amend* $commit_type]
2273                && [$ui_comm edit modified]
2274                && $msg ne {}} {
2275                catch {
2276                        set fd [open $save w]
2277                        puts $fd [string trim [$ui_comm get 0.0 end]]
2278                        close $fd
2279                }
2280        } else {
2281                catch {file delete $save}
2282        }
2283
2284        # -- Stash our current window geometry into this repository.
2285        #
2286        set cfg_geometry [list]
2287        lappend cfg_geometry [wm geometry .]
2288        lappend cfg_geometry [lindex [.vpane sash coord 0] 1]
2289        lappend cfg_geometry [lindex [.vpane.files sash coord 0] 0]
2290        if {[catch {set rc_geometry $repo_config(gui.geometry)}]} {
2291                set rc_geometry {}
2292        }
2293        if {$cfg_geometry ne $rc_geometry} {
2294                catch {exec git repo-config gui.geometry $cfg_geometry}
2295        }
2296
2297        destroy .
2298}
2299
2300proc do_rescan {} {
2301        rescan {set ui_status_value {Ready.}}
2302}
2303
2304proc remove_helper {txt paths} {
2305        global file_states current_diff
2306
2307        if {![lock_index begin-update]} return
2308
2309        set pathList [list]
2310        set after {}
2311        foreach path $paths {
2312                switch -glob -- [lindex $file_states($path) 0] {
2313                A? -
2314                M? -
2315                D? {
2316                        lappend pathList $path
2317                        if {$path eq $current_diff} {
2318                                set after {reshow_diff;}
2319                        }
2320                }
2321                }
2322        }
2323        if {$pathList eq {}} {
2324                unlock_index
2325        } else {
2326                update_indexinfo \
2327                        $txt \
2328                        $pathList \
2329                        [concat $after {set ui_status_value {Ready.}}]
2330        }
2331}
2332
2333proc do_remove_selection {} {
2334        global current_diff selected_paths
2335
2336        if {[array size selected_paths] > 0} {
2337                remove_helper \
2338                        {Removing selected files from commit} \
2339                        [array names selected_paths]
2340        } elseif {$current_diff ne {}} {
2341                remove_helper \
2342                        "Removing [short_path $current_diff] from commit" \
2343                        [list $current_diff]
2344        }
2345}
2346
2347proc include_helper {txt paths} {
2348        global file_states current_diff
2349
2350        if {![lock_index begin-update]} return
2351
2352        set pathList [list]
2353        set after {}
2354        foreach path $paths {
2355                switch -glob -- [lindex $file_states($path) 0] {
2356                AM -
2357                AD -
2358                MM -
2359                MD -
2360                U? -
2361                _M -
2362                _D -
2363                _O {
2364                        lappend pathList $path
2365                        if {$path eq $current_diff} {
2366                                set after {reshow_diff;}
2367                        }
2368                }
2369                }
2370        }
2371        if {$pathList eq {}} {
2372                unlock_index
2373        } else {
2374                update_index \
2375                        $txt \
2376                        $pathList \
2377                        [concat $after {set ui_status_value {Ready to commit.}}]
2378        }
2379}
2380
2381proc do_include_selection {} {
2382        global current_diff selected_paths
2383
2384        if {[array size selected_paths] > 0} {
2385                include_helper \
2386                        {Adding selected files} \
2387                        [array names selected_paths]
2388        } elseif {$current_diff ne {}} {
2389                include_helper \
2390                        "Adding [short_path $current_diff]" \
2391                        [list $current_diff]
2392        }
2393}
2394
2395proc do_include_all {} {
2396        global file_states
2397
2398        set paths [list]
2399        foreach path [array names file_states] {
2400                switch -- [lindex $file_states($path) 0] {
2401                AM -
2402                AD -
2403                MM -
2404                MD -
2405                _M -
2406                _D {lappend paths $path}
2407                }
2408        }
2409        include_helper \
2410                {Adding all modified files} \
2411                $paths
2412}
2413
2414proc revert_helper {txt paths} {
2415        global file_states current_diff
2416
2417        if {![lock_index begin-update]} return
2418
2419        set pathList [list]
2420        set after {}
2421        foreach path $paths {
2422                switch -glob -- [lindex $file_states($path) 0] {
2423                AM -
2424                AD -
2425                MM -
2426                MD -
2427                _M -
2428                _D {
2429                        lappend pathList $path
2430                        if {$path eq $current_diff} {
2431                                set after {reshow_diff;}
2432                        }
2433                }
2434                }
2435        }
2436
2437        set n [llength $pathList]
2438        if {$n == 0} {
2439                unlock_index
2440                return
2441        } elseif {$n == 1} {
2442                set s "[short_path [lindex $pathList]]"
2443        } else {
2444                set s "these $n files"
2445        }
2446
2447        set reply [tk_dialog \
2448                .confirm_revert \
2449                "[appname] ([reponame])" \
2450                "Revert changes in $s?
2451
2452Any unadded changes will be permanently lost by the revert." \
2453                question \
2454                1 \
2455                {Do Nothing} \
2456                {Revert Changes} \
2457                ]
2458        if {$reply == 1} {
2459                checkout_index \
2460                        $txt \
2461                        $pathList \
2462                        [concat $after {set ui_status_value {Ready.}}]
2463        } else {
2464                unlock_index
2465        }
2466}
2467
2468proc do_revert_selection {} {
2469        global current_diff selected_paths
2470
2471        if {[array size selected_paths] > 0} {
2472                revert_helper \
2473                        {Reverting selected files} \
2474                        [array names selected_paths]
2475        } elseif {$current_diff ne {}} {
2476                revert_helper \
2477                        "Reverting [short_path $current_diff]" \
2478                        [list $current_diff]
2479        }
2480}
2481
2482proc do_signoff {} {
2483        global ui_comm
2484
2485        set me [committer_ident]
2486        if {$me eq {}} return
2487
2488        set sob "Signed-off-by: $me"
2489        set last [$ui_comm get {end -1c linestart} {end -1c}]
2490        if {$last ne $sob} {
2491                $ui_comm edit separator
2492                if {$last ne {}
2493                        && ![regexp {^[A-Z][A-Za-z]*-[A-Za-z-]+: *} $last]} {
2494                        $ui_comm insert end "\n"
2495                }
2496                $ui_comm insert end "\n$sob"
2497                $ui_comm edit separator
2498                $ui_comm see end
2499        }
2500}
2501
2502proc do_select_commit_type {} {
2503        global commit_type selected_commit_type
2504
2505        if {$selected_commit_type eq {new}
2506                && [string match amend* $commit_type]} {
2507                create_new_commit
2508        } elseif {$selected_commit_type eq {amend}
2509                && ![string match amend* $commit_type]} {
2510                load_last_commit
2511
2512                # The amend request was rejected...
2513                #
2514                if {![string match amend* $commit_type]} {
2515                        set selected_commit_type new
2516                }
2517        }
2518}
2519
2520proc do_commit {} {
2521        commit_tree
2522}
2523
2524proc do_about {} {
2525        global appvers copyright
2526        global tcl_patchLevel tk_patchLevel
2527
2528        set w .about_dialog
2529        toplevel $w
2530        wm geometry $w "+[winfo rootx .]+[winfo rooty .]"
2531
2532        label $w.header -text "About [appname]" \
2533                -font font_uibold
2534        pack $w.header -side top -fill x
2535
2536        frame $w.buttons
2537        button $w.buttons.close -text {Close} \
2538                -font font_ui \
2539                -command [list destroy $w]
2540        pack $w.buttons.close -side right
2541        pack $w.buttons -side bottom -fill x -pady 10 -padx 10
2542
2543        label $w.desc \
2544                -text "[appname] - a commit creation tool for Git.
2545$copyright" \
2546                -padx 5 -pady 5 \
2547                -justify left \
2548                -anchor w \
2549                -borderwidth 1 \
2550                -relief solid \
2551                -font font_ui
2552        pack $w.desc -side top -fill x -padx 5 -pady 5
2553
2554        set v {}
2555        append v "[appname] version $appvers\n"
2556        append v "[exec git version]\n"
2557        append v "\n"
2558        if {$tcl_patchLevel eq $tk_patchLevel} {
2559                append v "Tcl/Tk version $tcl_patchLevel"
2560        } else {
2561                append v "Tcl version $tcl_patchLevel"
2562                append v ", Tk version $tk_patchLevel"
2563        }
2564
2565        label $w.vers \
2566                -text $v \
2567                -padx 5 -pady 5 \
2568                -justify left \
2569                -anchor w \
2570                -borderwidth 1 \
2571                -relief solid \
2572                -font font_ui
2573        pack $w.vers -side top -fill x -padx 5 -pady 5
2574
2575        menu $w.ctxm -tearoff 0
2576        $w.ctxm add command \
2577                -label {Copy} \
2578                -font font_ui \
2579                -command "
2580                clipboard clear
2581                clipboard append -format STRING -type STRING -- \[$w.vers cget -text\]
2582        "
2583
2584        bind $w <Visibility> "grab $w; focus $w"
2585        bind $w <Key-Escape> "destroy $w"
2586        bind_button3 $w.vers "tk_popup $w.ctxm %X %Y; grab $w; focus $w"
2587        wm title $w "About [appname]"
2588        tkwait window $w
2589}
2590
2591proc do_options {} {
2592        global repo_config global_config font_descs
2593        global repo_config_new global_config_new
2594
2595        array unset repo_config_new
2596        array unset global_config_new
2597        foreach name [array names repo_config] {
2598                set repo_config_new($name) $repo_config($name)
2599        }
2600        load_config 1
2601        foreach name [array names repo_config] {
2602                switch -- $name {
2603                gui.diffcontext {continue}
2604                }
2605                set repo_config_new($name) $repo_config($name)
2606        }
2607        foreach name [array names global_config] {
2608                set global_config_new($name) $global_config($name)
2609        }
2610
2611        set w .options_editor
2612        toplevel $w
2613        wm geometry $w "+[winfo rootx .]+[winfo rooty .]"
2614
2615        label $w.header -text "[appname] Options" \
2616                -font font_uibold
2617        pack $w.header -side top -fill x
2618
2619        frame $w.buttons
2620        button $w.buttons.restore -text {Restore Defaults} \
2621                -font font_ui \
2622                -command do_restore_defaults
2623        pack $w.buttons.restore -side left
2624        button $w.buttons.save -text Save \
2625                -font font_ui \
2626                -command [list do_save_config $w]
2627        pack $w.buttons.save -side right
2628        button $w.buttons.cancel -text {Cancel} \
2629                -font font_ui \
2630                -command [list destroy $w]
2631        pack $w.buttons.cancel -side right
2632        pack $w.buttons -side bottom -fill x -pady 10 -padx 10
2633
2634        labelframe $w.repo -text "[reponame] Repository" \
2635                -font font_ui \
2636                -relief raised -borderwidth 2
2637        labelframe $w.global -text {Global (All Repositories)} \
2638                -font font_ui \
2639                -relief raised -borderwidth 2
2640        pack $w.repo -side left -fill both -expand 1 -pady 5 -padx 5
2641        pack $w.global -side right -fill both -expand 1 -pady 5 -padx 5
2642
2643        foreach option {
2644                {b partialinclude {Allow Partially Added Files}}
2645                {b pullsummary {Show Pull Summary}}
2646                {b trustmtime  {Trust File Modification Timestamps}}
2647                {i diffcontext {Number of Diff Context Lines}}
2648                } {
2649                set type [lindex $option 0]
2650                set name [lindex $option 1]
2651                set text [lindex $option 2]
2652                foreach f {repo global} {
2653                        switch $type {
2654                        b {
2655                                checkbutton $w.$f.$name -text $text \
2656                                        -variable ${f}_config_new(gui.$name) \
2657                                        -onvalue true \
2658                                        -offvalue false \
2659                                        -font font_ui
2660                                pack $w.$f.$name -side top -anchor w
2661                        }
2662                        i {
2663                                frame $w.$f.$name
2664                                label $w.$f.$name.l -text "$text:" -font font_ui
2665                                pack $w.$f.$name.l -side left -anchor w -fill x
2666                                spinbox $w.$f.$name.v \
2667                                        -textvariable ${f}_config_new(gui.$name) \
2668                                        -from 1 -to 99 -increment 1 \
2669                                        -width 3 \
2670                                        -font font_ui
2671                                pack $w.$f.$name.v -side right -anchor e
2672                                pack $w.$f.$name -side top -anchor w -fill x
2673                        }
2674                        }
2675                }
2676        }
2677
2678        set all_fonts [lsort [font families]]
2679        foreach option $font_descs {
2680                set name [lindex $option 0]
2681                set font [lindex $option 1]
2682                set text [lindex $option 2]
2683
2684                set global_config_new(gui.$font^^family) \
2685                        [font configure $font -family]
2686                set global_config_new(gui.$font^^size) \
2687                        [font configure $font -size]
2688
2689                frame $w.global.$name
2690                label $w.global.$name.l -text "$text:" -font font_ui
2691                pack $w.global.$name.l -side left -anchor w -fill x
2692                eval tk_optionMenu $w.global.$name.family \
2693                        global_config_new(gui.$font^^family) \
2694                        $all_fonts
2695                spinbox $w.global.$name.size \
2696                        -textvariable global_config_new(gui.$font^^size) \
2697                        -from 2 -to 80 -increment 1 \
2698                        -width 3 \
2699                        -font font_ui
2700                pack $w.global.$name.size -side right -anchor e
2701                pack $w.global.$name.family -side right -anchor e
2702                pack $w.global.$name -side top -anchor w -fill x
2703        }
2704
2705        bind $w <Visibility> "grab $w; focus $w"
2706        bind $w <Key-Escape> "destroy $w"
2707        wm title $w "[appname] ([reponame]): Options"
2708        tkwait window $w
2709}
2710
2711proc do_restore_defaults {} {
2712        global font_descs default_config repo_config
2713        global repo_config_new global_config_new
2714
2715        foreach name [array names default_config] {
2716                set repo_config_new($name) $default_config($name)
2717                set global_config_new($name) $default_config($name)
2718        }
2719
2720        foreach option $font_descs {
2721                set name [lindex $option 0]
2722                set repo_config(gui.$name) $default_config(gui.$name)
2723        }
2724        apply_config
2725
2726        foreach option $font_descs {
2727                set name [lindex $option 0]
2728                set font [lindex $option 1]
2729                set global_config_new(gui.$font^^family) \
2730                        [font configure $font -family]
2731                set global_config_new(gui.$font^^size) \
2732                        [font configure $font -size]
2733        }
2734}
2735
2736proc do_save_config {w} {
2737        if {[catch {save_config} err]} {
2738                error_popup "Failed to completely save options:\n\n$err"
2739        }
2740        reshow_diff
2741        destroy $w
2742}
2743
2744proc do_windows_shortcut {} {
2745        global argv0
2746
2747        if {[catch {
2748                set desktop [exec cygpath \
2749                        --windows \
2750                        --absolute \
2751                        --long-name \
2752                        --desktop]
2753                }]} {
2754                        set desktop .
2755        }
2756        set fn [tk_getSaveFile \
2757                -parent . \
2758                -title "[appname] ([reponame]): Create Desktop Icon" \
2759                -initialdir $desktop \
2760                -initialfile "Git [reponame].bat"]
2761        if {$fn != {}} {
2762                if {[catch {
2763                                set fd [open $fn w]
2764                                set sh [exec cygpath \
2765                                        --windows \
2766                                        --absolute \
2767                                        /bin/sh]
2768                                set me [exec cygpath \
2769                                        --unix \
2770                                        --absolute \
2771                                        $argv0]
2772                                set gd [exec cygpath \
2773                                        --unix \
2774                                        --absolute \
2775                                        [gitdir]]
2776                                regsub -all ' $me "'\\''" me
2777                                regsub -all ' $gd "'\\''" gd
2778                                puts $fd "@ECHO Starting git-gui... Please wait..."
2779                                puts -nonewline $fd "@\"$sh\" --login -c \""
2780                                puts -nonewline $fd "GIT_DIR='$gd'"
2781                                puts -nonewline $fd " '$me'"
2782                                puts $fd "&\""
2783                                close $fd
2784                        } err]} {
2785                        error_popup "Cannot write script:\n\n$err"
2786                }
2787        }
2788}
2789
2790proc do_macosx_app {} {
2791        global argv0 env
2792
2793        set fn [tk_getSaveFile \
2794                -parent . \
2795                -title "[appname] ([reponame]): Create Desktop Icon" \
2796                -initialdir [file join $env(HOME) Desktop] \
2797                -initialfile "Git [reponame].app"]
2798        if {$fn != {}} {
2799                if {[catch {
2800                                set Contents [file join $fn Contents]
2801                                set MacOS [file join $Contents MacOS]
2802                                set exe [file join $MacOS git-gui]
2803
2804                                file mkdir $MacOS
2805
2806                                set fd [open [file join $Contents Info.plist] w]
2807                                puts $fd {<?xml version="1.0" encoding="UTF-8"?>
2808<!DOCTYPE plist PUBLIC "-//Apple Computer//DTD PLIST 1.0//EN" "http://www.apple.com/DTDs/PropertyList-1.0.dtd">
2809<plist version="1.0">
2810<dict>
2811        <key>CFBundleDevelopmentRegion</key>
2812        <string>English</string>
2813        <key>CFBundleExecutable</key>
2814        <string>git-gui</string>
2815        <key>CFBundleIdentifier</key>
2816        <string>org.spearce.git-gui</string>
2817        <key>CFBundleInfoDictionaryVersion</key>
2818        <string>6.0</string>
2819        <key>CFBundlePackageType</key>
2820        <string>APPL</string>
2821        <key>CFBundleSignature</key>
2822        <string>????</string>
2823        <key>CFBundleVersion</key>
2824        <string>1.0</string>
2825        <key>NSPrincipalClass</key>
2826        <string>NSApplication</string>
2827</dict>
2828</plist>}
2829                                close $fd
2830
2831                                set fd [open $exe w]
2832                                set gd [file normalize [gitdir]]
2833                                set ep [file normalize [exec git --exec-path]]
2834                                regsub -all ' $gd "'\\''" gd
2835                                regsub -all ' $ep "'\\''" ep
2836                                puts $fd "#!/bin/sh"
2837                                foreach name [array names env] {
2838                                        if {[string match GIT_* $name]} {
2839                                                regsub -all ' $env($name) "'\\''" v
2840                                                puts $fd "export $name='$v'"
2841                                        }
2842                                }
2843                                puts $fd "export PATH='$ep':\$PATH"
2844                                puts $fd "export GIT_DIR='$gd'"
2845                                puts $fd "exec [file normalize $argv0]"
2846                                close $fd
2847
2848                                file attributes $exe -permissions u+x,g+x,o+x
2849                        } err]} {
2850                        error_popup "Cannot write icon:\n\n$err"
2851                }
2852        }
2853}
2854
2855proc toggle_or_diff {w x y} {
2856        global file_states file_lists current_diff ui_index ui_other
2857        global last_clicked selected_paths
2858
2859        set pos [split [$w index @$x,$y] .]
2860        set lno [lindex $pos 0]
2861        set col [lindex $pos 1]
2862        set path [lindex $file_lists($w) [expr {$lno - 1}]]
2863        if {$path eq {}} {
2864                set last_clicked {}
2865                return
2866        }
2867
2868        set last_clicked [list $w $lno]
2869        array unset selected_paths
2870        $ui_index tag remove in_sel 0.0 end
2871        $ui_other tag remove in_sel 0.0 end
2872
2873        if {$col == 0} {
2874                if {$current_diff eq $path} {
2875                        set after {reshow_diff;}
2876                } else {
2877                        set after {}
2878                }
2879                switch -glob -- [lindex $file_states($path) 0] {
2880                A_ -
2881                M_ -
2882                DD -
2883                DO -
2884                DM {
2885                        update_indexinfo \
2886                                "Removing [short_path $path] from commit" \
2887                                [list $path] \
2888                                [concat $after {set ui_status_value {Ready.}}]
2889                }
2890                ?? {
2891                        update_index \
2892                                "Adding [short_path $path]" \
2893                                [list $path] \
2894                                [concat $after {set ui_status_value {Ready.}}]
2895                }
2896                }
2897        } else {
2898                show_diff $path $w $lno
2899        }
2900}
2901
2902proc add_one_to_selection {w x y} {
2903        global file_lists
2904        global last_clicked selected_paths
2905
2906        set pos [split [$w index @$x,$y] .]
2907        set lno [lindex $pos 0]
2908        set col [lindex $pos 1]
2909        set path [lindex $file_lists($w) [expr {$lno - 1}]]
2910        if {$path eq {}} {
2911                set last_clicked {}
2912                return
2913        }
2914
2915        set last_clicked [list $w $lno]
2916        if {[catch {set in_sel $selected_paths($path)}]} {
2917                set in_sel 0
2918        }
2919        if {$in_sel} {
2920                unset selected_paths($path)
2921                $w tag remove in_sel $lno.0 [expr {$lno + 1}].0
2922        } else {
2923                set selected_paths($path) 1
2924                $w tag add in_sel $lno.0 [expr {$lno + 1}].0
2925        }
2926}
2927
2928proc add_range_to_selection {w x y} {
2929        global file_lists
2930        global last_clicked selected_paths
2931
2932        if {[lindex $last_clicked 0] ne $w} {
2933                toggle_or_diff $w $x $y
2934                return
2935        }
2936
2937        set pos [split [$w index @$x,$y] .]
2938        set lno [lindex $pos 0]
2939        set lc [lindex $last_clicked 1]
2940        if {$lc < $lno} {
2941                set begin $lc
2942                set end $lno
2943        } else {
2944                set begin $lno
2945                set end $lc
2946        }
2947
2948        foreach path [lrange $file_lists($w) \
2949                [expr {$begin - 1}] \
2950                [expr {$end - 1}]] {
2951                set selected_paths($path) 1
2952        }
2953        $w tag add in_sel $begin.0 [expr {$end + 1}].0
2954}
2955
2956######################################################################
2957##
2958## config defaults
2959
2960set cursor_ptr arrow
2961font create font_diff -family Courier -size 10
2962font create font_ui
2963catch {
2964        label .dummy
2965        eval font configure font_ui [font actual [.dummy cget -font]]
2966        destroy .dummy
2967}
2968
2969font create font_uibold
2970font create font_diffbold
2971
2972if {[is_Windows]} {
2973        set M1B Control
2974        set M1T Ctrl
2975} elseif {[is_MacOSX]} {
2976        set M1B M1
2977        set M1T Cmd
2978} else {
2979        set M1B M1
2980        set M1T M1
2981}
2982
2983proc apply_config {} {
2984        global repo_config font_descs
2985
2986        foreach option $font_descs {
2987                set name [lindex $option 0]
2988                set font [lindex $option 1]
2989                if {[catch {
2990                        foreach {cn cv} $repo_config(gui.$name) {
2991                                font configure $font $cn $cv
2992                        }
2993                        } err]} {
2994                        error_popup "Invalid font specified in gui.$name:\n\n$err"
2995                }
2996                foreach {cn cv} [font configure $font] {
2997                        font configure ${font}bold $cn $cv
2998                }
2999                font configure ${font}bold -weight bold
3000        }
3001}
3002
3003set default_config(gui.trustmtime) false
3004set default_config(gui.pullsummary) true
3005set default_config(gui.partialinclude) false
3006set default_config(gui.diffcontext) 5
3007set default_config(gui.fontui) [font configure font_ui]
3008set default_config(gui.fontdiff) [font configure font_diff]
3009set font_descs {
3010        {fontui   font_ui   {Main Font}}
3011        {fontdiff font_diff {Diff/Console Font}}
3012}
3013load_config 0
3014apply_config
3015
3016######################################################################
3017##
3018## ui construction
3019
3020# -- Menu Bar
3021#
3022menu .mbar -tearoff 0
3023.mbar add cascade -label Repository -menu .mbar.repository
3024.mbar add cascade -label Edit -menu .mbar.edit
3025if {!$single_commit} {
3026        .mbar add cascade -label Branch -menu .mbar.branch
3027}
3028.mbar add cascade -label Commit -menu .mbar.commit
3029if {!$single_commit} {
3030        .mbar add cascade -label Fetch -menu .mbar.fetch
3031        .mbar add cascade -label Pull -menu .mbar.pull
3032        .mbar add cascade -label Push -menu .mbar.push
3033}
3034. configure -menu .mbar
3035
3036# -- Repository Menu
3037#
3038menu .mbar.repository
3039.mbar.repository add command \
3040        -label {Visualize Current Branch} \
3041        -command {do_gitk {}} \
3042        -font font_ui
3043if {![is_MacOSX]} {
3044        .mbar.repository add command \
3045                -label {Visualize All Branches} \
3046                -command {do_gitk {--all}} \
3047                -font font_ui
3048}
3049.mbar.repository add separator
3050
3051if {!$single_commit} {
3052        .mbar.repository add command -label {Compress Database} \
3053                -command do_gc \
3054                -font font_ui
3055
3056        .mbar.repository add command -label {Verify Database} \
3057                -command do_fsck_objects \
3058                -font font_ui
3059
3060        .mbar.repository add separator
3061
3062        if {[is_Windows]} {
3063                .mbar.repository add command \
3064                        -label {Create Desktop Icon} \
3065                        -command do_windows_shortcut \
3066                        -font font_ui
3067        } elseif {[is_MacOSX]} {
3068                .mbar.repository add command \
3069                        -label {Create Desktop Icon} \
3070                        -command do_macosx_app \
3071                        -font font_ui
3072        }
3073}
3074
3075.mbar.repository add command -label Quit \
3076        -command do_quit \
3077        -accelerator $M1T-Q \
3078        -font font_ui
3079
3080# -- Edit Menu
3081#
3082menu .mbar.edit
3083.mbar.edit add command -label Undo \
3084        -command {catch {[focus] edit undo}} \
3085        -accelerator $M1T-Z \
3086        -font font_ui
3087.mbar.edit add command -label Redo \
3088        -command {catch {[focus] edit redo}} \
3089        -accelerator $M1T-Y \
3090        -font font_ui
3091.mbar.edit add separator
3092.mbar.edit add command -label Cut \
3093        -command {catch {tk_textCut [focus]}} \
3094        -accelerator $M1T-X \
3095        -font font_ui
3096.mbar.edit add command -label Copy \
3097        -command {catch {tk_textCopy [focus]}} \
3098        -accelerator $M1T-C \
3099        -font font_ui
3100.mbar.edit add command -label Paste \
3101        -command {catch {tk_textPaste [focus]; [focus] see insert}} \
3102        -accelerator $M1T-V \
3103        -font font_ui
3104.mbar.edit add command -label Delete \
3105        -command {catch {[focus] delete sel.first sel.last}} \
3106        -accelerator Del \
3107        -font font_ui
3108.mbar.edit add separator
3109.mbar.edit add command -label {Select All} \
3110        -command {catch {[focus] tag add sel 0.0 end}} \
3111        -accelerator $M1T-A \
3112        -font font_ui
3113
3114# -- Branch Menu
3115#
3116if {!$single_commit} {
3117        menu .mbar.branch
3118
3119        .mbar.branch add command -label {Create...} \
3120                -command do_create_branch \
3121                -font font_ui
3122        lappend disable_on_lock [list .mbar.branch entryconf \
3123                [.mbar.branch index last] -state]
3124
3125        .mbar.branch add command -label {Delete...} \
3126                -command do_delete_branch \
3127                -font font_ui
3128        lappend disable_on_lock [list .mbar.branch entryconf \
3129                [.mbar.branch index last] -state]
3130}
3131
3132# -- Commit Menu
3133#
3134menu .mbar.commit
3135
3136.mbar.commit add radiobutton \
3137        -label {New Commit} \
3138        -command do_select_commit_type \
3139        -variable selected_commit_type \
3140        -value new \
3141        -font font_ui
3142lappend disable_on_lock \
3143        [list .mbar.commit entryconf [.mbar.commit index last] -state]
3144
3145.mbar.commit add radiobutton \
3146        -label {Amend Last Commit} \
3147        -command do_select_commit_type \
3148        -variable selected_commit_type \
3149        -value amend \
3150        -font font_ui
3151lappend disable_on_lock \
3152        [list .mbar.commit entryconf [.mbar.commit index last] -state]
3153
3154.mbar.commit add separator
3155
3156.mbar.commit add command -label Rescan \
3157        -command do_rescan \
3158        -accelerator F5 \
3159        -font font_ui
3160lappend disable_on_lock \
3161        [list .mbar.commit entryconf [.mbar.commit index last] -state]
3162
3163.mbar.commit add command -label {Add To Commit} \
3164        -command do_include_selection \
3165        -font font_ui
3166lappend disable_on_lock \
3167        [list .mbar.commit entryconf [.mbar.commit index last] -state]
3168
3169.mbar.commit add command -label {Add All To Commit} \
3170        -command do_include_all \
3171        -accelerator $M1T-I \
3172        -font font_ui
3173lappend disable_on_lock \
3174        [list .mbar.commit entryconf [.mbar.commit index last] -state]
3175
3176.mbar.commit add command -label {Remove From Commit} \
3177        -command do_remove_selection \
3178        -font font_ui
3179lappend disable_on_lock \
3180        [list .mbar.commit entryconf [.mbar.commit index last] -state]
3181
3182.mbar.commit add command -label {Revert Changes} \
3183        -command do_revert_selection \
3184        -font font_ui
3185lappend disable_on_lock \
3186        [list .mbar.commit entryconf [.mbar.commit index last] -state]
3187
3188.mbar.commit add separator
3189
3190.mbar.commit add command -label {Sign Off} \
3191        -command do_signoff \
3192        -accelerator $M1T-S \
3193        -font font_ui
3194
3195.mbar.commit add command -label Commit \
3196        -command do_commit \
3197        -accelerator $M1T-Return \
3198        -font font_ui
3199lappend disable_on_lock \
3200        [list .mbar.commit entryconf [.mbar.commit index last] -state]
3201
3202# -- Transport menus
3203#
3204if {!$single_commit} {
3205        menu .mbar.fetch
3206        menu .mbar.pull
3207        menu .mbar.push
3208}
3209
3210if {[is_MacOSX]} {
3211        # -- Apple Menu (Mac OS X only)
3212        #
3213        .mbar add cascade -label Apple -menu .mbar.apple
3214        menu .mbar.apple
3215
3216        .mbar.apple add command -label "About [appname]" \
3217                -command do_about \
3218                -font font_ui
3219        .mbar.apple add command -label "[appname] Options..." \
3220                -command do_options \
3221                -font font_ui
3222} else {
3223        # -- Edit Menu
3224        #
3225        .mbar.edit add separator
3226        .mbar.edit add command -label {Options...} \
3227                -command do_options \
3228                -font font_ui
3229
3230        # -- Tools Menu
3231        #
3232        if {[file exists /usr/local/miga/lib/gui-miga]
3233                && [file exists .pvcsrc]} {
3234        proc do_miga {} {
3235                global ui_status_value
3236                if {![lock_index update]} return
3237                set cmd [list sh --login -c "/usr/local/miga/lib/gui-miga \"[pwd]\""]
3238                set miga_fd [open "|$cmd" r]
3239                fconfigure $miga_fd -blocking 0
3240                fileevent $miga_fd readable [list miga_done $miga_fd]
3241                set ui_status_value {Running miga...}
3242        }
3243        proc miga_done {fd} {
3244                read $fd 512
3245                if {[eof $fd]} {
3246                        close $fd
3247                        unlock_index
3248                        rescan [list set ui_status_value {Ready.}]
3249                }
3250        }
3251        .mbar add cascade -label Tools -menu .mbar.tools
3252        menu .mbar.tools
3253        .mbar.tools add command -label "Migrate" \
3254                -command do_miga \
3255                -font font_ui
3256        lappend disable_on_lock \
3257                [list .mbar.tools entryconf [.mbar.tools index last] -state]
3258        }
3259
3260        # -- Help Menu
3261        #
3262        .mbar add cascade -label Help -menu .mbar.help
3263        menu .mbar.help
3264
3265        .mbar.help add command -label "About [appname]" \
3266                -command do_about \
3267                -font font_ui
3268}
3269
3270
3271# -- Branch Control
3272#
3273frame .branch \
3274        -borderwidth 1 \
3275        -relief sunken
3276label .branch.l1 \
3277        -text {Current Branch:} \
3278        -anchor w \
3279        -justify left \
3280        -font font_ui
3281label .branch.cb \
3282        -textvariable current_branch \
3283        -anchor w \
3284        -justify left \
3285        -font font_ui
3286pack .branch.l1 -side left
3287pack .branch.cb -side left -fill x
3288pack .branch -side top -fill x
3289
3290# -- Main Window Layout
3291#
3292panedwindow .vpane -orient vertical
3293panedwindow .vpane.files -orient horizontal
3294.vpane add .vpane.files -sticky nsew -height 100 -width 400
3295pack .vpane -anchor n -side top -fill both -expand 1
3296
3297# -- Index File List
3298#
3299frame .vpane.files.index -height 100 -width 400
3300label .vpane.files.index.title -text {Modified Files} \
3301        -background green \
3302        -font font_ui
3303text $ui_index -background white -borderwidth 0 \
3304        -width 40 -height 10 \
3305        -font font_ui \
3306        -cursor $cursor_ptr \
3307        -yscrollcommand {.vpane.files.index.sb set} \
3308        -state disabled
3309scrollbar .vpane.files.index.sb -command [list $ui_index yview]
3310pack .vpane.files.index.title -side top -fill x
3311pack .vpane.files.index.sb -side right -fill y
3312pack $ui_index -side left -fill both -expand 1
3313.vpane.files add .vpane.files.index -sticky nsew
3314
3315# -- Other (Add) File List
3316#
3317frame .vpane.files.other -height 100 -width 100
3318label .vpane.files.other.title -text {Untracked Files} \
3319        -background red \
3320        -font font_ui
3321text $ui_other -background white -borderwidth 0 \
3322        -width 40 -height 10 \
3323        -font font_ui \
3324        -cursor $cursor_ptr \
3325        -yscrollcommand {.vpane.files.other.sb set} \
3326        -state disabled
3327scrollbar .vpane.files.other.sb -command [list $ui_other yview]
3328pack .vpane.files.other.title -side top -fill x
3329pack .vpane.files.other.sb -side right -fill y
3330pack $ui_other -side left -fill both -expand 1
3331.vpane.files add .vpane.files.other -sticky nsew
3332
3333foreach i [list $ui_index $ui_other] {
3334        $i tag conf in_diff -font font_uibold
3335        $i tag conf in_sel \
3336                -background [$i cget -foreground] \
3337                -foreground [$i cget -background]
3338}
3339unset i
3340
3341# -- Diff and Commit Area
3342#
3343frame .vpane.lower -height 300 -width 400
3344frame .vpane.lower.commarea
3345frame .vpane.lower.diff -relief sunken -borderwidth 1
3346pack .vpane.lower.commarea -side top -fill x
3347pack .vpane.lower.diff -side bottom -fill both -expand 1
3348.vpane add .vpane.lower -stick nsew
3349
3350# -- Commit Area Buttons
3351#
3352frame .vpane.lower.commarea.buttons
3353label .vpane.lower.commarea.buttons.l -text {} \
3354        -anchor w \
3355        -justify left \
3356        -font font_ui
3357pack .vpane.lower.commarea.buttons.l -side top -fill x
3358pack .vpane.lower.commarea.buttons -side left -fill y
3359
3360button .vpane.lower.commarea.buttons.rescan -text {Rescan} \
3361        -command do_rescan \
3362        -font font_ui
3363pack .vpane.lower.commarea.buttons.rescan -side top -fill x
3364lappend disable_on_lock \
3365        {.vpane.lower.commarea.buttons.rescan conf -state}
3366
3367button .vpane.lower.commarea.buttons.incall -text {Add All} \
3368        -command do_include_all \
3369        -font font_ui
3370pack .vpane.lower.commarea.buttons.incall -side top -fill x
3371lappend disable_on_lock \
3372        {.vpane.lower.commarea.buttons.incall conf -state}
3373
3374button .vpane.lower.commarea.buttons.signoff -text {Sign Off} \
3375        -command do_signoff \
3376        -font font_ui
3377pack .vpane.lower.commarea.buttons.signoff -side top -fill x
3378
3379button .vpane.lower.commarea.buttons.commit -text {Commit} \
3380        -command do_commit \
3381        -font font_ui
3382pack .vpane.lower.commarea.buttons.commit -side top -fill x
3383lappend disable_on_lock \
3384        {.vpane.lower.commarea.buttons.commit conf -state}
3385
3386# -- Commit Message Buffer
3387#
3388frame .vpane.lower.commarea.buffer
3389frame .vpane.lower.commarea.buffer.header
3390set ui_comm .vpane.lower.commarea.buffer.t
3391set ui_coml .vpane.lower.commarea.buffer.header.l
3392radiobutton .vpane.lower.commarea.buffer.header.new \
3393        -text {New Commit} \
3394        -command do_select_commit_type \
3395        -variable selected_commit_type \
3396        -value new \
3397        -font font_ui
3398lappend disable_on_lock \
3399        [list .vpane.lower.commarea.buffer.header.new conf -state]
3400radiobutton .vpane.lower.commarea.buffer.header.amend \
3401        -text {Amend Last Commit} \
3402        -command do_select_commit_type \
3403        -variable selected_commit_type \
3404        -value amend \
3405        -font font_ui
3406lappend disable_on_lock \
3407        [list .vpane.lower.commarea.buffer.header.amend conf -state]
3408label $ui_coml \
3409        -anchor w \
3410        -justify left \
3411        -font font_ui
3412proc trace_commit_type {varname args} {
3413        global ui_coml commit_type
3414        switch -glob -- $commit_type {
3415        initial       {set txt {Initial Commit Message:}}
3416        amend         {set txt {Amended Commit Message:}}
3417        amend-initial {set txt {Amended Initial Commit Message:}}
3418        amend-merge   {set txt {Amended Merge Commit Message:}}
3419        merge         {set txt {Merge Commit Message:}}
3420        *             {set txt {Commit Message:}}
3421        }
3422        $ui_coml conf -text $txt
3423}
3424trace add variable commit_type write trace_commit_type
3425pack $ui_coml -side left -fill x
3426pack .vpane.lower.commarea.buffer.header.amend -side right
3427pack .vpane.lower.commarea.buffer.header.new -side right
3428
3429text $ui_comm -background white -borderwidth 1 \
3430        -undo true \
3431        -maxundo 20 \
3432        -autoseparators true \
3433        -relief sunken \
3434        -width 75 -height 9 -wrap none \
3435        -font font_diff \
3436        -yscrollcommand {.vpane.lower.commarea.buffer.sby set}
3437scrollbar .vpane.lower.commarea.buffer.sby \
3438        -command [list $ui_comm yview]
3439pack .vpane.lower.commarea.buffer.header -side top -fill x
3440pack .vpane.lower.commarea.buffer.sby -side right -fill y
3441pack $ui_comm -side left -fill y
3442pack .vpane.lower.commarea.buffer -side left -fill y
3443
3444# -- Commit Message Buffer Context Menu
3445#
3446set ctxm .vpane.lower.commarea.buffer.ctxm
3447menu $ctxm -tearoff 0
3448$ctxm add command \
3449        -label {Cut} \
3450        -font font_ui \
3451        -command {tk_textCut $ui_comm}
3452$ctxm add command \
3453        -label {Copy} \
3454        -font font_ui \
3455        -command {tk_textCopy $ui_comm}
3456$ctxm add command \
3457        -label {Paste} \
3458        -font font_ui \
3459        -command {tk_textPaste $ui_comm}
3460$ctxm add command \
3461        -label {Delete} \
3462        -font font_ui \
3463        -command {$ui_comm delete sel.first sel.last}
3464$ctxm add separator
3465$ctxm add command \
3466        -label {Select All} \
3467        -font font_ui \
3468        -command {$ui_comm tag add sel 0.0 end}
3469$ctxm add command \
3470        -label {Copy All} \
3471        -font font_ui \
3472        -command {
3473                $ui_comm tag add sel 0.0 end
3474                tk_textCopy $ui_comm
3475                $ui_comm tag remove sel 0.0 end
3476        }
3477$ctxm add separator
3478$ctxm add command \
3479        -label {Sign Off} \
3480        -font font_ui \
3481        -command do_signoff
3482bind_button3 $ui_comm "tk_popup $ctxm %X %Y"
3483
3484# -- Diff Header
3485#
3486set current_diff {}
3487set diff_actions [list]
3488proc trace_current_diff {varname args} {
3489        global current_diff diff_actions file_states
3490        if {$current_diff eq {}} {
3491                set s {}
3492                set f {}
3493                set p {}
3494                set o disabled
3495        } else {
3496                set p $current_diff
3497                set s [mapdesc [lindex $file_states($p) 0] $p]
3498                set f {File:}
3499                set p [escape_path $p]
3500                set o normal
3501        }
3502
3503        .vpane.lower.diff.header.status configure -text $s
3504        .vpane.lower.diff.header.file configure -text $f
3505        .vpane.lower.diff.header.path configure -text $p
3506        foreach w $diff_actions {
3507                uplevel #0 $w $o
3508        }
3509}
3510trace add variable current_diff write trace_current_diff
3511
3512frame .vpane.lower.diff.header -background orange
3513label .vpane.lower.diff.header.status \
3514        -background orange \
3515        -width $max_status_desc \
3516        -anchor w \
3517        -justify left \
3518        -font font_ui
3519label .vpane.lower.diff.header.file \
3520        -background orange \
3521        -anchor w \
3522        -justify left \
3523        -font font_ui
3524label .vpane.lower.diff.header.path \
3525        -background orange \
3526        -anchor w \
3527        -justify left \
3528        -font font_ui
3529pack .vpane.lower.diff.header.status -side left
3530pack .vpane.lower.diff.header.file -side left
3531pack .vpane.lower.diff.header.path -fill x
3532set ctxm .vpane.lower.diff.header.ctxm
3533menu $ctxm -tearoff 0
3534$ctxm add command \
3535        -label {Copy} \
3536        -font font_ui \
3537        -command {
3538                clipboard clear
3539                clipboard append \
3540                        -format STRING \
3541                        -type STRING \
3542                        -- $current_diff
3543        }
3544lappend diff_actions [list $ctxm entryconf [$ctxm index last] -state]
3545bind_button3 .vpane.lower.diff.header.path "tk_popup $ctxm %X %Y"
3546
3547# -- Diff Body
3548#
3549frame .vpane.lower.diff.body
3550set ui_diff .vpane.lower.diff.body.t
3551text $ui_diff -background white -borderwidth 0 \
3552        -width 80 -height 15 -wrap none \
3553        -font font_diff \
3554        -xscrollcommand {.vpane.lower.diff.body.sbx set} \
3555        -yscrollcommand {.vpane.lower.diff.body.sby set} \
3556        -state disabled
3557scrollbar .vpane.lower.diff.body.sbx -orient horizontal \
3558        -command [list $ui_diff xview]
3559scrollbar .vpane.lower.diff.body.sby -orient vertical \
3560        -command [list $ui_diff yview]
3561pack .vpane.lower.diff.body.sbx -side bottom -fill x
3562pack .vpane.lower.diff.body.sby -side right -fill y
3563pack $ui_diff -side left -fill both -expand 1
3564pack .vpane.lower.diff.header -side top -fill x
3565pack .vpane.lower.diff.body -side bottom -fill both -expand 1
3566
3567$ui_diff tag conf d_@ -font font_diffbold
3568$ui_diff tag conf d_+  -foreground blue
3569$ui_diff tag conf d_-  -foreground red
3570$ui_diff tag conf d_++ -foreground {#00a000}
3571$ui_diff tag conf d_-- -foreground {#a000a0}
3572$ui_diff tag conf d_+- \
3573        -foreground red \
3574        -background {light goldenrod yellow}
3575$ui_diff tag conf d_-+ \
3576        -foreground blue \
3577        -background azure2
3578
3579# -- Diff Body Context Menu
3580#
3581set ctxm .vpane.lower.diff.body.ctxm
3582menu $ctxm -tearoff 0
3583$ctxm add command \
3584        -label {Copy} \
3585        -font font_ui \
3586        -command {tk_textCopy $ui_diff}
3587lappend diff_actions [list $ctxm entryconf [$ctxm index last] -state]
3588$ctxm add command \
3589        -label {Select All} \
3590        -font font_ui \
3591        -command {$ui_diff tag add sel 0.0 end}
3592lappend diff_actions [list $ctxm entryconf [$ctxm index last] -state]
3593$ctxm add command \
3594        -label {Copy All} \
3595        -font font_ui \
3596        -command {
3597                $ui_diff tag add sel 0.0 end
3598                tk_textCopy $ui_diff
3599                $ui_diff tag remove sel 0.0 end
3600        }
3601lappend diff_actions [list $ctxm entryconf [$ctxm index last] -state]
3602$ctxm add separator
3603$ctxm add command \
3604        -label {Decrease Font Size} \
3605        -font font_ui \
3606        -command {incr_font_size font_diff -1}
3607lappend diff_actions [list $ctxm entryconf [$ctxm index last] -state]
3608$ctxm add command \
3609        -label {Increase Font Size} \
3610        -font font_ui \
3611        -command {incr_font_size font_diff 1}
3612lappend diff_actions [list $ctxm entryconf [$ctxm index last] -state]
3613$ctxm add separator
3614$ctxm add command \
3615        -label {Show Less Context} \
3616        -font font_ui \
3617        -command {if {$repo_config(gui.diffcontext) >= 2} {
3618                incr repo_config(gui.diffcontext) -1
3619                reshow_diff
3620        }}
3621lappend diff_actions [list $ctxm entryconf [$ctxm index last] -state]
3622$ctxm add command \
3623        -label {Show More Context} \
3624        -font font_ui \
3625        -command {
3626                incr repo_config(gui.diffcontext)
3627                reshow_diff
3628        }
3629lappend diff_actions [list $ctxm entryconf [$ctxm index last] -state]
3630$ctxm add separator
3631$ctxm add command -label {Options...} \
3632        -font font_ui \
3633        -command do_options
3634bind_button3 $ui_diff "tk_popup $ctxm %X %Y"
3635
3636# -- Status Bar
3637#
3638set ui_status_value {Initializing...}
3639label .status -textvariable ui_status_value \
3640        -anchor w \
3641        -justify left \
3642        -borderwidth 1 \
3643        -relief sunken \
3644        -font font_ui
3645pack .status -anchor w -side bottom -fill x
3646
3647# -- Load geometry
3648#
3649catch {
3650set gm $repo_config(gui.geometry)
3651wm geometry . [lindex $gm 0]
3652.vpane sash place 0 \
3653        [lindex [.vpane sash coord 0] 0] \
3654        [lindex $gm 1]
3655.vpane.files sash place 0 \
3656        [lindex $gm 2] \
3657        [lindex [.vpane.files sash coord 0] 1]
3658unset gm
3659}
3660
3661# -- Key Bindings
3662#
3663bind $ui_comm <$M1B-Key-Return> {do_commit;break}
3664bind $ui_comm <$M1B-Key-i> {do_include_all;break}
3665bind $ui_comm <$M1B-Key-I> {do_include_all;break}
3666bind $ui_comm <$M1B-Key-x> {tk_textCut %W;break}
3667bind $ui_comm <$M1B-Key-X> {tk_textCut %W;break}
3668bind $ui_comm <$M1B-Key-c> {tk_textCopy %W;break}
3669bind $ui_comm <$M1B-Key-C> {tk_textCopy %W;break}
3670bind $ui_comm <$M1B-Key-v> {tk_textPaste %W; %W see insert; break}
3671bind $ui_comm <$M1B-Key-V> {tk_textPaste %W; %W see insert; break}
3672bind $ui_comm <$M1B-Key-a> {%W tag add sel 0.0 end;break}
3673bind $ui_comm <$M1B-Key-A> {%W tag add sel 0.0 end;break}
3674
3675bind $ui_diff <$M1B-Key-x> {tk_textCopy %W;break}
3676bind $ui_diff <$M1B-Key-X> {tk_textCopy %W;break}
3677bind $ui_diff <$M1B-Key-c> {tk_textCopy %W;break}
3678bind $ui_diff <$M1B-Key-C> {tk_textCopy %W;break}
3679bind $ui_diff <$M1B-Key-v> {break}
3680bind $ui_diff <$M1B-Key-V> {break}
3681bind $ui_diff <$M1B-Key-a> {%W tag add sel 0.0 end;break}
3682bind $ui_diff <$M1B-Key-A> {%W tag add sel 0.0 end;break}
3683bind $ui_diff <Key-Up>     {catch {%W yview scroll -1 units};break}
3684bind $ui_diff <Key-Down>   {catch {%W yview scroll  1 units};break}
3685bind $ui_diff <Key-Left>   {catch {%W xview scroll -1 units};break}
3686bind $ui_diff <Key-Right>  {catch {%W xview scroll  1 units};break}
3687
3688bind .   <Destroy> do_quit
3689bind all <Key-F5> do_rescan
3690bind all <$M1B-Key-r> do_rescan
3691bind all <$M1B-Key-R> do_rescan
3692bind .   <$M1B-Key-s> do_signoff
3693bind .   <$M1B-Key-S> do_signoff
3694bind .   <$M1B-Key-i> do_include_all
3695bind .   <$M1B-Key-I> do_include_all
3696bind .   <$M1B-Key-Return> do_commit
3697bind all <$M1B-Key-q> do_quit
3698bind all <$M1B-Key-Q> do_quit
3699bind all <$M1B-Key-w> {destroy [winfo toplevel %W]}
3700bind all <$M1B-Key-W> {destroy [winfo toplevel %W]}
3701foreach i [list $ui_index $ui_other] {
3702        bind $i <Button-1>       "toggle_or_diff         $i %x %y; break"
3703        bind $i <$M1B-Button-1>  "add_one_to_selection   $i %x %y; break"
3704        bind $i <Shift-Button-1> "add_range_to_selection $i %x %y; break"
3705}
3706unset i
3707
3708set file_lists($ui_index) [list]
3709set file_lists($ui_other) [list]
3710
3711set HEAD {}
3712set PARENT {}
3713set MERGE_HEAD [list]
3714set commit_type {}
3715set empty_tree {}
3716set current_branch {}
3717set current_diff {}
3718set selected_commit_type new
3719
3720wm title . "[appname] ([file normalize [file dirname [gitdir]]])"
3721focus -force $ui_comm
3722
3723# -- Warn the user about environmental problems.  Cygwin's Tcl
3724#    does *not* pass its env array onto any processes it spawns.
3725#    This means that git processes get none of our environment.
3726#
3727if {[is_Windows]} {
3728        set ignored_env 0
3729        set suggest_user {}
3730        set msg "Possible environment issues exist.
3731
3732The following environment variables are probably
3733going to be ignored by any Git subprocess run
3734by [appname]:
3735
3736"
3737        foreach name [array names env] {
3738                switch -regexp -- $name {
3739                {^GIT_INDEX_FILE$} -
3740                {^GIT_OBJECT_DIRECTORY$} -
3741                {^GIT_ALTERNATE_OBJECT_DIRECTORIES$} -
3742                {^GIT_DIFF_OPTS$} -
3743                {^GIT_EXTERNAL_DIFF$} -
3744                {^GIT_PAGER$} -
3745                {^GIT_TRACE$} -
3746                {^GIT_CONFIG$} -
3747                {^GIT_CONFIG_LOCAL$} -
3748                {^GIT_(AUTHOR|COMMITTER)_DATE$} {
3749                        append msg " - $name\n"
3750                        incr ignored_env
3751                }
3752                {^GIT_(AUTHOR|COMMITTER)_(NAME|EMAIL)$} {
3753                        append msg " - $name\n"
3754                        incr ignored_env
3755                        set suggest_user $name
3756                }
3757                }
3758        }
3759        if {$ignored_env > 0} {
3760                append msg "
3761This is due to a known issue with the
3762Tcl binary distributed by Cygwin."
3763
3764                if {$suggest_user ne {}} {
3765                        append msg "
3766
3767A good replacement for $suggest_user
3768is placing values for the user.name and
3769user.email settings into your personal
3770~/.gitconfig file.
3771"
3772                }
3773                warn_popup $msg
3774        }
3775        unset ignored_env msg suggest_user name
3776}
3777
3778# -- Only initialize complex UI if we are going to stay running.
3779#
3780if {!$single_commit} {
3781        load_all_remotes
3782        load_all_heads
3783
3784        populate_branch_menu .mbar.branch
3785        populate_fetch_menu .mbar.fetch
3786        populate_pull_menu .mbar.pull
3787        populate_push_menu .mbar.push
3788}
3789
3790# -- Only suggest a gc run if we are going to stay running.
3791#
3792if {!$single_commit} {
3793        set object_limit 2000
3794        if {[is_Windows]} {set object_limit 200}
3795        regexp {^([0-9]+) objects,} [exec git count-objects] _junk objects_current
3796        if {$objects_current >= $object_limit} {
3797                if {[ask_popup \
3798                        "This repository currently has $objects_current loose objects.
3799
3800To maintain optimal performance it is strongly
3801recommended that you compress the database
3802when more than $object_limit loose objects exist.
3803
3804Compress the database now?"] eq yes} {
3805                        do_gc
3806                }
3807        }
3808        unset object_limit _junk objects_current
3809}
3810
3811lock_index begin-read
3812after 1 do_rescan