3ee32e105c2f5be814042ff1aea910fea06a76b4
   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##
  13## status
  14
  15set status_active 0
  16set diff_active 0
  17set checkin_active 0
  18set update_index_fd {}
  19
  20proc is_busy {} {
  21        global status_active diff_active checkin_active update_index_fd
  22
  23        if {$status_active > 0
  24                || $diff_active
  25                || $checkin_active
  26                || $update_index_fd != {}} {
  27                return 1
  28        }
  29        return 0
  30}
  31
  32proc update_status {} {
  33        global gitdir HEAD commit_type
  34        global ui_index ui_other ui_status_value ui_comm
  35        global status_active file_states
  36
  37        if {[is_busy]} return
  38
  39        array unset file_states
  40        foreach w [list $ui_index $ui_other] {
  41                $w conf -state normal
  42                $w delete 0.0 end
  43                $w conf -state disabled
  44        }
  45
  46        if {[catch {set HEAD [exec git rev-parse --verify HEAD]}]} {
  47                set commit_type initial
  48        } else {
  49                set commit_type normal
  50        }
  51
  52        if {![$ui_comm edit modified]
  53            || [string trim [$ui_comm get 0.0 end]] == {}} {
  54                if {[load_message GITGUI_MSG]} {
  55                } elseif {[load_message MERGE_MSG]} {
  56                } elseif {[load_message SQUASH_MSG]} {
  57                }
  58                $ui_comm edit modified false
  59        }
  60
  61        set status_active 1
  62        set ui_status_value {Refreshing file status...}
  63        set fd_rf [open "| git update-index -q --unmerged --refresh" r]
  64        fconfigure $fd_rf -blocking 0 -translation binary
  65        fileevent $fd_rf readable [list read_refresh $fd_rf]
  66}
  67
  68proc read_refresh {fd} {
  69        global gitdir HEAD commit_type
  70        global ui_index ui_other ui_status_value ui_comm
  71        global status_active file_states
  72
  73        read $fd
  74        if {![eof $fd]} return
  75        close $fd
  76
  77        set ls_others [list | git ls-files --others -z \
  78                --exclude-per-directory=.gitignore]
  79        set info_exclude [file join $gitdir info exclude]
  80        if {[file readable $info_exclude]} {
  81                lappend ls_others "--exclude-from=$info_exclude"
  82        }
  83
  84        set status_active 3
  85        set ui_status_value {Scanning for modified files ...}
  86        set fd_di [open "| git diff-index --cached -z $HEAD" r]
  87        set fd_df [open "| git diff-files -z" r]
  88        set fd_lo [open $ls_others r]
  89
  90        fconfigure $fd_di -blocking 0 -translation binary
  91        fconfigure $fd_df -blocking 0 -translation binary
  92        fconfigure $fd_lo -blocking 0 -translation binary
  93        fileevent $fd_di readable [list read_diff_index $fd_di]
  94        fileevent $fd_df readable [list read_diff_files $fd_df]
  95        fileevent $fd_lo readable [list read_ls_others $fd_lo]
  96}
  97
  98proc load_message {file} {
  99        global gitdir ui_comm
 100
 101        set f [file join $gitdir $file]
 102        if {[file exists $f]} {
 103                if {[catch {set fd [open $f r]}]} {
 104                        return 0
 105                }
 106                set content [read $fd]
 107                close $fd
 108                $ui_comm delete 0.0 end
 109                $ui_comm insert end $content
 110                return 1
 111        }
 112        return 0
 113}
 114
 115proc read_diff_index {fd} {
 116        global buf_rdi
 117
 118        append buf_rdi [read $fd]
 119        set pck [split $buf_rdi "\0"]
 120        set buf_rdi [lindex $pck end]
 121        foreach {m p} [lrange $pck 0 end-1] {
 122                if {$m != {} && $p != {}} {
 123                        display_file $p [string index $m end]_
 124                }
 125        }
 126        status_eof $fd buf_rdi
 127}
 128
 129proc read_diff_files {fd} {
 130        global buf_rdf
 131
 132        append buf_rdf [read $fd]
 133        set pck [split $buf_rdf "\0"]
 134        set buf_rdf [lindex $pck end]
 135        foreach {m p} [lrange $pck 0 end-1] {
 136                if {$m != {} && $p != {}} {
 137                        display_file $p _[string index $m end]
 138                }
 139        }
 140        status_eof $fd buf_rdf
 141}
 142
 143proc read_ls_others {fd} {
 144        global buf_rlo
 145
 146        append buf_rlo [read $fd]
 147        set pck [split $buf_rlo "\0"]
 148        set buf_rlo [lindex $pck end]
 149        foreach p [lrange $pck 0 end-1] {
 150                display_file $p _O
 151        }
 152        status_eof $fd buf_rlo
 153}
 154
 155proc status_eof {fd buf} {
 156        global status_active $buf
 157        global ui_fname_value ui_status_value
 158
 159        if {[eof $fd]} {
 160                set $buf {}
 161                close $fd
 162                if {[incr status_active -1] == 0} {
 163                        set ui_status_value {Ready.}
 164                        if {$ui_fname_value != {}} {
 165                                show_diff $ui_fname_value
 166                        }
 167                }
 168        }
 169}
 170
 171######################################################################
 172##
 173## diff
 174
 175proc clear_diff {} {
 176        global ui_diff ui_fname_value ui_fstatus_value
 177
 178        $ui_diff conf -state normal
 179        $ui_diff delete 0.0 end
 180        $ui_diff conf -state disabled
 181        set ui_fname_value {}
 182        set ui_fstatus_value {}
 183}
 184
 185proc show_diff {path} {
 186        global file_states HEAD diff_3way diff_active
 187        global ui_diff ui_fname_value ui_fstatus_value ui_status_value
 188
 189        if {[is_busy]} return
 190
 191        clear_diff
 192        set s $file_states($path)
 193        set m [lindex $s 0]
 194        set diff_3way 0
 195        set diff_active 1
 196        set ui_fname_value $path
 197        set ui_fstatus_value [mapdesc $m $path]
 198        set ui_status_value "Loading diff of $path..."
 199
 200        set cmd [list | git diff-index -p $HEAD -- $path]
 201        switch $m {
 202        AM {
 203        }
 204        MM {
 205                set cmd [list | git diff-index -p -c $HEAD $path]
 206        }
 207        _O {
 208                if {[catch {
 209                                set fd [open $path r]
 210                                set content [read $fd]
 211                                close $fd
 212                        } err ]} {
 213                        set diff_active 0
 214                        set ui_status_value "Unable to display $path"
 215                        error_popup "Error loading file:\n$err"
 216                        return
 217                }
 218                $ui_diff conf -state normal
 219                $ui_diff insert end $content
 220                $ui_diff conf -state disabled
 221                return
 222        }
 223        }
 224
 225        if {[catch {set fd [open $cmd r]} err]} {
 226                set diff_active 0
 227                set ui_status_value "Unable to display $path"
 228                error_popup "Error loading diff:\n$err"
 229                return
 230        }
 231
 232        fconfigure $fd -blocking 0 -translation auto
 233        fileevent $fd readable [list read_diff $fd]
 234}
 235
 236proc read_diff {fd} {
 237        global ui_diff ui_status_value diff_3way diff_active
 238
 239        while {[gets $fd line] >= 0} {
 240                if {[string match {diff --git *} $line]} continue
 241                if {[string match {diff --combined *} $line]} continue
 242                if {[string match {--- *} $line]} continue
 243                if {[string match {+++ *} $line]} continue
 244                if {[string match index* $line]} {
 245                        if {[string first , $line] >= 0} {
 246                                set diff_3way 1
 247                        }
 248                }
 249
 250                $ui_diff conf -state normal
 251                if {!$diff_3way} {
 252                        set x [string index $line 0]
 253                        switch -- $x {
 254                        "@" {set tags da}
 255                        "+" {set tags dp}
 256                        "-" {set tags dm}
 257                        default {set tags {}}
 258                        }
 259                } else {
 260                        set x [string range $line 0 1]
 261                        switch -- $x {
 262                        default {set tags {}}
 263                        "@@" {set tags da}
 264                        "++" {set tags dp; set x " +"}
 265                        " +" {set tags {di bold}; set x "++"}
 266                        "+ " {set tags dni; set x "-+"}
 267                        "--" {set tags dm; set x " -"}
 268                        " -" {set tags {dm bold}; set x "--"}
 269                        "- " {set tags di; set x "+-"}
 270                        default {set tags {}}
 271                        }
 272                        set line [string replace $line 0 1 $x]
 273                }
 274                $ui_diff insert end $line $tags
 275                $ui_diff insert end "\n"
 276                $ui_diff conf -state disabled
 277        }
 278
 279        if {[eof $fd]} {
 280                close $fd
 281                set diff_active 0
 282                set ui_status_value {Ready.}
 283        }
 284}
 285
 286######################################################################
 287##
 288## ui helpers
 289
 290proc mapcol {state path} {
 291        global all_cols
 292
 293        if {[catch {set r $all_cols($state)}]} {
 294                puts "error: no column for state={$state} $path"
 295                return o
 296        }
 297        return $r
 298}
 299
 300proc mapicon {state path} {
 301        global all_icons
 302
 303        if {[catch {set r $all_icons($state)}]} {
 304                puts "error: no icon for state={$state} $path"
 305                return file_plain
 306        }
 307        return $r
 308}
 309
 310proc mapdesc {state path} {
 311        global all_descs
 312
 313        if {[catch {set r $all_descs($state)}]} {
 314                puts "error: no desc for state={$state} $path"
 315                return $state
 316        }
 317        return $r
 318}
 319
 320proc bsearch {w path} {
 321        set hi [expr [lindex [split [$w index end] .] 0] - 2]
 322        if {$hi == 0} {
 323                return -1
 324        }
 325        set lo 0
 326        while {$lo < $hi} {
 327                set mi [expr [expr $lo + $hi] / 2]
 328                set ti [expr $mi + 1]
 329                set cmp [string compare [$w get $ti.1 $ti.end] $path]
 330                if {$cmp < 0} {
 331                        set lo $ti
 332                } elseif {$cmp == 0} {
 333                        return $mi
 334                } else {
 335                        set hi $mi
 336                }
 337        }
 338        return -[expr $lo + 1]
 339}
 340
 341proc merge_state {path state} {
 342        global file_states
 343
 344        if {[array names file_states -exact $path] == {}}  {
 345                set o __
 346                set s [list $o none none]
 347        } else {
 348                set s $file_states($path)
 349                set o [lindex $s 0]
 350        }
 351
 352        set m [lindex $s 0]
 353        if {[string index $state 0] == "_"} {
 354                set state [string index $m 0][string index $state 1]
 355        } elseif {[string index $state 0] == "*"} {
 356                set state _[string index $state 1]
 357        }
 358
 359        if {[string index $state 1] == "_"} {
 360                set state [string index $state 0][string index $m 1]
 361        } elseif {[string index $state 1] == "*"} {
 362                set state [string index $state 0]_
 363        }
 364
 365        set file_states($path) [lreplace $s 0 0 $state]
 366        return $o
 367}
 368
 369proc display_file {path state} {
 370        global ui_index ui_other file_states
 371
 372        set old_m [merge_state $path $state]
 373        set s $file_states($path)
 374        set m [lindex $s 0]
 375
 376        if {[mapcol $m $path] == "o"} {
 377                set ii 1
 378                set ai 2
 379                set iw $ui_index
 380                set aw $ui_other
 381        } else {
 382                set ii 2
 383                set ai 1
 384                set iw $ui_other
 385                set aw $ui_index
 386        }
 387
 388        set d [lindex $s $ii]
 389        if {$d != "none"} {
 390                set lno [bsearch $iw $path]
 391                if {$lno >= 0} {
 392                        incr lno
 393                        $iw conf -state normal
 394                        $iw delete $lno.0 [expr $lno + 1].0
 395                        $iw conf -state disabled
 396                        set s [lreplace $s $ii $ii none]
 397                }
 398        }
 399
 400        set d [lindex $s $ai]
 401        if {$d == "none"} {
 402                set lno [expr abs([bsearch $aw $path] + 1) + 1]
 403                $aw conf -state normal
 404                set ico [$aw image create $lno.0 \
 405                        -align center -padx 5 -pady 1 \
 406                        -image [mapicon $m $path]]
 407                $aw insert $lno.1 "$path\n"
 408                $aw conf -state disabled
 409                set file_states($path) [lreplace $s $ai $ai [list $ico]]
 410        } elseif {[mapicon $m $path] != [mapicon $old_m $path]} {
 411                set ico [lindex $d 0]
 412                $aw image conf $ico -image [mapicon $m $path]
 413        }
 414}
 415
 416proc with_update_index {body} {
 417        global update_index_fd
 418
 419        if {$update_index_fd == {}} {
 420                set update_index_fd [open \
 421                        "| git update-index --add --remove -z --stdin" \
 422                        w]
 423                fconfigure $update_index_fd -translation binary
 424                uplevel 1 $body
 425                close $update_index_fd
 426                set update_index_fd {}
 427        } else {
 428                uplevel 1 $body
 429        }
 430}
 431
 432proc update_index {path} {
 433        global update_index_fd
 434
 435        if {$update_index_fd == {}} {
 436                error {not in with_update_index}
 437        } else {
 438                puts -nonewline $update_index_fd "$path\0"
 439        }
 440}
 441
 442proc toggle_mode {path} {
 443        global file_states
 444
 445        set s $file_states($path)
 446        set m [lindex $s 0]
 447
 448        switch -- $m {
 449        AM -
 450        _O {set new A*}
 451        _M -
 452        MM {set new M*}
 453        _D {set new D*}
 454        default {return}
 455        }
 456
 457        with_update_index {update_index $path}
 458        display_file $path $new
 459}
 460
 461######################################################################
 462##
 463## icons
 464
 465set filemask {
 466#define mask_width 14
 467#define mask_height 15
 468static unsigned char mask_bits[] = {
 469   0xfe, 0x1f, 0xfe, 0x1f, 0xfe, 0x1f, 0xfe, 0x1f, 0xfe, 0x1f, 0xfe, 0x1f,
 470   0xfe, 0x1f, 0xfe, 0x1f, 0xfe, 0x1f, 0xfe, 0x1f, 0xfe, 0x1f, 0xfe, 0x1f,
 471   0xfe, 0x1f, 0xfe, 0x1f, 0xfe, 0x1f};
 472}
 473
 474image create bitmap file_plain -background white -foreground black -data {
 475#define plain_width 14
 476#define plain_height 15
 477static unsigned char plain_bits[] = {
 478   0xfe, 0x01, 0x02, 0x03, 0x02, 0x05, 0x02, 0x09, 0x02, 0x1f, 0x02, 0x10,
 479   0x02, 0x10, 0x02, 0x10, 0x02, 0x10, 0x02, 0x10, 0x02, 0x10, 0x02, 0x10,
 480   0x02, 0x10, 0x02, 0x10, 0xfe, 0x1f};
 481} -maskdata $filemask
 482
 483image create bitmap file_mod -background white -foreground blue -data {
 484#define mod_width 14
 485#define mod_height 15
 486static unsigned char mod_bits[] = {
 487   0xfe, 0x01, 0x02, 0x03, 0x7a, 0x05, 0x02, 0x09, 0x7a, 0x1f, 0x02, 0x10,
 488   0xfa, 0x17, 0x02, 0x10, 0xfa, 0x17, 0x02, 0x10, 0xfa, 0x17, 0x02, 0x10,
 489   0xfa, 0x17, 0x02, 0x10, 0xfe, 0x1f};
 490} -maskdata $filemask
 491
 492image create bitmap file_fulltick -background white -foreground "#007000" -data {
 493#define file_fulltick_width 14
 494#define file_fulltick_height 15
 495static unsigned char file_fulltick_bits[] = {
 496   0xfe, 0x01, 0x02, 0x1a, 0x02, 0x0c, 0x02, 0x0c, 0x02, 0x16, 0x02, 0x16,
 497   0x02, 0x13, 0x00, 0x13, 0x86, 0x11, 0x8c, 0x11, 0xd8, 0x10, 0xf2, 0x10,
 498   0x62, 0x10, 0x02, 0x10, 0xfe, 0x1f};
 499} -maskdata $filemask
 500
 501image create bitmap file_parttick -background white -foreground "#005050" -data {
 502#define parttick_width 14
 503#define parttick_height 15
 504static unsigned char parttick_bits[] = {
 505   0xfe, 0x01, 0x02, 0x03, 0x7a, 0x05, 0x02, 0x09, 0x7a, 0x1f, 0x02, 0x10,
 506   0x7a, 0x14, 0x02, 0x16, 0x02, 0x13, 0x8a, 0x11, 0xda, 0x10, 0x72, 0x10,
 507   0x22, 0x10, 0x02, 0x10, 0xfe, 0x1f};
 508} -maskdata $filemask
 509
 510image create bitmap file_question -background white -foreground black -data {
 511#define file_question_width 14
 512#define file_question_height 15
 513static unsigned char file_question_bits[] = {
 514   0xfe, 0x01, 0x02, 0x02, 0xe2, 0x04, 0xf2, 0x09, 0x1a, 0x1b, 0x0a, 0x13,
 515   0x82, 0x11, 0xc2, 0x10, 0x62, 0x10, 0x62, 0x10, 0x02, 0x10, 0x62, 0x10,
 516   0x62, 0x10, 0x02, 0x10, 0xfe, 0x1f};
 517} -maskdata $filemask
 518
 519image create bitmap file_removed -background white -foreground red -data {
 520#define file_removed_width 14
 521#define file_removed_height 15
 522static unsigned char file_removed_bits[] = {
 523   0xfe, 0x01, 0x02, 0x03, 0x02, 0x05, 0x02, 0x09, 0x02, 0x1f, 0x02, 0x10,
 524   0x1a, 0x16, 0x32, 0x13, 0xe2, 0x11, 0xc2, 0x10, 0xe2, 0x11, 0x32, 0x13,
 525   0x1a, 0x16, 0x02, 0x10, 0xfe, 0x1f};
 526} -maskdata $filemask
 527
 528image create bitmap file_merge -background white -foreground blue -data {
 529#define file_merge_width 14
 530#define file_merge_height 15
 531static unsigned char file_merge_bits[] = {
 532   0xfe, 0x01, 0x02, 0x03, 0x62, 0x05, 0x62, 0x09, 0x62, 0x1f, 0x62, 0x10,
 533   0xfa, 0x11, 0xf2, 0x10, 0x62, 0x10, 0x02, 0x10, 0xfa, 0x17, 0x02, 0x10,
 534   0xfa, 0x17, 0x02, 0x10, 0xfe, 0x1f};
 535} -maskdata $filemask
 536
 537set max_status_desc 0
 538foreach i {
 539                {__ i plain    "Unmodified"}
 540                {_M i mod      "Modified"}
 541                {M_ i fulltick "Checked in"}
 542                {MM i parttick "Partially checked in"}
 543
 544                {_O o plain    "Untracked"}
 545                {A_ o fulltick "Added"}
 546                {AM o parttick "Partially added"}
 547
 548                {_D i question "Missing"}
 549                {D_ i removed  "Removed"}
 550                {DD i removed  "Removed"}
 551                {DO i removed  "Removed (still exists)"}
 552
 553                {UM i merge    "Merge conflicts"}
 554                {U_ i merge    "Merge conflicts"}
 555        } {
 556        if {$max_status_desc < [string length [lindex $i 3]]} {
 557                set max_status_desc [string length [lindex $i 3]]
 558        }
 559        set all_cols([lindex $i 0]) [lindex $i 1]
 560        set all_icons([lindex $i 0]) file_[lindex $i 2]
 561        set all_descs([lindex $i 0]) [lindex $i 3]
 562}
 563unset filemask i
 564
 565######################################################################
 566##
 567## util
 568
 569proc error_popup {msg} {
 570        set w .error
 571        toplevel $w
 572        wm transient $w .
 573        show_msg $w $w $msg
 574}
 575
 576proc show_msg {w top msg} {
 577        message $w.m -text $msg -justify center -aspect 400
 578        pack $w.m -side top -fill x -padx 20 -pady 20
 579        button $w.ok -text OK -command "destroy $top"
 580        pack $w.ok -side bottom -fill x
 581        bind $top <Visibility> "grab $top; focus $top"
 582        bind $top <Key-Return> "destroy $top"
 583        tkwait window $top
 584}
 585
 586######################################################################
 587##
 588## ui commands
 589
 590proc do_gitk {} {
 591        global tcl_platform ui_status_value
 592
 593        set ui_status_value "Please wait... Starting gitk..."
 594    if {$tcl_platform(platform) == "windows"} {
 595                exec sh -c gitk &
 596        } else {
 597                exec gitk &
 598        }
 599}
 600
 601proc do_quit {} {
 602        global gitdir ui_comm
 603
 604        set save [file join $gitdir GITGUI_MSG]
 605        if {[$ui_comm edit modified]
 606            && [string trim [$ui_comm get 0.0 end]] != {}} {
 607                catch {
 608                        set fd [open $save w]
 609                        puts $fd [string trim [$ui_comm get 0.0 end]]
 610                        close $fd
 611                }
 612        } elseif {[file exists $save]} {
 613                file delete $save
 614        }
 615
 616        destroy .
 617}
 618
 619proc do_rescan {} {
 620        update_status
 621}
 622
 623proc do_checkin_all {} {
 624        global checkin_active ui_status_value
 625
 626        if {[is_busy]} return
 627
 628        set checkin_active 1
 629        set ui_status_value {Checking in all files...}
 630        after 1 {
 631                with_update_index {
 632                        foreach path [array names file_states] {
 633                                set s $file_states($path)
 634                                set m [lindex $s 0]
 635                                switch -- $m {
 636                                AM -
 637                                MM -
 638                                _M -
 639                                _D {toggle_mode $path}
 640                                }
 641                        }
 642                }
 643                set checkin_active 0
 644                set ui_status_value {Ready.}
 645        }
 646}
 647
 648proc do_signoff {} {
 649        global ui_comm
 650
 651        catch {
 652                set me [exec git var GIT_COMMITTER_IDENT]
 653                if {[regexp {(.*) [0-9]+ [-+0-9]+$} $me me name]} {
 654                        set str "Signed-off-by: $name"
 655                        if {[$ui_comm get {end -1c linestart} {end -1c}] != $str} {
 656                                $ui_comm insert end "\n"
 657                                $ui_comm insert end $str
 658                                $ui_comm see end
 659                        }
 660                }
 661        }
 662}
 663
 664# shift == 1: left click
 665#          3: right click  
 666proc click {w x y shift wx wy} {
 667        global ui_index ui_other
 668
 669        set pos [split [$w index @$x,$y] .]
 670        set lno [lindex $pos 0]
 671        set col [lindex $pos 1]
 672        set path [$w get $lno.1 $lno.end]
 673        if {$path == {}} return
 674
 675        if {$col > 0 && $shift == 1} {
 676                $ui_index tag remove in_diff 0.0 end
 677                $ui_other tag remove in_diff 0.0 end
 678                $w tag add in_diff $lno.0 [expr $lno + 1].0
 679                show_diff $path
 680        }
 681}
 682
 683proc unclick {w x y} {
 684        set pos [split [$w index @$x,$y] .]
 685        set lno [lindex $pos 0]
 686        set col [lindex $pos 1]
 687        set path [$w get $lno.1 $lno.end]
 688        if {$path == {}} return
 689
 690        if {$col == 0 && ![is_busy]} {
 691                toggle_mode $path
 692        }
 693}
 694
 695######################################################################
 696##
 697## ui init
 698
 699set mainfont {Helvetica 10}
 700set difffont {Courier 10}
 701set maincursor [. cget -cursor]
 702
 703# -- Menu Bar
 704menu .mbar -tearoff 0
 705.mbar add cascade -label Project -menu .mbar.project
 706.mbar add cascade -label Commit -menu .mbar.commit
 707.mbar add cascade -label Fetch -menu .mbar.fetch
 708.mbar add cascade -label Pull -menu .mbar.pull
 709. configure -menu .mbar
 710
 711# -- Project Menu
 712menu .mbar.project
 713.mbar.project add command -label Visualize \
 714        -command do_gitk \
 715        -font $mainfont
 716.mbar.project add command -label Quit \
 717        -command do_quit \
 718        -font $mainfont
 719
 720# -- Commit Menu
 721menu .mbar.commit
 722.mbar.commit add command -label Rescan \
 723        -command do_rescan \
 724        -font $mainfont
 725.mbar.commit add command -label {Check-in All Files} \
 726        -command do_checkin_all \
 727        -font $mainfont
 728.mbar.commit add command -label {Sign Off} \
 729        -command do_signoff \
 730        -font $mainfont
 731.mbar.commit add command -label Commit \
 732        -command do_commit \
 733        -font $mainfont
 734
 735# -- Fetch Menu
 736menu .mbar.fetch
 737
 738# -- Pull Menu
 739menu .mbar.pull
 740
 741# -- Main Window Layout
 742panedwindow .vpane -orient vertical
 743panedwindow .vpane.files -orient horizontal
 744.vpane add .vpane.files -sticky nsew -height 100 -width 400
 745pack .vpane -anchor n -side top -fill both -expand 1
 746
 747# -- Index File List
 748set ui_index .vpane.files.index.list
 749frame .vpane.files.index -height 100 -width 400
 750label .vpane.files.index.title -text {Modified Files} \
 751        -background green \
 752        -font $mainfont
 753text $ui_index -background white -borderwidth 0 \
 754        -width 40 -height 10 \
 755        -font $mainfont \
 756        -yscrollcommand {.vpane.files.index.sb set} \
 757        -cursor $maincursor \
 758        -state disabled
 759scrollbar .vpane.files.index.sb -command [list $ui_index yview]
 760pack .vpane.files.index.title -side top -fill x
 761pack .vpane.files.index.sb -side right -fill y
 762pack $ui_index -side left -fill both -expand 1
 763.vpane.files add .vpane.files.index -sticky nsew
 764
 765# -- Other (Add) File List
 766set ui_other .vpane.files.other.list
 767frame .vpane.files.other -height 100 -width 100
 768label .vpane.files.other.title -text {Untracked Files} \
 769        -background red \
 770        -font $mainfont
 771text $ui_other -background white -borderwidth 0 \
 772        -width 40 -height 10 \
 773        -font $mainfont \
 774        -yscrollcommand {.vpane.files.other.sb set} \
 775        -cursor $maincursor \
 776        -state disabled
 777scrollbar .vpane.files.other.sb -command [list $ui_other yview]
 778pack .vpane.files.other.title -side top -fill x
 779pack .vpane.files.other.sb -side right -fill y
 780pack $ui_other -side left -fill both -expand 1
 781.vpane.files add .vpane.files.other -sticky nsew
 782
 783$ui_index tag conf in_diff -font [concat $mainfont bold]
 784$ui_other tag conf in_diff -font [concat $mainfont bold]
 785
 786# -- Diff Header
 787set ui_fname_value {}
 788set ui_fstatus_value {}
 789frame .vpane.diff -height 200 -width 400
 790frame .vpane.diff.header
 791label .vpane.diff.header.l1 -text {File:} -font $mainfont
 792label .vpane.diff.header.l2 -textvariable ui_fname_value \
 793        -anchor w \
 794        -justify left \
 795        -font $mainfont
 796label .vpane.diff.header.l3 -text {Status:} -font $mainfont
 797label .vpane.diff.header.l4 -textvariable ui_fstatus_value \
 798        -width $max_status_desc \
 799        -anchor w \
 800        -justify left \
 801        -font $mainfont
 802pack .vpane.diff.header.l1 -side left
 803pack .vpane.diff.header.l2 -side left -fill x
 804pack .vpane.diff.header.l4 -side right
 805pack .vpane.diff.header.l3 -side right
 806
 807# -- Diff Body
 808frame .vpane.diff.body
 809set ui_diff .vpane.diff.body.t
 810text $ui_diff -background white -borderwidth 0 \
 811        -width 80 -height 15 -wrap none \
 812        -font $difffont \
 813        -xscrollcommand {.vpane.diff.body.sbx set} \
 814        -yscrollcommand {.vpane.diff.body.sby set} \
 815        -cursor $maincursor \
 816        -state disabled
 817scrollbar .vpane.diff.body.sbx -orient horizontal \
 818        -command [list $ui_diff xview]
 819scrollbar .vpane.diff.body.sby -orient vertical \
 820        -command [list $ui_diff yview]
 821pack .vpane.diff.body.sbx -side bottom -fill x
 822pack .vpane.diff.body.sby -side right -fill y
 823pack $ui_diff -side left -fill both -expand 1
 824pack .vpane.diff.header -side top -fill x
 825pack .vpane.diff.body -side bottom -fill both -expand 1
 826.vpane add .vpane.diff -stick nsew
 827
 828$ui_diff tag conf dm -foreground red
 829$ui_diff tag conf dp -foreground blue
 830$ui_diff tag conf da -font [concat $difffont bold]
 831$ui_diff tag conf di -foreground "#00a000"
 832$ui_diff tag conf dni -foreground "#a000a0"
 833$ui_diff tag conf bold -font [concat $difffont bold]
 834
 835# -- Commit Area
 836frame .vpane.commarea -height 150
 837.vpane add .vpane.commarea -stick nsew
 838
 839# -- Commit Area Buttons
 840frame .vpane.commarea.buttons
 841label .vpane.commarea.buttons.l -text {} \
 842        -anchor w \
 843        -justify left \
 844        -font $mainfont
 845pack .vpane.commarea.buttons.l -side top -fill x
 846pack .vpane.commarea.buttons -side left -fill y
 847
 848button .vpane.commarea.buttons.rescan -text {Rescan} \
 849        -command do_rescan \
 850        -font $mainfont
 851pack .vpane.commarea.buttons.rescan -side top -fill x
 852
 853button .vpane.commarea.buttons.ciall -text {Check-in All} \
 854        -command do_checkin_all \
 855        -font $mainfont
 856pack .vpane.commarea.buttons.ciall -side top -fill x
 857
 858button .vpane.commarea.buttons.signoff -text {Sign Off} \
 859        -command do_signoff \
 860        -font $mainfont
 861pack .vpane.commarea.buttons.signoff -side top -fill x
 862
 863button .vpane.commarea.buttons.commit -text {Commit} \
 864        -command do_commit \
 865        -font $mainfont
 866pack .vpane.commarea.buttons.commit -side top -fill x
 867
 868# -- Commit Message Buffer
 869frame .vpane.commarea.buffer
 870set ui_comm .vpane.commarea.buffer.t
 871label .vpane.commarea.buffer.l -text {Commit Message:} \
 872        -anchor w \
 873        -justify left \
 874        -font $mainfont
 875text $ui_comm -background white -borderwidth 1 \
 876        -relief sunken \
 877        -width 75 -height 10 -wrap none \
 878        -font $difffont \
 879        -yscrollcommand {.vpane.commarea.buffer.sby set} \
 880        -cursor $maincursor
 881scrollbar .vpane.commarea.buffer.sby -command [list $ui_comm yview]
 882pack .vpane.commarea.buffer.l -side top -fill x
 883pack .vpane.commarea.buffer.sby -side right -fill y
 884pack $ui_comm -side left -fill y
 885pack .vpane.commarea.buffer -side left -fill y
 886
 887# -- Status Bar
 888set ui_status_value {Initializing...}
 889label .status -textvariable ui_status_value \
 890        -anchor w \
 891        -justify left \
 892        -borderwidth 1 \
 893        -relief sunken \
 894        -font $mainfont
 895pack .status -anchor w -side bottom -fill x
 896
 897# -- Key Bindings
 898bind . <Destroy> do_quit
 899bind . <Key-F5> do_rescan
 900bind . <M1-Key-r> do_rescan
 901bind . <M1-Key-R> do_rescan
 902bind . <M1-Key-s> do_signoff
 903bind . <M1-Key-S> do_signoff
 904bind . <M1-Key-u> do_checkin_all
 905bind . <M1-Key-U> do_checkin_all
 906bind . <M1-Key-Return> do_commit
 907bind . <M1-Key-q> do_quit
 908bind . <M1-Key-Q> do_quit
 909foreach i [list $ui_index $ui_other] {
 910        bind $i <Button-1> {click %W %x %y 1 %X %Y; break}
 911        bind $i <Button-3> {click %W %x %y 3 %X %Y; break}
 912        bind $i <ButtonRelease-1> {unclick %W %x %y; break}
 913}
 914unset i
 915
 916######################################################################
 917##
 918## main
 919
 920if {[catch {set gitdir [exec git rev-parse --git-dir]} err]} {
 921        show_msg {} . "Cannot find the git directory: $err"
 922        exit 1
 923}
 924
 925wm title . "git-ui ([file normalize [file dirname $gitdir]])"
 926focus -force $ui_comm
 927update_status