git-guion commit git-gui: Added support for pulling from default branch of a remote. (d33ba5f)
   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 $remote
 596        console_exec $w $cmd
 597}
 598
 599proc pull_remote {remote branch} {
 600        set w [new_console "pull $remote $branch" \
 601                "Pulling new changes from branch $branch in $remote"]
 602        set cmd [list git pull]
 603        lappend cmd $remote
 604        lappend cmd $branch
 605        console_exec $w $cmd [list post_pull_remote $remote $branch]
 606}
 607
 608proc post_pull_remote {remote branch success} {
 609        if {$success} {
 610                update_status "Successfully pulled $branch from $remote."
 611        } else {
 612                update_status "Conflicts detected while pulling $branch from $remote."
 613        }
 614}
 615
 616proc push_to {remote} {
 617        set w [new_console "push $remote" \
 618                "Pushing changes to $remote"]
 619        set cmd [list git push]
 620        lappend cmd $remote
 621        console_exec $w $cmd
 622}
 623
 624######################################################################
 625##
 626## ui helpers
 627
 628proc mapcol {state path} {
 629        global all_cols
 630
 631        if {[catch {set r $all_cols($state)}]} {
 632                puts "error: no column for state={$state} $path"
 633                return o
 634        }
 635        return $r
 636}
 637
 638proc mapicon {state path} {
 639        global all_icons
 640
 641        if {[catch {set r $all_icons($state)}]} {
 642                puts "error: no icon for state={$state} $path"
 643                return file_plain
 644        }
 645        return $r
 646}
 647
 648proc mapdesc {state path} {
 649        global all_descs
 650
 651        if {[catch {set r $all_descs($state)}]} {
 652                puts "error: no desc for state={$state} $path"
 653                return $state
 654        }
 655        return $r
 656}
 657
 658proc bsearch {w path} {
 659        set hi [expr [lindex [split [$w index end] .] 0] - 2]
 660        if {$hi == 0} {
 661                return -1
 662        }
 663        set lo 0
 664        while {$lo < $hi} {
 665                set mi [expr [expr $lo + $hi] / 2]
 666                set ti [expr $mi + 1]
 667                set cmp [string compare [$w get $ti.1 $ti.end] $path]
 668                if {$cmp < 0} {
 669                        set lo $ti
 670                } elseif {$cmp == 0} {
 671                        return $mi
 672                } else {
 673                        set hi $mi
 674                }
 675        }
 676        return -[expr $lo + 1]
 677}
 678
 679proc merge_state {path state} {
 680        global file_states
 681
 682        if {[array names file_states -exact $path] == {}}  {
 683                set o __
 684                set s [list $o none none]
 685        } else {
 686                set s $file_states($path)
 687                set o [lindex $s 0]
 688        }
 689
 690        set m [lindex $s 0]
 691        if {[string index $state 0] == "_"} {
 692                set state [string index $m 0][string index $state 1]
 693        } elseif {[string index $state 0] == "*"} {
 694                set state _[string index $state 1]
 695        }
 696
 697        if {[string index $state 1] == "_"} {
 698                set state [string index $state 0][string index $m 1]
 699        } elseif {[string index $state 1] == "*"} {
 700                set state [string index $state 0]_
 701        }
 702
 703        set file_states($path) [lreplace $s 0 0 $state]
 704        return $o
 705}
 706
 707proc display_file {path state} {
 708        global ui_index ui_other file_states
 709
 710        set old_m [merge_state $path $state]
 711        set s $file_states($path)
 712        set m [lindex $s 0]
 713
 714        if {[mapcol $m $path] == "o"} {
 715                set ii 1
 716                set ai 2
 717                set iw $ui_index
 718                set aw $ui_other
 719        } else {
 720                set ii 2
 721                set ai 1
 722                set iw $ui_other
 723                set aw $ui_index
 724        }
 725
 726        set d [lindex $s $ii]
 727        if {$d != "none"} {
 728                set lno [bsearch $iw $path]
 729                if {$lno >= 0} {
 730                        incr lno
 731                        $iw conf -state normal
 732                        $iw delete $lno.0 [expr $lno + 1].0
 733                        $iw conf -state disabled
 734                        set s [lreplace $s $ii $ii none]
 735                }
 736        }
 737
 738        set d [lindex $s $ai]
 739        if {$d == "none"} {
 740                set lno [expr abs([bsearch $aw $path] + 1) + 1]
 741                $aw conf -state normal
 742                set ico [$aw image create $lno.0 \
 743                        -align center -padx 5 -pady 1 \
 744                        -image [mapicon $m $path]]
 745                $aw insert $lno.1 "$path\n"
 746                $aw conf -state disabled
 747                set file_states($path) [lreplace $s $ai $ai [list $ico]]
 748        } elseif {[mapicon $m $path] != [mapicon $old_m $path]} {
 749                set ico [lindex $d 0]
 750                $aw image conf $ico -image [mapicon $m $path]
 751        }
 752}
 753
 754proc with_update_index {body} {
 755        global update_index_fd
 756
 757        if {$update_index_fd == {}} {
 758                if {![lock_index update]} return
 759                set update_index_fd [open \
 760                        "| git update-index --add --remove -z --stdin" \
 761                        w]
 762                fconfigure $update_index_fd -translation binary
 763                uplevel 1 $body
 764                close $update_index_fd
 765                set update_index_fd {}
 766                unlock_index
 767        } else {
 768                uplevel 1 $body
 769        }
 770}
 771
 772proc update_index {path} {
 773        global update_index_fd
 774
 775        if {$update_index_fd == {}} {
 776                error {not in with_update_index}
 777        } else {
 778                puts -nonewline $update_index_fd "$path\0"
 779        }
 780}
 781
 782proc toggle_mode {path} {
 783        global file_states ui_fname_value
 784
 785        set s $file_states($path)
 786        set m [lindex $s 0]
 787
 788        switch -- $m {
 789        AM -
 790        _O {set new A*}
 791        _M -
 792        MM {set new M*}
 793        AD -
 794        _D {set new D*}
 795        default {return}
 796        }
 797
 798        with_update_index {update_index $path}
 799        display_file $path $new
 800        if {$ui_fname_value == $path} {
 801                show_diff $path
 802        }
 803}
 804
 805######################################################################
 806##
 807## config (fetch push pull)
 808
 809proc load_repo_config {} {
 810        global repo_config
 811
 812        array unset repo_config
 813        catch {
 814                set fd_rc [open "| git repo-config --list" r]
 815                while {[gets $fd_rc line] >= 0} {
 816                        if {[regexp {^([^=]+)=(.*)$} $line line name value]} {
 817                                lappend repo_config($name) $value
 818                        }
 819                }
 820                close $fd_rc
 821        }
 822}
 823
 824proc load_all_remotes {} {
 825        global gitdir all_remotes repo_config
 826
 827        set all_remotes [list]
 828        set rm_dir [file join $gitdir remotes]
 829        if {[file isdirectory $rm_dir]} {
 830                set all_remotes [concat $all_remotes [glob \
 831                        -types f \
 832                        -tails \
 833                        -nocomplain \
 834                        -directory $rm_dir *]]
 835        }
 836
 837        foreach line [array names repo_config remote.*.url] {
 838                if {[regexp ^remote\.(.*)\.url\$ $line line name]} {
 839                        lappend all_remotes $name
 840                }
 841        }
 842
 843        set all_remotes [lsort -unique $all_remotes]
 844}
 845
 846proc populate_remote_menu {m pfx op} {
 847        global all_remotes mainfont
 848
 849        foreach remote $all_remotes {
 850                $m add command -label "$pfx $remote..." \
 851                        -command [list $op $remote] \
 852                        -font $mainfont
 853        }
 854}
 855
 856proc populate_pull_menu {m} {
 857        global gitdir repo_config all_remotes mainfont
 858
 859        foreach remote $all_remotes {
 860                set rb {}
 861                if {[array get repo_config remote.$remote.url] != {}} {
 862                        if {[array get repo_config remote.$remote.fetch] != {}} {
 863                                regexp {^([^:]+):} \
 864                                        [lindex $repo_config(remote.$remote.fetch) 0] \
 865                                        line rb
 866                        }
 867                } else {
 868                        catch {
 869                                set fd [open [file join $gitdir remotes $remote] r]
 870                                while {[gets $fd line] >= 0} {
 871                                        if {[regexp {^Pull:[ \t]*([^:]+):} $line line rb]} {
 872                                                break
 873                                        }
 874                                }
 875                                close $fd
 876                        }
 877                }
 878
 879                set rb_short $rb
 880                regsub ^refs/heads/ $rb {} rb_short
 881                if {$rb_short != {}} {
 882                        $m add command \
 883                                -label "Branch $rb_short from $remote..." \
 884                                -command [list pull_remote $remote $rb] \
 885                                -font $mainfont
 886                }
 887        }
 888}
 889
 890######################################################################
 891##
 892## icons
 893
 894set filemask {
 895#define mask_width 14
 896#define mask_height 15
 897static unsigned char mask_bits[] = {
 898   0xfe, 0x1f, 0xfe, 0x1f, 0xfe, 0x1f, 0xfe, 0x1f, 0xfe, 0x1f, 0xfe, 0x1f,
 899   0xfe, 0x1f, 0xfe, 0x1f, 0xfe, 0x1f, 0xfe, 0x1f, 0xfe, 0x1f, 0xfe, 0x1f,
 900   0xfe, 0x1f, 0xfe, 0x1f, 0xfe, 0x1f};
 901}
 902
 903image create bitmap file_plain -background white -foreground black -data {
 904#define plain_width 14
 905#define plain_height 15
 906static unsigned char plain_bits[] = {
 907   0xfe, 0x01, 0x02, 0x03, 0x02, 0x05, 0x02, 0x09, 0x02, 0x1f, 0x02, 0x10,
 908   0x02, 0x10, 0x02, 0x10, 0x02, 0x10, 0x02, 0x10, 0x02, 0x10, 0x02, 0x10,
 909   0x02, 0x10, 0x02, 0x10, 0xfe, 0x1f};
 910} -maskdata $filemask
 911
 912image create bitmap file_mod -background white -foreground blue -data {
 913#define mod_width 14
 914#define mod_height 15
 915static unsigned char mod_bits[] = {
 916   0xfe, 0x01, 0x02, 0x03, 0x7a, 0x05, 0x02, 0x09, 0x7a, 0x1f, 0x02, 0x10,
 917   0xfa, 0x17, 0x02, 0x10, 0xfa, 0x17, 0x02, 0x10, 0xfa, 0x17, 0x02, 0x10,
 918   0xfa, 0x17, 0x02, 0x10, 0xfe, 0x1f};
 919} -maskdata $filemask
 920
 921image create bitmap file_fulltick -background white -foreground "#007000" -data {
 922#define file_fulltick_width 14
 923#define file_fulltick_height 15
 924static unsigned char file_fulltick_bits[] = {
 925   0xfe, 0x01, 0x02, 0x1a, 0x02, 0x0c, 0x02, 0x0c, 0x02, 0x16, 0x02, 0x16,
 926   0x02, 0x13, 0x00, 0x13, 0x86, 0x11, 0x8c, 0x11, 0xd8, 0x10, 0xf2, 0x10,
 927   0x62, 0x10, 0x02, 0x10, 0xfe, 0x1f};
 928} -maskdata $filemask
 929
 930image create bitmap file_parttick -background white -foreground "#005050" -data {
 931#define parttick_width 14
 932#define parttick_height 15
 933static unsigned char parttick_bits[] = {
 934   0xfe, 0x01, 0x02, 0x03, 0x7a, 0x05, 0x02, 0x09, 0x7a, 0x1f, 0x02, 0x10,
 935   0x7a, 0x14, 0x02, 0x16, 0x02, 0x13, 0x8a, 0x11, 0xda, 0x10, 0x72, 0x10,
 936   0x22, 0x10, 0x02, 0x10, 0xfe, 0x1f};
 937} -maskdata $filemask
 938
 939image create bitmap file_question -background white -foreground black -data {
 940#define file_question_width 14
 941#define file_question_height 15
 942static unsigned char file_question_bits[] = {
 943   0xfe, 0x01, 0x02, 0x02, 0xe2, 0x04, 0xf2, 0x09, 0x1a, 0x1b, 0x0a, 0x13,
 944   0x82, 0x11, 0xc2, 0x10, 0x62, 0x10, 0x62, 0x10, 0x02, 0x10, 0x62, 0x10,
 945   0x62, 0x10, 0x02, 0x10, 0xfe, 0x1f};
 946} -maskdata $filemask
 947
 948image create bitmap file_removed -background white -foreground red -data {
 949#define file_removed_width 14
 950#define file_removed_height 15
 951static unsigned char file_removed_bits[] = {
 952   0xfe, 0x01, 0x02, 0x03, 0x02, 0x05, 0x02, 0x09, 0x02, 0x1f, 0x02, 0x10,
 953   0x1a, 0x16, 0x32, 0x13, 0xe2, 0x11, 0xc2, 0x10, 0xe2, 0x11, 0x32, 0x13,
 954   0x1a, 0x16, 0x02, 0x10, 0xfe, 0x1f};
 955} -maskdata $filemask
 956
 957image create bitmap file_merge -background white -foreground blue -data {
 958#define file_merge_width 14
 959#define file_merge_height 15
 960static unsigned char file_merge_bits[] = {
 961   0xfe, 0x01, 0x02, 0x03, 0x62, 0x05, 0x62, 0x09, 0x62, 0x1f, 0x62, 0x10,
 962   0xfa, 0x11, 0xf2, 0x10, 0x62, 0x10, 0x02, 0x10, 0xfa, 0x17, 0x02, 0x10,
 963   0xfa, 0x17, 0x02, 0x10, 0xfe, 0x1f};
 964} -maskdata $filemask
 965
 966set max_status_desc 0
 967foreach i {
 968                {__ i plain    "Unmodified"}
 969                {_M i mod      "Modified"}
 970                {M_ i fulltick "Checked in"}
 971                {MM i parttick "Partially checked in"}
 972
 973                {_O o plain    "Untracked"}
 974                {A_ o fulltick "Added"}
 975                {AM o parttick "Partially added"}
 976                {AD o question  "Added (but now gone)"}
 977
 978                {_D i question "Missing"}
 979                {D_ i removed  "Removed"}
 980                {DD i removed  "Removed"}
 981                {DO i removed  "Removed (still exists)"}
 982
 983                {UM i merge    "Merge conflicts"}
 984                {U_ i merge    "Merge conflicts"}
 985        } {
 986        if {$max_status_desc < [string length [lindex $i 3]]} {
 987                set max_status_desc [string length [lindex $i 3]]
 988        }
 989        set all_cols([lindex $i 0]) [lindex $i 1]
 990        set all_icons([lindex $i 0]) file_[lindex $i 2]
 991        set all_descs([lindex $i 0]) [lindex $i 3]
 992}
 993unset filemask i
 994
 995######################################################################
 996##
 997## util
 998
 999proc error_popup {msg} {
1000        set w .error
1001        toplevel $w
1002        wm transient $w .
1003        show_msg $w $w $msg
1004}
1005
1006proc show_msg {w top msg} {
1007        global gitdir appname mainfont
1008
1009        message $w.m -text $msg -justify left -aspect 400
1010        pack $w.m -side top -fill x -padx 5 -pady 10
1011        button $w.ok -text OK \
1012                -width 15 \
1013                -font $mainfont \
1014                -command "destroy $top"
1015        pack $w.ok -side bottom
1016        bind $top <Visibility> "grab $top; focus $top"
1017        bind $top <Key-Return> "destroy $top"
1018        wm title $w "$appname ([lindex [file split \
1019                [file normalize [file dirname $gitdir]]] \
1020                end]): error"
1021        tkwait window $top
1022}
1023
1024proc hook_failed_popup {hook msg} {
1025        global gitdir mainfont difffont appname
1026
1027        set w .hookfail
1028        toplevel $w
1029        wm transient $w .
1030
1031        frame $w.m
1032        label $w.m.l1 -text "$hook hook failed:" \
1033                -anchor w \
1034                -justify left \
1035                -font [concat $mainfont bold]
1036        text $w.m.t \
1037                -background white -borderwidth 1 \
1038                -relief sunken \
1039                -width 80 -height 10 \
1040                -font $difffont \
1041                -yscrollcommand [list $w.m.sby set]
1042        label $w.m.l2 \
1043                -text {You must correct the above errors before committing.} \
1044                -anchor w \
1045                -justify left \
1046                -font [concat $mainfont bold]
1047        scrollbar $w.m.sby -command [list $w.m.t yview]
1048        pack $w.m.l1 -side top -fill x
1049        pack $w.m.l2 -side bottom -fill x
1050        pack $w.m.sby -side right -fill y
1051        pack $w.m.t -side left -fill both -expand 1
1052        pack $w.m -side top -fill both -expand 1 -padx 5 -pady 10
1053
1054        $w.m.t insert 1.0 $msg
1055        $w.m.t conf -state disabled
1056
1057        button $w.ok -text OK \
1058                -width 15 \
1059                -font $mainfont \
1060                -command "destroy $w"
1061        pack $w.ok -side bottom
1062
1063        bind $w <Visibility> "grab $w; focus $w"
1064        bind $w <Key-Return> "destroy $w"
1065        wm title $w "$appname ([lindex [file split \
1066                [file normalize [file dirname $gitdir]]] \
1067                end]): error"
1068        tkwait window $w
1069}
1070
1071set next_console_id 0
1072
1073proc new_console {short_title long_title} {
1074        global next_console_id console_data
1075        set w .console[incr next_console_id]
1076        set console_data($w) [list $short_title $long_title]
1077        return [console_init $w]
1078}
1079
1080proc console_init {w} {
1081        global console_cr console_data
1082        global gitdir appname mainfont difffont
1083
1084        set console_cr($w) 1.0
1085        toplevel $w
1086        frame $w.m
1087        label $w.m.l1 -text "[lindex $console_data($w) 1]:" \
1088                -anchor w \
1089                -justify left \
1090                -font [concat $mainfont bold]
1091        text $w.m.t \
1092                -background white -borderwidth 1 \
1093                -relief sunken \
1094                -width 80 -height 10 \
1095                -font $difffont \
1096                -state disabled \
1097                -yscrollcommand [list $w.m.sby set]
1098        label $w.m.s -anchor w \
1099                -justify left \
1100                -font [concat $mainfont bold]
1101        scrollbar $w.m.sby -command [list $w.m.t yview]
1102        pack $w.m.l1 -side top -fill x
1103        pack $w.m.s -side bottom -fill x
1104        pack $w.m.sby -side right -fill y
1105        pack $w.m.t -side left -fill both -expand 1
1106        pack $w.m -side top -fill both -expand 1 -padx 5 -pady 10
1107
1108        button $w.ok -text {Running...} \
1109                -width 15 \
1110                -font $mainfont \
1111                -state disabled \
1112                -command "destroy $w"
1113        pack $w.ok -side bottom
1114
1115        bind $w <Visibility> "focus $w"
1116        wm title $w "$appname ([lindex [file split \
1117                [file normalize [file dirname $gitdir]]] \
1118                end]): [lindex $console_data($w) 0]"
1119        return $w
1120}
1121
1122proc console_exec {w cmd {after {}}} {
1123        global tcl_platform
1124
1125        # -- Windows tosses the enviroment when we exec our child.
1126        #    But most users need that so we have to relogin. :-(
1127        #
1128        if {$tcl_platform(platform) == {windows}} {
1129                set cmd [list sh --login -c "cd \"[pwd]\" && [join $cmd { }]"]
1130        }
1131
1132        # -- Tcl won't let us redirect both stdout and stderr to
1133        #    the same pipe.  So pass it through cat...
1134        #
1135        set cmd [concat | $cmd |& cat]
1136
1137        set fd_f [open $cmd r]
1138        fconfigure $fd_f -blocking 0 -translation binary
1139        fileevent $fd_f readable [list console_read $w $fd_f $after]
1140}
1141
1142proc console_read {w fd after} {
1143        global console_cr console_data
1144
1145        set buf [read $fd]
1146        if {$buf != {}} {
1147                if {![winfo exists $w]} {console_init $w}
1148                $w.m.t conf -state normal
1149                set c 0
1150                set n [string length $buf]
1151                while {$c < $n} {
1152                        set cr [string first "\r" $buf $c]
1153                        set lf [string first "\n" $buf $c]
1154                        if {$cr < 0} {set cr [expr $n + 1]}
1155                        if {$lf < 0} {set lf [expr $n + 1]}
1156
1157                        if {$lf < $cr} {
1158                                $w.m.t insert end [string range $buf $c $lf]
1159                                set console_cr($w) [$w.m.t index {end -1c}]
1160                                set c $lf
1161                                incr c
1162                        } else {
1163                                $w.m.t delete $console_cr($w) end
1164                                $w.m.t insert end "\n"
1165                                $w.m.t insert end [string range $buf $c $cr]
1166                                set c $cr
1167                                incr c
1168                        }
1169                }
1170                $w.m.t conf -state disabled
1171                $w.m.t see end
1172        }
1173
1174        fconfigure $fd -blocking 1
1175        if {[eof $fd]} {
1176                if {[catch {close $fd}]} {
1177                        if {![winfo exists $w]} {console_init $w}
1178                        $w.m.s conf -background red -text {Error: Command Failed}
1179                        $w.ok conf -text Close
1180                        $w.ok conf -state normal
1181                        set ok 0
1182                } elseif {[winfo exists $w]} {
1183                        $w.m.s conf -background green -text {Success}
1184                        $w.ok conf -text Close
1185                        $w.ok conf -state normal
1186                        set ok 1
1187                }
1188                array unset console_cr $w
1189                array unset console_data $w
1190                if {$after != {}} {
1191                        uplevel #0 $after $ok
1192                }
1193                return
1194        }
1195        fconfigure $fd -blocking 0
1196}
1197
1198######################################################################
1199##
1200## ui commands
1201
1202set starting_gitk_msg {Please wait... Starting gitk...}
1203
1204proc do_gitk {} {
1205        global tcl_platform ui_status_value starting_gitk_msg
1206
1207        set ui_status_value $starting_gitk_msg
1208        after 10000 {
1209                if {$ui_status_value == $starting_gitk_msg} {
1210                        set ui_status_value {Ready.}
1211                }
1212        }
1213
1214        if {$tcl_platform(platform) == {windows}} {
1215                exec sh -c gitk &
1216        } else {
1217                exec gitk &
1218        }
1219}
1220
1221proc do_quit {} {
1222        global gitdir ui_comm
1223
1224        set save [file join $gitdir GITGUI_MSG]
1225        set msg [string trim [$ui_comm get 0.0 end]]
1226        if {[$ui_comm edit modified] && $msg != {}} {
1227                catch {
1228                        set fd [open $save w]
1229                        puts $fd [string trim [$ui_comm get 0.0 end]]
1230                        close $fd
1231                }
1232        } elseif {$msg == {} && [file exists $save]} {
1233                file delete $save
1234        }
1235
1236        destroy .
1237}
1238
1239proc do_rescan {} {
1240        update_status
1241}
1242
1243proc do_checkin_all {} {
1244        global checkin_active ui_status_value
1245
1246        if {$checkin_active || ![lock_index begin-update]} return
1247
1248        set checkin_active 1
1249        set ui_status_value {Checking in all files...}
1250        after 1 {
1251                with_update_index {
1252                        foreach path [array names file_states] {
1253                                set s $file_states($path)
1254                                set m [lindex $s 0]
1255                                switch -- $m {
1256                                AM -
1257                                MM -
1258                                _M -
1259                                _D {toggle_mode $path}
1260                                }
1261                        }
1262                }
1263                set checkin_active 0
1264                set ui_status_value {Ready.}
1265        }
1266}
1267
1268proc do_signoff {} {
1269        global ui_comm
1270
1271        catch {
1272                set me [exec git var GIT_COMMITTER_IDENT]
1273                if {[regexp {(.*) [0-9]+ [-+0-9]+$} $me me name]} {
1274                        set str "Signed-off-by: $name"
1275                        if {[$ui_comm get {end -1c linestart} {end -1c}] != $str} {
1276                                $ui_comm insert end "\n"
1277                                $ui_comm insert end $str
1278                                $ui_comm see end
1279                        }
1280                }
1281        }
1282}
1283
1284proc do_amend_last {} {
1285        load_last_commit
1286}
1287
1288proc do_commit {} {
1289        commit_tree
1290}
1291
1292# shift == 1: left click
1293#          3: right click  
1294proc click {w x y shift wx wy} {
1295        global ui_index ui_other
1296
1297        set pos [split [$w index @$x,$y] .]
1298        set lno [lindex $pos 0]
1299        set col [lindex $pos 1]
1300        set path [$w get $lno.1 $lno.end]
1301        if {$path == {}} return
1302
1303        if {$col > 0 && $shift == 1} {
1304                $ui_index tag remove in_diff 0.0 end
1305                $ui_other tag remove in_diff 0.0 end
1306                $w tag add in_diff $lno.0 [expr $lno + 1].0
1307                show_diff $path
1308        }
1309}
1310
1311proc unclick {w x y} {
1312        set pos [split [$w index @$x,$y] .]
1313        set lno [lindex $pos 0]
1314        set col [lindex $pos 1]
1315        set path [$w get $lno.1 $lno.end]
1316        if {$path == {}} return
1317
1318        if {$col == 0} {
1319                toggle_mode $path
1320        }
1321}
1322
1323######################################################################
1324##
1325## ui init
1326
1327set mainfont {Helvetica 10}
1328set difffont {Courier 10}
1329set maincursor [. cget -cursor]
1330
1331switch -glob -- "$tcl_platform(platform),$tcl_platform(os)" {
1332windows,*   {set M1B Control; set M1T Ctrl}
1333unix,Darwin {set M1B M1; set M1T Cmd}
1334default     {set M1B M1; set M1T M1}
1335}
1336
1337# -- Menu Bar
1338menu .mbar -tearoff 0
1339.mbar add cascade -label Project -menu .mbar.project
1340.mbar add cascade -label Commit -menu .mbar.commit
1341.mbar add cascade -label Fetch -menu .mbar.fetch
1342.mbar add cascade -label Pull -menu .mbar.pull
1343.mbar add cascade -label Push -menu .mbar.push
1344. configure -menu .mbar
1345
1346# -- Project Menu
1347menu .mbar.project
1348.mbar.project add command -label Visualize \
1349        -command do_gitk \
1350        -font $mainfont
1351.mbar.project add command -label Quit \
1352        -command do_quit \
1353        -accelerator $M1T-Q \
1354        -font $mainfont
1355
1356# -- Commit Menu
1357menu .mbar.commit
1358.mbar.commit add command -label Rescan \
1359        -command do_rescan \
1360        -accelerator F5 \
1361        -font $mainfont
1362lappend disable_on_lock \
1363        [list .mbar.commit entryconf [.mbar.commit index last] -state]
1364.mbar.commit add command -label {Amend Last Commit} \
1365        -command do_amend_last \
1366        -font $mainfont
1367lappend disable_on_lock \
1368        [list .mbar.commit entryconf [.mbar.commit index last] -state]
1369.mbar.commit add command -label {Check-in All Files} \
1370        -command do_checkin_all \
1371        -accelerator $M1T-U \
1372        -font $mainfont
1373lappend disable_on_lock \
1374        [list .mbar.commit entryconf [.mbar.commit index last] -state]
1375.mbar.commit add command -label {Sign Off} \
1376        -command do_signoff \
1377        -accelerator $M1T-S \
1378        -font $mainfont
1379.mbar.commit add command -label Commit \
1380        -command do_commit \
1381        -accelerator $M1T-Return \
1382        -font $mainfont
1383lappend disable_on_lock \
1384        [list .mbar.commit entryconf [.mbar.commit index last] -state]
1385
1386# -- Fetch Menu
1387menu .mbar.fetch
1388
1389# -- Pull Menu
1390menu .mbar.pull
1391
1392# -- Push Menu
1393menu .mbar.push
1394
1395# -- Main Window Layout
1396panedwindow .vpane -orient vertical
1397panedwindow .vpane.files -orient horizontal
1398.vpane add .vpane.files -sticky nsew -height 100 -width 400
1399pack .vpane -anchor n -side top -fill both -expand 1
1400
1401# -- Index File List
1402set ui_index .vpane.files.index.list
1403frame .vpane.files.index -height 100 -width 400
1404label .vpane.files.index.title -text {Modified Files} \
1405        -background green \
1406        -font $mainfont
1407text $ui_index -background white -borderwidth 0 \
1408        -width 40 -height 10 \
1409        -font $mainfont \
1410        -yscrollcommand {.vpane.files.index.sb set} \
1411        -cursor $maincursor \
1412        -state disabled
1413scrollbar .vpane.files.index.sb -command [list $ui_index yview]
1414pack .vpane.files.index.title -side top -fill x
1415pack .vpane.files.index.sb -side right -fill y
1416pack $ui_index -side left -fill both -expand 1
1417.vpane.files add .vpane.files.index -sticky nsew
1418
1419# -- Other (Add) File List
1420set ui_other .vpane.files.other.list
1421frame .vpane.files.other -height 100 -width 100
1422label .vpane.files.other.title -text {Untracked Files} \
1423        -background red \
1424        -font $mainfont
1425text $ui_other -background white -borderwidth 0 \
1426        -width 40 -height 10 \
1427        -font $mainfont \
1428        -yscrollcommand {.vpane.files.other.sb set} \
1429        -cursor $maincursor \
1430        -state disabled
1431scrollbar .vpane.files.other.sb -command [list $ui_other yview]
1432pack .vpane.files.other.title -side top -fill x
1433pack .vpane.files.other.sb -side right -fill y
1434pack $ui_other -side left -fill both -expand 1
1435.vpane.files add .vpane.files.other -sticky nsew
1436
1437$ui_index tag conf in_diff -font [concat $mainfont bold]
1438$ui_other tag conf in_diff -font [concat $mainfont bold]
1439
1440# -- Diff Header
1441set ui_fname_value {}
1442set ui_fstatus_value {}
1443frame .vpane.diff -height 200 -width 400
1444frame .vpane.diff.header
1445label .vpane.diff.header.l1 -text {File:} -font $mainfont
1446label .vpane.diff.header.l2 -textvariable ui_fname_value \
1447        -anchor w \
1448        -justify left \
1449        -font $mainfont
1450label .vpane.diff.header.l3 -text {Status:} -font $mainfont
1451label .vpane.diff.header.l4 -textvariable ui_fstatus_value \
1452        -width $max_status_desc \
1453        -anchor w \
1454        -justify left \
1455        -font $mainfont
1456pack .vpane.diff.header.l1 -side left
1457pack .vpane.diff.header.l2 -side left -fill x
1458pack .vpane.diff.header.l4 -side right
1459pack .vpane.diff.header.l3 -side right
1460
1461# -- Diff Body
1462frame .vpane.diff.body
1463set ui_diff .vpane.diff.body.t
1464text $ui_diff -background white -borderwidth 0 \
1465        -width 80 -height 15 -wrap none \
1466        -font $difffont \
1467        -xscrollcommand {.vpane.diff.body.sbx set} \
1468        -yscrollcommand {.vpane.diff.body.sby set} \
1469        -cursor $maincursor \
1470        -state disabled
1471scrollbar .vpane.diff.body.sbx -orient horizontal \
1472        -command [list $ui_diff xview]
1473scrollbar .vpane.diff.body.sby -orient vertical \
1474        -command [list $ui_diff yview]
1475pack .vpane.diff.body.sbx -side bottom -fill x
1476pack .vpane.diff.body.sby -side right -fill y
1477pack $ui_diff -side left -fill both -expand 1
1478pack .vpane.diff.header -side top -fill x
1479pack .vpane.diff.body -side bottom -fill both -expand 1
1480.vpane add .vpane.diff -stick nsew
1481
1482$ui_diff tag conf dm -foreground red
1483$ui_diff tag conf dp -foreground blue
1484$ui_diff tag conf da -font [concat $difffont bold]
1485$ui_diff tag conf di -foreground "#00a000"
1486$ui_diff tag conf dni -foreground "#a000a0"
1487$ui_diff tag conf bold -font [concat $difffont bold]
1488
1489# -- Commit Area
1490frame .vpane.commarea -height 170
1491.vpane add .vpane.commarea -stick nsew
1492
1493# -- Commit Area Buttons
1494frame .vpane.commarea.buttons
1495label .vpane.commarea.buttons.l -text {} \
1496        -anchor w \
1497        -justify left \
1498        -font $mainfont
1499pack .vpane.commarea.buttons.l -side top -fill x
1500pack .vpane.commarea.buttons -side left -fill y
1501
1502button .vpane.commarea.buttons.rescan -text {Rescan} \
1503        -command do_rescan \
1504        -font $mainfont
1505pack .vpane.commarea.buttons.rescan -side top -fill x
1506lappend disable_on_lock {.vpane.commarea.buttons.rescan conf -state}
1507
1508button .vpane.commarea.buttons.amend -text {Amend Last} \
1509        -command do_amend_last \
1510        -font $mainfont
1511pack .vpane.commarea.buttons.amend -side top -fill x
1512lappend disable_on_lock {.vpane.commarea.buttons.amend conf -state}
1513
1514button .vpane.commarea.buttons.ciall -text {Check-in All} \
1515        -command do_checkin_all \
1516        -font $mainfont
1517pack .vpane.commarea.buttons.ciall -side top -fill x
1518lappend disable_on_lock {.vpane.commarea.buttons.ciall conf -state}
1519
1520button .vpane.commarea.buttons.signoff -text {Sign Off} \
1521        -command do_signoff \
1522        -font $mainfont
1523pack .vpane.commarea.buttons.signoff -side top -fill x
1524
1525button .vpane.commarea.buttons.commit -text {Commit} \
1526        -command do_commit \
1527        -font $mainfont
1528pack .vpane.commarea.buttons.commit -side top -fill x
1529lappend disable_on_lock {.vpane.commarea.buttons.commit conf -state}
1530
1531# -- Commit Message Buffer
1532frame .vpane.commarea.buffer
1533set ui_comm .vpane.commarea.buffer.t
1534set ui_coml .vpane.commarea.buffer.l
1535label $ui_coml -text {Commit Message:} \
1536        -anchor w \
1537        -justify left \
1538        -font $mainfont
1539trace add variable commit_type write {uplevel #0 {
1540        switch -glob $commit_type \
1541        initial {$ui_coml conf -text {Initial Commit Message:}} \
1542        amend   {$ui_coml conf -text {Amended Commit Message:}} \
1543        merge   {$ui_coml conf -text {Merge Commit Message:}} \
1544        *       {$ui_coml conf -text {Commit Message:}}
1545}}
1546text $ui_comm -background white -borderwidth 1 \
1547        -relief sunken \
1548        -width 75 -height 10 -wrap none \
1549        -font $difffont \
1550        -yscrollcommand {.vpane.commarea.buffer.sby set} \
1551        -cursor $maincursor
1552scrollbar .vpane.commarea.buffer.sby -command [list $ui_comm yview]
1553pack $ui_coml -side top -fill x
1554pack .vpane.commarea.buffer.sby -side right -fill y
1555pack $ui_comm -side left -fill y
1556pack .vpane.commarea.buffer -side left -fill y
1557
1558# -- Status Bar
1559set ui_status_value {Initializing...}
1560label .status -textvariable ui_status_value \
1561        -anchor w \
1562        -justify left \
1563        -borderwidth 1 \
1564        -relief sunken \
1565        -font $mainfont
1566pack .status -anchor w -side bottom -fill x
1567
1568# -- Key Bindings
1569bind $ui_comm <$M1B-Key-Return> {do_commit;break}
1570bind .   <Destroy> do_quit
1571bind all <Key-F5> do_rescan
1572bind all <$M1B-Key-r> do_rescan
1573bind all <$M1B-Key-R> do_rescan
1574bind .   <$M1B-Key-s> do_signoff
1575bind .   <$M1B-Key-S> do_signoff
1576bind .   <$M1B-Key-u> do_checkin_all
1577bind .   <$M1B-Key-U> do_checkin_all
1578bind .   <$M1B-Key-Return> do_commit
1579bind all <$M1B-Key-q> do_quit
1580bind all <$M1B-Key-Q> do_quit
1581bind all <$M1B-Key-w> {destroy [winfo toplevel %W]}
1582bind all <$M1B-Key-W> {destroy [winfo toplevel %W]}
1583foreach i [list $ui_index $ui_other] {
1584        bind $i <Button-1> {click %W %x %y 1 %X %Y; break}
1585        bind $i <Button-3> {click %W %x %y 3 %X %Y; break}
1586        bind $i <ButtonRelease-1> {unclick %W %x %y; break}
1587}
1588unset i M1B M1T
1589
1590######################################################################
1591##
1592## main
1593
1594set appname [lindex [file split $argv0] end]
1595set gitdir {}
1596
1597if {[catch {set cdup [exec git rev-parse --show-cdup]} err]} {
1598        show_msg {} . "Cannot find the git directory: $err"
1599        exit 1
1600}
1601if {$cdup != ""} {
1602        cd $cdup
1603}
1604unset cdup
1605
1606if {[catch {set gitdir [exec git rev-parse --git-dir]} err]} {
1607        show_msg {} . "Cannot find the git directory: $err"
1608        exit 1
1609}
1610
1611if {$appname == {git-citool}} {
1612        set single_commit 1
1613}
1614
1615wm title . "$appname ([file normalize [file dirname $gitdir]])"
1616focus -force $ui_comm
1617load_repo_config
1618load_all_remotes
1619populate_remote_menu .mbar.fetch From fetch_from
1620populate_remote_menu .mbar.push To push_to
1621populate_pull_menu .mbar.pull
1622update_status