git-guion commit git-gui: Additional early feature development. (131f503)
   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 binary
 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 index* $line]} {
 241                        if {[string first , $line] >= 0} {
 242                                set diff_3way 1
 243                        }
 244                }
 245
 246                $ui_diff conf -state normal
 247                if {!$diff_3way} {
 248                        set x [string index $line 0]
 249                        switch -- $x {
 250                        "@" {set tags da}
 251                        "+" {set tags dp}
 252                        "-" {set tags dm}
 253                        default {set tags {}}
 254                        }
 255                } else {
 256                        set x [string range $line 0 1]
 257                        switch -- $x {
 258                        default {set tags {}}
 259                        "@@" {set tags da}
 260                        "++" {set tags dp; set x " +"}
 261                        " +" {set tags {di bold}; set x "++"}
 262                        "+ " {set tags dni; set x "-+"}
 263                        "--" {set tags dm; set x " -"}
 264                        " -" {set tags {dm bold}; set x "--"}
 265                        "- " {set tags di; set x "+-"}
 266                        default {set tags {}}
 267                        }
 268                        set line [string replace $line 0 1 $x]
 269                }
 270                $ui_diff insert end $line $tags
 271                $ui_diff insert end "\n"
 272                $ui_diff conf -state disabled
 273        }
 274
 275        if {[eof $fd]} {
 276                close $fd
 277                set diff_active 0
 278                set ui_status_value {Ready.}
 279        }
 280}
 281
 282######################################################################
 283##
 284## ui helpers
 285
 286proc mapcol {state path} {
 287        global all_cols
 288
 289        if {[catch {set r $all_cols($state)}]} {
 290                puts "error: no column for state={$state} $path"
 291                return o
 292        }
 293        return $r
 294}
 295
 296proc mapicon {state path} {
 297        global all_icons
 298
 299        if {[catch {set r $all_icons($state)}]} {
 300                puts "error: no icon for state={$state} $path"
 301                return file_plain
 302        }
 303        return $r
 304}
 305
 306proc mapdesc {state path} {
 307        global all_descs
 308
 309        if {[catch {set r $all_descs($state)}]} {
 310                puts "error: no desc for state={$state} $path"
 311                return $state
 312        }
 313        return $r
 314}
 315
 316proc bsearch {w path} {
 317        set hi [expr [lindex [split [$w index end] .] 0] - 2]
 318        if {$hi == 0} {
 319                return -1
 320        }
 321        set lo 0
 322        while {$lo < $hi} {
 323                set mi [expr [expr $lo + $hi] / 2]
 324                set ti [expr $mi + 1]
 325                set cmp [string compare [$w get $ti.1 $ti.end] $path]
 326                if {$cmp < 0} {
 327                        set lo $ti
 328                } elseif {$cmp == 0} {
 329                        return $mi
 330                } else {
 331                        set hi $mi
 332                }
 333        }
 334        return -[expr $lo + 1]
 335}
 336
 337proc merge_state {path state} {
 338        global file_states
 339
 340        if {[array names file_states -exact $path] == {}}  {
 341                set o __
 342                set s [list $o none none]
 343        } else {
 344                set s $file_states($path)
 345                set o [lindex $s 0]
 346        }
 347
 348        set m [lindex $s 0]
 349        if {[string index $state 0] == "_"} {
 350                set state [string index $m 0][string index $state 1]
 351        } elseif {[string index $state 0] == "*"} {
 352                set state _[string index $state 1]
 353        }
 354
 355        if {[string index $state 1] == "_"} {
 356                set state [string index $state 0][string index $m 1]
 357        } elseif {[string index $state 1] == "*"} {
 358                set state [string index $state 0]_
 359        }
 360
 361        set file_states($path) [lreplace $s 0 0 $state]
 362        return $o
 363}
 364
 365proc display_file {path state} {
 366        global ui_index ui_other file_states
 367
 368        set old_m [merge_state $path $state]
 369        set s $file_states($path)
 370        set m [lindex $s 0]
 371
 372        if {[mapcol $m $path] == "o"} {
 373                set ii 1
 374                set ai 2
 375                set iw $ui_index
 376                set aw $ui_other
 377        } else {
 378                set ii 2
 379                set ai 1
 380                set iw $ui_other
 381                set aw $ui_index
 382        }
 383
 384        set d [lindex $s $ii]
 385        if {$d != "none"} {
 386                set lno [bsearch $iw $path]
 387                if {$lno >= 0} {
 388                        incr lno
 389                        $iw conf -state normal
 390                        $iw delete $lno.0 [expr $lno + 1].0
 391                        $iw conf -state disabled
 392                        set s [lreplace $s $ii $ii none]
 393                }
 394        }
 395
 396        set d [lindex $s $ai]
 397        if {$d == "none"} {
 398                set lno [expr abs([bsearch $aw $path] + 1) + 1]
 399                $aw conf -state normal
 400                set ico [$aw image create $lno.0 \
 401                        -align center -padx 5 -pady 1 \
 402                        -image [mapicon $m $path]]
 403                $aw insert $lno.1 "$path\n"
 404                $aw conf -state disabled
 405                set file_states($path) [lreplace $s $ai $ai [list $ico]]
 406        } elseif {[mapicon $m $path] != [mapicon $old_m $path]} {
 407                set ico [lindex $d 0]
 408                $aw image conf $ico -image [mapicon $m $path]
 409        }
 410}
 411
 412proc with_update_index {body} {
 413        global update_index_fd
 414
 415        if {$update_index_fd == {}} {
 416                set update_index_fd [open \
 417                        "| git update-index --add --remove -z --stdin" \
 418                        w]
 419                fconfigure $update_index_fd -translation binary
 420                uplevel 1 $body
 421                close $update_index_fd
 422                set update_index_fd {}
 423        } else {
 424                uplevel 1 $body
 425        }
 426}
 427
 428proc update_index {path} {
 429        global update_index_fd
 430
 431        if {$update_index_fd == {}} {
 432                error {not in with_update_index}
 433        } else {
 434                puts -nonewline $update_index_fd "$path\0"
 435        }
 436}
 437
 438proc toggle_mode {path} {
 439        global file_states
 440
 441        set s $file_states($path)
 442        set m [lindex $s 0]
 443
 444        switch -- $m {
 445        AM -
 446        _O {set new A*}
 447        _M -
 448        MM {set new M*}
 449        _D {set new D*}
 450        default {return}
 451        }
 452
 453        with_update_index {update_index $path}
 454        display_file $path $new
 455}
 456
 457######################################################################
 458##
 459## icons
 460
 461set filemask {
 462#define mask_width 14
 463#define mask_height 15
 464static unsigned char mask_bits[] = {
 465   0xfe, 0x1f, 0xfe, 0x1f, 0xfe, 0x1f, 0xfe, 0x1f, 0xfe, 0x1f, 0xfe, 0x1f,
 466   0xfe, 0x1f, 0xfe, 0x1f, 0xfe, 0x1f, 0xfe, 0x1f, 0xfe, 0x1f, 0xfe, 0x1f,
 467   0xfe, 0x1f, 0xfe, 0x1f, 0xfe, 0x1f};
 468}
 469
 470image create bitmap file_plain -background white -foreground black -data {
 471#define plain_width 14
 472#define plain_height 15
 473static unsigned char plain_bits[] = {
 474   0xfe, 0x01, 0x02, 0x03, 0x02, 0x05, 0x02, 0x09, 0x02, 0x1f, 0x02, 0x10,
 475   0x02, 0x10, 0x02, 0x10, 0x02, 0x10, 0x02, 0x10, 0x02, 0x10, 0x02, 0x10,
 476   0x02, 0x10, 0x02, 0x10, 0xfe, 0x1f};
 477} -maskdata $filemask
 478
 479image create bitmap file_mod -background white -foreground blue -data {
 480#define mod_width 14
 481#define mod_height 15
 482static unsigned char mod_bits[] = {
 483   0xfe, 0x01, 0x02, 0x03, 0x7a, 0x05, 0x02, 0x09, 0x7a, 0x1f, 0x02, 0x10,
 484   0xfa, 0x17, 0x02, 0x10, 0xfa, 0x17, 0x02, 0x10, 0xfa, 0x17, 0x02, 0x10,
 485   0xfa, 0x17, 0x02, 0x10, 0xfe, 0x1f};
 486} -maskdata $filemask
 487
 488image create bitmap file_fulltick -background white -foreground "#007000" -data {
 489#define file_fulltick_width 14
 490#define file_fulltick_height 15
 491static unsigned char file_fulltick_bits[] = {
 492   0xfe, 0x01, 0x02, 0x1a, 0x02, 0x0c, 0x02, 0x0c, 0x02, 0x16, 0x02, 0x16,
 493   0x02, 0x13, 0x00, 0x13, 0x86, 0x11, 0x8c, 0x11, 0xd8, 0x10, 0xf2, 0x10,
 494   0x62, 0x10, 0x02, 0x10, 0xfe, 0x1f};
 495} -maskdata $filemask
 496
 497image create bitmap file_parttick -background white -foreground "#005050" -data {
 498#define parttick_width 14
 499#define parttick_height 15
 500static unsigned char parttick_bits[] = {
 501   0xfe, 0x01, 0x02, 0x03, 0x7a, 0x05, 0x02, 0x09, 0x7a, 0x1f, 0x02, 0x10,
 502   0x7a, 0x14, 0x02, 0x16, 0x02, 0x13, 0x8a, 0x11, 0xda, 0x10, 0x72, 0x10,
 503   0x22, 0x10, 0x02, 0x10, 0xfe, 0x1f};
 504} -maskdata $filemask
 505
 506image create bitmap file_question -background white -foreground black -data {
 507#define file_question_width 14
 508#define file_question_height 15
 509static unsigned char file_question_bits[] = {
 510   0xfe, 0x01, 0x02, 0x02, 0xe2, 0x04, 0xf2, 0x09, 0x1a, 0x1b, 0x0a, 0x13,
 511   0x82, 0x11, 0xc2, 0x10, 0x62, 0x10, 0x62, 0x10, 0x02, 0x10, 0x62, 0x10,
 512   0x62, 0x10, 0x02, 0x10, 0xfe, 0x1f};
 513} -maskdata $filemask
 514
 515image create bitmap file_removed -background white -foreground red -data {
 516#define file_removed_width 14
 517#define file_removed_height 15
 518static unsigned char file_removed_bits[] = {
 519   0xfe, 0x01, 0x02, 0x03, 0x02, 0x05, 0x02, 0x09, 0x02, 0x1f, 0x02, 0x10,
 520   0x1a, 0x16, 0x32, 0x13, 0xe2, 0x11, 0xc2, 0x10, 0xe2, 0x11, 0x32, 0x13,
 521   0x1a, 0x16, 0x02, 0x10, 0xfe, 0x1f};
 522} -maskdata $filemask
 523
 524image create bitmap file_merge -background white -foreground blue -data {
 525#define file_merge_width 14
 526#define file_merge_height 15
 527static unsigned char file_merge_bits[] = {
 528   0xfe, 0x01, 0x02, 0x03, 0x62, 0x05, 0x62, 0x09, 0x62, 0x1f, 0x62, 0x10,
 529   0xfa, 0x11, 0xf2, 0x10, 0x62, 0x10, 0x02, 0x10, 0xfa, 0x17, 0x02, 0x10,
 530   0xfa, 0x17, 0x02, 0x10, 0xfe, 0x1f};
 531} -maskdata $filemask
 532
 533set max_status_desc 0
 534foreach i {
 535                {__ i plain    "Unmodified"}
 536                {_M i mod      "Modified"}
 537                {M_ i fulltick "Checked in"}
 538                {MM i parttick "Partially checked in"}
 539
 540                {_O o plain    "Untracked"}
 541                {A_ o fulltick "Added"}
 542                {AM o parttick "Partially added"}
 543
 544                {_D i question "Missing"}
 545                {D_ i removed  "Removed"}
 546                {DD i removed  "Removed"}
 547                {DO i removed  "Removed (still exists)"}
 548
 549                {UM i merge    "Merge conflicts"}
 550                {U_ i merge    "Merge conflicts"}
 551        } {
 552        if {$max_status_desc < [string length [lindex $i 3]]} {
 553                set max_status_desc [string length [lindex $i 3]]
 554        }
 555        set all_cols([lindex $i 0]) [lindex $i 1]
 556        set all_icons([lindex $i 0]) file_[lindex $i 2]
 557        set all_descs([lindex $i 0]) [lindex $i 3]
 558}
 559unset filemask i
 560
 561######################################################################
 562##
 563## util
 564
 565proc error_popup {msg} {
 566        set w .error
 567        toplevel $w
 568        wm transient $w .
 569        show_msg $w $w $msg
 570}
 571
 572proc show_msg {w top msg} {
 573        message $w.m -text $msg -justify center -aspect 400
 574        pack $w.m -side top -fill x -padx 20 -pady 20
 575        button $w.ok -text OK -command "destroy $top"
 576        pack $w.ok -side bottom -fill x
 577        bind $top <Visibility> "grab $top; focus $top"
 578        bind $top <Key-Return> "destroy $top"
 579        tkwait window $top
 580}
 581
 582######################################################################
 583##
 584## ui commands
 585
 586proc do_gitk {} {
 587        global tcl_platform
 588
 589    if {$tcl_platform(platform) == "windows"} {
 590                exec sh -c gitk &
 591        } else {
 592                exec gitk &
 593        }
 594}
 595
 596proc do_quit {} {
 597        global gitdir ui_comm
 598
 599        set save [file join $gitdir GITGUI_MSG]
 600        if {[$ui_comm edit modified]
 601            && [string trim [$ui_comm get 0.0 end]] != {}} {
 602                catch {
 603                        set fd [open $save w]
 604                        puts $fd [string trim [$ui_comm get 0.0 end]]
 605                        close $fd
 606                }
 607        } elseif {[file exists $save]} {
 608                file delete $save
 609        }
 610
 611        destroy .
 612}
 613
 614proc do_rescan {} {
 615        update_status
 616}
 617
 618proc do_checkin_all {} {
 619        global checkin_active ui_status_value
 620
 621        if {[is_busy]} return
 622
 623        set checkin_active 1
 624        set ui_status_value {Checking in all files...}
 625        after 1 {
 626                with_update_index {
 627                        foreach path [array names file_states] {
 628                                set s $file_states($path)
 629                                set m [lindex $s 0]
 630                                switch -- $m {
 631                                AM -
 632                                MM -
 633                                _M -
 634                                _D {toggle_mode $path}
 635                                }
 636                        }
 637                }
 638                set checkin_active 0
 639                set ui_status_value {Ready.}
 640        }
 641}
 642
 643proc do_signoff {} {
 644        global ui_comm
 645
 646        catch {
 647                set me [exec git var GIT_COMMITTER_IDENT]
 648                if {[regexp {(.*) [0-9]+ [-+0-9]+$} $me me name]} {
 649                        set str "Signed-off-by: $name"
 650                        if {[$ui_comm get {end -1c linestart} {end -1c}] != $str} {
 651                                $ui_comm insert end "\n"
 652                                $ui_comm insert end $str
 653                                $ui_comm see end
 654                        }
 655                }
 656        }
 657}
 658
 659# shift == 1: left click
 660#          3: right click  
 661proc click {w x y shift wx wy} {
 662        global ui_index ui_other
 663
 664        set pos [split [$w index @$x,$y] .]
 665        set lno [lindex $pos 0]
 666        set col [lindex $pos 1]
 667        set path [$w get $lno.1 $lno.end]
 668        if {$path == {}} return
 669
 670        if {$col > 0 && $shift == 1} {
 671                $ui_index tag remove in_diff 0.0 end
 672                $ui_other tag remove in_diff 0.0 end
 673                $w tag add in_diff $lno.0 [expr $lno + 1].0
 674                show_diff $path
 675        }
 676}
 677
 678proc unclick {w x y} {
 679        set pos [split [$w index @$x,$y] .]
 680        set lno [lindex $pos 0]
 681        set col [lindex $pos 1]
 682        set path [$w get $lno.1 $lno.end]
 683        if {$path == {}} return
 684
 685        if {$col == 0 && ![is_busy]} {
 686                toggle_mode $path
 687        }
 688}
 689
 690######################################################################
 691##
 692## ui init
 693
 694set mainfont {Helvetica 10}
 695set difffont {Courier 10}
 696set maincursor [. cget -cursor]
 697
 698# -- Menu Bar
 699menu .mbar -tearoff 0
 700.mbar add cascade -label Project -menu .mbar.project
 701.mbar add cascade -label Commit -menu .mbar.commit
 702.mbar add cascade -label Fetch -menu .mbar.fetch
 703.mbar add cascade -label Pull -menu .mbar.pull
 704. configure -menu .mbar
 705
 706# -- Project Menu
 707menu .mbar.project
 708.mbar.project add command -label Visulize \
 709        -command do_gitk \
 710        -font $mainfont
 711.mbar.project add command -label Quit \
 712        -command do_quit \
 713        -font $mainfont
 714
 715# -- Commit Menu
 716menu .mbar.commit
 717.mbar.commit add command -label Rescan \
 718        -command do_rescan \
 719        -font $mainfont
 720.mbar.commit add command -label {Check-in All Files} \
 721        -command do_checkin_all \
 722        -font $mainfont
 723.mbar.commit add command -label {Sign Off} \
 724        -command do_signoff \
 725        -font $mainfont
 726.mbar.commit add command -label Commit \
 727        -command do_commit \
 728        -font $mainfont
 729
 730# -- Fetch Menu
 731menu .mbar.fetch
 732
 733# -- Pull Menu
 734menu .mbar.pull
 735
 736# -- Main Window Layout
 737panedwindow .vpane -orient vertical
 738panedwindow .vpane.files -orient horizontal
 739.vpane add .vpane.files -sticky nsew
 740pack .vpane -anchor n -side top -fill both -expand 1
 741
 742# -- Index File List
 743set ui_index .vpane.files.index.list
 744frame .vpane.files.index -height 100 -width 400
 745label .vpane.files.index.title -text {Modified Files} \
 746        -background green \
 747        -font $mainfont
 748text $ui_index -background white -borderwidth 0 \
 749        -width 40 -height 10 \
 750        -font $mainfont \
 751        -yscrollcommand {.vpane.files.index.sb set} \
 752        -cursor $maincursor \
 753        -state disabled
 754scrollbar .vpane.files.index.sb -command [list $ui_index yview]
 755pack .vpane.files.index.title -side top -fill x
 756pack .vpane.files.index.sb -side right -fill y
 757pack $ui_index -side left -fill both -expand 1
 758.vpane.files add .vpane.files.index -sticky nsew
 759
 760# -- Other (Add) File List
 761set ui_other .vpane.files.other.list
 762frame .vpane.files.other -height 100 -width 100
 763label .vpane.files.other.title -text {Untracked Files} \
 764        -background red \
 765        -font $mainfont
 766text $ui_other -background white -borderwidth 0 \
 767        -width 40 -height 10 \
 768        -font $mainfont \
 769        -yscrollcommand {.vpane.files.other.sb set} \
 770        -cursor $maincursor \
 771        -state disabled
 772scrollbar .vpane.files.other.sb -command [list $ui_other yview]
 773pack .vpane.files.other.title -side top -fill x
 774pack .vpane.files.other.sb -side right -fill y
 775pack $ui_other -side left -fill both -expand 1
 776.vpane.files add .vpane.files.other -sticky nsew
 777
 778$ui_index tag conf in_diff -font [concat $mainfont bold]
 779$ui_other tag conf in_diff -font [concat $mainfont bold]
 780
 781# -- Diff Header
 782set ui_fname_value {}
 783set ui_fstatus_value {}
 784frame .vpane.diff -height 50 -width 400
 785frame .vpane.diff.header
 786label .vpane.diff.header.l1 -text {File:} -font $mainfont
 787label .vpane.diff.header.l2 -textvariable ui_fname_value \
 788        -anchor w \
 789        -justify left \
 790        -font $mainfont
 791label .vpane.diff.header.l3 -text {Status:} -font $mainfont
 792label .vpane.diff.header.l4 -textvariable ui_fstatus_value \
 793        -width $max_status_desc \
 794        -anchor w \
 795        -justify left \
 796        -font $mainfont
 797pack .vpane.diff.header.l1 -side left
 798pack .vpane.diff.header.l2 -side left -fill x
 799pack .vpane.diff.header.l4 -side right
 800pack .vpane.diff.header.l3 -side right
 801
 802# -- Diff Body
 803frame .vpane.diff.body
 804set ui_diff .vpane.diff.body.t
 805text $ui_diff -background white -borderwidth 0 \
 806        -width 80 -height 15 \
 807        -font $difffont \
 808        -xscrollcommand {.vpane.diff.body.sbx set} \
 809        -yscrollcommand {.vpane.diff.body.sby set} \
 810        -cursor $maincursor \
 811        -state disabled
 812scrollbar .vpane.diff.body.sbx -orient horizontal \
 813        -command [list $ui_diff xview]
 814scrollbar .vpane.diff.body.sby -orient vertical \
 815        -command [list $ui_diff yview]
 816pack .vpane.diff.body.sbx -side bottom -fill x
 817pack .vpane.diff.body.sby -side right -fill y
 818pack $ui_diff -side left -fill both -expand 1
 819pack .vpane.diff.header -side top -fill x
 820pack .vpane.diff.body -side bottom -fill both -expand 1
 821.vpane add .vpane.diff -stick nsew
 822
 823$ui_diff tag conf dm -foreground red
 824$ui_diff tag conf dp -foreground blue
 825$ui_diff tag conf da -font [concat $difffont bold]
 826$ui_diff tag conf di -foreground "#00a000"
 827$ui_diff tag conf dni -foreground "#a000a0"
 828$ui_diff tag conf bold -font [concat $difffont bold]
 829
 830# -- Commit Area
 831frame .vpane.commarea -height 50
 832.vpane add .vpane.commarea -stick nsew
 833
 834# -- Commit Area Buttons
 835frame .vpane.commarea.buttons
 836label .vpane.commarea.buttons.l -text {} \
 837        -anchor w \
 838        -justify left \
 839        -font $mainfont
 840pack .vpane.commarea.buttons.l -side top -fill x
 841pack .vpane.commarea.buttons -side left -fill y
 842
 843button .vpane.commarea.buttons.rescan -text {Rescan} \
 844        -command do_rescan \
 845        -font $mainfont
 846pack .vpane.commarea.buttons.rescan -side top -fill x
 847
 848button .vpane.commarea.buttons.ciall -text {Check-in All} \
 849        -command do_checkin_all \
 850        -font $mainfont
 851pack .vpane.commarea.buttons.ciall -side top -fill x
 852
 853button .vpane.commarea.buttons.signoff -text {Sign Off} \
 854        -command do_signoff \
 855        -font $mainfont
 856pack .vpane.commarea.buttons.signoff -side top -fill x
 857
 858button .vpane.commarea.buttons.commit -text {Commit} \
 859        -command do_commit \
 860        -font $mainfont
 861pack .vpane.commarea.buttons.commit -side top -fill x
 862
 863# -- Commit Message Buffer
 864frame .vpane.commarea.buffer
 865set ui_comm .vpane.commarea.buffer.t
 866label .vpane.commarea.buffer.l -text {Commit Message:} \
 867        -anchor w \
 868        -justify left \
 869        -font $mainfont
 870text $ui_comm -background white -borderwidth 1 \
 871        -relief sunken \
 872        -width 75 -height 10 -wrap none \
 873        -font $difffont \
 874        -yscrollcommand {.vpane.commarea.buffer.sby set} \
 875        -cursor $maincursor
 876scrollbar .vpane.commarea.buffer.sby -command [list $ui_comm yview]
 877pack .vpane.commarea.buffer.l -side top -fill x
 878pack .vpane.commarea.buffer.sby -side right -fill y
 879pack $ui_comm -side left -fill y
 880pack .vpane.commarea.buffer -side left -fill y
 881
 882# -- Status Bar
 883set ui_status_value {Initializing...}
 884label .status -textvariable ui_status_value \
 885        -anchor w \
 886        -justify left \
 887        -borderwidth 1 \
 888        -relief sunken \
 889        -font $mainfont
 890pack .status -anchor w -side bottom -fill x
 891
 892# -- Key Bindings
 893bind . <Destroy> do_quit
 894bind . <Key-F5> do_rescan
 895bind . <M1-Key-r> do_rescan
 896bind . <M1-Key-R> do_rescan
 897bind . <M1-Key-s> do_signoff
 898bind . <M1-Key-S> do_signoff
 899bind . <M1-Key-u> do_checkin_all
 900bind . <M1-Key-U> do_checkin_all
 901bind . <M1-Key-Return> do_commit
 902bind . <M1-Key-q> do_quit
 903bind . <M1-Key-Q> do_quit
 904foreach i [list $ui_index $ui_other] {
 905        bind $i <Button-1> {click %W %x %y 1 %X %Y; break}
 906        bind $i <Button-3> {click %W %x %y 3 %X %Y; break}
 907        bind $i <ButtonRelease-1> {unclick %W %x %y; break}
 908}
 909unset i
 910
 911######################################################################
 912##
 913## main
 914
 915if {[catch {set gitdir [exec git rev-parse --git-dir]} err]} {
 916        show_msg {} . "Cannot find the git directory: $err"
 917        exit 1
 918}
 919
 920wm title . "git-ui ([file normalize [file dirname $gitdir]])"
 921focus -force $ui_comm
 922update_status