git-guion commit git-gui: Hide non-commit related commands when invoked as git-citool. (4ccdab0)
   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 {} {
  28        global repo_config global_config default_config
  29
  30        array unset global_config
  31        array unset repo_config
  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        catch {
  46                set fd_rc [open "| git repo-config --list" r]
  47                while {[gets $fd_rc line] >= 0} {
  48                        if {[regexp {^([^=]+)=(.*)$} $line line name value]} {
  49                                if {[is_many_config $name]} {
  50                                        lappend repo_config($name) $value
  51                                } else {
  52                                        set repo_config($name) $value
  53                                }
  54                        }
  55                }
  56                close $fd_rc
  57        }
  58
  59        foreach name [array names default_config] {
  60                if {[catch {set v $global_config($name)}]} {
  61                        set global_config($name) $default_config($name)
  62                }
  63                if {[catch {set v $repo_config($name)}]} {
  64                        set repo_config($name) $default_config($name)
  65                }
  66        }
  67}
  68
  69proc save_config {} {
  70        global default_config font_descs
  71        global repo_config global_config
  72        global repo_config_new global_config_new
  73
  74        foreach option $font_descs {
  75                set name [lindex $option 0]
  76                set font [lindex $option 1]
  77                font configure $font \
  78                        -family $global_config_new(gui.$font^^family) \
  79                        -size $global_config_new(gui.$font^^size)
  80                font configure ${font}bold \
  81                        -family $global_config_new(gui.$font^^family) \
  82                        -size $global_config_new(gui.$font^^size)
  83                set global_config_new(gui.$name) [font configure $font]
  84                unset global_config_new(gui.$font^^family)
  85                unset global_config_new(gui.$font^^size)
  86        }
  87
  88        foreach name [array names default_config] {
  89                set value $global_config_new($name)
  90                if {$value != $global_config($name)} {
  91                        if {$value == $default_config($name)} {
  92                                catch {exec git repo-config --global --unset $name}
  93                        } else {
  94                                regsub -all "\[{}\]" $value {"} value
  95                                exec git repo-config --global $name $value
  96                        }
  97                        set global_config($name) $value
  98                        if {$value == $repo_config($name)} {
  99                                catch {exec git repo-config --unset $name}
 100                                set repo_config($name) $value
 101                        }
 102                }
 103        }
 104
 105        foreach name [array names default_config] {
 106                set value $repo_config_new($name)
 107                if {$value != $repo_config($name)} {
 108                        if {$value == $global_config($name)} {
 109                                catch {exec git repo-config --unset $name}
 110                        } else {
 111                                regsub -all "\[{}\]" $value {"} value
 112                                exec git repo-config $name $value
 113                        }
 114                        set repo_config($name) $value
 115                }
 116        }
 117}
 118
 119proc error_popup {msg} {
 120        global gitdir appname
 121
 122        set title $appname
 123        if {$gitdir != {}} {
 124                append title { (}
 125                append title [lindex \
 126                        [file split [file normalize [file dirname $gitdir]]] \
 127                        end]
 128                append title {)}
 129        }
 130        tk_messageBox \
 131                -parent . \
 132                -icon error \
 133                -type ok \
 134                -title "$title: error" \
 135                -message $msg
 136}
 137
 138proc info_popup {msg} {
 139        global gitdir appname
 140
 141        set title $appname
 142        if {$gitdir != {}} {
 143                append title { (}
 144                append title [lindex \
 145                        [file split [file normalize [file dirname $gitdir]]] \
 146                        end]
 147                append title {)}
 148        }
 149        tk_messageBox \
 150                -parent . \
 151                -icon error \
 152                -type ok \
 153                -title $title \
 154                -message $msg
 155}
 156
 157######################################################################
 158##
 159## repository setup
 160
 161if {   [catch {set cdup [exec git rev-parse --show-cdup]} err]
 162        || [catch {set gitdir [exec git rev-parse --git-dir]} err]} {
 163        catch {wm withdraw .}
 164        error_popup "Cannot find the git directory:\n\n$err"
 165        exit 1
 166}
 167if {$cdup != ""} {
 168        cd $cdup
 169}
 170unset cdup
 171
 172set single_commit 0
 173if {$appname == {git-citool}} {
 174        set single_commit 1
 175}
 176
 177######################################################################
 178##
 179## task management
 180
 181set status_active 0
 182set diff_active 0
 183set commit_active 0
 184
 185set disable_on_lock [list]
 186set index_lock_type none
 187
 188set HEAD {}
 189set PARENT {}
 190set commit_type {}
 191
 192proc lock_index {type} {
 193        global index_lock_type disable_on_lock
 194
 195        if {$index_lock_type == {none}} {
 196                set index_lock_type $type
 197                foreach w $disable_on_lock {
 198                        uplevel #0 $w disabled
 199                }
 200                return 1
 201        } elseif {$index_lock_type == {begin-update} && $type == {update}} {
 202                set index_lock_type $type
 203                return 1
 204        }
 205        return 0
 206}
 207
 208proc unlock_index {} {
 209        global index_lock_type disable_on_lock
 210
 211        set index_lock_type none
 212        foreach w $disable_on_lock {
 213                uplevel #0 $w normal
 214        }
 215}
 216
 217######################################################################
 218##
 219## status
 220
 221proc repository_state {hdvar ctvar} {
 222        global gitdir
 223        upvar $hdvar hd $ctvar ct
 224
 225        if {[catch {set hd [exec git rev-parse --verify HEAD]}]} {
 226                set ct initial
 227        } elseif {[file exists [file join $gitdir MERGE_HEAD]]} {
 228                set ct merge
 229        } else {
 230                set ct normal
 231        }
 232}
 233
 234proc update_status {{final Ready.}} {
 235        global HEAD PARENT commit_type
 236        global ui_index ui_other ui_status_value ui_comm
 237        global status_active file_states
 238        global repo_config
 239
 240        if {$status_active || ![lock_index read]} return
 241
 242        repository_state new_HEAD new_type
 243        if {$commit_type == {amend} 
 244                && $new_type == {normal}
 245                && $new_HEAD == $HEAD} {
 246        } else {
 247                set HEAD $new_HEAD
 248                set PARENT $new_HEAD
 249                set commit_type $new_type
 250        }
 251
 252        array unset file_states
 253
 254        if {![$ui_comm edit modified]
 255                || [string trim [$ui_comm get 0.0 end]] == {}} {
 256                if {[load_message GITGUI_MSG]} {
 257                } elseif {[load_message MERGE_MSG]} {
 258                } elseif {[load_message SQUASH_MSG]} {
 259                }
 260                $ui_comm edit modified false
 261                $ui_comm edit reset
 262        }
 263
 264        if {$repo_config(gui.trustmtime) == {true}} {
 265                update_status_stage2 {} $final
 266        } else {
 267                set status_active 1
 268                set ui_status_value {Refreshing file status...}
 269                set cmd [list git update-index]
 270                lappend cmd -q
 271                lappend cmd --unmerged
 272                lappend cmd --ignore-missing
 273                lappend cmd --refresh
 274                set fd_rf [open "| $cmd" r]
 275                fconfigure $fd_rf -blocking 0 -translation binary
 276                fileevent $fd_rf readable \
 277                        [list update_status_stage2 $fd_rf $final]
 278        }
 279}
 280
 281proc update_status_stage2 {fd final} {
 282        global gitdir PARENT commit_type
 283        global ui_index ui_other ui_status_value ui_comm
 284        global status_active
 285        global buf_rdi buf_rdf buf_rlo
 286
 287        if {$fd != {}} {
 288                read $fd
 289                if {![eof $fd]} return
 290                close $fd
 291        }
 292
 293        set ls_others [list | git ls-files --others -z \
 294                --exclude-per-directory=.gitignore]
 295        set info_exclude [file join $gitdir info exclude]
 296        if {[file readable $info_exclude]} {
 297                lappend ls_others "--exclude-from=$info_exclude"
 298        }
 299
 300        set buf_rdi {}
 301        set buf_rdf {}
 302        set buf_rlo {}
 303
 304        set status_active 3
 305        set ui_status_value {Scanning for modified files ...}
 306        set fd_di [open "| git diff-index --cached -z $PARENT" r]
 307        set fd_df [open "| git diff-files -z" r]
 308        set fd_lo [open $ls_others r]
 309
 310        fconfigure $fd_di -blocking 0 -translation binary
 311        fconfigure $fd_df -blocking 0 -translation binary
 312        fconfigure $fd_lo -blocking 0 -translation binary
 313        fileevent $fd_di readable [list read_diff_index $fd_di $final]
 314        fileevent $fd_df readable [list read_diff_files $fd_df $final]
 315        fileevent $fd_lo readable [list read_ls_others $fd_lo $final]
 316}
 317
 318proc load_message {file} {
 319        global gitdir ui_comm
 320
 321        set f [file join $gitdir $file]
 322        if {[file isfile $f]} {
 323                if {[catch {set fd [open $f r]}]} {
 324                        return 0
 325                }
 326                set content [string trim [read $fd]]
 327                close $fd
 328                $ui_comm delete 0.0 end
 329                $ui_comm insert end $content
 330                return 1
 331        }
 332        return 0
 333}
 334
 335proc read_diff_index {fd final} {
 336        global buf_rdi
 337
 338        append buf_rdi [read $fd]
 339        set c 0
 340        set n [string length $buf_rdi]
 341        while {$c < $n} {
 342                set z1 [string first "\0" $buf_rdi $c]
 343                if {$z1 == -1} break
 344                incr z1
 345                set z2 [string first "\0" $buf_rdi $z1]
 346                if {$z2 == -1} break
 347
 348                set c $z2
 349                incr z2 -1
 350                display_file \
 351                        [string range $buf_rdi $z1 $z2] \
 352                        [string index $buf_rdi [expr $z1 - 2]]_
 353                incr c
 354        }
 355        if {$c < $n} {
 356                set buf_rdi [string range $buf_rdi $c end]
 357        } else {
 358                set buf_rdi {}
 359        }
 360
 361        status_eof $fd buf_rdi $final
 362}
 363
 364proc read_diff_files {fd final} {
 365        global buf_rdf
 366
 367        append buf_rdf [read $fd]
 368        set c 0
 369        set n [string length $buf_rdf]
 370        while {$c < $n} {
 371                set z1 [string first "\0" $buf_rdf $c]
 372                if {$z1 == -1} break
 373                incr z1
 374                set z2 [string first "\0" $buf_rdf $z1]
 375                if {$z2 == -1} break
 376
 377                set c $z2
 378                incr z2 -1
 379                display_file \
 380                        [string range $buf_rdf $z1 $z2] \
 381                        _[string index $buf_rdf [expr $z1 - 2]]
 382                incr c
 383        }
 384        if {$c < $n} {
 385                set buf_rdf [string range $buf_rdf $c end]
 386        } else {
 387                set buf_rdf {}
 388        }
 389
 390        status_eof $fd buf_rdf $final
 391}
 392
 393proc read_ls_others {fd final} {
 394        global buf_rlo
 395
 396        append buf_rlo [read $fd]
 397        set pck [split $buf_rlo "\0"]
 398        set buf_rlo [lindex $pck end]
 399        foreach p [lrange $pck 0 end-1] {
 400                display_file $p _O
 401        }
 402        status_eof $fd buf_rlo $final
 403}
 404
 405proc status_eof {fd buf final} {
 406        global status_active ui_status_value
 407        upvar $buf to_clear
 408
 409        if {[eof $fd]} {
 410                set to_clear {}
 411                close $fd
 412
 413                if {[incr status_active -1] == 0} {
 414                        display_all_files
 415                        unlock_index
 416                        reshow_diff
 417                        set ui_status_value $final
 418                }
 419        }
 420}
 421
 422######################################################################
 423##
 424## diff
 425
 426proc clear_diff {} {
 427        global ui_diff ui_fname_value ui_fstatus_value ui_index ui_other
 428
 429        $ui_diff conf -state normal
 430        $ui_diff delete 0.0 end
 431        $ui_diff conf -state disabled
 432
 433        set ui_fname_value {}
 434        set ui_fstatus_value {}
 435
 436        $ui_index tag remove in_diff 0.0 end
 437        $ui_other tag remove in_diff 0.0 end
 438}
 439
 440proc reshow_diff {} {
 441        global ui_fname_value ui_status_value file_states
 442
 443        if {$ui_fname_value == {}
 444                || [catch {set s $file_states($ui_fname_value)}]} {
 445                clear_diff
 446        } else {
 447                show_diff $ui_fname_value
 448        }
 449}
 450
 451proc handle_empty_diff {} {
 452        global ui_fname_value file_states file_lists
 453
 454        set path $ui_fname_value
 455        set s $file_states($path)
 456        if {[lindex $s 0] != {_M}} return
 457
 458        info_popup "No differences detected.
 459
 460[short_path $path] has no changes.
 461
 462The modification date of this file was updated by another
 463application and you currently have the Trust File Modification
 464Timestamps option enabled, so Git did not automatically detect
 465that there are no content differences in this file.
 466
 467This file will now be removed from the modified files list, to
 468prevent possible confusion.
 469"
 470        if {[catch {exec git update-index -- $path} err]} {
 471                error_popup "Failed to refresh index:\n\n$err"
 472        }
 473
 474        clear_diff
 475        set old_w [mapcol [lindex $file_states($path) 0] $path]
 476        set lno [lsearch -sorted $file_lists($old_w) $path]
 477        if {$lno >= 0} {
 478                set file_lists($old_w) \
 479                        [lreplace $file_lists($old_w) $lno $lno]
 480                incr lno
 481                $old_w conf -state normal
 482                $old_w delete $lno.0 [expr $lno + 1].0
 483                $old_w conf -state disabled
 484        }
 485}
 486
 487proc show_diff {path {w {}} {lno {}}} {
 488        global file_states file_lists
 489        global PARENT diff_3way diff_active
 490        global ui_diff ui_fname_value ui_fstatus_value ui_status_value
 491
 492        if {$diff_active || ![lock_index read]} return
 493
 494        clear_diff
 495        if {$w == {} || $lno == {}} {
 496                foreach w [array names file_lists] {
 497                        set lno [lsearch -sorted $file_lists($w) $path]
 498                        if {$lno >= 0} {
 499                                incr lno
 500                                break
 501                        }
 502                }
 503        }
 504        if {$w != {} && $lno >= 1} {
 505                $w tag add in_diff $lno.0 [expr $lno + 1].0
 506        }
 507
 508        set s $file_states($path)
 509        set m [lindex $s 0]
 510        set diff_3way 0
 511        set diff_active 1
 512        set ui_fname_value [escape_path $path]
 513        set ui_fstatus_value [mapdesc $m $path]
 514        set ui_status_value "Loading diff of [escape_path $path]..."
 515
 516        set cmd [list | git diff-index -p $PARENT -- $path]
 517        switch $m {
 518        MM {
 519                set cmd [list | git diff-index -p -c $PARENT $path]
 520        }
 521        _O {
 522                if {[catch {
 523                                set fd [open $path r]
 524                                set content [read $fd]
 525                                close $fd
 526                        } err ]} {
 527                        set diff_active 0
 528                        unlock_index
 529                        set ui_status_value "Unable to display [escape_path $path]"
 530                        error_popup "Error loading file:\n\n$err"
 531                        return
 532                }
 533                $ui_diff conf -state normal
 534                $ui_diff insert end $content
 535                $ui_diff conf -state disabled
 536                set diff_active 0
 537                unlock_index
 538                set ui_status_value {Ready.}
 539                return
 540        }
 541        }
 542
 543        if {[catch {set fd [open $cmd r]} err]} {
 544                set diff_active 0
 545                unlock_index
 546                set ui_status_value "Unable to display [escape_path $path]"
 547                error_popup "Error loading diff:\n\n$err"
 548                return
 549        }
 550
 551        fconfigure $fd -blocking 0 -translation auto
 552        fileevent $fd readable [list read_diff $fd]
 553}
 554
 555proc read_diff {fd} {
 556        global ui_diff ui_status_value diff_3way diff_active
 557        global repo_config
 558
 559        while {[gets $fd line] >= 0} {
 560                if {[string match {diff --git *} $line]} continue
 561                if {[string match {diff --combined *} $line]} continue
 562                if {[string match {--- *} $line]} continue
 563                if {[string match {+++ *} $line]} continue
 564                if {[string match index* $line]} {
 565                        if {[string first , $line] >= 0} {
 566                                set diff_3way 1
 567                        }
 568                }
 569
 570                $ui_diff conf -state normal
 571                if {!$diff_3way} {
 572                        set x [string index $line 0]
 573                        switch -- $x {
 574                        "@" {set tags da}
 575                        "+" {set tags dp}
 576                        "-" {set tags dm}
 577                        default {set tags {}}
 578                        }
 579                } else {
 580                        set x [string range $line 0 1]
 581                        switch -- $x {
 582                        default {set tags {}}
 583                        "@@" {set tags da}
 584                        "++" {set tags dp; set x " +"}
 585                        " +" {set tags {di bold}; set x "++"}
 586                        "+ " {set tags dni; set x "-+"}
 587                        "--" {set tags dm; set x " -"}
 588                        " -" {set tags {dm bold}; set x "--"}
 589                        "- " {set tags di; set x "+-"}
 590                        default {set tags {}}
 591                        }
 592                        set line [string replace $line 0 1 $x]
 593                }
 594                $ui_diff insert end $line $tags
 595                $ui_diff insert end "\n"
 596                $ui_diff conf -state disabled
 597        }
 598
 599        if {[eof $fd]} {
 600                close $fd
 601                set diff_active 0
 602                unlock_index
 603                set ui_status_value {Ready.}
 604
 605                if {$repo_config(gui.trustmtime) == {true}
 606                        && [$ui_diff index end] == {2.0}} {
 607                        handle_empty_diff
 608                }
 609        }
 610}
 611
 612######################################################################
 613##
 614## commit
 615
 616proc load_last_commit {} {
 617        global HEAD PARENT commit_type ui_comm
 618
 619        if {$commit_type == {amend}} return
 620        if {$commit_type != {normal}} {
 621                error_popup "Can't amend a $commit_type commit."
 622                return
 623        }
 624
 625        set msg {}
 626        set parent {}
 627        set parent_count 0
 628        if {[catch {
 629                        set fd [open "| git cat-file commit $HEAD" r]
 630                        while {[gets $fd line] > 0} {
 631                                if {[string match {parent *} $line]} {
 632                                        set parent [string range $line 7 end]
 633                                        incr parent_count
 634                                }
 635                        }
 636                        set msg [string trim [read $fd]]
 637                        close $fd
 638                } err]} {
 639                error_popup "Error loading commit data for amend:\n\n$err"
 640                return
 641        }
 642
 643        if {$parent_count == 0} {
 644                set commit_type amend
 645                set HEAD {}
 646                set PARENT {}
 647                update_status
 648        } elseif {$parent_count == 1} {
 649                set commit_type amend
 650                set PARENT $parent
 651                $ui_comm delete 0.0 end
 652                $ui_comm insert end $msg
 653                $ui_comm edit modified false
 654                $ui_comm edit reset
 655                update_status
 656        } else {
 657                error_popup {You can't amend a merge commit.}
 658                return
 659        }
 660}
 661
 662proc commit_tree {} {
 663        global tcl_platform HEAD gitdir commit_type file_states
 664        global commit_active ui_status_value
 665        global ui_comm
 666
 667        if {$commit_active || ![lock_index update]} return
 668
 669        # -- Our in memory state should match the repository.
 670        #
 671        repository_state curHEAD cur_type
 672        if {$commit_type == {amend} 
 673                && $cur_type == {normal}
 674                && $curHEAD == $HEAD} {
 675        } elseif {$commit_type != $cur_type || $HEAD != $curHEAD} {
 676                error_popup {Last scanned state does not match repository state.
 677
 678Its highly likely that another Git program modified the
 679repository since our last scan.  A rescan is required
 680before committing.
 681}
 682                unlock_index
 683                update_status
 684                return
 685        }
 686
 687        # -- At least one file should differ in the index.
 688        #
 689        set files_ready 0
 690        foreach path [array names file_states] {
 691                set s $file_states($path)
 692                switch -glob -- [lindex $s 0] {
 693                _? {continue}
 694                A? -
 695                D? -
 696                M? {set files_ready 1; break}
 697                U? {
 698                        error_popup "Unmerged files cannot be committed.
 699
 700File [short_path $path] has merge conflicts.
 701You must resolve them and include the file before committing.
 702"
 703                        unlock_index
 704                        return
 705                }
 706                default {
 707                        error_popup "Unknown file state [lindex $s 0] detected.
 708
 709File [short_path $path] cannot be committed by this program.
 710"
 711                }
 712                }
 713        }
 714        if {!$files_ready} {
 715                error_popup {No included files to commit.
 716
 717You must include at least 1 file before you can commit.
 718}
 719                unlock_index
 720                return
 721        }
 722
 723        # -- A message is required.
 724        #
 725        set msg [string trim [$ui_comm get 1.0 end]]
 726        if {$msg == {}} {
 727                error_popup {Please supply a commit message.
 728
 729A good commit message has the following format:
 730
 731- First line: Describe in one sentance what you did.
 732- Second line: Blank
 733- Remaining lines: Describe why this change is good.
 734}
 735                unlock_index
 736                return
 737        }
 738
 739        # -- Ask the pre-commit hook for the go-ahead.
 740        #
 741        set pchook [file join $gitdir hooks pre-commit]
 742        if {$tcl_platform(platform) == {windows} && [file isfile $pchook]} {
 743                set pchook [list sh -c \
 744                        "if test -x \"$pchook\"; then exec \"$pchook\"; fi"]
 745        } elseif {[file executable $pchook]} {
 746                set pchook [list $pchook]
 747        } else {
 748                set pchook {}
 749        }
 750        if {$pchook != {} && [catch {eval exec $pchook} err]} {
 751                hook_failed_popup pre-commit $err
 752                unlock_index
 753                return
 754        }
 755
 756        # -- Write the tree in the background.
 757        #
 758        set commit_active 1
 759        set ui_status_value {Committing changes...}
 760
 761        set fd_wt [open "| git write-tree" r]
 762        fileevent $fd_wt readable [list commit_stage2 $fd_wt $curHEAD $msg]
 763}
 764
 765proc commit_stage2 {fd_wt curHEAD msg} {
 766        global single_commit gitdir HEAD PARENT commit_type
 767        global commit_active ui_status_value ui_comm
 768        global file_states
 769
 770        gets $fd_wt tree_id
 771        if {$tree_id == {} || [catch {close $fd_wt} err]} {
 772                error_popup "write-tree failed:\n\n$err"
 773                set commit_active 0
 774                set ui_status_value {Commit failed.}
 775                unlock_index
 776                return
 777        }
 778
 779        # -- Create the commit.
 780        #
 781        set cmd [list git commit-tree $tree_id]
 782        if {$PARENT != {}} {
 783                lappend cmd -p $PARENT
 784        }
 785        if {$commit_type == {merge}} {
 786                if {[catch {
 787                                set fd_mh [open [file join $gitdir MERGE_HEAD] r]
 788                                while {[gets $fd_mh merge_head] >= 0} {
 789                                        lappend cmd -p $merge_head
 790                                }
 791                                close $fd_mh
 792                        } err]} {
 793                        error_popup "Loading MERGE_HEAD failed:\n\n$err"
 794                        set commit_active 0
 795                        set ui_status_value {Commit failed.}
 796                        unlock_index
 797                        return
 798                }
 799        }
 800        if {$PARENT == {}} {
 801                # git commit-tree writes to stderr during initial commit.
 802                lappend cmd 2>/dev/null
 803        }
 804        lappend cmd << $msg
 805        if {[catch {set cmt_id [eval exec $cmd]} err]} {
 806                error_popup "commit-tree failed:\n\n$err"
 807                set commit_active 0
 808                set ui_status_value {Commit failed.}
 809                unlock_index
 810                return
 811        }
 812
 813        # -- Update the HEAD ref.
 814        #
 815        set reflogm commit
 816        if {$commit_type != {normal}} {
 817                append reflogm " ($commit_type)"
 818        }
 819        set i [string first "\n" $msg]
 820        if {$i >= 0} {
 821                append reflogm {: } [string range $msg 0 [expr $i - 1]]
 822        } else {
 823                append reflogm {: } $msg
 824        }
 825        set cmd [list git update-ref -m $reflogm HEAD $cmt_id $curHEAD]
 826        if {[catch {eval exec $cmd} err]} {
 827                error_popup "update-ref failed:\n\n$err"
 828                set commit_active 0
 829                set ui_status_value {Commit failed.}
 830                unlock_index
 831                return
 832        }
 833
 834        # -- Cleanup after ourselves.
 835        #
 836        catch {file delete [file join $gitdir MERGE_HEAD]}
 837        catch {file delete [file join $gitdir MERGE_MSG]}
 838        catch {file delete [file join $gitdir SQUASH_MSG]}
 839        catch {file delete [file join $gitdir GITGUI_MSG]}
 840
 841        # -- Let rerere do its thing.
 842        #
 843        if {[file isdirectory [file join $gitdir rr-cache]]} {
 844                catch {exec git rerere}
 845        }
 846
 847        $ui_comm delete 0.0 end
 848        $ui_comm edit modified false
 849        $ui_comm edit reset
 850
 851        if {$single_commit} do_quit
 852
 853        # -- Update status without invoking any git commands.
 854        #
 855        set commit_active 0
 856        set commit_type normal
 857        set HEAD $cmt_id
 858        set PARENT $cmt_id
 859
 860        foreach path [array names file_states] {
 861                set s $file_states($path)
 862                set m [lindex $s 0]
 863                switch -glob -- $m {
 864                A? -
 865                M? -
 866                D? {set m _[string index $m 1]}
 867                }
 868
 869                if {$m == {__}} {
 870                        unset file_states($path)
 871                } else {
 872                        lset file_states($path) 0 $m
 873                }
 874        }
 875
 876        display_all_files
 877        unlock_index
 878        reshow_diff
 879        set ui_status_value \
 880                "Changes committed as [string range $cmt_id 0 7]."
 881}
 882
 883######################################################################
 884##
 885## fetch pull push
 886
 887proc fetch_from {remote} {
 888        set w [new_console "fetch $remote" \
 889                "Fetching new changes from $remote"]
 890        set cmd [list git fetch]
 891        lappend cmd $remote
 892        console_exec $w $cmd
 893}
 894
 895proc pull_remote {remote branch} {
 896        global HEAD commit_type
 897        global file_states
 898
 899        if {![lock_index update]} return
 900
 901        # -- Our in memory state should match the repository.
 902        #
 903        repository_state curHEAD cur_type
 904        if {$commit_type != $cur_type || $HEAD != $curHEAD} {
 905                error_popup {Last scanned state does not match repository state.
 906
 907Its highly likely that another Git program modified the
 908repository since our last scan.  A rescan is required
 909before a pull can be started.
 910}
 911                unlock_index
 912                update_status
 913                return
 914        }
 915
 916        # -- No differences should exist before a pull.
 917        #
 918        if {[array size file_states] != 0} {
 919                error_popup {Uncommitted but modified files are present.
 920
 921You should not perform a pull with unmodified files in your working
 922directory as Git would be unable to recover from an incorrect merge.
 923
 924Commit or throw away all changes before starting a pull operation.
 925}
 926                unlock_index
 927                return
 928        }
 929
 930        set w [new_console "pull $remote $branch" \
 931                "Pulling new changes from branch $branch in $remote"]
 932        set cmd [list git pull]
 933        lappend cmd $remote
 934        lappend cmd $branch
 935        console_exec $w $cmd [list post_pull_remote $remote $branch]
 936}
 937
 938proc post_pull_remote {remote branch success} {
 939        global HEAD PARENT commit_type
 940        global ui_status_value
 941
 942        unlock_index
 943        if {$success} {
 944                repository_state HEAD commit_type
 945                set PARENT $HEAD
 946                set $ui_status_value {Ready.}
 947        } else {
 948                update_status \
 949                        "Conflicts detected while pulling $branch from $remote."
 950        }
 951}
 952
 953proc push_to {remote} {
 954        set w [new_console "push $remote" \
 955                "Pushing changes to $remote"]
 956        set cmd [list git push]
 957        lappend cmd $remote
 958        console_exec $w $cmd
 959}
 960
 961######################################################################
 962##
 963## ui helpers
 964
 965proc mapcol {state path} {
 966        global all_cols ui_other
 967
 968        if {[catch {set r $all_cols($state)}]} {
 969                puts "error: no column for state={$state} $path"
 970                return $ui_other
 971        }
 972        return $r
 973}
 974
 975proc mapicon {state path} {
 976        global all_icons
 977
 978        if {[catch {set r $all_icons($state)}]} {
 979                puts "error: no icon for state={$state} $path"
 980                return file_plain
 981        }
 982        return $r
 983}
 984
 985proc mapdesc {state path} {
 986        global all_descs
 987
 988        if {[catch {set r $all_descs($state)}]} {
 989                puts "error: no desc for state={$state} $path"
 990                return $state
 991        }
 992        return $r
 993}
 994
 995proc escape_path {path} {
 996        regsub -all "\n" $path "\\n" path
 997        return $path
 998}
 999
1000proc short_path {path} {
1001        return [escape_path [lindex [file split $path] end]]
1002}
1003
1004set next_icon_id 0
1005
1006proc merge_state {path new_state} {
1007        global file_states next_icon_id
1008
1009        set s0 [string index $new_state 0]
1010        set s1 [string index $new_state 1]
1011
1012        if {[catch {set info $file_states($path)}]} {
1013                set state __
1014                set icon n[incr next_icon_id]
1015        } else {
1016                set state [lindex $info 0]
1017                set icon [lindex $info 1]
1018        }
1019
1020        if {$s0 == {_}} {
1021                set s0 [string index $state 0]
1022        } elseif {$s0 == {*}} {
1023                set s0 _
1024        }
1025
1026        if {$s1 == {_}} {
1027                set s1 [string index $state 1]
1028        } elseif {$s1 == {*}} {
1029                set s1 _
1030        }
1031
1032        set file_states($path) [list $s0$s1 $icon]
1033        return $state
1034}
1035
1036proc display_file {path state} {
1037        global file_states file_lists status_active
1038
1039        set old_m [merge_state $path $state]
1040        if {$status_active} return
1041
1042        set s $file_states($path)
1043        set new_m [lindex $s 0]
1044        set new_w [mapcol $new_m $path] 
1045        set old_w [mapcol $old_m $path]
1046        set new_icon [mapicon $new_m $path]
1047
1048        if {$new_w != $old_w} {
1049                set lno [lsearch -sorted $file_lists($old_w) $path]
1050                if {$lno >= 0} {
1051                        incr lno
1052                        $old_w conf -state normal
1053                        $old_w delete $lno.0 [expr $lno + 1].0
1054                        $old_w conf -state disabled
1055                }
1056
1057                lappend file_lists($new_w) $path
1058                set file_lists($new_w) [lsort $file_lists($new_w)]
1059                set lno [lsearch -sorted $file_lists($new_w) $path]
1060                incr lno
1061                $new_w conf -state normal
1062                $new_w image create $lno.0 \
1063                        -align center -padx 5 -pady 1 \
1064                        -name [lindex $s 1] \
1065                        -image $new_icon
1066                $new_w insert $lno.1 "[escape_path $path]\n"
1067                $new_w conf -state disabled
1068        } elseif {$new_icon != [mapicon $old_m $path]} {
1069                $new_w conf -state normal
1070                $new_w image conf [lindex $s 1] -image $new_icon
1071                $new_w conf -state disabled
1072        }
1073}
1074
1075proc display_all_files {} {
1076        global ui_index ui_other file_states file_lists
1077
1078        $ui_index conf -state normal
1079        $ui_other conf -state normal
1080
1081        $ui_index delete 0.0 end
1082        $ui_other delete 0.0 end
1083
1084        set file_lists($ui_index) [list]
1085        set file_lists($ui_other) [list]
1086
1087        foreach path [lsort [array names file_states]] {
1088                set s $file_states($path)
1089                set m [lindex $s 0]
1090                set w [mapcol $m $path]
1091                lappend file_lists($w) $path
1092                $w image create end \
1093                        -align center -padx 5 -pady 1 \
1094                        -name [lindex $s 1] \
1095                        -image [mapicon $m $path]
1096                $w insert end "[escape_path $path]\n"
1097        }
1098
1099        $ui_index conf -state disabled
1100        $ui_other conf -state disabled
1101}
1102
1103proc update_index {pathList} {
1104        global update_index_cp ui_status_value
1105
1106        if {![lock_index update]} return
1107
1108        set update_index_cp 0
1109        set totalCnt [llength $pathList]
1110        set batch [expr {int($totalCnt * .01) + 1}]
1111        if {$batch > 25} {set batch 25}
1112
1113        set ui_status_value "Including files ... 0/$totalCnt 0%"
1114        set ui_status_value [format \
1115                "Including files ... %i/%i files (%.2f%%)" \
1116                $update_index_cp \
1117                $totalCnt \
1118                0.0]
1119        set fd [open "| git update-index --add --remove -z --stdin" w]
1120        fconfigure $fd -blocking 0 -translation binary
1121        fileevent $fd writable [list \
1122                write_update_index \
1123                $fd \
1124                $pathList \
1125                $totalCnt \
1126                $batch \
1127                ]
1128}
1129
1130proc write_update_index {fd pathList totalCnt batch} {
1131        global update_index_cp ui_status_value
1132        global file_states ui_fname_value
1133
1134        if {$update_index_cp >= $totalCnt} {
1135                close $fd
1136                unlock_index
1137                set ui_status_value {Ready.}
1138                return
1139        }
1140
1141        for {set i $batch} \
1142                {$update_index_cp < $totalCnt && $i > 0} \
1143                {incr i -1} {
1144                set path [lindex $pathList $update_index_cp]
1145                incr update_index_cp
1146
1147                switch -- [lindex $file_states($path) 0] {
1148                AM -
1149                _O {set new A*}
1150                _M -
1151                MM {set new M*}
1152                AD -
1153                _D {set new D*}
1154                default {continue}
1155                }
1156
1157                puts -nonewline $fd $path
1158                puts -nonewline $fd "\0"
1159                display_file $path $new
1160                if {$ui_fname_value == $path} {
1161                        show_diff $path
1162                }
1163        }
1164
1165        set ui_status_value [format \
1166                "Including files ... %i/%i files (%.2f%%)" \
1167                $update_index_cp \
1168                $totalCnt \
1169                [expr {100.0 * $update_index_cp / $totalCnt}]]
1170}
1171
1172######################################################################
1173##
1174## remote management
1175
1176proc load_all_remotes {} {
1177        global gitdir all_remotes repo_config
1178
1179        set all_remotes [list]
1180        set rm_dir [file join $gitdir remotes]
1181        if {[file isdirectory $rm_dir]} {
1182                set all_remotes [concat $all_remotes [glob \
1183                        -types f \
1184                        -tails \
1185                        -nocomplain \
1186                        -directory $rm_dir *]]
1187        }
1188
1189        foreach line [array names repo_config remote.*.url] {
1190                if {[regexp ^remote\.(.*)\.url\$ $line line name]} {
1191                        lappend all_remotes $name
1192                }
1193        }
1194
1195        set all_remotes [lsort -unique $all_remotes]
1196}
1197
1198proc populate_remote_menu {m pfx op} {
1199        global all_remotes
1200
1201        foreach remote $all_remotes {
1202                $m add command -label "$pfx $remote..." \
1203                        -command [list $op $remote] \
1204                        -font font_ui
1205        }
1206}
1207
1208proc populate_pull_menu {m} {
1209        global gitdir repo_config all_remotes disable_on_lock
1210
1211        foreach remote $all_remotes {
1212                set rb {}
1213                if {[array get repo_config remote.$remote.url] != {}} {
1214                        if {[array get repo_config remote.$remote.fetch] != {}} {
1215                                regexp {^([^:]+):} \
1216                                        [lindex $repo_config(remote.$remote.fetch) 0] \
1217                                        line rb
1218                        }
1219                } else {
1220                        catch {
1221                                set fd [open [file join $gitdir remotes $remote] r]
1222                                while {[gets $fd line] >= 0} {
1223                                        if {[regexp {^Pull:[ \t]*([^:]+):} $line line rb]} {
1224                                                break
1225                                        }
1226                                }
1227                                close $fd
1228                        }
1229                }
1230
1231                set rb_short $rb
1232                regsub ^refs/heads/ $rb {} rb_short
1233                if {$rb_short != {}} {
1234                        $m add command \
1235                                -label "Branch $rb_short from $remote..." \
1236                                -command [list pull_remote $remote $rb] \
1237                                -font font_ui
1238                        lappend disable_on_lock \
1239                                [list $m entryconf [$m index last] -state]
1240                }
1241        }
1242}
1243
1244######################################################################
1245##
1246## icons
1247
1248set filemask {
1249#define mask_width 14
1250#define mask_height 15
1251static unsigned char mask_bits[] = {
1252   0xfe, 0x1f, 0xfe, 0x1f, 0xfe, 0x1f, 0xfe, 0x1f, 0xfe, 0x1f, 0xfe, 0x1f,
1253   0xfe, 0x1f, 0xfe, 0x1f, 0xfe, 0x1f, 0xfe, 0x1f, 0xfe, 0x1f, 0xfe, 0x1f,
1254   0xfe, 0x1f, 0xfe, 0x1f, 0xfe, 0x1f};
1255}
1256
1257image create bitmap file_plain -background white -foreground black -data {
1258#define plain_width 14
1259#define plain_height 15
1260static unsigned char plain_bits[] = {
1261   0xfe, 0x01, 0x02, 0x03, 0x02, 0x05, 0x02, 0x09, 0x02, 0x1f, 0x02, 0x10,
1262   0x02, 0x10, 0x02, 0x10, 0x02, 0x10, 0x02, 0x10, 0x02, 0x10, 0x02, 0x10,
1263   0x02, 0x10, 0x02, 0x10, 0xfe, 0x1f};
1264} -maskdata $filemask
1265
1266image create bitmap file_mod -background white -foreground blue -data {
1267#define mod_width 14
1268#define mod_height 15
1269static unsigned char mod_bits[] = {
1270   0xfe, 0x01, 0x02, 0x03, 0x7a, 0x05, 0x02, 0x09, 0x7a, 0x1f, 0x02, 0x10,
1271   0xfa, 0x17, 0x02, 0x10, 0xfa, 0x17, 0x02, 0x10, 0xfa, 0x17, 0x02, 0x10,
1272   0xfa, 0x17, 0x02, 0x10, 0xfe, 0x1f};
1273} -maskdata $filemask
1274
1275image create bitmap file_fulltick -background white -foreground "#007000" -data {
1276#define file_fulltick_width 14
1277#define file_fulltick_height 15
1278static unsigned char file_fulltick_bits[] = {
1279   0xfe, 0x01, 0x02, 0x1a, 0x02, 0x0c, 0x02, 0x0c, 0x02, 0x16, 0x02, 0x16,
1280   0x02, 0x13, 0x00, 0x13, 0x86, 0x11, 0x8c, 0x11, 0xd8, 0x10, 0xf2, 0x10,
1281   0x62, 0x10, 0x02, 0x10, 0xfe, 0x1f};
1282} -maskdata $filemask
1283
1284image create bitmap file_parttick -background white -foreground "#005050" -data {
1285#define parttick_width 14
1286#define parttick_height 15
1287static unsigned char parttick_bits[] = {
1288   0xfe, 0x01, 0x02, 0x03, 0x7a, 0x05, 0x02, 0x09, 0x7a, 0x1f, 0x02, 0x10,
1289   0x7a, 0x14, 0x02, 0x16, 0x02, 0x13, 0x8a, 0x11, 0xda, 0x10, 0x72, 0x10,
1290   0x22, 0x10, 0x02, 0x10, 0xfe, 0x1f};
1291} -maskdata $filemask
1292
1293image create bitmap file_question -background white -foreground black -data {
1294#define file_question_width 14
1295#define file_question_height 15
1296static unsigned char file_question_bits[] = {
1297   0xfe, 0x01, 0x02, 0x02, 0xe2, 0x04, 0xf2, 0x09, 0x1a, 0x1b, 0x0a, 0x13,
1298   0x82, 0x11, 0xc2, 0x10, 0x62, 0x10, 0x62, 0x10, 0x02, 0x10, 0x62, 0x10,
1299   0x62, 0x10, 0x02, 0x10, 0xfe, 0x1f};
1300} -maskdata $filemask
1301
1302image create bitmap file_removed -background white -foreground red -data {
1303#define file_removed_width 14
1304#define file_removed_height 15
1305static unsigned char file_removed_bits[] = {
1306   0xfe, 0x01, 0x02, 0x03, 0x02, 0x05, 0x02, 0x09, 0x02, 0x1f, 0x02, 0x10,
1307   0x1a, 0x16, 0x32, 0x13, 0xe2, 0x11, 0xc2, 0x10, 0xe2, 0x11, 0x32, 0x13,
1308   0x1a, 0x16, 0x02, 0x10, 0xfe, 0x1f};
1309} -maskdata $filemask
1310
1311image create bitmap file_merge -background white -foreground blue -data {
1312#define file_merge_width 14
1313#define file_merge_height 15
1314static unsigned char file_merge_bits[] = {
1315   0xfe, 0x01, 0x02, 0x03, 0x62, 0x05, 0x62, 0x09, 0x62, 0x1f, 0x62, 0x10,
1316   0xfa, 0x11, 0xf2, 0x10, 0x62, 0x10, 0x02, 0x10, 0xfa, 0x17, 0x02, 0x10,
1317   0xfa, 0x17, 0x02, 0x10, 0xfe, 0x1f};
1318} -maskdata $filemask
1319
1320set ui_index .vpane.files.index.list
1321set ui_other .vpane.files.other.list
1322set max_status_desc 0
1323foreach i {
1324                {__ i plain    "Unmodified"}
1325                {_M i mod      "Modified"}
1326                {M_ i fulltick "Checked in"}
1327                {MM i parttick "Partially included"}
1328
1329                {_O o plain    "Untracked"}
1330                {A_ o fulltick "Added"}
1331                {AM o parttick "Partially added"}
1332                {AD o question "Added (but now gone)"}
1333
1334                {_D i question "Missing"}
1335                {D_ i removed  "Removed"}
1336                {DD i removed  "Removed"}
1337                {DO i removed  "Removed (still exists)"}
1338
1339                {UM i merge    "Merge conflicts"}
1340                {U_ i merge    "Merge conflicts"}
1341        } {
1342        if {$max_status_desc < [string length [lindex $i 3]]} {
1343                set max_status_desc [string length [lindex $i 3]]
1344        }
1345        if {[lindex $i 1] == {i}} {
1346                set all_cols([lindex $i 0]) $ui_index
1347        } else {
1348                set all_cols([lindex $i 0]) $ui_other
1349        }
1350        set all_icons([lindex $i 0]) file_[lindex $i 2]
1351        set all_descs([lindex $i 0]) [lindex $i 3]
1352}
1353unset filemask i
1354
1355######################################################################
1356##
1357## util
1358
1359proc is_MacOSX {} {
1360        global tcl_platform tk_library
1361        if {$tcl_platform(platform) == {unix}
1362                && $tcl_platform(os) == {Darwin}
1363                && [string match /Library/Frameworks/* $tk_library]} {
1364                return 1
1365        }
1366        return 0
1367}
1368
1369proc bind_button3 {w cmd} {
1370        bind $w <Any-Button-3> $cmd
1371        if {[is_MacOSX]} {
1372                bind $w <Control-Button-1> $cmd
1373        }
1374}
1375
1376proc incr_font_size {font {amt 1}} {
1377        set sz [font configure $font -size]
1378        incr sz $amt
1379        font configure $font -size $sz
1380        font configure ${font}bold -size $sz
1381}
1382
1383proc hook_failed_popup {hook msg} {
1384        global gitdir appname
1385
1386        set w .hookfail
1387        toplevel $w
1388
1389        frame $w.m
1390        label $w.m.l1 -text "$hook hook failed:" \
1391                -anchor w \
1392                -justify left \
1393                -font font_uibold
1394        text $w.m.t \
1395                -background white -borderwidth 1 \
1396                -relief sunken \
1397                -width 80 -height 10 \
1398                -font font_diff \
1399                -yscrollcommand [list $w.m.sby set]
1400        label $w.m.l2 \
1401                -text {You must correct the above errors before committing.} \
1402                -anchor w \
1403                -justify left \
1404                -font font_uibold
1405        scrollbar $w.m.sby -command [list $w.m.t yview]
1406        pack $w.m.l1 -side top -fill x
1407        pack $w.m.l2 -side bottom -fill x
1408        pack $w.m.sby -side right -fill y
1409        pack $w.m.t -side left -fill both -expand 1
1410        pack $w.m -side top -fill both -expand 1 -padx 5 -pady 10
1411
1412        $w.m.t insert 1.0 $msg
1413        $w.m.t conf -state disabled
1414
1415        button $w.ok -text OK \
1416                -width 15 \
1417                -font font_ui \
1418                -command "destroy $w"
1419        pack $w.ok -side bottom
1420
1421        bind $w <Visibility> "grab $w; focus $w"
1422        bind $w <Key-Return> "destroy $w"
1423        wm title $w "$appname ([lindex [file split \
1424                [file normalize [file dirname $gitdir]]] \
1425                end]): error"
1426        tkwait window $w
1427}
1428
1429set next_console_id 0
1430
1431proc new_console {short_title long_title} {
1432        global next_console_id console_data
1433        set w .console[incr next_console_id]
1434        set console_data($w) [list $short_title $long_title]
1435        return [console_init $w]
1436}
1437
1438proc console_init {w} {
1439        global console_cr console_data
1440        global gitdir appname M1B
1441
1442        set console_cr($w) 1.0
1443        toplevel $w
1444        frame $w.m
1445        label $w.m.l1 -text "[lindex $console_data($w) 1]:" \
1446                -anchor w \
1447                -justify left \
1448                -font font_uibold
1449        text $w.m.t \
1450                -background white -borderwidth 1 \
1451                -relief sunken \
1452                -width 80 -height 10 \
1453                -font font_diff \
1454                -state disabled \
1455                -yscrollcommand [list $w.m.sby set]
1456        label $w.m.s -anchor w \
1457                -justify left \
1458                -font font_uibold
1459        scrollbar $w.m.sby -command [list $w.m.t yview]
1460        pack $w.m.l1 -side top -fill x
1461        pack $w.m.s -side bottom -fill x
1462        pack $w.m.sby -side right -fill y
1463        pack $w.m.t -side left -fill both -expand 1
1464        pack $w.m -side top -fill both -expand 1 -padx 5 -pady 10
1465
1466        menu $w.ctxm -tearoff 0
1467        $w.ctxm add command -label "Copy" \
1468                -font font_ui \
1469                -command "tk_textCopy $w.m.t"
1470        $w.ctxm add command -label "Select All" \
1471                -font font_ui \
1472                -command "$w.m.t tag add sel 0.0 end"
1473        $w.ctxm add command -label "Copy All" \
1474                -font font_ui \
1475                -command "
1476                        $w.m.t tag add sel 0.0 end
1477                        tk_textCopy $w.m.t
1478                        $w.m.t tag remove sel 0.0 end
1479                "
1480
1481        button $w.ok -text {Running...} \
1482                -width 15 \
1483                -font font_ui \
1484                -state disabled \
1485                -command "destroy $w"
1486        pack $w.ok -side bottom
1487
1488        bind_button3 $w.m.t "tk_popup $w.ctxm %X %Y"
1489        bind $w.m.t <$M1B-Key-a> "$w.m.t tag add sel 0.0 end;break"
1490        bind $w.m.t <$M1B-Key-A> "$w.m.t tag add sel 0.0 end;break"
1491        bind $w <Visibility> "focus $w"
1492        wm title $w "$appname ([lindex [file split \
1493                [file normalize [file dirname $gitdir]]] \
1494                end]): [lindex $console_data($w) 0]"
1495        return $w
1496}
1497
1498proc console_exec {w cmd {after {}}} {
1499        global tcl_platform
1500
1501        # -- Windows tosses the enviroment when we exec our child.
1502        #    But most users need that so we have to relogin. :-(
1503        #
1504        if {$tcl_platform(platform) == {windows}} {
1505                set cmd [list sh --login -c "cd \"[pwd]\" && [join $cmd { }]"]
1506        }
1507
1508        # -- Tcl won't let us redirect both stdout and stderr to
1509        #    the same pipe.  So pass it through cat...
1510        #
1511        set cmd [concat | $cmd |& cat]
1512
1513        set fd_f [open $cmd r]
1514        fconfigure $fd_f -blocking 0 -translation binary
1515        fileevent $fd_f readable [list console_read $w $fd_f $after]
1516}
1517
1518proc console_read {w fd after} {
1519        global console_cr console_data
1520
1521        set buf [read $fd]
1522        if {$buf != {}} {
1523                if {![winfo exists $w]} {console_init $w}
1524                $w.m.t conf -state normal
1525                set c 0
1526                set n [string length $buf]
1527                while {$c < $n} {
1528                        set cr [string first "\r" $buf $c]
1529                        set lf [string first "\n" $buf $c]
1530                        if {$cr < 0} {set cr [expr $n + 1]}
1531                        if {$lf < 0} {set lf [expr $n + 1]}
1532
1533                        if {$lf < $cr} {
1534                                $w.m.t insert end [string range $buf $c $lf]
1535                                set console_cr($w) [$w.m.t index {end -1c}]
1536                                set c $lf
1537                                incr c
1538                        } else {
1539                                $w.m.t delete $console_cr($w) end
1540                                $w.m.t insert end "\n"
1541                                $w.m.t insert end [string range $buf $c $cr]
1542                                set c $cr
1543                                incr c
1544                        }
1545                }
1546                $w.m.t conf -state disabled
1547                $w.m.t see end
1548        }
1549
1550        fconfigure $fd -blocking 1
1551        if {[eof $fd]} {
1552                if {[catch {close $fd}]} {
1553                        if {![winfo exists $w]} {console_init $w}
1554                        $w.m.s conf -background red -text {Error: Command Failed}
1555                        $w.ok conf -text Close
1556                        $w.ok conf -state normal
1557                        set ok 0
1558                } elseif {[winfo exists $w]} {
1559                        $w.m.s conf -background green -text {Success}
1560                        $w.ok conf -text Close
1561                        $w.ok conf -state normal
1562                        set ok 1
1563                }
1564                array unset console_cr $w
1565                array unset console_data $w
1566                if {$after != {}} {
1567                        uplevel #0 $after $ok
1568                }
1569                return
1570        }
1571        fconfigure $fd -blocking 0
1572}
1573
1574######################################################################
1575##
1576## ui commands
1577
1578set starting_gitk_msg {Please wait... Starting gitk...}
1579
1580proc do_gitk {} {
1581        global tcl_platform ui_status_value starting_gitk_msg
1582
1583        set ui_status_value $starting_gitk_msg
1584        after 10000 {
1585                if {$ui_status_value == $starting_gitk_msg} {
1586                        set ui_status_value {Ready.}
1587                }
1588        }
1589
1590        if {$tcl_platform(platform) == {windows}} {
1591                exec sh -c gitk &
1592        } else {
1593                exec gitk &
1594        }
1595}
1596
1597proc do_repack {} {
1598        set w [new_console "repack" "Repacking the object database"]
1599        set cmd [list git repack]
1600        lappend cmd -a
1601        lappend cmd -d
1602        console_exec $w $cmd
1603}
1604
1605set is_quitting 0
1606
1607proc do_quit {} {
1608        global gitdir ui_comm is_quitting repo_config
1609
1610        if {$is_quitting} return
1611        set is_quitting 1
1612
1613        # -- Stash our current commit buffer.
1614        #
1615        set save [file join $gitdir GITGUI_MSG]
1616        set msg [string trim [$ui_comm get 0.0 end]]
1617        if {[$ui_comm edit modified] && $msg != {}} {
1618                catch {
1619                        set fd [open $save w]
1620                        puts $fd [string trim [$ui_comm get 0.0 end]]
1621                        close $fd
1622                }
1623        } elseif {$msg == {} && [file exists $save]} {
1624                file delete $save
1625        }
1626
1627        # -- Stash our current window geometry into this repository.
1628        #
1629        set cfg_geometry [list]
1630        lappend cfg_geometry [wm geometry .]
1631        lappend cfg_geometry [lindex [.vpane sash coord 0] 1]
1632        lappend cfg_geometry [lindex [.vpane.files sash coord 0] 0]
1633        if {[catch {set rc_geometry $repo_config(gui.geometry)}]} {
1634                set rc_geometry {}
1635        }
1636        if {$cfg_geometry != $rc_geometry} {
1637                catch {exec git repo-config gui.geometry $cfg_geometry}
1638        }
1639
1640        destroy .
1641}
1642
1643proc do_rescan {} {
1644        update_status
1645}
1646
1647proc do_include_all {} {
1648        global file_states
1649
1650        if {![lock_index begin-update]} return
1651
1652        set pathList [list]
1653        foreach path [array names file_states] {
1654                set s $file_states($path)
1655                set m [lindex $s 0]
1656                switch -- $m {
1657                AM -
1658                MM -
1659                _M -
1660                _D {lappend pathList $path}
1661                }
1662        }
1663        if {$pathList == {}} {
1664                unlock_index
1665        } else {
1666                update_index $pathList
1667        }
1668}
1669
1670set GIT_COMMITTER_IDENT {}
1671
1672proc do_signoff {} {
1673        global ui_comm GIT_COMMITTER_IDENT
1674
1675        if {$GIT_COMMITTER_IDENT == {}} {
1676                if {[catch {set me [exec git var GIT_COMMITTER_IDENT]} err]} {
1677                        error_popup "Unable to obtain your identity:\n\n$err"
1678                        return
1679                }
1680                if {![regexp {^(.*) [0-9]+ [-+0-9]+$} \
1681                        $me me GIT_COMMITTER_IDENT]} {
1682                        error_popup "Invalid GIT_COMMITTER_IDENT:\n\n$me"
1683                        return
1684                }
1685        }
1686
1687        set sob "Signed-off-by: $GIT_COMMITTER_IDENT"
1688        set last [$ui_comm get {end -1c linestart} {end -1c}]
1689        if {$last != $sob} {
1690                $ui_comm edit separator
1691                if {$last != {}
1692                        && ![regexp {^[A-Z][A-Za-z]*-[A-Za-z-]+: *} $last]} {
1693                        $ui_comm insert end "\n"
1694                }
1695                $ui_comm insert end "\n$sob"
1696                $ui_comm edit separator
1697                $ui_comm see end
1698        }
1699}
1700
1701proc do_amend_last {} {
1702        load_last_commit
1703}
1704
1705proc do_commit {} {
1706        commit_tree
1707}
1708
1709proc do_options {} {
1710        global appname gitdir font_descs
1711        global repo_config global_config
1712        global repo_config_new global_config_new
1713
1714        load_config
1715        array unset repo_config_new
1716        array unset global_config_new
1717        foreach name [array names repo_config] {
1718                set repo_config_new($name) $repo_config($name)
1719        }
1720        foreach name [array names global_config] {
1721                set global_config_new($name) $global_config($name)
1722        }
1723        set reponame [lindex [file split \
1724                [file normalize [file dirname $gitdir]]] \
1725                end]
1726
1727        set w .options_editor
1728        toplevel $w
1729        wm geometry $w "+[winfo rootx .]+[winfo rooty .]"
1730
1731        label $w.header -text "$appname Options" \
1732                -font font_uibold
1733        pack $w.header -side top -fill x
1734
1735        frame $w.buttons
1736        button $w.buttons.restore -text {Restore Defaults} \
1737                -font font_ui \
1738                -command do_restore_defaults
1739        pack $w.buttons.restore -side left
1740        button $w.buttons.save -text Save \
1741                -font font_ui \
1742                -command [list do_save_config $w]
1743        pack $w.buttons.save -side right
1744        button $w.buttons.cancel -text {Cancel} \
1745                -font font_ui \
1746                -command [list destroy $w]
1747        pack $w.buttons.cancel -side right
1748        pack $w.buttons -side bottom -fill x -pady 10 -padx 10
1749
1750        labelframe $w.repo -text "$reponame Repository" \
1751                -font font_ui \
1752                -relief raised -borderwidth 2
1753        labelframe $w.global -text {Global (All Repositories)} \
1754                -font font_ui \
1755                -relief raised -borderwidth 2
1756        pack $w.repo -side left -fill both -expand 1 -pady 5 -padx 5
1757        pack $w.global -side right -fill both -expand 1 -pady 5 -padx 5
1758
1759        foreach option {
1760                {trustmtime {Trust File Modification Timestamps}}
1761                } {
1762                set name [lindex $option 0]
1763                set text [lindex $option 1]
1764                foreach f {repo global} {
1765                        checkbutton $w.$f.$name -text $text \
1766                                -variable ${f}_config_new(gui.$name) \
1767                                -onvalue true \
1768                                -offvalue false \
1769                                -font font_ui
1770                        pack $w.$f.$name -side top -anchor w
1771                }
1772        }
1773
1774        set all_fonts [lsort [font families]]
1775        foreach option $font_descs {
1776                set name [lindex $option 0]
1777                set font [lindex $option 1]
1778                set text [lindex $option 2]
1779
1780                set global_config_new(gui.$font^^family) \
1781                        [font configure $font -family]
1782                set global_config_new(gui.$font^^size) \
1783                        [font configure $font -size]
1784
1785                frame $w.global.$name
1786                label $w.global.$name.l -text "$text:" -font font_ui
1787                pack $w.global.$name.l -side left -anchor w -fill x
1788                eval tk_optionMenu $w.global.$name.family \
1789                        global_config_new(gui.$font^^family) \
1790                        $all_fonts
1791                spinbox $w.global.$name.size \
1792                        -textvariable global_config_new(gui.$font^^size) \
1793                        -from 2 -to 80 -increment 1 \
1794                        -width 3 \
1795                        -font font_ui
1796                pack $w.global.$name.size -side right -anchor e
1797                pack $w.global.$name.family -side right -anchor e
1798                pack $w.global.$name -side top -anchor w -fill x
1799        }
1800
1801        bind $w <Visibility> "grab $w; focus $w"
1802        bind $w <Key-Escape> "destroy $w"
1803        wm title $w "$appname ($reponame): Options"
1804        tkwait window $w
1805}
1806
1807proc do_restore_defaults {} {
1808        global font_descs default_config repo_config
1809        global repo_config_new global_config_new
1810
1811        foreach name [array names default_config] {
1812                set repo_config_new($name) $default_config($name)
1813                set global_config_new($name) $default_config($name)
1814        }
1815
1816        foreach option $font_descs {
1817                set name [lindex $option 0]
1818                set repo_config(gui.$name) $default_config(gui.$name)
1819        }
1820        apply_config
1821
1822        foreach option $font_descs {
1823                set name [lindex $option 0]
1824                set font [lindex $option 1]
1825                set global_config_new(gui.$font^^family) \
1826                        [font configure $font -family]
1827                set global_config_new(gui.$font^^size) \
1828                        [font configure $font -size]
1829        }
1830}
1831
1832proc do_save_config {w} {
1833        if {[catch {save_config} err]} {
1834                error_popup "Failed to completely save options:\n\n$err"
1835        }
1836        destroy $w
1837}
1838
1839# shift == 1: left click
1840#          3: right click  
1841proc click {w x y shift wx wy} {
1842        global ui_index ui_other file_lists
1843
1844        set pos [split [$w index @$x,$y] .]
1845        set lno [lindex $pos 0]
1846        set col [lindex $pos 1]
1847        set path [lindex $file_lists($w) [expr $lno - 1]]
1848        if {$path == {}} return
1849
1850        if {$col > 0 && $shift == 1} {
1851                show_diff $path $w $lno
1852        }
1853}
1854
1855proc unclick {w x y} {
1856        global file_lists
1857
1858        set pos [split [$w index @$x,$y] .]
1859        set lno [lindex $pos 0]
1860        set col [lindex $pos 1]
1861        set path [lindex $file_lists($w) [expr $lno - 1]]
1862        if {$path == {}} return
1863
1864        if {$col == 0} {
1865                update_index [list $path]
1866        }
1867}
1868
1869######################################################################
1870##
1871## config defaults
1872
1873set cursor_ptr arrow
1874font create font_diff -family Courier -size 10
1875font create font_ui
1876catch {
1877        label .dummy
1878        eval font configure font_ui [font actual [.dummy cget -font]]
1879        destroy .dummy
1880}
1881
1882font create font_uibold
1883font create font_diffbold
1884
1885set M1B M1
1886set M1T M1
1887if {$tcl_platform(platform) == {windows}} {
1888        set M1B Control
1889        set M1T Ctrl
1890} elseif {[is_MacOSX]} {
1891        set M1B M1
1892        set M1T Cmd
1893}
1894
1895proc apply_config {} {
1896        global repo_config font_descs
1897
1898        foreach option $font_descs {
1899                set name [lindex $option 0]
1900                set font [lindex $option 1]
1901                if {[catch {
1902                        foreach {cn cv} $repo_config(gui.$name) {
1903                                font configure $font $cn $cv
1904                        }
1905                        } err]} {
1906                        error_popup "Invalid font specified in gui.$name:\n\n$err"
1907                }
1908                foreach {cn cv} [font configure $font] {
1909                        font configure ${font}bold $cn $cv
1910                }
1911                font configure ${font}bold -weight bold
1912        }
1913}
1914
1915set default_config(gui.trustmtime) false
1916set default_config(gui.fontui) [font configure font_ui]
1917set default_config(gui.fontdiff) [font configure font_diff]
1918set font_descs {
1919        {fontui   font_ui   {Main Font}}
1920        {fontdiff font_diff {Diff/Console Font}}
1921}
1922load_config
1923apply_config
1924
1925######################################################################
1926##
1927## ui construction
1928
1929# -- Menu Bar
1930menu .mbar -tearoff 0
1931.mbar add cascade -label Project -menu .mbar.project
1932.mbar add cascade -label Edit -menu .mbar.edit
1933.mbar add cascade -label Commit -menu .mbar.commit
1934if {!$single_commit} {
1935        .mbar add cascade -label Fetch -menu .mbar.fetch
1936        .mbar add cascade -label Pull -menu .mbar.pull
1937        .mbar add cascade -label Push -menu .mbar.push
1938}
1939. configure -menu .mbar
1940
1941# -- Project Menu
1942menu .mbar.project
1943.mbar.project add command -label Visualize \
1944        -command do_gitk \
1945        -font font_ui
1946if {!$single_commit} {
1947        .mbar.project add command -label {Repack Database} \
1948                -command do_repack \
1949                -font font_ui
1950}
1951.mbar.project add command -label Quit \
1952        -command do_quit \
1953        -accelerator $M1T-Q \
1954        -font font_ui
1955
1956# -- Edit Menu
1957#
1958menu .mbar.edit
1959.mbar.edit add command -label Undo \
1960        -command {catch {[focus] edit undo}} \
1961        -accelerator $M1T-Z \
1962        -font font_ui
1963.mbar.edit add command -label Redo \
1964        -command {catch {[focus] edit redo}} \
1965        -accelerator $M1T-Y \
1966        -font font_ui
1967.mbar.edit add separator
1968.mbar.edit add command -label Cut \
1969        -command {catch {tk_textCut [focus]}} \
1970        -accelerator $M1T-X \
1971        -font font_ui
1972.mbar.edit add command -label Copy \
1973        -command {catch {tk_textCopy [focus]}} \
1974        -accelerator $M1T-C \
1975        -font font_ui
1976.mbar.edit add command -label Paste \
1977        -command {catch {tk_textPaste [focus]; [focus] see insert}} \
1978        -accelerator $M1T-V \
1979        -font font_ui
1980.mbar.edit add command -label Delete \
1981        -command {catch {[focus] delete sel.first sel.last}} \
1982        -accelerator Del \
1983        -font font_ui
1984.mbar.edit add separator
1985.mbar.edit add command -label {Select All} \
1986        -command {catch {[focus] tag add sel 0.0 end}} \
1987        -accelerator $M1T-A \
1988        -font font_ui
1989.mbar.edit add separator
1990.mbar.edit add command -label {Options...} \
1991        -command do_options \
1992        -font font_ui
1993
1994# -- Commit Menu
1995menu .mbar.commit
1996.mbar.commit add command -label Rescan \
1997        -command do_rescan \
1998        -accelerator F5 \
1999        -font font_ui
2000lappend disable_on_lock \
2001        [list .mbar.commit entryconf [.mbar.commit index last] -state]
2002.mbar.commit add command -label {Amend Last Commit} \
2003        -command do_amend_last \
2004        -font font_ui
2005lappend disable_on_lock \
2006        [list .mbar.commit entryconf [.mbar.commit index last] -state]
2007.mbar.commit add command -label {Include All Files} \
2008        -command do_include_all \
2009        -accelerator $M1T-I \
2010        -font font_ui
2011lappend disable_on_lock \
2012        [list .mbar.commit entryconf [.mbar.commit index last] -state]
2013.mbar.commit add command -label {Sign Off} \
2014        -command do_signoff \
2015        -accelerator $M1T-S \
2016        -font font_ui
2017.mbar.commit add command -label Commit \
2018        -command do_commit \
2019        -accelerator $M1T-Return \
2020        -font font_ui
2021lappend disable_on_lock \
2022        [list .mbar.commit entryconf [.mbar.commit index last] -state]
2023
2024if {!$single_commit} {
2025        # -- Fetch Menu
2026        menu .mbar.fetch
2027
2028        # -- Pull Menu
2029        menu .mbar.pull
2030
2031        # -- Push Menu
2032        menu .mbar.push
2033}
2034
2035# -- Main Window Layout
2036panedwindow .vpane -orient vertical
2037panedwindow .vpane.files -orient horizontal
2038.vpane add .vpane.files -sticky nsew -height 100 -width 400
2039pack .vpane -anchor n -side top -fill both -expand 1
2040
2041# -- Index File List
2042frame .vpane.files.index -height 100 -width 400
2043label .vpane.files.index.title -text {Modified Files} \
2044        -background green \
2045        -font font_ui
2046text $ui_index -background white -borderwidth 0 \
2047        -width 40 -height 10 \
2048        -font font_ui \
2049        -cursor $cursor_ptr \
2050        -yscrollcommand {.vpane.files.index.sb set} \
2051        -state disabled
2052scrollbar .vpane.files.index.sb -command [list $ui_index yview]
2053pack .vpane.files.index.title -side top -fill x
2054pack .vpane.files.index.sb -side right -fill y
2055pack $ui_index -side left -fill both -expand 1
2056.vpane.files add .vpane.files.index -sticky nsew
2057
2058# -- Other (Add) File List
2059frame .vpane.files.other -height 100 -width 100
2060label .vpane.files.other.title -text {Untracked Files} \
2061        -background red \
2062        -font font_ui
2063text $ui_other -background white -borderwidth 0 \
2064        -width 40 -height 10 \
2065        -font font_ui \
2066        -cursor $cursor_ptr \
2067        -yscrollcommand {.vpane.files.other.sb set} \
2068        -state disabled
2069scrollbar .vpane.files.other.sb -command [list $ui_other yview]
2070pack .vpane.files.other.title -side top -fill x
2071pack .vpane.files.other.sb -side right -fill y
2072pack $ui_other -side left -fill both -expand 1
2073.vpane.files add .vpane.files.other -sticky nsew
2074
2075$ui_index tag conf in_diff -font font_uibold
2076$ui_other tag conf in_diff -font font_uibold
2077
2078# -- Diff and Commit Area
2079frame .vpane.lower -height 300 -width 400
2080frame .vpane.lower.commarea
2081frame .vpane.lower.diff -relief sunken -borderwidth 1
2082pack .vpane.lower.commarea -side top -fill x
2083pack .vpane.lower.diff -side bottom -fill both -expand 1
2084.vpane add .vpane.lower -stick nsew
2085
2086# -- Commit Area Buttons
2087frame .vpane.lower.commarea.buttons
2088label .vpane.lower.commarea.buttons.l -text {} \
2089        -anchor w \
2090        -justify left \
2091        -font font_ui
2092pack .vpane.lower.commarea.buttons.l -side top -fill x
2093pack .vpane.lower.commarea.buttons -side left -fill y
2094
2095button .vpane.lower.commarea.buttons.rescan -text {Rescan} \
2096        -command do_rescan \
2097        -font font_ui
2098pack .vpane.lower.commarea.buttons.rescan -side top -fill x
2099lappend disable_on_lock \
2100        {.vpane.lower.commarea.buttons.rescan conf -state}
2101
2102button .vpane.lower.commarea.buttons.amend -text {Amend Last} \
2103        -command do_amend_last \
2104        -font font_ui
2105pack .vpane.lower.commarea.buttons.amend -side top -fill x
2106lappend disable_on_lock \
2107        {.vpane.lower.commarea.buttons.amend conf -state}
2108
2109button .vpane.lower.commarea.buttons.incall -text {Include All} \
2110        -command do_include_all \
2111        -font font_ui
2112pack .vpane.lower.commarea.buttons.incall -side top -fill x
2113lappend disable_on_lock \
2114        {.vpane.lower.commarea.buttons.incall conf -state}
2115
2116button .vpane.lower.commarea.buttons.signoff -text {Sign Off} \
2117        -command do_signoff \
2118        -font font_ui
2119pack .vpane.lower.commarea.buttons.signoff -side top -fill x
2120
2121button .vpane.lower.commarea.buttons.commit -text {Commit} \
2122        -command do_commit \
2123        -font font_ui
2124pack .vpane.lower.commarea.buttons.commit -side top -fill x
2125lappend disable_on_lock \
2126        {.vpane.lower.commarea.buttons.commit conf -state}
2127
2128# -- Commit Message Buffer
2129frame .vpane.lower.commarea.buffer
2130set ui_comm .vpane.lower.commarea.buffer.t
2131set ui_coml .vpane.lower.commarea.buffer.l
2132label $ui_coml -text {Commit Message:} \
2133        -anchor w \
2134        -justify left \
2135        -font font_ui
2136trace add variable commit_type write {uplevel #0 {
2137        switch -glob $commit_type \
2138        initial {$ui_coml conf -text {Initial Commit Message:}} \
2139        amend   {$ui_coml conf -text {Amended Commit Message:}} \
2140        merge   {$ui_coml conf -text {Merge Commit Message:}} \
2141        *       {$ui_coml conf -text {Commit Message:}}
2142}}
2143text $ui_comm -background white -borderwidth 1 \
2144        -undo true \
2145        -maxundo 20 \
2146        -autoseparators true \
2147        -relief sunken \
2148        -width 75 -height 9 -wrap none \
2149        -font font_diff \
2150        -yscrollcommand {.vpane.lower.commarea.buffer.sby set}
2151scrollbar .vpane.lower.commarea.buffer.sby \
2152        -command [list $ui_comm yview]
2153pack $ui_coml -side top -fill x
2154pack .vpane.lower.commarea.buffer.sby -side right -fill y
2155pack $ui_comm -side left -fill y
2156pack .vpane.lower.commarea.buffer -side left -fill y
2157
2158# -- Commit Message Buffer Context Menu
2159#
2160menu $ui_comm.ctxm -tearoff 0
2161$ui_comm.ctxm add command -label "Cut" \
2162        -font font_ui \
2163        -command "tk_textCut $ui_comm"
2164$ui_comm.ctxm add command -label "Copy" \
2165        -font font_ui \
2166        -command "tk_textCopy $ui_comm"
2167$ui_comm.ctxm add command -label "Paste" \
2168        -font font_ui \
2169        -command "tk_textPaste $ui_comm"
2170$ui_comm.ctxm add command -label "Delete" \
2171        -font font_ui \
2172        -command "$ui_comm delete sel.first sel.last"
2173$ui_comm.ctxm add separator
2174$ui_comm.ctxm add command -label "Select All" \
2175        -font font_ui \
2176        -command "$ui_comm tag add sel 0.0 end"
2177$ui_comm.ctxm add command -label "Copy All" \
2178        -font font_ui \
2179        -command "
2180                $ui_comm tag add sel 0.0 end
2181                tk_textCopy $ui_comm
2182                $ui_comm tag remove sel 0.0 end
2183        "
2184$ui_comm.ctxm add separator
2185$ui_comm.ctxm add command -label "Sign Off" \
2186        -font font_ui \
2187        -command do_signoff
2188bind_button3 $ui_comm "tk_popup $ui_comm.ctxm %X %Y"
2189
2190# -- Diff Header
2191set ui_fname_value {}
2192set ui_fstatus_value {}
2193frame .vpane.lower.diff.header -background orange
2194label .vpane.lower.diff.header.l1 -text {File:} \
2195        -background orange \
2196        -font font_ui
2197label .vpane.lower.diff.header.l2 -textvariable ui_fname_value \
2198        -background orange \
2199        -anchor w \
2200        -justify left \
2201        -font font_ui
2202label .vpane.lower.diff.header.l3 -text {Status:} \
2203        -background orange \
2204        -font font_ui
2205label .vpane.lower.diff.header.l4 -textvariable ui_fstatus_value \
2206        -background orange \
2207        -width $max_status_desc \
2208        -anchor w \
2209        -justify left \
2210        -font font_ui
2211pack .vpane.lower.diff.header.l1 -side left
2212pack .vpane.lower.diff.header.l2 -side left -fill x
2213pack .vpane.lower.diff.header.l4 -side right
2214pack .vpane.lower.diff.header.l3 -side right
2215
2216# -- Diff Body
2217frame .vpane.lower.diff.body
2218set ui_diff .vpane.lower.diff.body.t
2219text $ui_diff -background white -borderwidth 0 \
2220        -width 80 -height 15 -wrap none \
2221        -font font_diff \
2222        -xscrollcommand {.vpane.lower.diff.body.sbx set} \
2223        -yscrollcommand {.vpane.lower.diff.body.sby set} \
2224        -state disabled
2225scrollbar .vpane.lower.diff.body.sbx -orient horizontal \
2226        -command [list $ui_diff xview]
2227scrollbar .vpane.lower.diff.body.sby -orient vertical \
2228        -command [list $ui_diff yview]
2229pack .vpane.lower.diff.body.sbx -side bottom -fill x
2230pack .vpane.lower.diff.body.sby -side right -fill y
2231pack $ui_diff -side left -fill both -expand 1
2232pack .vpane.lower.diff.header -side top -fill x
2233pack .vpane.lower.diff.body -side bottom -fill both -expand 1
2234
2235$ui_diff tag conf dm -foreground red
2236$ui_diff tag conf dp -foreground blue
2237$ui_diff tag conf di -foreground {#00a000}
2238$ui_diff tag conf dni -foreground {#a000a0}
2239$ui_diff tag conf da -font font_diffbold
2240$ui_diff tag conf bold -font font_diffbold
2241
2242# -- Diff Body Context Menu
2243#
2244menu $ui_diff.ctxm -tearoff 0
2245$ui_diff.ctxm add command -label "Copy" \
2246        -font font_ui \
2247        -command "tk_textCopy $ui_diff"
2248$ui_diff.ctxm add command -label "Select All" \
2249        -font font_ui \
2250        -command "$ui_diff tag add sel 0.0 end"
2251$ui_diff.ctxm add command -label "Copy All" \
2252        -font font_ui \
2253        -command "
2254                $ui_diff tag add sel 0.0 end
2255                tk_textCopy $ui_diff
2256                $ui_diff tag remove sel 0.0 end
2257        "
2258$ui_diff.ctxm add separator
2259$ui_diff.ctxm add command -label "Decrease Font Size" \
2260        -font font_ui \
2261        -command {incr_font_size font_diff -1}
2262$ui_diff.ctxm add command -label "Increase Font Size" \
2263        -font font_ui \
2264        -command {incr_font_size font_diff 1}
2265$ui_diff.ctxm add command -label {Options...} \
2266        -font font_ui \
2267        -command do_options
2268bind_button3 $ui_diff "tk_popup $ui_diff.ctxm %X %Y"
2269
2270# -- Status Bar
2271set ui_status_value {Initializing...}
2272label .status -textvariable ui_status_value \
2273        -anchor w \
2274        -justify left \
2275        -borderwidth 1 \
2276        -relief sunken \
2277        -font font_ui
2278pack .status -anchor w -side bottom -fill x
2279
2280# -- Load geometry
2281catch {
2282set gm $repo_config(gui.geometry)
2283wm geometry . [lindex $gm 0]
2284.vpane sash place 0 \
2285        [lindex [.vpane sash coord 0] 0] \
2286        [lindex $gm 1]
2287.vpane.files sash place 0 \
2288        [lindex $gm 2] \
2289        [lindex [.vpane.files sash coord 0] 1]
2290unset gm
2291}
2292
2293# -- Key Bindings
2294bind $ui_comm <$M1B-Key-Return> {do_commit;break}
2295bind $ui_comm <$M1B-Key-i> {do_include_all;break}
2296bind $ui_comm <$M1B-Key-I> {do_include_all;break}
2297bind $ui_comm <$M1B-Key-x> {tk_textCut %W;break}
2298bind $ui_comm <$M1B-Key-X> {tk_textCut %W;break}
2299bind $ui_comm <$M1B-Key-c> {tk_textCopy %W;break}
2300bind $ui_comm <$M1B-Key-C> {tk_textCopy %W;break}
2301bind $ui_comm <$M1B-Key-v> {tk_textPaste %W; %W see insert; break}
2302bind $ui_comm <$M1B-Key-V> {tk_textPaste %W; %W see insert; break}
2303bind $ui_comm <$M1B-Key-a> {%W tag add sel 0.0 end;break}
2304bind $ui_comm <$M1B-Key-A> {%W tag add sel 0.0 end;break}
2305
2306bind $ui_diff <$M1B-Key-x> {tk_textCopy %W;break}
2307bind $ui_diff <$M1B-Key-X> {tk_textCopy %W;break}
2308bind $ui_diff <$M1B-Key-c> {tk_textCopy %W;break}
2309bind $ui_diff <$M1B-Key-C> {tk_textCopy %W;break}
2310bind $ui_diff <$M1B-Key-v> {break}
2311bind $ui_diff <$M1B-Key-V> {break}
2312bind $ui_diff <$M1B-Key-a> {%W tag add sel 0.0 end;break}
2313bind $ui_diff <$M1B-Key-A> {%W tag add sel 0.0 end;break}
2314bind $ui_diff <Key-Up>     {catch {%W yview scroll -1 units};break}
2315bind $ui_diff <Key-Down>   {catch {%W yview scroll  1 units};break}
2316bind $ui_diff <Key-Left>   {catch {%W xview scroll -1 units};break}
2317bind $ui_diff <Key-Right>  {catch {%W xview scroll  1 units};break}
2318
2319bind .   <Destroy> do_quit
2320bind all <Key-F5> do_rescan
2321bind all <$M1B-Key-r> do_rescan
2322bind all <$M1B-Key-R> do_rescan
2323bind .   <$M1B-Key-s> do_signoff
2324bind .   <$M1B-Key-S> do_signoff
2325bind .   <$M1B-Key-i> do_include_all
2326bind .   <$M1B-Key-I> do_include_all
2327bind .   <$M1B-Key-Return> do_commit
2328bind all <$M1B-Key-q> do_quit
2329bind all <$M1B-Key-Q> do_quit
2330bind all <$M1B-Key-w> {destroy [winfo toplevel %W]}
2331bind all <$M1B-Key-W> {destroy [winfo toplevel %W]}
2332foreach i [list $ui_index $ui_other] {
2333        bind $i <Button-1> {click %W %x %y 1 %X %Y; break}
2334        bind $i <ButtonRelease-1> {unclick %W %x %y; break}
2335        bind_button3 $i {click %W %x %y 3 %X %Y; break}
2336}
2337unset i
2338
2339set file_lists($ui_index) [list]
2340set file_lists($ui_other) [list]
2341
2342wm title . "$appname ([file normalize [file dirname $gitdir]])"
2343focus -force $ui_comm
2344if {!$single_commit} {
2345        load_all_remotes
2346        populate_remote_menu .mbar.fetch From fetch_from
2347        populate_remote_menu .mbar.push To push_to
2348        populate_pull_menu .mbar.pull
2349}
2350after 1 update_status