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