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