git-guion commit git-gui: Cache all repo-config data in an array. (0d4f3eb)
   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
  10######################################################################
  11##
  12## task management
  13
  14set single_commit 0
  15set status_active 0
  16set diff_active 0
  17set checkin_active 0
  18set commit_active 0
  19set update_index_fd {}
  20
  21set disable_on_lock [list]
  22set index_lock_type none
  23
  24set HEAD {}
  25set PARENT {}
  26set commit_type {}
  27
  28proc lock_index {type} {
  29        global index_lock_type disable_on_lock
  30
  31        if {$index_lock_type == {none}} {
  32                set index_lock_type $type
  33                foreach w $disable_on_lock {
  34                        uplevel #0 $w disabled
  35                }
  36                return 1
  37        } elseif {$index_lock_type == {begin-update} && $type == {update}} {
  38                set index_lock_type $type
  39                return 1
  40        }
  41        return 0
  42}
  43
  44proc unlock_index {} {
  45        global index_lock_type disable_on_lock
  46
  47        set index_lock_type none
  48        foreach w $disable_on_lock {
  49                uplevel #0 $w normal
  50        }
  51}
  52
  53######################################################################
  54##
  55## status
  56
  57proc repository_state {hdvar ctvar} {
  58        global gitdir
  59        upvar $hdvar hd $ctvar ct
  60
  61        if {[catch {set hd [exec git rev-parse --verify HEAD]}]} {
  62                set ct initial
  63        } elseif {[file exists [file join $gitdir MERGE_HEAD]]} {
  64                set ct merge
  65        } else {
  66                set ct normal
  67        }
  68}
  69
  70proc update_status {{final Ready.}} {
  71        global HEAD PARENT commit_type
  72        global ui_index ui_other ui_status_value ui_comm
  73        global status_active file_states
  74
  75        if {$status_active || ![lock_index read]} return
  76
  77        repository_state new_HEAD new_type
  78        if {$commit_type == {amend} 
  79                && $new_type == {normal}
  80                && $new_HEAD == $HEAD} {
  81        } else {
  82                set HEAD $new_HEAD
  83                set PARENT $new_HEAD
  84                set commit_type $new_type
  85        }
  86
  87        array unset file_states
  88        foreach w [list $ui_index $ui_other] {
  89                $w conf -state normal
  90                $w delete 0.0 end
  91                $w conf -state disabled
  92        }
  93
  94        if {![$ui_comm edit modified]
  95                || [string trim [$ui_comm get 0.0 end]] == {}} {
  96                if {[load_message GITGUI_MSG]} {
  97                } elseif {[load_message MERGE_MSG]} {
  98                } elseif {[load_message SQUASH_MSG]} {
  99                }
 100                $ui_comm edit modified false
 101        }
 102
 103        set status_active 1
 104        set ui_status_value {Refreshing file status...}
 105        set fd_rf [open "| git update-index -q --unmerged --refresh" r]
 106        fconfigure $fd_rf -blocking 0 -translation binary
 107        fileevent $fd_rf readable [list read_refresh $fd_rf $final]
 108}
 109
 110proc read_refresh {fd final} {
 111        global gitdir PARENT commit_type
 112        global ui_index ui_other ui_status_value ui_comm
 113        global status_active file_states
 114
 115        read $fd
 116        if {![eof $fd]} return
 117        close $fd
 118
 119        set ls_others [list | git ls-files --others -z \
 120                --exclude-per-directory=.gitignore]
 121        set info_exclude [file join $gitdir info exclude]
 122        if {[file readable $info_exclude]} {
 123                lappend ls_others "--exclude-from=$info_exclude"
 124        }
 125
 126        set status_active 3
 127        set ui_status_value {Scanning for modified files ...}
 128        set fd_di [open "| git diff-index --cached -z $PARENT" r]
 129        set fd_df [open "| git diff-files -z" r]
 130        set fd_lo [open $ls_others r]
 131
 132        fconfigure $fd_di -blocking 0 -translation binary
 133        fconfigure $fd_df -blocking 0 -translation binary
 134        fconfigure $fd_lo -blocking 0 -translation binary
 135        fileevent $fd_di readable [list read_diff_index $fd_di $final]
 136        fileevent $fd_df readable [list read_diff_files $fd_df $final]
 137        fileevent $fd_lo readable [list read_ls_others $fd_lo $final]
 138}
 139
 140proc load_message {file} {
 141        global gitdir ui_comm
 142
 143        set f [file join $gitdir $file]
 144        if {[file isfile $f]} {
 145                if {[catch {set fd [open $f r]}]} {
 146                        return 0
 147                }
 148                set content [string trim [read $fd]]
 149                close $fd
 150                $ui_comm delete 0.0 end
 151                $ui_comm insert end $content
 152                return 1
 153        }
 154        return 0
 155}
 156
 157proc read_diff_index {fd final} {
 158        global buf_rdi
 159
 160        append buf_rdi [read $fd]
 161        set pck [split $buf_rdi "\0"]
 162        set buf_rdi [lindex $pck end]
 163        foreach {m p} [lrange $pck 0 end-1] {
 164                if {$m != {} && $p != {}} {
 165                        display_file $p [string index $m end]_
 166                }
 167        }
 168        status_eof $fd buf_rdi $final
 169}
 170
 171proc read_diff_files {fd final} {
 172        global buf_rdf
 173
 174        append buf_rdf [read $fd]
 175        set pck [split $buf_rdf "\0"]
 176        set buf_rdf [lindex $pck end]
 177        foreach {m p} [lrange $pck 0 end-1] {
 178                if {$m != {} && $p != {}} {
 179                        display_file $p _[string index $m end]
 180                }
 181        }
 182        status_eof $fd buf_rdf $final
 183}
 184
 185proc read_ls_others {fd final} {
 186        global buf_rlo
 187
 188        append buf_rlo [read $fd]
 189        set pck [split $buf_rlo "\0"]
 190        set buf_rlo [lindex $pck end]
 191        foreach p [lrange $pck 0 end-1] {
 192                display_file $p _O
 193        }
 194        status_eof $fd buf_rlo $final
 195}
 196
 197proc status_eof {fd buf final} {
 198        global status_active $buf
 199        global ui_fname_value ui_status_value file_states
 200
 201        if {[eof $fd]} {
 202                set $buf {}
 203                close $fd
 204                if {[incr status_active -1] == 0} {
 205                        unlock_index
 206
 207                        set ui_status_value $final
 208                        if {$ui_fname_value != {} && [array names file_states \
 209                                -exact $ui_fname_value] != {}}  {
 210                                show_diff $ui_fname_value
 211                        } else {
 212                                clear_diff
 213                        }
 214                }
 215        }
 216}
 217
 218######################################################################
 219##
 220## diff
 221
 222proc clear_diff {} {
 223        global ui_diff ui_fname_value ui_fstatus_value
 224
 225        $ui_diff conf -state normal
 226        $ui_diff delete 0.0 end
 227        $ui_diff conf -state disabled
 228        set ui_fname_value {}
 229        set ui_fstatus_value {}
 230}
 231
 232proc show_diff {path} {
 233        global file_states PARENT diff_3way diff_active
 234        global ui_diff ui_fname_value ui_fstatus_value ui_status_value
 235
 236        if {$diff_active || ![lock_index read]} return
 237
 238        clear_diff
 239        set s $file_states($path)
 240        set m [lindex $s 0]
 241        set diff_3way 0
 242        set diff_active 1
 243        set ui_fname_value $path
 244        set ui_fstatus_value [mapdesc $m $path]
 245        set ui_status_value "Loading diff of $path..."
 246
 247        set cmd [list | git diff-index -p $PARENT -- $path]
 248        switch $m {
 249        AM {
 250        }
 251        MM {
 252                set cmd [list | git diff-index -p -c $PARENT $path]
 253        }
 254        _O {
 255                if {[catch {
 256                                set fd [open $path r]
 257                                set content [read $fd]
 258                                close $fd
 259                        } err ]} {
 260                        set diff_active 0
 261                        unlock_index
 262                        set ui_status_value "Unable to display $path"
 263                        error_popup "Error loading file:\n$err"
 264                        return
 265                }
 266                $ui_diff conf -state normal
 267                $ui_diff insert end $content
 268                $ui_diff conf -state disabled
 269                set diff_active 0
 270                unlock_index
 271                set ui_status_value {Ready.}
 272                return
 273        }
 274        }
 275
 276        if {[catch {set fd [open $cmd r]} err]} {
 277                set diff_active 0
 278                unlock_index
 279                set ui_status_value "Unable to display $path"
 280                error_popup "Error loading diff:\n$err"
 281                return
 282        }
 283
 284        fconfigure $fd -blocking 0 -translation auto
 285        fileevent $fd readable [list read_diff $fd]
 286}
 287
 288proc read_diff {fd} {
 289        global ui_diff ui_status_value diff_3way diff_active
 290
 291        while {[gets $fd line] >= 0} {
 292                if {[string match {diff --git *} $line]} continue
 293                if {[string match {diff --combined *} $line]} continue
 294                if {[string match {--- *} $line]} continue
 295                if {[string match {+++ *} $line]} continue
 296                if {[string match index* $line]} {
 297                        if {[string first , $line] >= 0} {
 298                                set diff_3way 1
 299                        }
 300                }
 301
 302                $ui_diff conf -state normal
 303                if {!$diff_3way} {
 304                        set x [string index $line 0]
 305                        switch -- $x {
 306                        "@" {set tags da}
 307                        "+" {set tags dp}
 308                        "-" {set tags dm}
 309                        default {set tags {}}
 310                        }
 311                } else {
 312                        set x [string range $line 0 1]
 313                        switch -- $x {
 314                        default {set tags {}}
 315                        "@@" {set tags da}
 316                        "++" {set tags dp; set x " +"}
 317                        " +" {set tags {di bold}; set x "++"}
 318                        "+ " {set tags dni; set x "-+"}
 319                        "--" {set tags dm; set x " -"}
 320                        " -" {set tags {dm bold}; set x "--"}
 321                        "- " {set tags di; set x "+-"}
 322                        default {set tags {}}
 323                        }
 324                        set line [string replace $line 0 1 $x]
 325                }
 326                $ui_diff insert end $line $tags
 327                $ui_diff insert end "\n"
 328                $ui_diff conf -state disabled
 329        }
 330
 331        if {[eof $fd]} {
 332                close $fd
 333                set diff_active 0
 334                unlock_index
 335                set ui_status_value {Ready.}
 336        }
 337}
 338
 339######################################################################
 340##
 341## commit
 342
 343proc load_last_commit {} {
 344        global HEAD PARENT commit_type ui_comm
 345
 346        if {$commit_type == {amend}} return
 347        if {$commit_type != {normal}} {
 348                error_popup "Can't amend a $commit_type commit."
 349                return
 350        }
 351
 352        set msg {}
 353        set parent {}
 354        set parent_count 0
 355        if {[catch {
 356                        set fd [open "| git cat-file commit $HEAD" r]
 357                        while {[gets $fd line] > 0} {
 358                                if {[string match {parent *} $line]} {
 359                                        set parent [string range $line 7 end]
 360                                        incr parent_count
 361                                }
 362                        }
 363                        set msg [string trim [read $fd]]
 364                        close $fd
 365                } err]} {
 366                error_popup "Error loading commit data for amend:\n$err"
 367                return
 368        }
 369
 370        if {$parent_count == 0} {
 371                set commit_type amend
 372                set HEAD {}
 373                set PARENT {}
 374                update_status
 375        } elseif {$parent_count == 1} {
 376                set commit_type amend
 377                set PARENT $parent
 378                $ui_comm delete 0.0 end
 379                $ui_comm insert end $msg
 380                $ui_comm edit modified false
 381                update_status
 382        } else {
 383                error_popup {You can't amend a merge commit.}
 384                return
 385        }
 386}
 387
 388proc commit_tree {} {
 389        global tcl_platform HEAD gitdir commit_type file_states
 390        global commit_active ui_status_value
 391        global ui_comm
 392
 393        if {$commit_active || ![lock_index update]} return
 394
 395        # -- Our in memory state should match the repository.
 396        #
 397        repository_state curHEAD cur_type
 398        if {$commit_type == {amend} 
 399                && $cur_type == {normal}
 400                && $curHEAD == $HEAD} {
 401        } elseif {$commit_type != $cur_type || $HEAD != $curHEAD} {
 402                error_popup {Last scanned state does not match repository state.
 403
 404Its highly likely that another Git program modified the
 405repository since our last scan.  A rescan is required
 406before committing.
 407}
 408                unlock_index
 409                update_status
 410                return
 411        }
 412
 413        # -- At least one file should differ in the index.
 414        #
 415        set files_ready 0
 416        foreach path [array names file_states] {
 417                set s $file_states($path)
 418                switch -glob -- [lindex $s 0] {
 419                _* {continue}
 420                A* -
 421                D* -
 422                M* {set files_ready 1; break}
 423                U* {
 424                        error_popup "Unmerged files cannot be committed.
 425
 426File $path has merge conflicts.
 427You must resolve them and check the file in before committing.
 428"
 429                        unlock_index
 430                        return
 431                }
 432                default {
 433                        error_popup "Unknown file state [lindex $s 0] detected.
 434
 435File $path cannot be committed by this program.
 436"
 437                }
 438                }
 439        }
 440        if {!$files_ready} {
 441                error_popup {No checked-in files to commit.
 442
 443You must check-in at least 1 file before you can commit.
 444}
 445                unlock_index
 446                return
 447        }
 448
 449        # -- A message is required.
 450        #
 451        set msg [string trim [$ui_comm get 1.0 end]]
 452        if {$msg == {}} {
 453                error_popup {Please supply a commit message.
 454
 455A good commit message has the following format:
 456
 457- First line: Describe in one sentance what you did.
 458- Second line: Blank
 459- Remaining lines: Describe why this change is good.
 460}
 461                unlock_index
 462                return
 463        }
 464
 465        # -- Ask the pre-commit hook for the go-ahead.
 466        #
 467        set pchook [file join $gitdir hooks pre-commit]
 468        if {$tcl_platform(platform) == {windows} && [file isfile $pchook]} {
 469                set pchook [list sh -c \
 470                        "if test -x \"$pchook\"; then exec \"$pchook\"; fi"]
 471        } elseif {[file executable $pchook]} {
 472                set pchook [list $pchook]
 473        } else {
 474                set pchook {}
 475        }
 476        if {$pchook != {} && [catch {eval exec $pchook} err]} {
 477                hook_failed_popup pre-commit $err
 478                unlock_index
 479                return
 480        }
 481
 482        # -- Write the tree in the background.
 483        #
 484        set commit_active 1
 485        set ui_status_value {Committing changes...}
 486
 487        set fd_wt [open "| git write-tree" r]
 488        fileevent $fd_wt readable [list commit_stage2 $fd_wt $curHEAD $msg]
 489}
 490
 491proc commit_stage2 {fd_wt curHEAD msg} {
 492        global single_commit gitdir PARENT commit_type
 493        global commit_active ui_status_value ui_comm
 494
 495        gets $fd_wt tree_id
 496        close $fd_wt
 497
 498        if {$tree_id == {}} {
 499                error_popup "write-tree failed"
 500                set commit_active 0
 501                set ui_status_value {Commit failed.}
 502                unlock_index
 503                return
 504        }
 505
 506        # -- Create the commit.
 507        #
 508        set cmd [list git commit-tree $tree_id]
 509        if {$PARENT != {}} {
 510                lappend cmd -p $PARENT
 511        }
 512        if {$commit_type == {merge}} {
 513                if {[catch {
 514                                set fd_mh [open [file join $gitdir MERGE_HEAD] r]
 515                                while {[gets $fd_mh merge_head] >= 0} {
 516                                        lappend cmd -p $merge_head
 517                                }
 518                                close $fd_mh
 519                        } err]} {
 520                        error_popup "Loading MERGE_HEADs failed:\n$err"
 521                        set commit_active 0
 522                        set ui_status_value {Commit failed.}
 523                        unlock_index
 524                        return
 525                }
 526        }
 527        if {$PARENT == {}} {
 528                # git commit-tree writes to stderr during initial commit.
 529                lappend cmd 2>/dev/null
 530        }
 531        lappend cmd << $msg
 532        if {[catch {set cmt_id [eval exec $cmd]} err]} {
 533                error_popup "commit-tree failed:\n$err"
 534                set commit_active 0
 535                set ui_status_value {Commit failed.}
 536                unlock_index
 537                return
 538        }
 539
 540        # -- Update the HEAD ref.
 541        #
 542        set reflogm commit
 543        if {$commit_type != {normal}} {
 544                append reflogm " ($commit_type)"
 545        }
 546        set i [string first "\n" $msg]
 547        if {$i >= 0} {
 548                append reflogm {: } [string range $msg 0 [expr $i - 1]]
 549        } else {
 550                append reflogm {: } $msg
 551        }
 552        set cmd [list git update-ref -m $reflogm HEAD $cmt_id $curHEAD]
 553        if {[catch {eval exec $cmd} err]} {
 554                error_popup "update-ref failed:\n$err"
 555                set commit_active 0
 556                set ui_status_value {Commit failed.}
 557                unlock_index
 558                return
 559        }
 560
 561        # -- Cleanup after ourselves.
 562        #
 563        catch {file delete [file join $gitdir MERGE_HEAD]}
 564        catch {file delete [file join $gitdir MERGE_MSG]}
 565        catch {file delete [file join $gitdir SQUASH_MSG]}
 566        catch {file delete [file join $gitdir GITGUI_MSG]}
 567
 568        # -- Let rerere do its thing.
 569        #
 570        if {[file isdirectory [file join $gitdir rr-cache]]} {
 571                catch {exec git rerere}
 572        }
 573
 574        $ui_comm delete 0.0 end
 575        $ui_comm edit modified false
 576
 577        if {$single_commit} do_quit
 578
 579        set commit_type {}
 580        set commit_active 0
 581        set HEAD $cmt_id
 582        set PARENT $cmt_id
 583        unlock_index
 584        update_status "Changes committed as $cmt_id."
 585}
 586
 587######################################################################
 588##
 589## fetch pull push
 590
 591proc fetch_from {remote} {
 592        set w [new_console "fetch $remote" \
 593                "Fetching new changes from $remote"]
 594        set cmd [list git fetch]
 595        lappend cmd -v
 596        lappend cmd $remote
 597        console_exec $w $cmd
 598}
 599
 600proc push_to {remote} {
 601        set w [new_console "push $remote" \
 602                "Pushing changes to $remote"]
 603        set cmd [list git push]
 604        lappend -v
 605        lappend cmd $remote
 606        console_exec $w $cmd
 607}
 608
 609######################################################################
 610##
 611## ui helpers
 612
 613proc mapcol {state path} {
 614        global all_cols
 615
 616        if {[catch {set r $all_cols($state)}]} {
 617                puts "error: no column for state={$state} $path"
 618                return o
 619        }
 620        return $r
 621}
 622
 623proc mapicon {state path} {
 624        global all_icons
 625
 626        if {[catch {set r $all_icons($state)}]} {
 627                puts "error: no icon for state={$state} $path"
 628                return file_plain
 629        }
 630        return $r
 631}
 632
 633proc mapdesc {state path} {
 634        global all_descs
 635
 636        if {[catch {set r $all_descs($state)}]} {
 637                puts "error: no desc for state={$state} $path"
 638                return $state
 639        }
 640        return $r
 641}
 642
 643proc bsearch {w path} {
 644        set hi [expr [lindex [split [$w index end] .] 0] - 2]
 645        if {$hi == 0} {
 646                return -1
 647        }
 648        set lo 0
 649        while {$lo < $hi} {
 650                set mi [expr [expr $lo + $hi] / 2]
 651                set ti [expr $mi + 1]
 652                set cmp [string compare [$w get $ti.1 $ti.end] $path]
 653                if {$cmp < 0} {
 654                        set lo $ti
 655                } elseif {$cmp == 0} {
 656                        return $mi
 657                } else {
 658                        set hi $mi
 659                }
 660        }
 661        return -[expr $lo + 1]
 662}
 663
 664proc merge_state {path state} {
 665        global file_states
 666
 667        if {[array names file_states -exact $path] == {}}  {
 668                set o __
 669                set s [list $o none none]
 670        } else {
 671                set s $file_states($path)
 672                set o [lindex $s 0]
 673        }
 674
 675        set m [lindex $s 0]
 676        if {[string index $state 0] == "_"} {
 677                set state [string index $m 0][string index $state 1]
 678        } elseif {[string index $state 0] == "*"} {
 679                set state _[string index $state 1]
 680        }
 681
 682        if {[string index $state 1] == "_"} {
 683                set state [string index $state 0][string index $m 1]
 684        } elseif {[string index $state 1] == "*"} {
 685                set state [string index $state 0]_
 686        }
 687
 688        set file_states($path) [lreplace $s 0 0 $state]
 689        return $o
 690}
 691
 692proc display_file {path state} {
 693        global ui_index ui_other file_states
 694
 695        set old_m [merge_state $path $state]
 696        set s $file_states($path)
 697        set m [lindex $s 0]
 698
 699        if {[mapcol $m $path] == "o"} {
 700                set ii 1
 701                set ai 2
 702                set iw $ui_index
 703                set aw $ui_other
 704        } else {
 705                set ii 2
 706                set ai 1
 707                set iw $ui_other
 708                set aw $ui_index
 709        }
 710
 711        set d [lindex $s $ii]
 712        if {$d != "none"} {
 713                set lno [bsearch $iw $path]
 714                if {$lno >= 0} {
 715                        incr lno
 716                        $iw conf -state normal
 717                        $iw delete $lno.0 [expr $lno + 1].0
 718                        $iw conf -state disabled
 719                        set s [lreplace $s $ii $ii none]
 720                }
 721        }
 722
 723        set d [lindex $s $ai]
 724        if {$d == "none"} {
 725                set lno [expr abs([bsearch $aw $path] + 1) + 1]
 726                $aw conf -state normal
 727                set ico [$aw image create $lno.0 \
 728                        -align center -padx 5 -pady 1 \
 729                        -image [mapicon $m $path]]
 730                $aw insert $lno.1 "$path\n"
 731                $aw conf -state disabled
 732                set file_states($path) [lreplace $s $ai $ai [list $ico]]
 733        } elseif {[mapicon $m $path] != [mapicon $old_m $path]} {
 734                set ico [lindex $d 0]
 735                $aw image conf $ico -image [mapicon $m $path]
 736        }
 737}
 738
 739proc with_update_index {body} {
 740        global update_index_fd
 741
 742        if {$update_index_fd == {}} {
 743                if {![lock_index update]} return
 744                set update_index_fd [open \
 745                        "| git update-index --add --remove -z --stdin" \
 746                        w]
 747                fconfigure $update_index_fd -translation binary
 748                uplevel 1 $body
 749                close $update_index_fd
 750                set update_index_fd {}
 751                unlock_index
 752        } else {
 753                uplevel 1 $body
 754        }
 755}
 756
 757proc update_index {path} {
 758        global update_index_fd
 759
 760        if {$update_index_fd == {}} {
 761                error {not in with_update_index}
 762        } else {
 763                puts -nonewline $update_index_fd "$path\0"
 764        }
 765}
 766
 767proc toggle_mode {path} {
 768        global file_states ui_fname_value
 769
 770        set s $file_states($path)
 771        set m [lindex $s 0]
 772
 773        switch -- $m {
 774        AM -
 775        _O {set new A*}
 776        _M -
 777        MM {set new M*}
 778        AD -
 779        _D {set new D*}
 780        default {return}
 781        }
 782
 783        with_update_index {update_index $path}
 784        display_file $path $new
 785        if {$ui_fname_value == $path} {
 786                show_diff $path
 787        }
 788}
 789
 790######################################################################
 791##
 792## config (fetch push pull)
 793
 794proc load_repo_config {} {
 795        global repo_config
 796
 797        array unset repo_config
 798        catch {
 799                set fd_rc [open "| git repo-config --list" r]
 800                while {[gets $fd_rc line] >= 0} {
 801                        if {[regexp {^([^=]+)=(.*)$} $line line name value]} {
 802                                lappend repo_config($name) $value
 803                        }
 804                }
 805                close $fd_rc
 806        }
 807}
 808
 809proc load_all_remotes {} {
 810        global gitdir all_remotes repo_config
 811
 812        set all_remotes [list]
 813        set rm_dir [file join $gitdir remotes]
 814        if {[file isdirectory $rm_dir]} {
 815                set all_remotes [concat $all_remotes [glob \
 816                        -types f \
 817                        -tails \
 818                        -nocomplain \
 819                        -directory $rm_dir *]]
 820        }
 821
 822        foreach line [array names repo_config remote.*.url] {
 823                if {[regexp ^remote\.(.*)\.url\$ $line line name]} {
 824                        lappend all_remotes $name
 825                }
 826        }
 827
 828        set all_remotes [lsort -unique $all_remotes]
 829}
 830
 831proc populate_remote_menu {m pfx op} {
 832        global gitdir all_remotes mainfont
 833
 834        foreach remote $all_remotes {
 835                $m add command -label "$pfx $remote..." \
 836                        -command [list $op $remote] \
 837                        -font $mainfont
 838        }
 839}
 840
 841######################################################################
 842##
 843## icons
 844
 845set filemask {
 846#define mask_width 14
 847#define mask_height 15
 848static unsigned char mask_bits[] = {
 849   0xfe, 0x1f, 0xfe, 0x1f, 0xfe, 0x1f, 0xfe, 0x1f, 0xfe, 0x1f, 0xfe, 0x1f,
 850   0xfe, 0x1f, 0xfe, 0x1f, 0xfe, 0x1f, 0xfe, 0x1f, 0xfe, 0x1f, 0xfe, 0x1f,
 851   0xfe, 0x1f, 0xfe, 0x1f, 0xfe, 0x1f};
 852}
 853
 854image create bitmap file_plain -background white -foreground black -data {
 855#define plain_width 14
 856#define plain_height 15
 857static unsigned char plain_bits[] = {
 858   0xfe, 0x01, 0x02, 0x03, 0x02, 0x05, 0x02, 0x09, 0x02, 0x1f, 0x02, 0x10,
 859   0x02, 0x10, 0x02, 0x10, 0x02, 0x10, 0x02, 0x10, 0x02, 0x10, 0x02, 0x10,
 860   0x02, 0x10, 0x02, 0x10, 0xfe, 0x1f};
 861} -maskdata $filemask
 862
 863image create bitmap file_mod -background white -foreground blue -data {
 864#define mod_width 14
 865#define mod_height 15
 866static unsigned char mod_bits[] = {
 867   0xfe, 0x01, 0x02, 0x03, 0x7a, 0x05, 0x02, 0x09, 0x7a, 0x1f, 0x02, 0x10,
 868   0xfa, 0x17, 0x02, 0x10, 0xfa, 0x17, 0x02, 0x10, 0xfa, 0x17, 0x02, 0x10,
 869   0xfa, 0x17, 0x02, 0x10, 0xfe, 0x1f};
 870} -maskdata $filemask
 871
 872image create bitmap file_fulltick -background white -foreground "#007000" -data {
 873#define file_fulltick_width 14
 874#define file_fulltick_height 15
 875static unsigned char file_fulltick_bits[] = {
 876   0xfe, 0x01, 0x02, 0x1a, 0x02, 0x0c, 0x02, 0x0c, 0x02, 0x16, 0x02, 0x16,
 877   0x02, 0x13, 0x00, 0x13, 0x86, 0x11, 0x8c, 0x11, 0xd8, 0x10, 0xf2, 0x10,
 878   0x62, 0x10, 0x02, 0x10, 0xfe, 0x1f};
 879} -maskdata $filemask
 880
 881image create bitmap file_parttick -background white -foreground "#005050" -data {
 882#define parttick_width 14
 883#define parttick_height 15
 884static unsigned char parttick_bits[] = {
 885   0xfe, 0x01, 0x02, 0x03, 0x7a, 0x05, 0x02, 0x09, 0x7a, 0x1f, 0x02, 0x10,
 886   0x7a, 0x14, 0x02, 0x16, 0x02, 0x13, 0x8a, 0x11, 0xda, 0x10, 0x72, 0x10,
 887   0x22, 0x10, 0x02, 0x10, 0xfe, 0x1f};
 888} -maskdata $filemask
 889
 890image create bitmap file_question -background white -foreground black -data {
 891#define file_question_width 14
 892#define file_question_height 15
 893static unsigned char file_question_bits[] = {
 894   0xfe, 0x01, 0x02, 0x02, 0xe2, 0x04, 0xf2, 0x09, 0x1a, 0x1b, 0x0a, 0x13,
 895   0x82, 0x11, 0xc2, 0x10, 0x62, 0x10, 0x62, 0x10, 0x02, 0x10, 0x62, 0x10,
 896   0x62, 0x10, 0x02, 0x10, 0xfe, 0x1f};
 897} -maskdata $filemask
 898
 899image create bitmap file_removed -background white -foreground red -data {
 900#define file_removed_width 14
 901#define file_removed_height 15
 902static unsigned char file_removed_bits[] = {
 903   0xfe, 0x01, 0x02, 0x03, 0x02, 0x05, 0x02, 0x09, 0x02, 0x1f, 0x02, 0x10,
 904   0x1a, 0x16, 0x32, 0x13, 0xe2, 0x11, 0xc2, 0x10, 0xe2, 0x11, 0x32, 0x13,
 905   0x1a, 0x16, 0x02, 0x10, 0xfe, 0x1f};
 906} -maskdata $filemask
 907
 908image create bitmap file_merge -background white -foreground blue -data {
 909#define file_merge_width 14
 910#define file_merge_height 15
 911static unsigned char file_merge_bits[] = {
 912   0xfe, 0x01, 0x02, 0x03, 0x62, 0x05, 0x62, 0x09, 0x62, 0x1f, 0x62, 0x10,
 913   0xfa, 0x11, 0xf2, 0x10, 0x62, 0x10, 0x02, 0x10, 0xfa, 0x17, 0x02, 0x10,
 914   0xfa, 0x17, 0x02, 0x10, 0xfe, 0x1f};
 915} -maskdata $filemask
 916
 917set max_status_desc 0
 918foreach i {
 919                {__ i plain    "Unmodified"}
 920                {_M i mod      "Modified"}
 921                {M_ i fulltick "Checked in"}
 922                {MM i parttick "Partially checked in"}
 923
 924                {_O o plain    "Untracked"}
 925                {A_ o fulltick "Added"}
 926                {AM o parttick "Partially added"}
 927                {AD o question  "Added (but now gone)"}
 928
 929                {_D i question "Missing"}
 930                {D_ i removed  "Removed"}
 931                {DD i removed  "Removed"}
 932                {DO i removed  "Removed (still exists)"}
 933
 934                {UM i merge    "Merge conflicts"}
 935                {U_ i merge    "Merge conflicts"}
 936        } {
 937        if {$max_status_desc < [string length [lindex $i 3]]} {
 938                set max_status_desc [string length [lindex $i 3]]
 939        }
 940        set all_cols([lindex $i 0]) [lindex $i 1]
 941        set all_icons([lindex $i 0]) file_[lindex $i 2]
 942        set all_descs([lindex $i 0]) [lindex $i 3]
 943}
 944unset filemask i
 945
 946######################################################################
 947##
 948## util
 949
 950proc error_popup {msg} {
 951        set w .error
 952        toplevel $w
 953        wm transient $w .
 954        show_msg $w $w $msg
 955}
 956
 957proc show_msg {w top msg} {
 958        global gitdir appname mainfont
 959
 960        message $w.m -text $msg -justify left -aspect 400
 961        pack $w.m -side top -fill x -padx 5 -pady 10
 962        button $w.ok -text OK \
 963                -width 15 \
 964                -font $mainfont \
 965                -command "destroy $top"
 966        pack $w.ok -side bottom
 967        bind $top <Visibility> "grab $top; focus $top"
 968        bind $top <Key-Return> "destroy $top"
 969        wm title $top "error: $appname ([file normalize [file dirname $gitdir]])"
 970        tkwait window $top
 971}
 972
 973proc hook_failed_popup {hook msg} {
 974        global gitdir mainfont difffont appname
 975
 976        set w .hookfail
 977        toplevel $w
 978        wm transient $w .
 979
 980        frame $w.m
 981        label $w.m.l1 -text "$hook hook failed:" \
 982                -anchor w \
 983                -justify left \
 984                -font [concat $mainfont bold]
 985        text $w.m.t \
 986                -background white -borderwidth 1 \
 987                -relief sunken \
 988                -width 80 -height 10 \
 989                -font $difffont \
 990                -yscrollcommand [list $w.m.sby set]
 991        label $w.m.l2 \
 992                -text {You must correct the above errors before committing.} \
 993                -anchor w \
 994                -justify left \
 995                -font [concat $mainfont bold]
 996        scrollbar $w.m.sby -command [list $w.m.t yview]
 997        pack $w.m.l1 -side top -fill x
 998        pack $w.m.l2 -side bottom -fill x
 999        pack $w.m.sby -side right -fill y
1000        pack $w.m.t -side left -fill both -expand 1
1001        pack $w.m -side top -fill both -expand 1 -padx 5 -pady 10
1002
1003        $w.m.t insert 1.0 $msg
1004        $w.m.t conf -state disabled
1005
1006        button $w.ok -text OK \
1007                -width 15 \
1008                -font $mainfont \
1009                -command "destroy $w"
1010        pack $w.ok -side bottom
1011
1012        bind $w <Visibility> "grab $w; focus $w"
1013        bind $w <Key-Return> "destroy $w"
1014        wm title $w "error: $appname ([file normalize [file dirname $gitdir]])"
1015        tkwait window $w
1016}
1017
1018set next_console_id 0
1019
1020proc new_console {short_title long_title} {
1021        global next_console_id console_data
1022        set w .console[incr next_console_id]
1023        set console_data($w) [list $short_title $long_title]
1024        return [console_init $w]
1025}
1026
1027proc console_init {w} {
1028        global console_cr console_data
1029        global gitdir appname mainfont difffont
1030
1031        set console_cr($w) 1.0
1032        toplevel $w
1033        frame $w.m
1034        label $w.m.l1 -text "[lindex $console_data($w) 1]:" \
1035                -anchor w \
1036                -justify left \
1037                -font [concat $mainfont bold]
1038        text $w.m.t \
1039                -background white -borderwidth 1 \
1040                -relief sunken \
1041                -width 80 -height 10 \
1042                -font $difffont \
1043                -state disabled \
1044                -yscrollcommand [list $w.m.sby set]
1045        label $w.m.s -anchor w \
1046                -justify left \
1047                -font [concat $mainfont bold]
1048        scrollbar $w.m.sby -command [list $w.m.t yview]
1049        pack $w.m.l1 -side top -fill x
1050        pack $w.m.s -side bottom -fill x
1051        pack $w.m.sby -side right -fill y
1052        pack $w.m.t -side left -fill both -expand 1
1053        pack $w.m -side top -fill both -expand 1 -padx 5 -pady 10
1054
1055        button $w.ok -text {Running...} \
1056                -width 15 \
1057                -font $mainfont \
1058                -state disabled \
1059                -command "destroy $w"
1060        pack $w.ok -side bottom
1061
1062        bind $w <Visibility> "focus $w"
1063        wm title $w "$appname ([file dirname [file normalize [file dirname $gitdir]]]): [lindex $console_data($w) 0]"
1064        return $w
1065}
1066
1067proc console_exec {w cmd} {
1068        global tcl_platform
1069
1070        # -- Windows tosses the enviroment when we exec our child.
1071        #    But most users need that so we have to relogin. :-(
1072        #
1073        if {$tcl_platform(platform) == {windows}} {
1074                set cmd [list sh --login -c "cd \"[pwd]\" && [join $cmd { }]"]
1075        }
1076
1077        # -- Tcl won't let us redirect both stdout and stderr to
1078        #    the same pipe.  So pass it through cat...
1079        #
1080        set cmd [concat | $cmd |& cat]
1081
1082        set fd_f [open $cmd r]
1083        fconfigure $fd_f -blocking 0 -translation binary
1084        fileevent $fd_f readable [list console_read $w $fd_f]
1085}
1086
1087proc console_read {w fd} {
1088        global console_cr console_data
1089
1090        set buf [read $fd]
1091        if {$buf != {}} {
1092                if {![winfo exists $w]} {console_init $w}
1093                $w.m.t conf -state normal
1094                set c 0
1095                set n [string length $buf]
1096                while {$c < $n} {
1097                        set cr [string first "\r" $buf $c]
1098                        set lf [string first "\n" $buf $c]
1099                        if {$cr < 0} {set cr [expr $n + 1]}
1100                        if {$lf < 0} {set lf [expr $n + 1]}
1101
1102                        if {$lf < $cr} {
1103                                $w.m.t insert end [string range $buf $c $lf]
1104                                set console_cr($w) [$w.m.t index {end -1c}]
1105                                set c $lf
1106                                incr c
1107                        } else {
1108                                $w.m.t delete $console_cr($w) end
1109                                $w.m.t insert end "\n"
1110                                $w.m.t insert end [string range $buf $c $cr]
1111                                set c $cr
1112                                incr c
1113                        }
1114                }
1115                $w.m.t conf -state disabled
1116                $w.m.t see end
1117        }
1118
1119        fconfigure $fd -blocking 1
1120        if {[eof $fd]} {
1121                if {[catch {close $fd}]} {
1122                        if {![winfo exists $w]} {console_init $w}
1123                        $w.m.s conf -background red -text {Error: Command Failed}
1124                        $w.ok conf -text Close
1125                        $w.ok conf -state normal
1126                } elseif {[winfo exists $w]} {
1127                        $w.m.s conf -background green -text {Success}
1128                        $w.ok conf -text Close
1129                        $w.ok conf -state normal
1130                }
1131                array unset console_cr $w
1132                array unset console_data $w
1133                return
1134        }
1135        fconfigure $fd -blocking 0
1136}
1137
1138######################################################################
1139##
1140## ui commands
1141
1142set starting_gitk_msg {Please wait... Starting gitk...}
1143
1144proc do_gitk {} {
1145        global tcl_platform ui_status_value starting_gitk_msg
1146
1147        set ui_status_value $starting_gitk_msg
1148        after 10000 {
1149                if {$ui_status_value == $starting_gitk_msg} {
1150                        set ui_status_value {Ready.}
1151                }
1152        }
1153
1154        if {$tcl_platform(platform) == {windows}} {
1155                exec sh -c gitk &
1156        } else {
1157                exec gitk &
1158        }
1159}
1160
1161proc do_quit {} {
1162        global gitdir ui_comm
1163
1164        set save [file join $gitdir GITGUI_MSG]
1165        set msg [string trim [$ui_comm get 0.0 end]]
1166        if {[$ui_comm edit modified] && $msg != {}} {
1167                catch {
1168                        set fd [open $save w]
1169                        puts $fd [string trim [$ui_comm get 0.0 end]]
1170                        close $fd
1171                }
1172        } elseif {$msg == {} && [file exists $save]} {
1173                file delete $save
1174        }
1175
1176        destroy .
1177}
1178
1179proc do_rescan {} {
1180        update_status
1181}
1182
1183proc do_checkin_all {} {
1184        global checkin_active ui_status_value
1185
1186        if {$checkin_active || ![lock_index begin-update]} return
1187
1188        set checkin_active 1
1189        set ui_status_value {Checking in all files...}
1190        after 1 {
1191                with_update_index {
1192                        foreach path [array names file_states] {
1193                                set s $file_states($path)
1194                                set m [lindex $s 0]
1195                                switch -- $m {
1196                                AM -
1197                                MM -
1198                                _M -
1199                                _D {toggle_mode $path}
1200                                }
1201                        }
1202                }
1203                set checkin_active 0
1204                set ui_status_value {Ready.}
1205        }
1206}
1207
1208proc do_signoff {} {
1209        global ui_comm
1210
1211        catch {
1212                set me [exec git var GIT_COMMITTER_IDENT]
1213                if {[regexp {(.*) [0-9]+ [-+0-9]+$} $me me name]} {
1214                        set str "Signed-off-by: $name"
1215                        if {[$ui_comm get {end -1c linestart} {end -1c}] != $str} {
1216                                $ui_comm insert end "\n"
1217                                $ui_comm insert end $str
1218                                $ui_comm see end
1219                        }
1220                }
1221        }
1222}
1223
1224proc do_amend_last {} {
1225        load_last_commit
1226}
1227
1228proc do_commit {} {
1229        commit_tree
1230}
1231
1232# shift == 1: left click
1233#          3: right click  
1234proc click {w x y shift wx wy} {
1235        global ui_index ui_other
1236
1237        set pos [split [$w index @$x,$y] .]
1238        set lno [lindex $pos 0]
1239        set col [lindex $pos 1]
1240        set path [$w get $lno.1 $lno.end]
1241        if {$path == {}} return
1242
1243        if {$col > 0 && $shift == 1} {
1244                $ui_index tag remove in_diff 0.0 end
1245                $ui_other tag remove in_diff 0.0 end
1246                $w tag add in_diff $lno.0 [expr $lno + 1].0
1247                show_diff $path
1248        }
1249}
1250
1251proc unclick {w x y} {
1252        set pos [split [$w index @$x,$y] .]
1253        set lno [lindex $pos 0]
1254        set col [lindex $pos 1]
1255        set path [$w get $lno.1 $lno.end]
1256        if {$path == {}} return
1257
1258        if {$col == 0} {
1259                toggle_mode $path
1260        }
1261}
1262
1263######################################################################
1264##
1265## ui init
1266
1267set mainfont {Helvetica 10}
1268set difffont {Courier 10}
1269set maincursor [. cget -cursor]
1270
1271switch -glob -- "$tcl_platform(platform),$tcl_platform(os)" {
1272windows,*   {set M1B Control; set M1T Ctrl}
1273unix,Darwin {set M1B M1; set M1T Cmd}
1274default     {set M1B M1; set M1T M1}
1275}
1276
1277# -- Menu Bar
1278menu .mbar -tearoff 0
1279.mbar add cascade -label Project -menu .mbar.project
1280.mbar add cascade -label Commit -menu .mbar.commit
1281.mbar add cascade -label Fetch -menu .mbar.fetch
1282.mbar add cascade -label Pull -menu .mbar.pull
1283.mbar add cascade -label Push -menu .mbar.push
1284. configure -menu .mbar
1285
1286# -- Project Menu
1287menu .mbar.project
1288.mbar.project add command -label Visualize \
1289        -command do_gitk \
1290        -font $mainfont
1291.mbar.project add command -label Quit \
1292        -command do_quit \
1293        -accelerator $M1T-Q \
1294        -font $mainfont
1295
1296# -- Commit Menu
1297menu .mbar.commit
1298.mbar.commit add command -label Rescan \
1299        -command do_rescan \
1300        -accelerator F5 \
1301        -font $mainfont
1302lappend disable_on_lock \
1303        [list .mbar.commit entryconf [.mbar.commit index last] -state]
1304.mbar.commit add command -label {Amend Last Commit} \
1305        -command do_amend_last \
1306        -font $mainfont
1307lappend disable_on_lock \
1308        [list .mbar.commit entryconf [.mbar.commit index last] -state]
1309.mbar.commit add command -label {Check-in All Files} \
1310        -command do_checkin_all \
1311        -accelerator $M1T-U \
1312        -font $mainfont
1313lappend disable_on_lock \
1314        [list .mbar.commit entryconf [.mbar.commit index last] -state]
1315.mbar.commit add command -label {Sign Off} \
1316        -command do_signoff \
1317        -accelerator $M1T-S \
1318        -font $mainfont
1319.mbar.commit add command -label Commit \
1320        -command do_commit \
1321        -accelerator $M1T-Return \
1322        -font $mainfont
1323lappend disable_on_lock \
1324        [list .mbar.commit entryconf [.mbar.commit index last] -state]
1325
1326# -- Fetch Menu
1327menu .mbar.fetch
1328
1329# -- Pull Menu
1330menu .mbar.pull
1331
1332# -- Push Menu
1333menu .mbar.push
1334
1335# -- Main Window Layout
1336panedwindow .vpane -orient vertical
1337panedwindow .vpane.files -orient horizontal
1338.vpane add .vpane.files -sticky nsew -height 100 -width 400
1339pack .vpane -anchor n -side top -fill both -expand 1
1340
1341# -- Index File List
1342set ui_index .vpane.files.index.list
1343frame .vpane.files.index -height 100 -width 400
1344label .vpane.files.index.title -text {Modified Files} \
1345        -background green \
1346        -font $mainfont
1347text $ui_index -background white -borderwidth 0 \
1348        -width 40 -height 10 \
1349        -font $mainfont \
1350        -yscrollcommand {.vpane.files.index.sb set} \
1351        -cursor $maincursor \
1352        -state disabled
1353scrollbar .vpane.files.index.sb -command [list $ui_index yview]
1354pack .vpane.files.index.title -side top -fill x
1355pack .vpane.files.index.sb -side right -fill y
1356pack $ui_index -side left -fill both -expand 1
1357.vpane.files add .vpane.files.index -sticky nsew
1358
1359# -- Other (Add) File List
1360set ui_other .vpane.files.other.list
1361frame .vpane.files.other -height 100 -width 100
1362label .vpane.files.other.title -text {Untracked Files} \
1363        -background red \
1364        -font $mainfont
1365text $ui_other -background white -borderwidth 0 \
1366        -width 40 -height 10 \
1367        -font $mainfont \
1368        -yscrollcommand {.vpane.files.other.sb set} \
1369        -cursor $maincursor \
1370        -state disabled
1371scrollbar .vpane.files.other.sb -command [list $ui_other yview]
1372pack .vpane.files.other.title -side top -fill x
1373pack .vpane.files.other.sb -side right -fill y
1374pack $ui_other -side left -fill both -expand 1
1375.vpane.files add .vpane.files.other -sticky nsew
1376
1377$ui_index tag conf in_diff -font [concat $mainfont bold]
1378$ui_other tag conf in_diff -font [concat $mainfont bold]
1379
1380# -- Diff Header
1381set ui_fname_value {}
1382set ui_fstatus_value {}
1383frame .vpane.diff -height 200 -width 400
1384frame .vpane.diff.header
1385label .vpane.diff.header.l1 -text {File:} -font $mainfont
1386label .vpane.diff.header.l2 -textvariable ui_fname_value \
1387        -anchor w \
1388        -justify left \
1389        -font $mainfont
1390label .vpane.diff.header.l3 -text {Status:} -font $mainfont
1391label .vpane.diff.header.l4 -textvariable ui_fstatus_value \
1392        -width $max_status_desc \
1393        -anchor w \
1394        -justify left \
1395        -font $mainfont
1396pack .vpane.diff.header.l1 -side left
1397pack .vpane.diff.header.l2 -side left -fill x
1398pack .vpane.diff.header.l4 -side right
1399pack .vpane.diff.header.l3 -side right
1400
1401# -- Diff Body
1402frame .vpane.diff.body
1403set ui_diff .vpane.diff.body.t
1404text $ui_diff -background white -borderwidth 0 \
1405        -width 80 -height 15 -wrap none \
1406        -font $difffont \
1407        -xscrollcommand {.vpane.diff.body.sbx set} \
1408        -yscrollcommand {.vpane.diff.body.sby set} \
1409        -cursor $maincursor \
1410        -state disabled
1411scrollbar .vpane.diff.body.sbx -orient horizontal \
1412        -command [list $ui_diff xview]
1413scrollbar .vpane.diff.body.sby -orient vertical \
1414        -command [list $ui_diff yview]
1415pack .vpane.diff.body.sbx -side bottom -fill x
1416pack .vpane.diff.body.sby -side right -fill y
1417pack $ui_diff -side left -fill both -expand 1
1418pack .vpane.diff.header -side top -fill x
1419pack .vpane.diff.body -side bottom -fill both -expand 1
1420.vpane add .vpane.diff -stick nsew
1421
1422$ui_diff tag conf dm -foreground red
1423$ui_diff tag conf dp -foreground blue
1424$ui_diff tag conf da -font [concat $difffont bold]
1425$ui_diff tag conf di -foreground "#00a000"
1426$ui_diff tag conf dni -foreground "#a000a0"
1427$ui_diff tag conf bold -font [concat $difffont bold]
1428
1429# -- Commit Area
1430frame .vpane.commarea -height 170
1431.vpane add .vpane.commarea -stick nsew
1432
1433# -- Commit Area Buttons
1434frame .vpane.commarea.buttons
1435label .vpane.commarea.buttons.l -text {} \
1436        -anchor w \
1437        -justify left \
1438        -font $mainfont
1439pack .vpane.commarea.buttons.l -side top -fill x
1440pack .vpane.commarea.buttons -side left -fill y
1441
1442button .vpane.commarea.buttons.rescan -text {Rescan} \
1443        -command do_rescan \
1444        -font $mainfont
1445pack .vpane.commarea.buttons.rescan -side top -fill x
1446lappend disable_on_lock {.vpane.commarea.buttons.rescan conf -state}
1447
1448button .vpane.commarea.buttons.amend -text {Amend Last} \
1449        -command do_amend_last \
1450        -font $mainfont
1451pack .vpane.commarea.buttons.amend -side top -fill x
1452lappend disable_on_lock {.vpane.commarea.buttons.amend conf -state}
1453
1454button .vpane.commarea.buttons.ciall -text {Check-in All} \
1455        -command do_checkin_all \
1456        -font $mainfont
1457pack .vpane.commarea.buttons.ciall -side top -fill x
1458lappend disable_on_lock {.vpane.commarea.buttons.ciall conf -state}
1459
1460button .vpane.commarea.buttons.signoff -text {Sign Off} \
1461        -command do_signoff \
1462        -font $mainfont
1463pack .vpane.commarea.buttons.signoff -side top -fill x
1464
1465button .vpane.commarea.buttons.commit -text {Commit} \
1466        -command do_commit \
1467        -font $mainfont
1468pack .vpane.commarea.buttons.commit -side top -fill x
1469lappend disable_on_lock {.vpane.commarea.buttons.commit conf -state}
1470
1471# -- Commit Message Buffer
1472frame .vpane.commarea.buffer
1473set ui_comm .vpane.commarea.buffer.t
1474set ui_coml .vpane.commarea.buffer.l
1475label $ui_coml -text {Commit Message:} \
1476        -anchor w \
1477        -justify left \
1478        -font $mainfont
1479trace add variable commit_type write {uplevel #0 {
1480        switch -glob $commit_type \
1481        initial {$ui_coml conf -text {Initial Commit Message:}} \
1482        amend   {$ui_coml conf -text {Amended Commit Message:}} \
1483        merge   {$ui_coml conf -text {Merge Commit Message:}} \
1484        *       {$ui_coml conf -text {Commit Message:}}
1485}}
1486text $ui_comm -background white -borderwidth 1 \
1487        -relief sunken \
1488        -width 75 -height 10 -wrap none \
1489        -font $difffont \
1490        -yscrollcommand {.vpane.commarea.buffer.sby set} \
1491        -cursor $maincursor
1492scrollbar .vpane.commarea.buffer.sby -command [list $ui_comm yview]
1493pack $ui_coml -side top -fill x
1494pack .vpane.commarea.buffer.sby -side right -fill y
1495pack $ui_comm -side left -fill y
1496pack .vpane.commarea.buffer -side left -fill y
1497
1498# -- Status Bar
1499set ui_status_value {Initializing...}
1500label .status -textvariable ui_status_value \
1501        -anchor w \
1502        -justify left \
1503        -borderwidth 1 \
1504        -relief sunken \
1505        -font $mainfont
1506pack .status -anchor w -side bottom -fill x
1507
1508# -- Key Bindings
1509bind $ui_comm <$M1B-Key-Return> {do_commit;break}
1510bind .   <Destroy> do_quit
1511bind all <Key-F5> do_rescan
1512bind all <$M1B-Key-r> do_rescan
1513bind all <$M1B-Key-R> do_rescan
1514bind .   <$M1B-Key-s> do_signoff
1515bind .   <$M1B-Key-S> do_signoff
1516bind .   <$M1B-Key-u> do_checkin_all
1517bind .   <$M1B-Key-U> do_checkin_all
1518bind .   <$M1B-Key-Return> do_commit
1519bind all <$M1B-Key-q> do_quit
1520bind all <$M1B-Key-Q> do_quit
1521bind all <$M1B-Key-w> {destroy [winfo toplevel %W]}
1522bind all <$M1B-Key-W> {destroy [winfo toplevel %W]}
1523foreach i [list $ui_index $ui_other] {
1524        bind $i <Button-1> {click %W %x %y 1 %X %Y; break}
1525        bind $i <Button-3> {click %W %x %y 3 %X %Y; break}
1526        bind $i <ButtonRelease-1> {unclick %W %x %y; break}
1527}
1528unset i M1B M1T
1529
1530######################################################################
1531##
1532## main
1533
1534set appname [lindex [file split $argv0] end]
1535set gitdir {}
1536
1537if {[catch {set cdup [exec git rev-parse --show-cdup]} err]} {
1538        show_msg {} . "Cannot find the git directory: $err"
1539        exit 1
1540}
1541if {$cdup != ""} {
1542        cd $cdup
1543}
1544unset cdup
1545
1546if {[catch {set gitdir [exec git rev-parse --git-dir]} err]} {
1547        show_msg {} . "Cannot find the git directory: $err"
1548        exit 1
1549}
1550
1551if {$appname == {git-citool}} {
1552        set single_commit 1
1553}
1554
1555wm title . "$appname ([file normalize [file dirname $gitdir]])"
1556focus -force $ui_comm
1557load_repo_config
1558load_all_remotes
1559populate_remote_menu .mbar.fetch From fetch_from
1560populate_remote_menu .mbar.push To push_to
1561update_status