git-guion commit git-gui: Toggle between new commit and amend commit modes. (24ac9b7)
   1#!/bin/sh
   2# Tcl ignores the next line -*- tcl -*- \
   3exec wish "$0" -- "$@"
   4
   5# Copyright (C) 2006 Shawn Pearce, Paul Mackerras.  All rights reserved.
   6# This program is free software; it may be used, copied, modified
   7# and distributed under the terms of the GNU General Public Licence,
   8# either version 2, or (at your option) any later version.
   9
  10set appname [lindex [file split $argv0] end]
  11set gitdir {}
  12
  13######################################################################
  14##
  15## config
  16
  17proc is_many_config {name} {
  18        switch -glob -- $name {
  19        remote.*.fetch -
  20        remote.*.push
  21                {return 1}
  22        *
  23                {return 0}
  24        }
  25}
  26
  27proc load_config {include_global} {
  28        global repo_config global_config default_config
  29
  30        array unset global_config
  31        if {$include_global} {
  32                catch {
  33                        set fd_rc [open "| git repo-config --global --list" r]
  34                        while {[gets $fd_rc line] >= 0} {
  35                                if {[regexp {^([^=]+)=(.*)$} $line line name value]} {
  36                                        if {[is_many_config $name]} {
  37                                                lappend global_config($name) $value
  38                                        } else {
  39                                                set global_config($name) $value
  40                                        }
  41                                }
  42                        }
  43                        close $fd_rc
  44                }
  45        }
  46
  47        array unset repo_config
  48        catch {
  49                set fd_rc [open "| git repo-config --list" r]
  50                while {[gets $fd_rc line] >= 0} {
  51                        if {[regexp {^([^=]+)=(.*)$} $line line name value]} {
  52                                if {[is_many_config $name]} {
  53                                        lappend repo_config($name) $value
  54                                } else {
  55                                        set repo_config($name) $value
  56                                }
  57                        }
  58                }
  59                close $fd_rc
  60        }
  61
  62        foreach name [array names default_config] {
  63                if {[catch {set v $global_config($name)}]} {
  64                        set global_config($name) $default_config($name)
  65                }
  66                if {[catch {set v $repo_config($name)}]} {
  67                        set repo_config($name) $default_config($name)
  68                }
  69        }
  70}
  71
  72proc save_config {} {
  73        global default_config font_descs
  74        global repo_config global_config
  75        global repo_config_new global_config_new
  76
  77        foreach option $font_descs {
  78                set name [lindex $option 0]
  79                set font [lindex $option 1]
  80                font configure $font \
  81                        -family $global_config_new(gui.$font^^family) \
  82                        -size $global_config_new(gui.$font^^size)
  83                font configure ${font}bold \
  84                        -family $global_config_new(gui.$font^^family) \
  85                        -size $global_config_new(gui.$font^^size)
  86                set global_config_new(gui.$name) [font configure $font]
  87                unset global_config_new(gui.$font^^family)
  88                unset global_config_new(gui.$font^^size)
  89        }
  90
  91        foreach name [array names default_config] {
  92                set value $global_config_new($name)
  93                if {$value ne $global_config($name)} {
  94                        if {$value eq $default_config($name)} {
  95                                catch {exec git repo-config --global --unset $name}
  96                        } else {
  97                                regsub -all "\[{}\]" $value {"} value
  98                                exec git repo-config --global $name $value
  99                        }
 100                        set global_config($name) $value
 101                        if {$value eq $repo_config($name)} {
 102                                catch {exec git repo-config --unset $name}
 103                                set repo_config($name) $value
 104                        }
 105                }
 106        }
 107
 108        foreach name [array names default_config] {
 109                set value $repo_config_new($name)
 110                if {$value ne $repo_config($name)} {
 111                        if {$value eq $global_config($name)} {
 112                                catch {exec git repo-config --unset $name}
 113                        } else {
 114                                regsub -all "\[{}\]" $value {"} value
 115                                exec git repo-config $name $value
 116                        }
 117                        set repo_config($name) $value
 118                }
 119        }
 120}
 121
 122proc error_popup {msg} {
 123        global gitdir appname
 124
 125        set title $appname
 126        if {$gitdir ne {}} {
 127                append title { (}
 128                append title [lindex \
 129                        [file split [file normalize [file dirname $gitdir]]] \
 130                        end]
 131                append title {)}
 132        }
 133        set cmd [list tk_messageBox \
 134                -icon error \
 135                -type ok \
 136                -title "$title: error" \
 137                -message $msg]
 138        if {[winfo ismapped .]} {
 139                lappend cmd -parent .
 140        }
 141        eval $cmd
 142}
 143
 144proc info_popup {msg} {
 145        global gitdir appname
 146
 147        set title $appname
 148        if {$gitdir ne {}} {
 149                append title { (}
 150                append title [lindex \
 151                        [file split [file normalize [file dirname $gitdir]]] \
 152                        end]
 153                append title {)}
 154        }
 155        tk_messageBox \
 156                -parent . \
 157                -icon error \
 158                -type ok \
 159                -title $title \
 160                -message $msg
 161}
 162
 163######################################################################
 164##
 165## repository setup
 166
 167if {   [catch {set gitdir $env(GIT_DIR)}]
 168        && [catch {set gitdir [exec git rev-parse --git-dir]} err]} {
 169        catch {wm withdraw .}
 170        error_popup "Cannot find the git directory:\n\n$err"
 171        exit 1
 172}
 173if {![file isdirectory $gitdir]} {
 174        catch {wm withdraw .}
 175        error_popup "Git directory not found:\n\n$gitdir"
 176        exit 1
 177}
 178if {[lindex [file split $gitdir] end] ne {.git}} {
 179        catch {wm withdraw .}
 180        error_popup "Cannot use funny .git directory:\n\n$gitdir"
 181        exit 1
 182}
 183if {[catch {cd [file dirname $gitdir]} err]} {
 184        catch {wm withdraw .}
 185        error_popup "No working directory [file dirname $gitdir]:\n\n$err"
 186        exit 1
 187}
 188
 189set single_commit 0
 190if {$appname eq {git-citool}} {
 191        set single_commit 1
 192}
 193
 194######################################################################
 195##
 196## task management
 197
 198set rescan_active 0
 199set diff_active 0
 200set last_clicked {}
 201
 202set disable_on_lock [list]
 203set index_lock_type none
 204
 205proc lock_index {type} {
 206        global index_lock_type disable_on_lock
 207
 208        if {$index_lock_type eq {none}} {
 209                set index_lock_type $type
 210                foreach w $disable_on_lock {
 211                        uplevel #0 $w disabled
 212                }
 213                return 1
 214        } elseif {$index_lock_type eq "begin-$type"} {
 215                set index_lock_type $type
 216                return 1
 217        }
 218        return 0
 219}
 220
 221proc unlock_index {} {
 222        global index_lock_type disable_on_lock
 223
 224        set index_lock_type none
 225        foreach w $disable_on_lock {
 226                uplevel #0 $w normal
 227        }
 228}
 229
 230######################################################################
 231##
 232## status
 233
 234proc repository_state {hdvar ctvar} {
 235        global gitdir
 236        upvar $hdvar hd $ctvar ct
 237
 238        if {[catch {set hd [exec git rev-parse --verify HEAD]}]} {
 239                set hd {}
 240                set ct initial
 241        } elseif {[file exists [file join $gitdir MERGE_HEAD]]} {
 242                set ct merge
 243        } else {
 244                set ct normal
 245        }
 246}
 247
 248proc PARENT {} {
 249        global PARENT empty_tree
 250
 251        if {$PARENT ne {}} {
 252                return $PARENT
 253        }
 254        if {$empty_tree eq {}} {
 255                set empty_tree [exec git mktree << {}]
 256        }
 257        return $empty_tree
 258}
 259
 260proc rescan {after} {
 261        global HEAD PARENT commit_type
 262        global ui_index ui_other ui_status_value ui_comm
 263        global rescan_active file_states
 264        global repo_config
 265
 266        if {$rescan_active > 0 || ![lock_index read]} return
 267
 268        repository_state new_HEAD new_type
 269        if {[string match amend* $commit_type]
 270                && $new_type eq {normal}
 271                && $new_HEAD eq $HEAD} {
 272        } else {
 273                set HEAD $new_HEAD
 274                set PARENT $new_HEAD
 275                set commit_type $new_type
 276        }
 277
 278        array unset file_states
 279
 280        if {![$ui_comm edit modified]
 281                || [string trim [$ui_comm get 0.0 end]] eq {}} {
 282                if {[load_message GITGUI_MSG]} {
 283                } elseif {[load_message MERGE_MSG]} {
 284                } elseif {[load_message SQUASH_MSG]} {
 285                }
 286                $ui_comm edit modified false
 287                $ui_comm edit reset
 288        }
 289
 290        if {$repo_config(gui.trustmtime) eq {true}} {
 291                rescan_stage2 {} $after
 292        } else {
 293                set rescan_active 1
 294                set ui_status_value {Refreshing file status...}
 295                set cmd [list git update-index]
 296                lappend cmd -q
 297                lappend cmd --unmerged
 298                lappend cmd --ignore-missing
 299                lappend cmd --refresh
 300                set fd_rf [open "| $cmd" r]
 301                fconfigure $fd_rf -blocking 0 -translation binary
 302                fileevent $fd_rf readable \
 303                        [list rescan_stage2 $fd_rf $after]
 304        }
 305}
 306
 307proc rescan_stage2 {fd after} {
 308        global gitdir ui_status_value
 309        global rescan_active buf_rdi buf_rdf buf_rlo
 310
 311        if {$fd ne {}} {
 312                read $fd
 313                if {![eof $fd]} return
 314                close $fd
 315        }
 316
 317        set ls_others [list | git ls-files --others -z \
 318                --exclude-per-directory=.gitignore]
 319        set info_exclude [file join $gitdir info exclude]
 320        if {[file readable $info_exclude]} {
 321                lappend ls_others "--exclude-from=$info_exclude"
 322        }
 323
 324        set buf_rdi {}
 325        set buf_rdf {}
 326        set buf_rlo {}
 327
 328        set rescan_active 3
 329        set ui_status_value {Scanning for modified files ...}
 330        set fd_di [open "| git diff-index --cached -z [PARENT]" r]
 331        set fd_df [open "| git diff-files -z" r]
 332        set fd_lo [open $ls_others r]
 333
 334        fconfigure $fd_di -blocking 0 -translation binary
 335        fconfigure $fd_df -blocking 0 -translation binary
 336        fconfigure $fd_lo -blocking 0 -translation binary
 337        fileevent $fd_di readable [list read_diff_index $fd_di $after]
 338        fileevent $fd_df readable [list read_diff_files $fd_df $after]
 339        fileevent $fd_lo readable [list read_ls_others $fd_lo $after]
 340}
 341
 342proc load_message {file} {
 343        global gitdir ui_comm
 344
 345        set f [file join $gitdir $file]
 346        if {[file isfile $f]} {
 347                if {[catch {set fd [open $f r]}]} {
 348                        return 0
 349                }
 350                set content [string trim [read $fd]]
 351                close $fd
 352                $ui_comm delete 0.0 end
 353                $ui_comm insert end $content
 354                return 1
 355        }
 356        return 0
 357}
 358
 359proc read_diff_index {fd after} {
 360        global buf_rdi
 361
 362        append buf_rdi [read $fd]
 363        set c 0
 364        set n [string length $buf_rdi]
 365        while {$c < $n} {
 366                set z1 [string first "\0" $buf_rdi $c]
 367                if {$z1 == -1} break
 368                incr z1
 369                set z2 [string first "\0" $buf_rdi $z1]
 370                if {$z2 == -1} break
 371
 372                set c $z2
 373                incr z2 -1
 374                display_file \
 375                        [string range $buf_rdi $z1 $z2] \
 376                        [string index $buf_rdi [expr {$z1 - 2}]]?
 377                incr c
 378        }
 379        if {$c < $n} {
 380                set buf_rdi [string range $buf_rdi $c end]
 381        } else {
 382                set buf_rdi {}
 383        }
 384
 385        rescan_done $fd buf_rdi $after
 386}
 387
 388proc read_diff_files {fd after} {
 389        global buf_rdf
 390
 391        append buf_rdf [read $fd]
 392        set c 0
 393        set n [string length $buf_rdf]
 394        while {$c < $n} {
 395                set z1 [string first "\0" $buf_rdf $c]
 396                if {$z1 == -1} break
 397                incr z1
 398                set z2 [string first "\0" $buf_rdf $z1]
 399                if {$z2 == -1} break
 400
 401                set c $z2
 402                incr z2 -1
 403                display_file \
 404                        [string range $buf_rdf $z1 $z2] \
 405                        ?[string index $buf_rdf [expr {$z1 - 2}]]
 406                incr c
 407        }
 408        if {$c < $n} {
 409                set buf_rdf [string range $buf_rdf $c end]
 410        } else {
 411                set buf_rdf {}
 412        }
 413
 414        rescan_done $fd buf_rdf $after
 415}
 416
 417proc read_ls_others {fd after} {
 418        global buf_rlo
 419
 420        append buf_rlo [read $fd]
 421        set pck [split $buf_rlo "\0"]
 422        set buf_rlo [lindex $pck end]
 423        foreach p [lrange $pck 0 end-1] {
 424                display_file $p ?O
 425        }
 426        rescan_done $fd buf_rlo $after
 427}
 428
 429proc rescan_done {fd buf after} {
 430        global rescan_active
 431        global file_states repo_config
 432        upvar $buf to_clear
 433
 434        if {![eof $fd]} return
 435        set to_clear {}
 436        close $fd
 437        if {[incr rescan_active -1] > 0} return
 438
 439        prune_selection
 440        unlock_index
 441        display_all_files
 442
 443        if {$repo_config(gui.partialinclude) ne {true}} {
 444                set pathList [list]
 445                foreach path [array names file_states] {
 446                        switch -- [lindex $file_states($path) 0] {
 447                        AM -
 448                        MM {lappend pathList $path}
 449                        }
 450                }
 451                if {$pathList ne {}} {
 452                        update_index \
 453                                "Updating included files" \
 454                                $pathList \
 455                                [concat {reshow_diff;} $after]
 456                        return
 457                }
 458        }
 459
 460        reshow_diff
 461        uplevel #0 $after
 462}
 463
 464proc prune_selection {} {
 465        global file_states selected_paths
 466
 467        foreach path [array names selected_paths] {
 468                if {[catch {set still_here $file_states($path)}]} {
 469                        unset selected_paths($path)
 470                }
 471        }
 472}
 473
 474######################################################################
 475##
 476## diff
 477
 478proc clear_diff {} {
 479        global ui_diff current_diff ui_index ui_other
 480
 481        $ui_diff conf -state normal
 482        $ui_diff delete 0.0 end
 483        $ui_diff conf -state disabled
 484
 485        set current_diff {}
 486
 487        $ui_index tag remove in_diff 0.0 end
 488        $ui_other tag remove in_diff 0.0 end
 489}
 490
 491proc reshow_diff {} {
 492        global current_diff ui_status_value file_states
 493
 494        if {$current_diff eq {}
 495                || [catch {set s $file_states($current_diff)}]} {
 496                clear_diff
 497        } else {
 498                show_diff $current_diff
 499        }
 500}
 501
 502proc handle_empty_diff {} {
 503        global current_diff file_states file_lists
 504
 505        set path $current_diff
 506        set s $file_states($path)
 507        if {[lindex $s 0] ne {_M}} return
 508
 509        info_popup "No differences detected.
 510
 511[short_path $path] has no changes.
 512
 513The modification date of this file was updated
 514by another application and you currently have
 515the Trust File Modification Timestamps option
 516enabled, so Git did not automatically detect
 517that there are no content differences in this
 518file.
 519
 520This file will now be removed from the modified
 521files list, to prevent possible confusion.
 522"
 523        if {[catch {exec git update-index -- $path} err]} {
 524                error_popup "Failed to refresh index:\n\n$err"
 525        }
 526
 527        clear_diff
 528        set old_w [mapcol [lindex $file_states($path) 0] $path]
 529        set lno [lsearch -sorted $file_lists($old_w) $path]
 530        if {$lno >= 0} {
 531                set file_lists($old_w) \
 532                        [lreplace $file_lists($old_w) $lno $lno]
 533                incr lno
 534                $old_w conf -state normal
 535                $old_w delete $lno.0 [expr {$lno + 1}].0
 536                $old_w conf -state disabled
 537        }
 538}
 539
 540proc show_diff {path {w {}} {lno {}}} {
 541        global file_states file_lists
 542        global diff_3way diff_active repo_config
 543        global ui_diff current_diff ui_status_value
 544
 545        if {$diff_active || ![lock_index read]} return
 546
 547        clear_diff
 548        if {$w eq {} || $lno == {}} {
 549                foreach w [array names file_lists] {
 550                        set lno [lsearch -sorted $file_lists($w) $path]
 551                        if {$lno >= 0} {
 552                                incr lno
 553                                break
 554                        }
 555                }
 556        }
 557        if {$w ne {} && $lno >= 1} {
 558                $w tag add in_diff $lno.0 [expr {$lno + 1}].0
 559        }
 560
 561        set s $file_states($path)
 562        set m [lindex $s 0]
 563        set diff_3way 0
 564        set diff_active 1
 565        set current_diff $path
 566        set ui_status_value "Loading diff of [escape_path $path]..."
 567
 568        set cmd [list | git diff-index]
 569        lappend cmd --no-color
 570        if {$repo_config(gui.diffcontext) > 0} {
 571                lappend cmd "-U$repo_config(gui.diffcontext)"
 572        }
 573        lappend cmd -p
 574
 575        switch $m {
 576        MM {
 577                lappend cmd -c
 578        }
 579        _O {
 580                if {[catch {
 581                                set fd [open $path r]
 582                                set content [read $fd]
 583                                close $fd
 584                        } err ]} {
 585                        set diff_active 0
 586                        unlock_index
 587                        set ui_status_value "Unable to display [escape_path $path]"
 588                        error_popup "Error loading file:\n\n$err"
 589                        return
 590                }
 591                $ui_diff conf -state normal
 592                $ui_diff insert end $content
 593                $ui_diff conf -state disabled
 594                set diff_active 0
 595                unlock_index
 596                set ui_status_value {Ready.}
 597                return
 598        }
 599        }
 600
 601        lappend cmd [PARENT]
 602        lappend cmd --
 603        lappend cmd $path
 604
 605        if {[catch {set fd [open $cmd r]} err]} {
 606                set diff_active 0
 607                unlock_index
 608                set ui_status_value "Unable to display [escape_path $path]"
 609                error_popup "Error loading diff:\n\n$err"
 610                return
 611        }
 612
 613        fconfigure $fd -blocking 0 -translation auto
 614        fileevent $fd readable [list read_diff $fd]
 615}
 616
 617proc read_diff {fd} {
 618        global ui_diff ui_status_value diff_3way diff_active
 619        global repo_config
 620
 621        while {[gets $fd line] >= 0} {
 622                if {[string match {diff --git *} $line]} continue
 623                if {[string match {diff --combined *} $line]} continue
 624                if {[string match {--- *} $line]} continue
 625                if {[string match {+++ *} $line]} continue
 626                if {[string match index* $line]} {
 627                        if {[string first , $line] >= 0} {
 628                                set diff_3way 1
 629                        }
 630                }
 631
 632                $ui_diff conf -state normal
 633                if {!$diff_3way} {
 634                        set x [string index $line 0]
 635                        switch -- $x {
 636                        "@" {set tags da}
 637                        "+" {set tags dp}
 638                        "-" {set tags dm}
 639                        default {set tags {}}
 640                        }
 641                } else {
 642                        set x [string range $line 0 1]
 643                        switch -- $x {
 644                        default {set tags {}}
 645                        "@@" {set tags da}
 646                        "++" {set tags dp; set x " +"}
 647                        " +" {set tags {di bold}; set x "++"}
 648                        "+ " {set tags dni; set x "-+"}
 649                        "--" {set tags dm; set x " -"}
 650                        " -" {set tags {dm bold}; set x "--"}
 651                        "- " {set tags di; set x "+-"}
 652                        default {set tags {}}
 653                        }
 654                        set line [string replace $line 0 1 $x]
 655                }
 656                $ui_diff insert end $line $tags
 657                $ui_diff insert end "\n"
 658                $ui_diff conf -state disabled
 659        }
 660
 661        if {[eof $fd]} {
 662                close $fd
 663                set diff_active 0
 664                unlock_index
 665                set ui_status_value {Ready.}
 666
 667                if {$repo_config(gui.trustmtime) eq {true}
 668                        && [$ui_diff index end] eq {2.0}} {
 669                        handle_empty_diff
 670                }
 671        }
 672}
 673
 674######################################################################
 675##
 676## commit
 677
 678proc load_last_commit {} {
 679        global HEAD PARENT commit_type ui_comm
 680
 681        if {[string match amend* $commit_type]} return
 682        if {$commit_type ne {normal}} {
 683                error_popup "Can't amend a $commit_type commit."
 684                return
 685        }
 686
 687        set msg {}
 688        set parent {}
 689        set parent_count 0
 690        if {[catch {
 691                        set fd [open "| git cat-file commit $HEAD" r]
 692                        while {[gets $fd line] > 0} {
 693                                if {[string match {parent *} $line]} {
 694                                        set parent [string range $line 7 end]
 695                                        incr parent_count
 696                                }
 697                        }
 698                        set msg [string trim [read $fd]]
 699                        close $fd
 700                } err]} {
 701                error_popup "Error loading commit data for amend:\n\n$err"
 702                return
 703        }
 704
 705        if {$parent_count > 1} {
 706                error_popup {Can't amend a merge commit.}
 707                return
 708        }
 709
 710        if {$parent_count == 0} {
 711                set commit_type amend-initial
 712                set PARENT {}
 713        } elseif {$parent_count == 1} {
 714                set commit_type amend
 715                set PARENT $parent
 716        }
 717
 718        $ui_comm delete 0.0 end
 719        $ui_comm insert end $msg
 720        $ui_comm edit modified false
 721        $ui_comm edit reset
 722        rescan {set ui_status_value {Ready.}}
 723}
 724
 725proc create_new_commit {} {
 726        global commit_type ui_comm
 727
 728        set commit_type normal
 729        $ui_comm delete 0.0 end
 730        $ui_comm edit modified false
 731        $ui_comm edit reset
 732        rescan {set ui_status_value {Ready.}}
 733}
 734
 735proc commit_tree {} {
 736        global HEAD commit_type file_states ui_comm repo_config
 737
 738        if {![lock_index update]} return
 739
 740        # -- Our in memory state should match the repository.
 741        #
 742        repository_state curHEAD cur_type
 743        if {[string match amend* $commit_type]
 744                && $cur_type eq {normal}
 745                && $curHEAD eq $HEAD} {
 746        } elseif {$commit_type ne $cur_type || $HEAD ne $curHEAD} {
 747                error_popup {Last scanned state does not match repository state.
 748
 749Its highly likely that another Git program modified the
 750repository since the last scan.  A rescan is required
 751before committing.
 752
 753A rescan will be automatically started now.
 754}
 755                unlock_index
 756                rescan {set ui_status_value {Ready.}}
 757                return
 758        }
 759
 760        # -- At least one file should differ in the index.
 761        #
 762        set files_ready 0
 763        foreach path [array names file_states] {
 764                switch -glob -- [lindex $file_states($path) 0] {
 765                _? {continue}
 766                A? -
 767                D? -
 768                M? {set files_ready 1; break}
 769                U? {
 770                        error_popup "Unmerged files cannot be committed.
 771
 772File [short_path $path] has merge conflicts.
 773You must resolve them and include the file before committing.
 774"
 775                        unlock_index
 776                        return
 777                }
 778                default {
 779                        error_popup "Unknown file state [lindex $s 0] detected.
 780
 781File [short_path $path] cannot be committed by this program.
 782"
 783                }
 784                }
 785        }
 786        if {!$files_ready} {
 787                error_popup {No included files to commit.
 788
 789You must include at least 1 file before you can commit.
 790}
 791                unlock_index
 792                return
 793        }
 794
 795        # -- A message is required.
 796        #
 797        set msg [string trim [$ui_comm get 1.0 end]]
 798        if {$msg eq {}} {
 799                error_popup {Please supply a commit message.
 800
 801A good commit message has the following format:
 802
 803- First line: Describe in one sentance what you did.
 804- Second line: Blank
 805- Remaining lines: Describe why this change is good.
 806}
 807                unlock_index
 808                return
 809        }
 810
 811        # -- Update included files if partialincludes are off.
 812        #
 813        if {$repo_config(gui.partialinclude) ne {true}} {
 814                set pathList [list]
 815                foreach path [array names file_states] {
 816                        switch -glob -- [lindex $file_states($path) 0] {
 817                        A? -
 818                        M? {lappend pathList $path}
 819                        }
 820                }
 821                if {$pathList ne {}} {
 822                        unlock_index
 823                        update_index \
 824                                "Updating included files" \
 825                                $pathList \
 826                                [concat {lock_index update;} \
 827                                        [list commit_prehook $curHEAD $msg]]
 828                        return
 829                }
 830        }
 831
 832        commit_prehook $curHEAD $msg
 833}
 834
 835proc commit_prehook {curHEAD msg} {
 836        global tcl_platform gitdir ui_status_value pch_error
 837
 838        # On Cygwin [file executable] might lie so we need to ask
 839        # the shell if the hook is executable.  Yes that's annoying.
 840
 841        set pchook [file join $gitdir hooks pre-commit]
 842        if {$tcl_platform(platform) eq {windows}
 843                && [file isfile $pchook]} {
 844                set pchook [list sh -c [concat \
 845                        "if test -x \"$pchook\";" \
 846                        "then exec \"$pchook\" 2>&1;" \
 847                        "fi"]]
 848        } elseif {[file executable $pchook]} {
 849                set pchook [list $pchook |& cat]
 850        } else {
 851                commit_writetree $curHEAD $msg
 852                return
 853        }
 854
 855        set ui_status_value {Calling pre-commit hook...}
 856        set pch_error {}
 857        set fd_ph [open "| $pchook" r]
 858        fconfigure $fd_ph -blocking 0 -translation binary
 859        fileevent $fd_ph readable \
 860                [list commit_prehook_wait $fd_ph $curHEAD $msg]
 861}
 862
 863proc commit_prehook_wait {fd_ph curHEAD msg} {
 864        global pch_error ui_status_value
 865
 866        append pch_error [read $fd_ph]
 867        fconfigure $fd_ph -blocking 1
 868        if {[eof $fd_ph]} {
 869                if {[catch {close $fd_ph}]} {
 870                        set ui_status_value {Commit declined by pre-commit hook.}
 871                        hook_failed_popup pre-commit $pch_error
 872                        unlock_index
 873                } else {
 874                        commit_writetree $curHEAD $msg
 875                }
 876                set pch_error {}
 877                return
 878        }
 879        fconfigure $fd_ph -blocking 0
 880}
 881
 882proc commit_writetree {curHEAD msg} {
 883        global ui_status_value
 884
 885        set ui_status_value {Committing changes...}
 886        set fd_wt [open "| git write-tree" r]
 887        fileevent $fd_wt readable \
 888                [list commit_committree $fd_wt $curHEAD $msg]
 889}
 890
 891proc commit_committree {fd_wt curHEAD msg} {
 892        global single_commit gitdir HEAD PARENT commit_type tcl_platform
 893        global ui_status_value ui_comm selected_commit_type
 894        global file_states selected_paths
 895
 896        gets $fd_wt tree_id
 897        if {$tree_id eq {} || [catch {close $fd_wt} err]} {
 898                error_popup "write-tree failed:\n\n$err"
 899                set ui_status_value {Commit failed.}
 900                unlock_index
 901                return
 902        }
 903
 904        # -- Create the commit.
 905        #
 906        set cmd [list git commit-tree $tree_id]
 907        if {$PARENT ne {}} {
 908                lappend cmd -p $PARENT
 909        }
 910        if {$commit_type eq {merge}} {
 911                if {[catch {
 912                                set fd_mh [open [file join $gitdir MERGE_HEAD] r]
 913                                while {[gets $fd_mh merge_head] >= 0} {
 914                                        lappend cmd -p $merge_head
 915                                }
 916                                close $fd_mh
 917                        } err]} {
 918                        error_popup "Loading MERGE_HEAD failed:\n\n$err"
 919                        set ui_status_value {Commit failed.}
 920                        unlock_index
 921                        return
 922                }
 923        }
 924        if {$PARENT eq {}} {
 925                # git commit-tree writes to stderr during initial commit.
 926                lappend cmd 2>/dev/null
 927        }
 928        lappend cmd << $msg
 929        if {[catch {set cmt_id [eval exec $cmd]} err]} {
 930                error_popup "commit-tree failed:\n\n$err"
 931                set ui_status_value {Commit failed.}
 932                unlock_index
 933                return
 934        }
 935
 936        # -- Update the HEAD ref.
 937        #
 938        set reflogm commit
 939        if {$commit_type ne {normal}} {
 940                append reflogm " ($commit_type)"
 941        }
 942        set i [string first "\n" $msg]
 943        if {$i >= 0} {
 944                append reflogm {: } [string range $msg 0 [expr {$i - 1}]]
 945        } else {
 946                append reflogm {: } $msg
 947        }
 948        set cmd [list git update-ref -m $reflogm HEAD $cmt_id $curHEAD]
 949        if {[catch {eval exec $cmd} err]} {
 950                error_popup "update-ref failed:\n\n$err"
 951                set ui_status_value {Commit failed.}
 952                unlock_index
 953                return
 954        }
 955
 956        # -- Cleanup after ourselves.
 957        #
 958        catch {file delete [file join $gitdir MERGE_HEAD]}
 959        catch {file delete [file join $gitdir MERGE_MSG]}
 960        catch {file delete [file join $gitdir SQUASH_MSG]}
 961        catch {file delete [file join $gitdir GITGUI_MSG]}
 962
 963        # -- Let rerere do its thing.
 964        #
 965        if {[file isdirectory [file join $gitdir rr-cache]]} {
 966                catch {exec git rerere}
 967        }
 968
 969        # -- Run the post-commit hook.
 970        #
 971        set pchook [file join $gitdir hooks post-commit]
 972        if {$tcl_platform(platform) eq {windows} && [file isfile $pchook]} {
 973                set pchook [list sh -c [concat \
 974                        "if test -x \"$pchook\";" \
 975                        "then exec \"$pchook\";" \
 976                        "fi"]]
 977        } elseif {![file executable $pchook]} {
 978                set pchook {}
 979        }
 980        if {$pchook ne {}} {
 981                catch {exec $pchook &}
 982        }
 983
 984        $ui_comm delete 0.0 end
 985        $ui_comm edit modified false
 986        $ui_comm edit reset
 987
 988        if {$single_commit} do_quit
 989
 990        # -- Update status without invoking any git commands.
 991        #
 992        set commit_type normal
 993        set selected_commit_type new
 994        set HEAD $cmt_id
 995        set PARENT $cmt_id
 996
 997        foreach path [array names file_states] {
 998                set s $file_states($path)
 999                set m [lindex $s 0]
1000                switch -glob -- $m {
1001                A? -
1002                M? -
1003                D? {set m _[string index $m 1]}
1004                }
1005
1006                if {$m eq {__}} {
1007                        unset file_states($path)
1008                        catch {unset selected_paths($path)}
1009                } else {
1010                        lset file_states($path) 0 $m
1011                }
1012        }
1013
1014        display_all_files
1015        unlock_index
1016        reshow_diff
1017        set ui_status_value \
1018                "Changes committed as [string range $cmt_id 0 7]."
1019}
1020
1021######################################################################
1022##
1023## fetch pull push
1024
1025proc fetch_from {remote} {
1026        set w [new_console "fetch $remote" \
1027                "Fetching new changes from $remote"]
1028        set cmd [list git fetch]
1029        lappend cmd $remote
1030        console_exec $w $cmd
1031}
1032
1033proc pull_remote {remote branch} {
1034        global HEAD commit_type file_states repo_config
1035
1036        if {![lock_index update]} return
1037
1038        # -- Our in memory state should match the repository.
1039        #
1040        repository_state curHEAD cur_type
1041        if {$commit_type ne $cur_type || $HEAD ne $curHEAD} {
1042                error_popup {Last scanned state does not match repository state.
1043
1044Its highly likely that another Git program modified the
1045repository since our last scan.  A rescan is required
1046before a pull can be started.
1047}
1048                unlock_index
1049                rescan {set ui_status_value {Ready.}}
1050                return
1051        }
1052
1053        # -- No differences should exist before a pull.
1054        #
1055        if {[array size file_states] != 0} {
1056                error_popup {Uncommitted but modified files are present.
1057
1058You should not perform a pull with unmodified files in your working
1059directory as Git would be unable to recover from an incorrect merge.
1060
1061Commit or throw away all changes before starting a pull operation.
1062}
1063                unlock_index
1064                return
1065        }
1066
1067        set w [new_console "pull $remote $branch" \
1068                "Pulling new changes from branch $branch in $remote"]
1069        set cmd [list git pull]
1070        if {$repo_config(gui.pullsummary) eq {false}} {
1071                lappend cmd --no-summary
1072        }
1073        lappend cmd $remote
1074        lappend cmd $branch
1075        console_exec $w $cmd [list post_pull_remote $remote $branch]
1076}
1077
1078proc post_pull_remote {remote branch success} {
1079        global HEAD PARENT commit_type selected_commit_type
1080        global ui_status_value
1081
1082        unlock_index
1083        if {$success} {
1084                repository_state HEAD commit_type
1085                set PARENT $HEAD
1086                set selected_commit_type new
1087                set $ui_status_value "Pulling $branch from $remote complete."
1088        } else {
1089                set m "Conflicts detected while pulling $branch from $remote."
1090                rescan "set ui_status_value {$m}"
1091        }
1092}
1093
1094proc push_to {remote} {
1095        set w [new_console "push $remote" \
1096                "Pushing changes to $remote"]
1097        set cmd [list git push]
1098        lappend cmd $remote
1099        console_exec $w $cmd
1100}
1101
1102######################################################################
1103##
1104## ui helpers
1105
1106proc mapcol {state path} {
1107        global all_cols ui_other
1108
1109        if {[catch {set r $all_cols($state)}]} {
1110                puts "error: no column for state={$state} $path"
1111                return $ui_other
1112        }
1113        return $r
1114}
1115
1116proc mapicon {state path} {
1117        global all_icons
1118
1119        if {[catch {set r $all_icons($state)}]} {
1120                puts "error: no icon for state={$state} $path"
1121                return file_plain
1122        }
1123        return $r
1124}
1125
1126proc mapdesc {state path} {
1127        global all_descs
1128
1129        if {[catch {set r $all_descs($state)}]} {
1130                puts "error: no desc for state={$state} $path"
1131                return $state
1132        }
1133        return $r
1134}
1135
1136proc escape_path {path} {
1137        regsub -all "\n" $path "\\n" path
1138        return $path
1139}
1140
1141proc short_path {path} {
1142        return [escape_path [lindex [file split $path] end]]
1143}
1144
1145set next_icon_id 0
1146
1147proc merge_state {path new_state} {
1148        global file_states next_icon_id
1149
1150        set s0 [string index $new_state 0]
1151        set s1 [string index $new_state 1]
1152
1153        if {[catch {set info $file_states($path)}]} {
1154                set state __
1155                set icon n[incr next_icon_id]
1156        } else {
1157                set state [lindex $info 0]
1158                set icon [lindex $info 1]
1159        }
1160
1161        if {$s0 eq {?}} {
1162                set s0 [string index $state 0]
1163        } elseif {$s0 eq {_}} {
1164                set s0 _
1165        }
1166
1167        if {$s1 eq {?}} {
1168                set s1 [string index $state 1]
1169        } elseif {$s1 eq {_}} {
1170                set s1 _
1171        }
1172
1173        set file_states($path) [list $s0$s1 $icon]
1174        return $state
1175}
1176
1177proc display_file {path state} {
1178        global file_states file_lists selected_paths rescan_active
1179
1180        set old_m [merge_state $path $state]
1181        if {$rescan_active > 0} return
1182
1183        set s $file_states($path)
1184        set new_m [lindex $s 0]
1185        set new_w [mapcol $new_m $path] 
1186        set old_w [mapcol $old_m $path]
1187        set new_icon [mapicon $new_m $path]
1188
1189        if {$new_w ne $old_w} {
1190                set lno [lsearch -sorted $file_lists($old_w) $path]
1191                if {$lno >= 0} {
1192                        incr lno
1193                        $old_w conf -state normal
1194                        $old_w delete $lno.0 [expr {$lno + 1}].0
1195                        $old_w conf -state disabled
1196                }
1197
1198                lappend file_lists($new_w) $path
1199                set file_lists($new_w) [lsort $file_lists($new_w)]
1200                set lno [lsearch -sorted $file_lists($new_w) $path]
1201                incr lno
1202                $new_w conf -state normal
1203                $new_w image create $lno.0 \
1204                        -align center -padx 5 -pady 1 \
1205                        -name [lindex $s 1] \
1206                        -image $new_icon
1207                $new_w insert $lno.1 "[escape_path $path]\n"
1208                if {[catch {set in_sel $selected_paths($path)}]} {
1209                        set in_sel 0
1210                }
1211                if {$in_sel} {
1212                        $new_w tag add in_sel $lno.0 [expr {$lno + 1}].0
1213                }
1214                $new_w conf -state disabled
1215        } elseif {$new_icon ne [mapicon $old_m $path]} {
1216                $new_w conf -state normal
1217                $new_w image conf [lindex $s 1] -image $new_icon
1218                $new_w conf -state disabled
1219        }
1220}
1221
1222proc display_all_files {} {
1223        global ui_index ui_other
1224        global file_states file_lists
1225        global last_clicked selected_paths
1226
1227        $ui_index conf -state normal
1228        $ui_other conf -state normal
1229
1230        $ui_index delete 0.0 end
1231        $ui_other delete 0.0 end
1232        set last_clicked {}
1233
1234        set file_lists($ui_index) [list]
1235        set file_lists($ui_other) [list]
1236
1237        foreach path [lsort [array names file_states]] {
1238                set s $file_states($path)
1239                set m [lindex $s 0]
1240                set w [mapcol $m $path]
1241                lappend file_lists($w) $path
1242                set lno [expr {[lindex [split [$w index end] .] 0] - 1}]
1243                $w image create end \
1244                        -align center -padx 5 -pady 1 \
1245                        -name [lindex $s 1] \
1246                        -image [mapicon $m $path]
1247                $w insert end "[escape_path $path]\n"
1248                if {[catch {set in_sel $selected_paths($path)}]} {
1249                        set in_sel 0
1250                }
1251                if {$in_sel} {
1252                        $w tag add in_sel $lno.0 [expr {$lno + 1}].0
1253                }
1254        }
1255
1256        $ui_index conf -state disabled
1257        $ui_other conf -state disabled
1258}
1259
1260proc update_index {msg pathList after} {
1261        global update_index_cp ui_status_value
1262
1263        if {![lock_index update]} return
1264
1265        set update_index_cp 0
1266        set pathList [lsort $pathList]
1267        set totalCnt [llength $pathList]
1268        set batch [expr {int($totalCnt * .01) + 1}]
1269        if {$batch > 25} {set batch 25}
1270
1271        set ui_status_value [format \
1272                "$msg... %i/%i files (%.2f%%)" \
1273                $update_index_cp \
1274                $totalCnt \
1275                0.0]
1276        set fd [open "| git update-index --add --remove -z --stdin" w]
1277        fconfigure $fd \
1278                -blocking 0 \
1279                -buffering full \
1280                -buffersize 512 \
1281                -translation binary
1282        fileevent $fd writable [list \
1283                write_update_index \
1284                $fd \
1285                $pathList \
1286                $totalCnt \
1287                $batch \
1288                $msg \
1289                $after \
1290                ]
1291}
1292
1293proc write_update_index {fd pathList totalCnt batch msg after} {
1294        global update_index_cp ui_status_value
1295        global file_states current_diff
1296
1297        if {$update_index_cp >= $totalCnt} {
1298                close $fd
1299                unlock_index
1300                uplevel #0 $after
1301                return
1302        }
1303
1304        for {set i $batch} \
1305                {$update_index_cp < $totalCnt && $i > 0} \
1306                {incr i -1} {
1307                set path [lindex $pathList $update_index_cp]
1308                incr update_index_cp
1309
1310                switch -glob -- [lindex $file_states($path) 0] {
1311                AD -
1312                MD -
1313                _D {set new D_}
1314
1315                _M -
1316                MM -
1317                M_ {set new M_}
1318
1319                _O -
1320                AM -
1321                A_ {set new A_}
1322
1323                ?? {continue}
1324                }
1325
1326                puts -nonewline $fd $path
1327                puts -nonewline $fd "\0"
1328                display_file $path $new
1329        }
1330
1331        set ui_status_value [format \
1332                "$msg... %i/%i files (%.2f%%)" \
1333                $update_index_cp \
1334                $totalCnt \
1335                [expr {100.0 * $update_index_cp / $totalCnt}]]
1336}
1337
1338######################################################################
1339##
1340## remote management
1341
1342proc load_all_remotes {} {
1343        global gitdir all_remotes repo_config
1344
1345        set all_remotes [list]
1346        set rm_dir [file join $gitdir remotes]
1347        if {[file isdirectory $rm_dir]} {
1348                set all_remotes [concat $all_remotes [glob \
1349                        -types f \
1350                        -tails \
1351                        -nocomplain \
1352                        -directory $rm_dir *]]
1353        }
1354
1355        foreach line [array names repo_config remote.*.url] {
1356                if {[regexp ^remote\.(.*)\.url\$ $line line name]} {
1357                        lappend all_remotes $name
1358                }
1359        }
1360
1361        set all_remotes [lsort -unique $all_remotes]
1362}
1363
1364proc populate_fetch_menu {m} {
1365        global gitdir all_remotes repo_config
1366
1367        foreach r $all_remotes {
1368                set enable 0
1369                if {![catch {set a $repo_config(remote.$r.url)}]} {
1370                        if {![catch {set a $repo_config(remote.$r.fetch)}]} {
1371                                set enable 1
1372                        }
1373                } else {
1374                        catch {
1375                                set fd [open [file join $gitdir remotes $r] r]
1376                                while {[gets $fd n] >= 0} {
1377                                        if {[regexp {^Pull:[ \t]*([^:]+):} $n]} {
1378                                                set enable 1
1379                                                break
1380                                        }
1381                                }
1382                                close $fd
1383                        }
1384                }
1385
1386                if {$enable} {
1387                        $m add command \
1388                                -label "Fetch from $r..." \
1389                                -command [list fetch_from $r] \
1390                                -font font_ui
1391                }
1392        }
1393}
1394
1395proc populate_push_menu {m} {
1396        global gitdir all_remotes repo_config
1397
1398        foreach r $all_remotes {
1399                set enable 0
1400                if {![catch {set a $repo_config(remote.$r.url)}]} {
1401                        if {![catch {set a $repo_config(remote.$r.push)}]} {
1402                                set enable 1
1403                        }
1404                } else {
1405                        catch {
1406                                set fd [open [file join $gitdir remotes $r] r]
1407                                while {[gets $fd n] >= 0} {
1408                                        if {[regexp {^Push:[ \t]*([^:]+):} $n]} {
1409                                                set enable 1
1410                                                break
1411                                        }
1412                                }
1413                                close $fd
1414                        }
1415                }
1416
1417                if {$enable} {
1418                        $m add command \
1419                                -label "Push to $r..." \
1420                                -command [list push_to $r] \
1421                                -font font_ui
1422                }
1423        }
1424}
1425
1426proc populate_pull_menu {m} {
1427        global gitdir repo_config all_remotes disable_on_lock
1428
1429        foreach remote $all_remotes {
1430                set rb {}
1431                if {[array get repo_config remote.$remote.url] ne {}} {
1432                        if {[array get repo_config remote.$remote.fetch] ne {}} {
1433                                regexp {^([^:]+):} \
1434                                        [lindex $repo_config(remote.$remote.fetch) 0] \
1435                                        line rb
1436                        }
1437                } else {
1438                        catch {
1439                                set fd [open [file join $gitdir remotes $remote] r]
1440                                while {[gets $fd line] >= 0} {
1441                                        if {[regexp {^Pull:[ \t]*([^:]+):} $line line rb]} {
1442                                                break
1443                                        }
1444                                }
1445                                close $fd
1446                        }
1447                }
1448
1449                set rb_short $rb
1450                regsub ^refs/heads/ $rb {} rb_short
1451                if {$rb_short ne {}} {
1452                        $m add command \
1453                                -label "Branch $rb_short from $remote..." \
1454                                -command [list pull_remote $remote $rb] \
1455                                -font font_ui
1456                        lappend disable_on_lock \
1457                                [list $m entryconf [$m index last] -state]
1458                }
1459        }
1460}
1461
1462######################################################################
1463##
1464## icons
1465
1466set filemask {
1467#define mask_width 14
1468#define mask_height 15
1469static unsigned char mask_bits[] = {
1470   0xfe, 0x1f, 0xfe, 0x1f, 0xfe, 0x1f, 0xfe, 0x1f, 0xfe, 0x1f, 0xfe, 0x1f,
1471   0xfe, 0x1f, 0xfe, 0x1f, 0xfe, 0x1f, 0xfe, 0x1f, 0xfe, 0x1f, 0xfe, 0x1f,
1472   0xfe, 0x1f, 0xfe, 0x1f, 0xfe, 0x1f};
1473}
1474
1475image create bitmap file_plain -background white -foreground black -data {
1476#define plain_width 14
1477#define plain_height 15
1478static unsigned char plain_bits[] = {
1479   0xfe, 0x01, 0x02, 0x03, 0x02, 0x05, 0x02, 0x09, 0x02, 0x1f, 0x02, 0x10,
1480   0x02, 0x10, 0x02, 0x10, 0x02, 0x10, 0x02, 0x10, 0x02, 0x10, 0x02, 0x10,
1481   0x02, 0x10, 0x02, 0x10, 0xfe, 0x1f};
1482} -maskdata $filemask
1483
1484image create bitmap file_mod -background white -foreground blue -data {
1485#define mod_width 14
1486#define mod_height 15
1487static unsigned char mod_bits[] = {
1488   0xfe, 0x01, 0x02, 0x03, 0x7a, 0x05, 0x02, 0x09, 0x7a, 0x1f, 0x02, 0x10,
1489   0xfa, 0x17, 0x02, 0x10, 0xfa, 0x17, 0x02, 0x10, 0xfa, 0x17, 0x02, 0x10,
1490   0xfa, 0x17, 0x02, 0x10, 0xfe, 0x1f};
1491} -maskdata $filemask
1492
1493image create bitmap file_fulltick -background white -foreground "#007000" -data {
1494#define file_fulltick_width 14
1495#define file_fulltick_height 15
1496static unsigned char file_fulltick_bits[] = {
1497   0xfe, 0x01, 0x02, 0x1a, 0x02, 0x0c, 0x02, 0x0c, 0x02, 0x16, 0x02, 0x16,
1498   0x02, 0x13, 0x00, 0x13, 0x86, 0x11, 0x8c, 0x11, 0xd8, 0x10, 0xf2, 0x10,
1499   0x62, 0x10, 0x02, 0x10, 0xfe, 0x1f};
1500} -maskdata $filemask
1501
1502image create bitmap file_parttick -background white -foreground "#005050" -data {
1503#define parttick_width 14
1504#define parttick_height 15
1505static unsigned char parttick_bits[] = {
1506   0xfe, 0x01, 0x02, 0x03, 0x7a, 0x05, 0x02, 0x09, 0x7a, 0x1f, 0x02, 0x10,
1507   0x7a, 0x14, 0x02, 0x16, 0x02, 0x13, 0x8a, 0x11, 0xda, 0x10, 0x72, 0x10,
1508   0x22, 0x10, 0x02, 0x10, 0xfe, 0x1f};
1509} -maskdata $filemask
1510
1511image create bitmap file_question -background white -foreground black -data {
1512#define file_question_width 14
1513#define file_question_height 15
1514static unsigned char file_question_bits[] = {
1515   0xfe, 0x01, 0x02, 0x02, 0xe2, 0x04, 0xf2, 0x09, 0x1a, 0x1b, 0x0a, 0x13,
1516   0x82, 0x11, 0xc2, 0x10, 0x62, 0x10, 0x62, 0x10, 0x02, 0x10, 0x62, 0x10,
1517   0x62, 0x10, 0x02, 0x10, 0xfe, 0x1f};
1518} -maskdata $filemask
1519
1520image create bitmap file_removed -background white -foreground red -data {
1521#define file_removed_width 14
1522#define file_removed_height 15
1523static unsigned char file_removed_bits[] = {
1524   0xfe, 0x01, 0x02, 0x03, 0x02, 0x05, 0x02, 0x09, 0x02, 0x1f, 0x02, 0x10,
1525   0x1a, 0x16, 0x32, 0x13, 0xe2, 0x11, 0xc2, 0x10, 0xe2, 0x11, 0x32, 0x13,
1526   0x1a, 0x16, 0x02, 0x10, 0xfe, 0x1f};
1527} -maskdata $filemask
1528
1529image create bitmap file_merge -background white -foreground blue -data {
1530#define file_merge_width 14
1531#define file_merge_height 15
1532static unsigned char file_merge_bits[] = {
1533   0xfe, 0x01, 0x02, 0x03, 0x62, 0x05, 0x62, 0x09, 0x62, 0x1f, 0x62, 0x10,
1534   0xfa, 0x11, 0xf2, 0x10, 0x62, 0x10, 0x02, 0x10, 0xfa, 0x17, 0x02, 0x10,
1535   0xfa, 0x17, 0x02, 0x10, 0xfe, 0x1f};
1536} -maskdata $filemask
1537
1538set ui_index .vpane.files.index.list
1539set ui_other .vpane.files.other.list
1540set max_status_desc 0
1541foreach i {
1542                {__ i plain    "Unmodified"}
1543                {_M i mod      "Modified"}
1544                {M_ i fulltick "Included in commit"}
1545                {MM i parttick "Partially included"}
1546
1547                {_O o plain    "Untracked"}
1548                {A_ o fulltick "Added by commit"}
1549                {AM o parttick "Partially added"}
1550                {AD o question "Added (but now gone)"}
1551
1552                {_D i question "Missing"}
1553                {D_ i removed  "Removed by commit"}
1554                {DD i removed  "Removed by commit"}
1555                {DO i removed  "Removed (still exists)"}
1556
1557                {UM i merge    "Merge conflicts"}
1558                {U_ i merge    "Merge conflicts"}
1559        } {
1560        if {$max_status_desc < [string length [lindex $i 3]]} {
1561                set max_status_desc [string length [lindex $i 3]]
1562        }
1563        if {[lindex $i 1] eq {i}} {
1564                set all_cols([lindex $i 0]) $ui_index
1565        } else {
1566                set all_cols([lindex $i 0]) $ui_other
1567        }
1568        set all_icons([lindex $i 0]) file_[lindex $i 2]
1569        set all_descs([lindex $i 0]) [lindex $i 3]
1570}
1571unset filemask i
1572
1573######################################################################
1574##
1575## util
1576
1577proc is_MacOSX {} {
1578        global tcl_platform tk_library
1579        if {$tcl_platform(platform) eq {unix}
1580                && $tcl_platform(os) eq {Darwin}
1581                && [string match /Library/Frameworks/* $tk_library]} {
1582                return 1
1583        }
1584        return 0
1585}
1586
1587proc bind_button3 {w cmd} {
1588        bind $w <Any-Button-3> $cmd
1589        if {[is_MacOSX]} {
1590                bind $w <Control-Button-1> $cmd
1591        }
1592}
1593
1594proc incr_font_size {font {amt 1}} {
1595        set sz [font configure $font -size]
1596        incr sz $amt
1597        font configure $font -size $sz
1598        font configure ${font}bold -size $sz
1599}
1600
1601proc hook_failed_popup {hook msg} {
1602        global gitdir appname
1603
1604        set w .hookfail
1605        toplevel $w
1606
1607        frame $w.m
1608        label $w.m.l1 -text "$hook hook failed:" \
1609                -anchor w \
1610                -justify left \
1611                -font font_uibold
1612        text $w.m.t \
1613                -background white -borderwidth 1 \
1614                -relief sunken \
1615                -width 80 -height 10 \
1616                -font font_diff \
1617                -yscrollcommand [list $w.m.sby set]
1618        label $w.m.l2 \
1619                -text {You must correct the above errors before committing.} \
1620                -anchor w \
1621                -justify left \
1622                -font font_uibold
1623        scrollbar $w.m.sby -command [list $w.m.t yview]
1624        pack $w.m.l1 -side top -fill x
1625        pack $w.m.l2 -side bottom -fill x
1626        pack $w.m.sby -side right -fill y
1627        pack $w.m.t -side left -fill both -expand 1
1628        pack $w.m -side top -fill both -expand 1 -padx 5 -pady 10
1629
1630        $w.m.t insert 1.0 $msg
1631        $w.m.t conf -state disabled
1632
1633        button $w.ok -text OK \
1634                -width 15 \
1635                -font font_ui \
1636                -command "destroy $w"
1637        pack $w.ok -side bottom -anchor e -pady 10 -padx 10
1638
1639        bind $w <Visibility> "grab $w; focus $w"
1640        bind $w <Key-Return> "destroy $w"
1641        wm title $w "$appname ([lindex [file split \
1642                [file normalize [file dirname $gitdir]]] \
1643                end]): error"
1644        tkwait window $w
1645}
1646
1647set next_console_id 0
1648
1649proc new_console {short_title long_title} {
1650        global next_console_id console_data
1651        set w .console[incr next_console_id]
1652        set console_data($w) [list $short_title $long_title]
1653        return [console_init $w]
1654}
1655
1656proc console_init {w} {
1657        global console_cr console_data
1658        global gitdir appname M1B
1659
1660        set console_cr($w) 1.0
1661        toplevel $w
1662        frame $w.m
1663        label $w.m.l1 -text "[lindex $console_data($w) 1]:" \
1664                -anchor w \
1665                -justify left \
1666                -font font_uibold
1667        text $w.m.t \
1668                -background white -borderwidth 1 \
1669                -relief sunken \
1670                -width 80 -height 10 \
1671                -font font_diff \
1672                -state disabled \
1673                -yscrollcommand [list $w.m.sby set]
1674        label $w.m.s -text {Working... please wait...} \
1675                -anchor w \
1676                -justify left \
1677                -font font_uibold
1678        scrollbar $w.m.sby -command [list $w.m.t yview]
1679        pack $w.m.l1 -side top -fill x
1680        pack $w.m.s -side bottom -fill x
1681        pack $w.m.sby -side right -fill y
1682        pack $w.m.t -side left -fill both -expand 1
1683        pack $w.m -side top -fill both -expand 1 -padx 5 -pady 10
1684
1685        menu $w.ctxm -tearoff 0
1686        $w.ctxm add command -label "Copy" \
1687                -font font_ui \
1688                -command "tk_textCopy $w.m.t"
1689        $w.ctxm add command -label "Select All" \
1690                -font font_ui \
1691                -command "$w.m.t tag add sel 0.0 end"
1692        $w.ctxm add command -label "Copy All" \
1693                -font font_ui \
1694                -command "
1695                        $w.m.t tag add sel 0.0 end
1696                        tk_textCopy $w.m.t
1697                        $w.m.t tag remove sel 0.0 end
1698                "
1699
1700        button $w.ok -text {Close} \
1701                -font font_ui \
1702                -state disabled \
1703                -command "destroy $w"
1704        pack $w.ok -side bottom -anchor e -pady 10 -padx 10
1705
1706        bind_button3 $w.m.t "tk_popup $w.ctxm %X %Y"
1707        bind $w.m.t <$M1B-Key-a> "$w.m.t tag add sel 0.0 end;break"
1708        bind $w.m.t <$M1B-Key-A> "$w.m.t tag add sel 0.0 end;break"
1709        bind $w <Visibility> "focus $w"
1710        wm title $w "$appname ([lindex [file split \
1711                [file normalize [file dirname $gitdir]]] \
1712                end]): [lindex $console_data($w) 0]"
1713        return $w
1714}
1715
1716proc console_exec {w cmd {after {}}} {
1717        global tcl_platform
1718
1719        # -- Windows tosses the enviroment when we exec our child.
1720        #    But most users need that so we have to relogin. :-(
1721        #
1722        if {$tcl_platform(platform) eq {windows}} {
1723                set cmd [list sh --login -c "cd \"[pwd]\" && [join $cmd { }]"]
1724        }
1725
1726        # -- Tcl won't let us redirect both stdout and stderr to
1727        #    the same pipe.  So pass it through cat...
1728        #
1729        set cmd [concat | $cmd |& cat]
1730
1731        set fd_f [open $cmd r]
1732        fconfigure $fd_f -blocking 0 -translation binary
1733        fileevent $fd_f readable [list console_read $w $fd_f $after]
1734}
1735
1736proc console_read {w fd after} {
1737        global console_cr console_data
1738
1739        set buf [read $fd]
1740        if {$buf ne {}} {
1741                if {![winfo exists $w]} {console_init $w}
1742                $w.m.t conf -state normal
1743                set c 0
1744                set n [string length $buf]
1745                while {$c < $n} {
1746                        set cr [string first "\r" $buf $c]
1747                        set lf [string first "\n" $buf $c]
1748                        if {$cr < 0} {set cr [expr {$n + 1}]}
1749                        if {$lf < 0} {set lf [expr {$n + 1}]}
1750
1751                        if {$lf < $cr} {
1752                                $w.m.t insert end [string range $buf $c $lf]
1753                                set console_cr($w) [$w.m.t index {end -1c}]
1754                                set c $lf
1755                                incr c
1756                        } else {
1757                                $w.m.t delete $console_cr($w) end
1758                                $w.m.t insert end "\n"
1759                                $w.m.t insert end [string range $buf $c $cr]
1760                                set c $cr
1761                                incr c
1762                        }
1763                }
1764                $w.m.t conf -state disabled
1765                $w.m.t see end
1766        }
1767
1768        fconfigure $fd -blocking 1
1769        if {[eof $fd]} {
1770                if {[catch {close $fd}]} {
1771                        if {![winfo exists $w]} {console_init $w}
1772                        $w.m.s conf -background red -text {Error: Command Failed}
1773                        $w.ok conf -state normal
1774                        set ok 0
1775                } elseif {[winfo exists $w]} {
1776                        $w.m.s conf -background green -text {Success}
1777                        $w.ok conf -state normal
1778                        set ok 1
1779                }
1780                array unset console_cr $w
1781                array unset console_data $w
1782                if {$after ne {}} {
1783                        uplevel #0 $after $ok
1784                }
1785                return
1786        }
1787        fconfigure $fd -blocking 0
1788}
1789
1790######################################################################
1791##
1792## ui commands
1793
1794set starting_gitk_msg {Please wait... Starting gitk...}
1795
1796proc do_gitk {} {
1797        global tcl_platform ui_status_value starting_gitk_msg
1798
1799        set ui_status_value $starting_gitk_msg
1800        after 10000 {
1801                if {$ui_status_value eq $starting_gitk_msg} {
1802                        set ui_status_value {Ready.}
1803                }
1804        }
1805
1806        if {$tcl_platform(platform) eq {windows}} {
1807                exec sh -c gitk &
1808        } else {
1809                exec gitk &
1810        }
1811}
1812
1813proc do_repack {} {
1814        set w [new_console "repack" "Repacking the object database"]
1815        set cmd [list git repack]
1816        lappend cmd -a
1817        lappend cmd -d
1818        console_exec $w $cmd
1819}
1820
1821set is_quitting 0
1822
1823proc do_quit {} {
1824        global gitdir ui_comm is_quitting repo_config
1825
1826        if {$is_quitting} return
1827        set is_quitting 1
1828
1829        # -- Stash our current commit buffer.
1830        #
1831        set save [file join $gitdir GITGUI_MSG]
1832        set msg [string trim [$ui_comm get 0.0 end]]
1833        if {[$ui_comm edit modified] && $msg ne {}} {
1834                catch {
1835                        set fd [open $save w]
1836                        puts $fd [string trim [$ui_comm get 0.0 end]]
1837                        close $fd
1838                }
1839        } elseif {$msg eq {} && [file exists $save]} {
1840                file delete $save
1841        }
1842
1843        # -- Stash our current window geometry into this repository.
1844        #
1845        set cfg_geometry [list]
1846        lappend cfg_geometry [wm geometry .]
1847        lappend cfg_geometry [lindex [.vpane sash coord 0] 1]
1848        lappend cfg_geometry [lindex [.vpane.files sash coord 0] 0]
1849        if {[catch {set rc_geometry $repo_config(gui.geometry)}]} {
1850                set rc_geometry {}
1851        }
1852        if {$cfg_geometry ne $rc_geometry} {
1853                catch {exec git repo-config gui.geometry $cfg_geometry}
1854        }
1855
1856        destroy .
1857}
1858
1859proc do_rescan {} {
1860        rescan {set ui_status_value {Ready.}}
1861}
1862
1863proc include_helper {txt paths} {
1864        global file_states current_diff
1865
1866        if {![lock_index begin-update]} return
1867
1868        set pathList [list]
1869        set after {}
1870        foreach path $paths {
1871                switch -- [lindex $file_states($path) 0] {
1872                AM -
1873                MM -
1874                _M -
1875                _D {
1876                        lappend pathList $path
1877                        if {$path eq $current_diff} {
1878                                set after {reshow_diff;}
1879                        }
1880                }
1881                }
1882        }
1883        if {$pathList eq {}} {
1884                unlock_index
1885        } else {
1886                update_index \
1887                        $txt \
1888                        $pathList \
1889                        [concat $after {set ui_status_value {Ready to commit.}}]
1890        }
1891}
1892
1893proc do_include_selection {} {
1894        global current_diff selected_paths
1895
1896        if {[array size selected_paths] > 0} {
1897                include_helper \
1898                        {Including selected files} \
1899                        [array names selected_paths]
1900        } elseif {$current_diff ne {}} {
1901                include_helper \
1902                        "Including [short_path $current_diff]" \
1903                        [list $current_diff]
1904        }
1905}
1906
1907proc do_include_all {} {
1908        global file_states
1909        include_helper \
1910                {Including all modified files} \
1911                [array names file_states]
1912}
1913
1914set GIT_COMMITTER_IDENT {}
1915
1916proc do_signoff {} {
1917        global ui_comm GIT_COMMITTER_IDENT
1918
1919        if {$GIT_COMMITTER_IDENT eq {}} {
1920                if {[catch {set me [exec git var GIT_COMMITTER_IDENT]} err]} {
1921                        error_popup "Unable to obtain your identity:\n\n$err"
1922                        return
1923                }
1924                if {![regexp {^(.*) [0-9]+ [-+0-9]+$} \
1925                        $me me GIT_COMMITTER_IDENT]} {
1926                        error_popup "Invalid GIT_COMMITTER_IDENT:\n\n$me"
1927                        return
1928                }
1929        }
1930
1931        set sob "Signed-off-by: $GIT_COMMITTER_IDENT"
1932        set last [$ui_comm get {end -1c linestart} {end -1c}]
1933        if {$last ne $sob} {
1934                $ui_comm edit separator
1935                if {$last ne {}
1936                        && ![regexp {^[A-Z][A-Za-z]*-[A-Za-z-]+: *} $last]} {
1937                        $ui_comm insert end "\n"
1938                }
1939                $ui_comm insert end "\n$sob"
1940                $ui_comm edit separator
1941                $ui_comm see end
1942        }
1943}
1944
1945proc do_select_commit_type {} {
1946        global commit_type selected_commit_type
1947
1948        if {$selected_commit_type eq {new}
1949                && [string match amend* $commit_type]} {
1950                create_new_commit
1951        } elseif {$selected_commit_type eq {amend}
1952                && ![string match amend* $commit_type]} {
1953                load_last_commit
1954
1955                # The amend request was rejected...
1956                #
1957                if {![string match amend* $commit_type]} {
1958                        set commit_type new
1959                }
1960        }
1961}
1962
1963proc do_commit {} {
1964        commit_tree
1965}
1966
1967proc do_options {} {
1968        global appname gitdir font_descs
1969        global repo_config global_config
1970        global repo_config_new global_config_new
1971
1972        array unset repo_config_new
1973        array unset global_config_new
1974        foreach name [array names repo_config] {
1975                set repo_config_new($name) $repo_config($name)
1976        }
1977        load_config 1
1978        foreach name [array names repo_config] {
1979                switch -- $name {
1980                gui.diffcontext {continue}
1981                }
1982                set repo_config_new($name) $repo_config($name)
1983        }
1984        foreach name [array names global_config] {
1985                set global_config_new($name) $global_config($name)
1986        }
1987        set reponame [lindex [file split \
1988                [file normalize [file dirname $gitdir]]] \
1989                end]
1990
1991        set w .options_editor
1992        toplevel $w
1993        wm geometry $w "+[winfo rootx .]+[winfo rooty .]"
1994
1995        label $w.header -text "$appname Options" \
1996                -font font_uibold
1997        pack $w.header -side top -fill x
1998
1999        frame $w.buttons
2000        button $w.buttons.restore -text {Restore Defaults} \
2001                -font font_ui \
2002                -command do_restore_defaults
2003        pack $w.buttons.restore -side left
2004        button $w.buttons.save -text Save \
2005                -font font_ui \
2006                -command [list do_save_config $w]
2007        pack $w.buttons.save -side right
2008        button $w.buttons.cancel -text {Cancel} \
2009                -font font_ui \
2010                -command [list destroy $w]
2011        pack $w.buttons.cancel -side right
2012        pack $w.buttons -side bottom -fill x -pady 10 -padx 10
2013
2014        labelframe $w.repo -text "$reponame Repository" \
2015                -font font_ui \
2016                -relief raised -borderwidth 2
2017        labelframe $w.global -text {Global (All Repositories)} \
2018                -font font_ui \
2019                -relief raised -borderwidth 2
2020        pack $w.repo -side left -fill both -expand 1 -pady 5 -padx 5
2021        pack $w.global -side right -fill both -expand 1 -pady 5 -padx 5
2022
2023        foreach option {
2024                {b partialinclude {Allow Partially Included Files}}
2025                {b pullsummary {Show Pull Summary}}
2026                {b trustmtime  {Trust File Modification Timestamps}}
2027                {i diffcontext {Number of Diff Context Lines}}
2028                } {
2029                set type [lindex $option 0]
2030                set name [lindex $option 1]
2031                set text [lindex $option 2]
2032                foreach f {repo global} {
2033                        switch $type {
2034                        b {
2035                                checkbutton $w.$f.$name -text $text \
2036                                        -variable ${f}_config_new(gui.$name) \
2037                                        -onvalue true \
2038                                        -offvalue false \
2039                                        -font font_ui
2040                                pack $w.$f.$name -side top -anchor w
2041                        }
2042                        i {
2043                                frame $w.$f.$name
2044                                label $w.$f.$name.l -text "$text:" -font font_ui
2045                                pack $w.$f.$name.l -side left -anchor w -fill x
2046                                spinbox $w.$f.$name.v \
2047                                        -textvariable ${f}_config_new(gui.$name) \
2048                                        -from 1 -to 99 -increment 1 \
2049                                        -width 3 \
2050                                        -font font_ui
2051                                pack $w.$f.$name.v -side right -anchor e
2052                                pack $w.$f.$name -side top -anchor w -fill x
2053                        }
2054                        }
2055                }
2056        }
2057
2058        set all_fonts [lsort [font families]]
2059        foreach option $font_descs {
2060                set name [lindex $option 0]
2061                set font [lindex $option 1]
2062                set text [lindex $option 2]
2063
2064                set global_config_new(gui.$font^^family) \
2065                        [font configure $font -family]
2066                set global_config_new(gui.$font^^size) \
2067                        [font configure $font -size]
2068
2069                frame $w.global.$name
2070                label $w.global.$name.l -text "$text:" -font font_ui
2071                pack $w.global.$name.l -side left -anchor w -fill x
2072                eval tk_optionMenu $w.global.$name.family \
2073                        global_config_new(gui.$font^^family) \
2074                        $all_fonts
2075                spinbox $w.global.$name.size \
2076                        -textvariable global_config_new(gui.$font^^size) \
2077                        -from 2 -to 80 -increment 1 \
2078                        -width 3 \
2079                        -font font_ui
2080                pack $w.global.$name.size -side right -anchor e
2081                pack $w.global.$name.family -side right -anchor e
2082                pack $w.global.$name -side top -anchor w -fill x
2083        }
2084
2085        bind $w <Visibility> "grab $w; focus $w"
2086        bind $w <Key-Escape> "destroy $w"
2087        wm title $w "$appname ($reponame): Options"
2088        tkwait window $w
2089}
2090
2091proc do_restore_defaults {} {
2092        global font_descs default_config repo_config
2093        global repo_config_new global_config_new
2094
2095        foreach name [array names default_config] {
2096                set repo_config_new($name) $default_config($name)
2097                set global_config_new($name) $default_config($name)
2098        }
2099
2100        foreach option $font_descs {
2101                set name [lindex $option 0]
2102                set repo_config(gui.$name) $default_config(gui.$name)
2103        }
2104        apply_config
2105
2106        foreach option $font_descs {
2107                set name [lindex $option 0]
2108                set font [lindex $option 1]
2109                set global_config_new(gui.$font^^family) \
2110                        [font configure $font -family]
2111                set global_config_new(gui.$font^^size) \
2112                        [font configure $font -size]
2113        }
2114}
2115
2116proc do_save_config {w} {
2117        if {[catch {save_config} err]} {
2118                error_popup "Failed to completely save options:\n\n$err"
2119        }
2120        reshow_diff
2121        destroy $w
2122}
2123
2124proc do_windows_shortcut {} {
2125        global gitdir appname argv0
2126
2127        set reponame [lindex [file split \
2128                [file normalize [file dirname $gitdir]]] \
2129                end]
2130
2131        if {[catch {
2132                set desktop [exec cygpath \
2133                        --windows \
2134                        --absolute \
2135                        --long-name \
2136                        --desktop]
2137                }]} {
2138                        set desktop .
2139        }
2140        set fn [tk_getSaveFile \
2141                -parent . \
2142                -title "$appname ($reponame): Create Desktop Icon" \
2143                -initialdir $desktop \
2144                -initialfile "Git $reponame.bat"]
2145        if {$fn != {}} {
2146                if {[catch {
2147                                set fd [open $fn w]
2148                                set sh [exec cygpath \
2149                                        --windows \
2150                                        --absolute \
2151                                        --long-name \
2152                                        /bin/sh]
2153                                set me [exec cygpath \
2154                                        --unix \
2155                                        --absolute \
2156                                        $argv0]
2157                                set gd [exec cygpath \
2158                                        --unix \
2159                                        --absolute \
2160                                        $gitdir]
2161                                regsub -all ' $me "'\\''" me
2162                                regsub -all ' $gd "'\\''" gd
2163                                puts -nonewline $fd "\"$sh\" --login -c \""
2164                                puts -nonewline $fd "GIT_DIR='$gd'"
2165                                puts -nonewline $fd " '$me'"
2166                                puts $fd "&\""
2167                                close $fd
2168                        } err]} {
2169                        error_popup "Cannot write script:\n\n$err"
2170                }
2171        }
2172}
2173
2174proc do_macosx_app {} {
2175        global gitdir appname argv0 env
2176
2177        set reponame [lindex [file split \
2178                [file normalize [file dirname $gitdir]]] \
2179                end]
2180
2181        set fn [tk_getSaveFile \
2182                -parent . \
2183                -title "$appname ($reponame): Create Desktop Icon" \
2184                -initialdir [file join $env(HOME) Desktop] \
2185                -initialfile "Git $reponame.app"]
2186        if {$fn != {}} {
2187                if {[catch {
2188                                set Contents [file join $fn Contents]
2189                                set MacOS [file join $Contents MacOS]
2190                                set exe [file join $MacOS git-gui]
2191
2192                                file mkdir $MacOS
2193
2194                                set fd [open [file join $Contents PkgInfo] w]
2195                                puts -nonewline $fd {APPL????}
2196                                close $fd
2197
2198                                set fd [open [file join $Contents Info.plist] w]
2199                                puts $fd {<?xml version="1.0" encoding="UTF-8"?>
2200<!DOCTYPE plist PUBLIC "-//Apple Computer//DTD PLIST 1.0//EN" "http://www.apple.com/DTDs/PropertyList-1.0.dtd">
2201<plist version="1.0">
2202<dict>
2203        <key>CFBundleDevelopmentRegion</key>
2204        <string>English</string>
2205        <key>CFBundleExecutable</key>
2206        <string>git-gui</string>
2207        <key>CFBundleIdentifier</key>
2208        <string>org.spearce.git-gui</string>
2209        <key>CFBundleInfoDictionaryVersion</key>
2210        <string>6.0</string>
2211        <key>CFBundlePackageType</key>
2212        <string>APPL</string>
2213        <key>CFBundleSignature</key>
2214        <string>????</string>
2215        <key>CFBundleVersion</key>
2216        <string>1.0</string>
2217        <key>NSPrincipalClass</key>
2218        <string>NSApplication</string>
2219</dict>
2220</plist>}
2221                                close $fd
2222
2223                                set fd [open $exe w]
2224                                set gd [file normalize $gitdir]
2225                                set ep [file normalize [exec git --exec-path]]
2226                                regsub -all ' $gd "'\\''" gd
2227                                regsub -all ' $ep "'\\''" ep
2228                                puts $fd "#!/bin/sh"
2229                                foreach name [array names env] {
2230                                        if {[string match GIT_* $name]} {
2231                                                regsub -all ' $env($name) "'\\''" v
2232                                                puts $fd "export $name='$v'"
2233                                        }
2234                                }
2235                                puts $fd "export PATH='$ep':\$PATH"
2236                                puts $fd "export GIT_DIR='$gd'"
2237                                puts $fd "exec [file normalize $argv0]"
2238                                close $fd
2239
2240                                file attributes $exe -permissions u+x,g+x,o+x
2241                        } err]} {
2242                        error_popup "Cannot write icon:\n\n$err"
2243                }
2244        }
2245}
2246
2247proc toggle_or_diff {w x y} {
2248        global file_lists current_diff ui_index ui_other
2249        global last_clicked selected_paths
2250
2251        set pos [split [$w index @$x,$y] .]
2252        set lno [lindex $pos 0]
2253        set col [lindex $pos 1]
2254        set path [lindex $file_lists($w) [expr {$lno - 1}]]
2255        if {$path eq {}} {
2256                set last_clicked {}
2257                return
2258        }
2259
2260        set last_clicked [list $w $lno]
2261        array unset selected_paths
2262        $ui_index tag remove in_sel 0.0 end
2263        $ui_other tag remove in_sel 0.0 end
2264
2265        if {$col == 0} {
2266                if {$current_diff eq $path} {
2267                        set after {reshow_diff;}
2268                } else {
2269                        set after {}
2270                }
2271                update_index \
2272                        "Including [short_path $path]" \
2273                        [list $path] \
2274                        [concat $after {set ui_status_value {Ready.}}]
2275        } else {
2276                show_diff $path $w $lno
2277        }
2278}
2279
2280proc add_one_to_selection {w x y} {
2281        global file_lists
2282        global last_clicked selected_paths
2283
2284        set pos [split [$w index @$x,$y] .]
2285        set lno [lindex $pos 0]
2286        set col [lindex $pos 1]
2287        set path [lindex $file_lists($w) [expr {$lno - 1}]]
2288        if {$path eq {}} {
2289                set last_clicked {}
2290                return
2291        }
2292
2293        set last_clicked [list $w $lno]
2294        if {[catch {set in_sel $selected_paths($path)}]} {
2295                set in_sel 0
2296        }
2297        if {$in_sel} {
2298                unset selected_paths($path)
2299                $w tag remove in_sel $lno.0 [expr {$lno + 1}].0
2300        } else {
2301                set selected_paths($path) 1
2302                $w tag add in_sel $lno.0 [expr {$lno + 1}].0
2303        }
2304}
2305
2306proc add_range_to_selection {w x y} {
2307        global file_lists
2308        global last_clicked selected_paths
2309
2310        if {[lindex $last_clicked 0] ne $w} {
2311                toggle_or_diff $w $x $y
2312                return
2313        }
2314
2315        set pos [split [$w index @$x,$y] .]
2316        set lno [lindex $pos 0]
2317        set lc [lindex $last_clicked 1]
2318        if {$lc < $lno} {
2319                set begin $lc
2320                set end $lno
2321        } else {
2322                set begin $lno
2323                set end $lc
2324        }
2325
2326        foreach path [lrange $file_lists($w) \
2327                [expr {$begin - 1}] \
2328                [expr {$end - 1}]] {
2329                set selected_paths($path) 1
2330        }
2331        $w tag add in_sel $begin.0 [expr {$end + 1}].0
2332}
2333
2334######################################################################
2335##
2336## config defaults
2337
2338set cursor_ptr arrow
2339font create font_diff -family Courier -size 10
2340font create font_ui
2341catch {
2342        label .dummy
2343        eval font configure font_ui [font actual [.dummy cget -font]]
2344        destroy .dummy
2345}
2346
2347font create font_uibold
2348font create font_diffbold
2349
2350set M1B M1
2351set M1T M1
2352if {$tcl_platform(platform) eq {windows}} {
2353        set M1B Control
2354        set M1T Ctrl
2355} elseif {[is_MacOSX]} {
2356        set M1B M1
2357        set M1T Cmd
2358}
2359
2360proc apply_config {} {
2361        global repo_config font_descs
2362
2363        foreach option $font_descs {
2364                set name [lindex $option 0]
2365                set font [lindex $option 1]
2366                if {[catch {
2367                        foreach {cn cv} $repo_config(gui.$name) {
2368                                font configure $font $cn $cv
2369                        }
2370                        } err]} {
2371                        error_popup "Invalid font specified in gui.$name:\n\n$err"
2372                }
2373                foreach {cn cv} [font configure $font] {
2374                        font configure ${font}bold $cn $cv
2375                }
2376                font configure ${font}bold -weight bold
2377        }
2378}
2379
2380set default_config(gui.trustmtime) false
2381set default_config(gui.pullsummary) true
2382set default_config(gui.partialinclude) false
2383set default_config(gui.diffcontext) 5
2384set default_config(gui.fontui) [font configure font_ui]
2385set default_config(gui.fontdiff) [font configure font_diff]
2386set font_descs {
2387        {fontui   font_ui   {Main Font}}
2388        {fontdiff font_diff {Diff/Console Font}}
2389}
2390load_config 0
2391apply_config
2392
2393######################################################################
2394##
2395## ui construction
2396
2397# -- Menu Bar
2398#
2399menu .mbar -tearoff 0
2400.mbar add cascade -label Project -menu .mbar.project
2401.mbar add cascade -label Edit -menu .mbar.edit
2402.mbar add cascade -label Commit -menu .mbar.commit
2403if {!$single_commit} {
2404        .mbar add cascade -label Fetch -menu .mbar.fetch
2405        .mbar add cascade -label Pull -menu .mbar.pull
2406        .mbar add cascade -label Push -menu .mbar.push
2407}
2408. configure -menu .mbar
2409
2410# -- Project Menu
2411#
2412menu .mbar.project
2413.mbar.project add command -label Visualize \
2414        -command do_gitk \
2415        -font font_ui
2416if {!$single_commit} {
2417        .mbar.project add command -label {Repack Database} \
2418                -command do_repack \
2419                -font font_ui
2420
2421        if {$tcl_platform(platform) eq {windows}} {
2422                .mbar.project add command \
2423                        -label {Create Desktop Icon} \
2424                        -command do_windows_shortcut \
2425                        -font font_ui
2426        } elseif {[is_MacOSX]} {
2427                .mbar.project add command \
2428                        -label {Create Desktop Icon} \
2429                        -command do_macosx_app \
2430                        -font font_ui
2431        }
2432}
2433.mbar.project add command -label Quit \
2434        -command do_quit \
2435        -accelerator $M1T-Q \
2436        -font font_ui
2437
2438# -- Edit Menu
2439#
2440menu .mbar.edit
2441.mbar.edit add command -label Undo \
2442        -command {catch {[focus] edit undo}} \
2443        -accelerator $M1T-Z \
2444        -font font_ui
2445.mbar.edit add command -label Redo \
2446        -command {catch {[focus] edit redo}} \
2447        -accelerator $M1T-Y \
2448        -font font_ui
2449.mbar.edit add separator
2450.mbar.edit add command -label Cut \
2451        -command {catch {tk_textCut [focus]}} \
2452        -accelerator $M1T-X \
2453        -font font_ui
2454.mbar.edit add command -label Copy \
2455        -command {catch {tk_textCopy [focus]}} \
2456        -accelerator $M1T-C \
2457        -font font_ui
2458.mbar.edit add command -label Paste \
2459        -command {catch {tk_textPaste [focus]; [focus] see insert}} \
2460        -accelerator $M1T-V \
2461        -font font_ui
2462.mbar.edit add command -label Delete \
2463        -command {catch {[focus] delete sel.first sel.last}} \
2464        -accelerator Del \
2465        -font font_ui
2466.mbar.edit add separator
2467.mbar.edit add command -label {Select All} \
2468        -command {catch {[focus] tag add sel 0.0 end}} \
2469        -accelerator $M1T-A \
2470        -font font_ui
2471.mbar.edit add separator
2472.mbar.edit add command -label {Options...} \
2473        -command do_options \
2474        -font font_ui
2475
2476# -- Commit Menu
2477#
2478menu .mbar.commit
2479
2480.mbar.commit add radiobutton \
2481        -label {New Commit} \
2482        -command do_select_commit_type \
2483        -variable selected_commit_type \
2484        -value new \
2485        -font font_ui
2486lappend disable_on_lock \
2487        [list .mbar.commit entryconf [.mbar.commit index last] -state]
2488
2489.mbar.commit add radiobutton \
2490        -label {Amend Last Commit} \
2491        -command do_select_commit_type \
2492        -variable selected_commit_type \
2493        -value amend \
2494        -font font_ui
2495lappend disable_on_lock \
2496        [list .mbar.commit entryconf [.mbar.commit index last] -state]
2497
2498.mbar.commit add separator
2499
2500.mbar.commit add command -label Rescan \
2501        -command do_rescan \
2502        -accelerator F5 \
2503        -font font_ui
2504lappend disable_on_lock \
2505        [list .mbar.commit entryconf [.mbar.commit index last] -state]
2506
2507.mbar.commit add command -label {Include Selected Files} \
2508        -command do_include_selection \
2509        -font font_ui
2510lappend disable_on_lock \
2511        [list .mbar.commit entryconf [.mbar.commit index last] -state]
2512
2513.mbar.commit add command -label {Include All Files} \
2514        -command do_include_all \
2515        -accelerator $M1T-I \
2516        -font font_ui
2517lappend disable_on_lock \
2518        [list .mbar.commit entryconf [.mbar.commit index last] -state]
2519
2520.mbar.commit add command -label {Sign Off} \
2521        -command do_signoff \
2522        -accelerator $M1T-S \
2523        -font font_ui
2524
2525.mbar.commit add command -label Commit \
2526        -command do_commit \
2527        -accelerator $M1T-Return \
2528        -font font_ui
2529lappend disable_on_lock \
2530        [list .mbar.commit entryconf [.mbar.commit index last] -state]
2531
2532# -- Transport menus
2533#
2534if {!$single_commit} {
2535        menu .mbar.fetch
2536        menu .mbar.pull
2537        menu .mbar.push
2538}
2539
2540# -- Main Window Layout
2541#
2542panedwindow .vpane -orient vertical
2543panedwindow .vpane.files -orient horizontal
2544.vpane add .vpane.files -sticky nsew -height 100 -width 400
2545pack .vpane -anchor n -side top -fill both -expand 1
2546
2547# -- Index File List
2548#
2549frame .vpane.files.index -height 100 -width 400
2550label .vpane.files.index.title -text {Modified Files} \
2551        -background green \
2552        -font font_ui
2553text $ui_index -background white -borderwidth 0 \
2554        -width 40 -height 10 \
2555        -font font_ui \
2556        -cursor $cursor_ptr \
2557        -yscrollcommand {.vpane.files.index.sb set} \
2558        -state disabled
2559scrollbar .vpane.files.index.sb -command [list $ui_index yview]
2560pack .vpane.files.index.title -side top -fill x
2561pack .vpane.files.index.sb -side right -fill y
2562pack $ui_index -side left -fill both -expand 1
2563.vpane.files add .vpane.files.index -sticky nsew
2564
2565# -- Other (Add) File List
2566#
2567frame .vpane.files.other -height 100 -width 100
2568label .vpane.files.other.title -text {Untracked Files} \
2569        -background red \
2570        -font font_ui
2571text $ui_other -background white -borderwidth 0 \
2572        -width 40 -height 10 \
2573        -font font_ui \
2574        -cursor $cursor_ptr \
2575        -yscrollcommand {.vpane.files.other.sb set} \
2576        -state disabled
2577scrollbar .vpane.files.other.sb -command [list $ui_other yview]
2578pack .vpane.files.other.title -side top -fill x
2579pack .vpane.files.other.sb -side right -fill y
2580pack $ui_other -side left -fill both -expand 1
2581.vpane.files add .vpane.files.other -sticky nsew
2582
2583foreach i [list $ui_index $ui_other] {
2584        $i tag conf in_diff -font font_uibold
2585        $i tag conf in_sel \
2586                -background [$i cget -foreground] \
2587                -foreground [$i cget -background]
2588}
2589unset i
2590
2591# -- Diff and Commit Area
2592#
2593frame .vpane.lower -height 300 -width 400
2594frame .vpane.lower.commarea
2595frame .vpane.lower.diff -relief sunken -borderwidth 1
2596pack .vpane.lower.commarea -side top -fill x
2597pack .vpane.lower.diff -side bottom -fill both -expand 1
2598.vpane add .vpane.lower -stick nsew
2599
2600# -- Commit Area Buttons
2601#
2602frame .vpane.lower.commarea.buttons
2603label .vpane.lower.commarea.buttons.l -text {} \
2604        -anchor w \
2605        -justify left \
2606        -font font_ui
2607pack .vpane.lower.commarea.buttons.l -side top -fill x
2608pack .vpane.lower.commarea.buttons -side left -fill y
2609
2610button .vpane.lower.commarea.buttons.rescan -text {Rescan} \
2611        -command do_rescan \
2612        -font font_ui
2613pack .vpane.lower.commarea.buttons.rescan -side top -fill x
2614lappend disable_on_lock \
2615        {.vpane.lower.commarea.buttons.rescan conf -state}
2616
2617button .vpane.lower.commarea.buttons.incall -text {Include All} \
2618        -command do_include_all \
2619        -font font_ui
2620pack .vpane.lower.commarea.buttons.incall -side top -fill x
2621lappend disable_on_lock \
2622        {.vpane.lower.commarea.buttons.incall conf -state}
2623
2624button .vpane.lower.commarea.buttons.signoff -text {Sign Off} \
2625        -command do_signoff \
2626        -font font_ui
2627pack .vpane.lower.commarea.buttons.signoff -side top -fill x
2628
2629button .vpane.lower.commarea.buttons.commit -text {Commit} \
2630        -command do_commit \
2631        -font font_ui
2632pack .vpane.lower.commarea.buttons.commit -side top -fill x
2633lappend disable_on_lock \
2634        {.vpane.lower.commarea.buttons.commit conf -state}
2635
2636# -- Commit Message Buffer
2637#
2638frame .vpane.lower.commarea.buffer
2639frame .vpane.lower.commarea.buffer.header
2640set ui_comm .vpane.lower.commarea.buffer.t
2641set ui_coml .vpane.lower.commarea.buffer.header.l
2642radiobutton .vpane.lower.commarea.buffer.header.new \
2643        -text {New Commit} \
2644        -command do_select_commit_type \
2645        -variable selected_commit_type \
2646        -value new \
2647        -font font_ui
2648lappend disable_on_lock \
2649        [list .vpane.lower.commarea.buffer.header.new conf -state]
2650radiobutton .vpane.lower.commarea.buffer.header.amend \
2651        -text {Amend Last Commit} \
2652        -command do_select_commit_type \
2653        -variable selected_commit_type \
2654        -value amend \
2655        -font font_ui
2656lappend disable_on_lock \
2657        [list .vpane.lower.commarea.buffer.header.amend conf -state]
2658label $ui_coml \
2659        -anchor w \
2660        -justify left \
2661        -font font_ui
2662proc trace_commit_type {varname args} {
2663        global ui_coml commit_type
2664        switch -glob -- $commit_type {
2665        initial       {set txt {Initial Commit Message:}}
2666        amend         {set txt {Amended Commit Message:}}
2667        amend-initial {set txt {Amended Initial Commit Message:}}
2668        merge         {set txt {Merge Commit Message:}}
2669        *             {set txt {Commit Message:}}
2670        }
2671        $ui_coml conf -text $txt
2672}
2673trace add variable commit_type write trace_commit_type
2674pack $ui_coml -side left -fill x
2675pack .vpane.lower.commarea.buffer.header.amend -side right
2676pack .vpane.lower.commarea.buffer.header.new -side right
2677
2678text $ui_comm -background white -borderwidth 1 \
2679        -undo true \
2680        -maxundo 20 \
2681        -autoseparators true \
2682        -relief sunken \
2683        -width 75 -height 9 -wrap none \
2684        -font font_diff \
2685        -yscrollcommand {.vpane.lower.commarea.buffer.sby set}
2686scrollbar .vpane.lower.commarea.buffer.sby \
2687        -command [list $ui_comm yview]
2688pack .vpane.lower.commarea.buffer.header -side top -fill x
2689pack .vpane.lower.commarea.buffer.sby -side right -fill y
2690pack $ui_comm -side left -fill y
2691pack .vpane.lower.commarea.buffer -side left -fill y
2692
2693# -- Commit Message Buffer Context Menu
2694#
2695set ctxm .vpane.lower.commarea.buffer.ctxm
2696menu $ctxm -tearoff 0
2697$ctxm add command \
2698        -label {Cut} \
2699        -font font_ui \
2700        -command {tk_textCut $ui_comm}
2701$ctxm add command \
2702        -label {Copy} \
2703        -font font_ui \
2704        -command {tk_textCopy $ui_comm}
2705$ctxm add command \
2706        -label {Paste} \
2707        -font font_ui \
2708        -command {tk_textPaste $ui_comm}
2709$ctxm add command \
2710        -label {Delete} \
2711        -font font_ui \
2712        -command {$ui_comm delete sel.first sel.last}
2713$ctxm add separator
2714$ctxm add command \
2715        -label {Select All} \
2716        -font font_ui \
2717        -command {$ui_comm tag add sel 0.0 end}
2718$ctxm add command \
2719        -label {Copy All} \
2720        -font font_ui \
2721        -command {
2722                $ui_comm tag add sel 0.0 end
2723                tk_textCopy $ui_comm
2724                $ui_comm tag remove sel 0.0 end
2725        }
2726$ctxm add separator
2727$ctxm add command \
2728        -label {Sign Off} \
2729        -font font_ui \
2730        -command do_signoff
2731bind_button3 $ui_comm "tk_popup $ctxm %X %Y"
2732
2733# -- Diff Header
2734#
2735set current_diff {}
2736set diff_actions [list]
2737proc trace_current_diff {varname args} {
2738        global current_diff diff_actions file_states
2739        if {$current_diff eq {}} {
2740                set s {}
2741                set f {}
2742                set p {}
2743                set o disabled
2744        } else {
2745                set p $current_diff
2746                set s [mapdesc [lindex $file_states($p) 0] $p]
2747                set f {File:}
2748                set p [escape_path $p]
2749                set o normal
2750        }
2751
2752        .vpane.lower.diff.header.status configure -text $s
2753        .vpane.lower.diff.header.file configure -text $f
2754        .vpane.lower.diff.header.path configure -text $p
2755        foreach w $diff_actions {
2756                uplevel #0 $w $o
2757        }
2758}
2759trace add variable current_diff write trace_current_diff
2760
2761frame .vpane.lower.diff.header -background orange
2762label .vpane.lower.diff.header.status \
2763        -background orange \
2764        -width $max_status_desc \
2765        -anchor w \
2766        -justify left \
2767        -font font_ui
2768label .vpane.lower.diff.header.file \
2769        -background orange \
2770        -anchor w \
2771        -justify left \
2772        -font font_ui
2773label .vpane.lower.diff.header.path \
2774        -background orange \
2775        -anchor w \
2776        -justify left \
2777        -font font_ui
2778pack .vpane.lower.diff.header.status -side left
2779pack .vpane.lower.diff.header.file -side left
2780pack .vpane.lower.diff.header.path -fill x
2781set ctxm .vpane.lower.diff.header.ctxm
2782menu $ctxm -tearoff 0
2783$ctxm add command \
2784        -label {Copy} \
2785        -font font_ui \
2786        -command {
2787                clipboard clear
2788                clipboard append \
2789                        -format STRING \
2790                        -type STRING \
2791                        -- $current_diff
2792        }
2793lappend diff_actions [list $ctxm entryconf [$ctxm index last] -state]
2794bind_button3 .vpane.lower.diff.header.path "tk_popup $ctxm %X %Y"
2795
2796# -- Diff Body
2797#
2798frame .vpane.lower.diff.body
2799set ui_diff .vpane.lower.diff.body.t
2800text $ui_diff -background white -borderwidth 0 \
2801        -width 80 -height 15 -wrap none \
2802        -font font_diff \
2803        -xscrollcommand {.vpane.lower.diff.body.sbx set} \
2804        -yscrollcommand {.vpane.lower.diff.body.sby set} \
2805        -state disabled
2806scrollbar .vpane.lower.diff.body.sbx -orient horizontal \
2807        -command [list $ui_diff xview]
2808scrollbar .vpane.lower.diff.body.sby -orient vertical \
2809        -command [list $ui_diff yview]
2810pack .vpane.lower.diff.body.sbx -side bottom -fill x
2811pack .vpane.lower.diff.body.sby -side right -fill y
2812pack $ui_diff -side left -fill both -expand 1
2813pack .vpane.lower.diff.header -side top -fill x
2814pack .vpane.lower.diff.body -side bottom -fill both -expand 1
2815
2816$ui_diff tag conf dm -foreground red
2817$ui_diff tag conf dp -foreground blue
2818$ui_diff tag conf di -foreground {#00a000}
2819$ui_diff tag conf dni -foreground {#a000a0}
2820$ui_diff tag conf da -font font_diffbold
2821$ui_diff tag conf bold -font font_diffbold
2822
2823# -- Diff Body Context Menu
2824#
2825set ctxm .vpane.lower.diff.body.ctxm
2826menu $ctxm -tearoff 0
2827$ctxm add command \
2828        -label {Copy} \
2829        -font font_ui \
2830        -command {tk_textCopy $ui_diff}
2831lappend diff_actions [list $ctxm entryconf [$ctxm index last] -state]
2832$ctxm add command \
2833        -label {Select All} \
2834        -font font_ui \
2835        -command {$ui_diff tag add sel 0.0 end}
2836lappend diff_actions [list $ctxm entryconf [$ctxm index last] -state]
2837$ctxm add command \
2838        -label {Copy All} \
2839        -font font_ui \
2840        -command {
2841                $ui_diff tag add sel 0.0 end
2842                tk_textCopy $ui_diff
2843                $ui_diff tag remove sel 0.0 end
2844        }
2845lappend diff_actions [list $ctxm entryconf [$ctxm index last] -state]
2846$ctxm add separator
2847$ctxm add command \
2848        -label {Decrease Font Size} \
2849        -font font_ui \
2850        -command {incr_font_size font_diff -1}
2851lappend diff_actions [list $ctxm entryconf [$ctxm index last] -state]
2852$ctxm add command \
2853        -label {Increase Font Size} \
2854        -font font_ui \
2855        -command {incr_font_size font_diff 1}
2856lappend diff_actions [list $ctxm entryconf [$ctxm index last] -state]
2857$ctxm add separator
2858$ctxm add command \
2859        -label {Show Less Context} \
2860        -font font_ui \
2861        -command {if {$repo_config(gui.diffcontext) >= 2} {
2862                incr repo_config(gui.diffcontext) -1
2863                reshow_diff
2864        }}
2865lappend diff_actions [list $ctxm entryconf [$ctxm index last] -state]
2866$ctxm add command \
2867        -label {Show More Context} \
2868        -font font_ui \
2869        -command {
2870                incr repo_config(gui.diffcontext)
2871                reshow_diff
2872        }
2873lappend diff_actions [list $ctxm entryconf [$ctxm index last] -state]
2874$ctxm add separator
2875$ctxm add command -label {Options...} \
2876        -font font_ui \
2877        -command do_options
2878bind_button3 $ui_diff "tk_popup $ctxm %X %Y"
2879
2880# -- Status Bar
2881#
2882set ui_status_value {Initializing...}
2883label .status -textvariable ui_status_value \
2884        -anchor w \
2885        -justify left \
2886        -borderwidth 1 \
2887        -relief sunken \
2888        -font font_ui
2889pack .status -anchor w -side bottom -fill x
2890
2891# -- Load geometry
2892#
2893catch {
2894set gm $repo_config(gui.geometry)
2895wm geometry . [lindex $gm 0]
2896.vpane sash place 0 \
2897        [lindex [.vpane sash coord 0] 0] \
2898        [lindex $gm 1]
2899.vpane.files sash place 0 \
2900        [lindex $gm 2] \
2901        [lindex [.vpane.files sash coord 0] 1]
2902unset gm
2903}
2904
2905# -- Key Bindings
2906#
2907bind $ui_comm <$M1B-Key-Return> {do_commit;break}
2908bind $ui_comm <$M1B-Key-i> {do_include_all;break}
2909bind $ui_comm <$M1B-Key-I> {do_include_all;break}
2910bind $ui_comm <$M1B-Key-x> {tk_textCut %W;break}
2911bind $ui_comm <$M1B-Key-X> {tk_textCut %W;break}
2912bind $ui_comm <$M1B-Key-c> {tk_textCopy %W;break}
2913bind $ui_comm <$M1B-Key-C> {tk_textCopy %W;break}
2914bind $ui_comm <$M1B-Key-v> {tk_textPaste %W; %W see insert; break}
2915bind $ui_comm <$M1B-Key-V> {tk_textPaste %W; %W see insert; break}
2916bind $ui_comm <$M1B-Key-a> {%W tag add sel 0.0 end;break}
2917bind $ui_comm <$M1B-Key-A> {%W tag add sel 0.0 end;break}
2918
2919bind $ui_diff <$M1B-Key-x> {tk_textCopy %W;break}
2920bind $ui_diff <$M1B-Key-X> {tk_textCopy %W;break}
2921bind $ui_diff <$M1B-Key-c> {tk_textCopy %W;break}
2922bind $ui_diff <$M1B-Key-C> {tk_textCopy %W;break}
2923bind $ui_diff <$M1B-Key-v> {break}
2924bind $ui_diff <$M1B-Key-V> {break}
2925bind $ui_diff <$M1B-Key-a> {%W tag add sel 0.0 end;break}
2926bind $ui_diff <$M1B-Key-A> {%W tag add sel 0.0 end;break}
2927bind $ui_diff <Key-Up>     {catch {%W yview scroll -1 units};break}
2928bind $ui_diff <Key-Down>   {catch {%W yview scroll  1 units};break}
2929bind $ui_diff <Key-Left>   {catch {%W xview scroll -1 units};break}
2930bind $ui_diff <Key-Right>  {catch {%W xview scroll  1 units};break}
2931
2932bind .   <Destroy> do_quit
2933bind all <Key-F5> do_rescan
2934bind all <$M1B-Key-r> do_rescan
2935bind all <$M1B-Key-R> do_rescan
2936bind .   <$M1B-Key-s> do_signoff
2937bind .   <$M1B-Key-S> do_signoff
2938bind .   <$M1B-Key-i> do_include_all
2939bind .   <$M1B-Key-I> do_include_all
2940bind .   <$M1B-Key-Return> do_commit
2941bind all <$M1B-Key-q> do_quit
2942bind all <$M1B-Key-Q> do_quit
2943bind all <$M1B-Key-w> {destroy [winfo toplevel %W]}
2944bind all <$M1B-Key-W> {destroy [winfo toplevel %W]}
2945foreach i [list $ui_index $ui_other] {
2946        bind $i <Button-1>       "toggle_or_diff         $i %x %y; break"
2947        bind $i <$M1B-Button-1>  "add_one_to_selection   $i %x %y; break"
2948        bind $i <Shift-Button-1> "add_range_to_selection $i %x %y; break"
2949}
2950unset i
2951
2952set file_lists($ui_index) [list]
2953set file_lists($ui_other) [list]
2954
2955set HEAD {}
2956set PARENT {}
2957set commit_type {}
2958set empty_tree {}
2959set current_diff {}
2960set selected_commit_type new
2961
2962wm title . "$appname ([file normalize [file dirname $gitdir]])"
2963focus -force $ui_comm
2964if {!$single_commit} {
2965        load_all_remotes
2966        populate_fetch_menu .mbar.fetch
2967        populate_pull_menu .mbar.pull
2968        populate_push_menu .mbar.push
2969}
2970lock_index begin-read
2971after 1 do_rescan