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