git-gui.shon commit git-gui: Refactor 'exec git subcmd' idiom. (8134722)
   1#!/bin/sh
   2# Tcl ignores the next line -*- tcl -*- \
   3exec wish "$0" -- "$@"
   4
   5set appvers {@@GITGUI_VERSION@@}
   6set copyright {
   7Copyright © 2006, 2007 Shawn Pearce, Paul Mackerras.
   8
   9This program is free software; you can redistribute it and/or modify
  10it under the terms of the GNU General Public License as published by
  11the Free Software Foundation; either version 2 of the License, or
  12(at your option) any later version.
  13
  14This program is distributed in the hope that it will be useful,
  15but WITHOUT ANY WARRANTY; without even the implied warranty of
  16MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
  17GNU General Public License for more details.
  18
  19You should have received a copy of the GNU General Public License
  20along with this program; if not, write to the Free Software
  21Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA  02111-1307  USA}
  22
  23######################################################################
  24##
  25## read only globals
  26
  27set _appname [lindex [file split $argv0] end]
  28set _gitdir {}
  29set _gitexec {}
  30set _reponame {}
  31set _iscygwin {}
  32
  33proc appname {} {
  34        global _appname
  35        return $_appname
  36}
  37
  38proc gitdir {args} {
  39        global _gitdir
  40        if {$args eq {}} {
  41                return $_gitdir
  42        }
  43        return [eval [concat [list file join $_gitdir] $args]]
  44}
  45
  46proc gitexec {args} {
  47        global _gitexec
  48        if {$_gitexec eq {}} {
  49                if {[catch {set _gitexec [git --exec-path]} err]} {
  50                        error "Git not installed?\n\n$err"
  51                }
  52        }
  53        if {$args eq {}} {
  54                return $_gitexec
  55        }
  56        return [eval [concat [list file join $_gitexec] $args]]
  57}
  58
  59proc reponame {} {
  60        global _reponame
  61        return $_reponame
  62}
  63
  64proc is_MacOSX {} {
  65        global tcl_platform tk_library
  66        if {[tk windowingsystem] eq {aqua}} {
  67                return 1
  68        }
  69        return 0
  70}
  71
  72proc is_Windows {} {
  73        global tcl_platform
  74        if {$tcl_platform(platform) eq {windows}} {
  75                return 1
  76        }
  77        return 0
  78}
  79
  80proc is_Cygwin {} {
  81        global tcl_platform _iscygwin
  82        if {$_iscygwin eq {}} {
  83                if {$tcl_platform(platform) eq {windows}} {
  84                        if {[catch {set p [exec cygpath --windir]} err]} {
  85                                set _iscygwin 0
  86                        } else {
  87                                set _iscygwin 1
  88                        }
  89                } else {
  90                        set _iscygwin 0
  91                }
  92        }
  93        return $_iscygwin
  94}
  95
  96proc is_enabled {option} {
  97        global enabled_options
  98        if {[catch {set on $enabled_options($option)}]} {return 0}
  99        return $on
 100}
 101
 102proc enable_option {option} {
 103        global enabled_options
 104        set enabled_options($option) 1
 105}
 106
 107proc disable_option {option} {
 108        global enabled_options
 109        set enabled_options($option) 0
 110}
 111
 112######################################################################
 113##
 114## config
 115
 116proc is_many_config {name} {
 117        switch -glob -- $name {
 118        remote.*.fetch -
 119        remote.*.push
 120                {return 1}
 121        *
 122                {return 0}
 123        }
 124}
 125
 126proc is_config_true {name} {
 127        global repo_config
 128        if {[catch {set v $repo_config($name)}]} {
 129                return 0
 130        } elseif {$v eq {true} || $v eq {1} || $v eq {yes}} {
 131                return 1
 132        } else {
 133                return 0
 134        }
 135}
 136
 137proc load_config {include_global} {
 138        global repo_config global_config default_config
 139
 140        array unset global_config
 141        if {$include_global} {
 142                catch {
 143                        set fd_rc [open "| git config --global --list" r]
 144                        while {[gets $fd_rc line] >= 0} {
 145                                if {[regexp {^([^=]+)=(.*)$} $line line name value]} {
 146                                        if {[is_many_config $name]} {
 147                                                lappend global_config($name) $value
 148                                        } else {
 149                                                set global_config($name) $value
 150                                        }
 151                                }
 152                        }
 153                        close $fd_rc
 154                }
 155        }
 156
 157        array unset repo_config
 158        catch {
 159                set fd_rc [open "| git config --list" r]
 160                while {[gets $fd_rc line] >= 0} {
 161                        if {[regexp {^([^=]+)=(.*)$} $line line name value]} {
 162                                if {[is_many_config $name]} {
 163                                        lappend repo_config($name) $value
 164                                } else {
 165                                        set repo_config($name) $value
 166                                }
 167                        }
 168                }
 169                close $fd_rc
 170        }
 171
 172        foreach name [array names default_config] {
 173                if {[catch {set v $global_config($name)}]} {
 174                        set global_config($name) $default_config($name)
 175                }
 176                if {[catch {set v $repo_config($name)}]} {
 177                        set repo_config($name) $default_config($name)
 178                }
 179        }
 180}
 181
 182proc save_config {} {
 183        global default_config font_descs
 184        global repo_config global_config
 185        global repo_config_new global_config_new
 186
 187        foreach option $font_descs {
 188                set name [lindex $option 0]
 189                set font [lindex $option 1]
 190                font configure $font \
 191                        -family $global_config_new(gui.$font^^family) \
 192                        -size $global_config_new(gui.$font^^size)
 193                font configure ${font}bold \
 194                        -family $global_config_new(gui.$font^^family) \
 195                        -size $global_config_new(gui.$font^^size)
 196                set global_config_new(gui.$name) [font configure $font]
 197                unset global_config_new(gui.$font^^family)
 198                unset global_config_new(gui.$font^^size)
 199        }
 200
 201        foreach name [array names default_config] {
 202                set value $global_config_new($name)
 203                if {$value ne $global_config($name)} {
 204                        if {$value eq $default_config($name)} {
 205                                catch {git config --global --unset $name}
 206                        } else {
 207                                regsub -all "\[{}\]" $value {"} value
 208                                git config --global $name $value
 209                        }
 210                        set global_config($name) $value
 211                        if {$value eq $repo_config($name)} {
 212                                catch {git config --unset $name}
 213                                set repo_config($name) $value
 214                        }
 215                }
 216        }
 217
 218        foreach name [array names default_config] {
 219                set value $repo_config_new($name)
 220                if {$value ne $repo_config($name)} {
 221                        if {$value eq $global_config($name)} {
 222                                catch {git config --unset $name}
 223                        } else {
 224                                regsub -all "\[{}\]" $value {"} value
 225                                git config $name $value
 226                        }
 227                        set repo_config($name) $value
 228                }
 229        }
 230}
 231
 232######################################################################
 233##
 234## handy utils
 235
 236proc git {args} {
 237        return [eval exec git $args]
 238}
 239
 240proc error_popup {msg} {
 241        set title [appname]
 242        if {[reponame] ne {}} {
 243                append title " ([reponame])"
 244        }
 245        set cmd [list tk_messageBox \
 246                -icon error \
 247                -type ok \
 248                -title "$title: error" \
 249                -message $msg]
 250        if {[winfo ismapped .]} {
 251                lappend cmd -parent .
 252        }
 253        eval $cmd
 254}
 255
 256proc warn_popup {msg} {
 257        set title [appname]
 258        if {[reponame] ne {}} {
 259                append title " ([reponame])"
 260        }
 261        set cmd [list tk_messageBox \
 262                -icon warning \
 263                -type ok \
 264                -title "$title: warning" \
 265                -message $msg]
 266        if {[winfo ismapped .]} {
 267                lappend cmd -parent .
 268        }
 269        eval $cmd
 270}
 271
 272proc info_popup {msg {parent .}} {
 273        set title [appname]
 274        if {[reponame] ne {}} {
 275                append title " ([reponame])"
 276        }
 277        tk_messageBox \
 278                -parent $parent \
 279                -icon info \
 280                -type ok \
 281                -title $title \
 282                -message $msg
 283}
 284
 285proc ask_popup {msg} {
 286        set title [appname]
 287        if {[reponame] ne {}} {
 288                append title " ([reponame])"
 289        }
 290        return [tk_messageBox \
 291                -parent . \
 292                -icon question \
 293                -type yesno \
 294                -title $title \
 295                -message $msg]
 296}
 297
 298######################################################################
 299##
 300## repository setup
 301
 302if {   [catch {set _gitdir $env(GIT_DIR)}]
 303        && [catch {set _gitdir [git rev-parse --git-dir]} err]} {
 304        catch {wm withdraw .}
 305        error_popup "Cannot find the git directory:\n\n$err"
 306        exit 1
 307}
 308if {![file isdirectory $_gitdir] && [is_Cygwin]} {
 309        catch {set _gitdir [exec cygpath --unix $_gitdir]}
 310}
 311if {![file isdirectory $_gitdir]} {
 312        catch {wm withdraw .}
 313        error_popup "Git directory not found:\n\n$_gitdir"
 314        exit 1
 315}
 316if {[lindex [file split $_gitdir] end] ne {.git}} {
 317        catch {wm withdraw .}
 318        error_popup "Cannot use funny .git directory:\n\n$_gitdir"
 319        exit 1
 320}
 321if {[catch {cd [file dirname $_gitdir]} err]} {
 322        catch {wm withdraw .}
 323        error_popup "No working directory [file dirname $_gitdir]:\n\n$err"
 324        exit 1
 325}
 326set _reponame [lindex [file split \
 327        [file normalize [file dirname $_gitdir]]] \
 328        end]
 329
 330######################################################################
 331##
 332## task management
 333
 334set rescan_active 0
 335set diff_active 0
 336set last_clicked {}
 337
 338set disable_on_lock [list]
 339set index_lock_type none
 340
 341proc lock_index {type} {
 342        global index_lock_type disable_on_lock
 343
 344        if {$index_lock_type eq {none}} {
 345                set index_lock_type $type
 346                foreach w $disable_on_lock {
 347                        uplevel #0 $w disabled
 348                }
 349                return 1
 350        } elseif {$index_lock_type eq "begin-$type"} {
 351                set index_lock_type $type
 352                return 1
 353        }
 354        return 0
 355}
 356
 357proc unlock_index {} {
 358        global index_lock_type disable_on_lock
 359
 360        set index_lock_type none
 361        foreach w $disable_on_lock {
 362                uplevel #0 $w normal
 363        }
 364}
 365
 366######################################################################
 367##
 368## status
 369
 370proc repository_state {ctvar hdvar mhvar} {
 371        global current_branch
 372        upvar $ctvar ct $hdvar hd $mhvar mh
 373
 374        set mh [list]
 375
 376        if {[catch {set current_branch [git symbolic-ref HEAD]}]} {
 377                set current_branch {}
 378        } else {
 379                regsub ^refs/((heads|tags|remotes)/)? \
 380                        $current_branch \
 381                        {} \
 382                        current_branch
 383        }
 384
 385        if {[catch {set hd [git rev-parse --verify HEAD]}]} {
 386                set hd {}
 387                set ct initial
 388                return
 389        }
 390
 391        set merge_head [gitdir MERGE_HEAD]
 392        if {[file exists $merge_head]} {
 393                set ct merge
 394                set fd_mh [open $merge_head r]
 395                while {[gets $fd_mh line] >= 0} {
 396                        lappend mh $line
 397                }
 398                close $fd_mh
 399                return
 400        }
 401
 402        set ct normal
 403}
 404
 405proc PARENT {} {
 406        global PARENT empty_tree
 407
 408        set p [lindex $PARENT 0]
 409        if {$p ne {}} {
 410                return $p
 411        }
 412        if {$empty_tree eq {}} {
 413                set empty_tree [git mktree << {}]
 414        }
 415        return $empty_tree
 416}
 417
 418proc rescan {after {honor_trustmtime 1}} {
 419        global HEAD PARENT MERGE_HEAD commit_type
 420        global ui_index ui_workdir ui_status_value ui_comm
 421        global rescan_active file_states
 422        global repo_config
 423
 424        if {$rescan_active > 0 || ![lock_index read]} return
 425
 426        repository_state newType newHEAD newMERGE_HEAD
 427        if {[string match amend* $commit_type]
 428                && $newType eq {normal}
 429                && $newHEAD eq $HEAD} {
 430        } else {
 431                set HEAD $newHEAD
 432                set PARENT $newHEAD
 433                set MERGE_HEAD $newMERGE_HEAD
 434                set commit_type $newType
 435        }
 436
 437        array unset file_states
 438
 439        if {![$ui_comm edit modified]
 440                || [string trim [$ui_comm get 0.0 end]] eq {}} {
 441                if {[load_message GITGUI_MSG]} {
 442                } elseif {[load_message MERGE_MSG]} {
 443                } elseif {[load_message SQUASH_MSG]} {
 444                }
 445                $ui_comm edit reset
 446                $ui_comm edit modified false
 447        }
 448
 449        if {[is_enabled branch]} {
 450                load_all_heads
 451                populate_branch_menu
 452        }
 453
 454        if {$honor_trustmtime && $repo_config(gui.trustmtime) eq {true}} {
 455                rescan_stage2 {} $after
 456        } else {
 457                set rescan_active 1
 458                set ui_status_value {Refreshing file status...}
 459                set cmd [list git update-index]
 460                lappend cmd -q
 461                lappend cmd --unmerged
 462                lappend cmd --ignore-missing
 463                lappend cmd --refresh
 464                set fd_rf [open "| $cmd" r]
 465                fconfigure $fd_rf -blocking 0 -translation binary
 466                fileevent $fd_rf readable \
 467                        [list rescan_stage2 $fd_rf $after]
 468        }
 469}
 470
 471proc rescan_stage2 {fd after} {
 472        global ui_status_value
 473        global rescan_active buf_rdi buf_rdf buf_rlo
 474
 475        if {$fd ne {}} {
 476                read $fd
 477                if {![eof $fd]} return
 478                close $fd
 479        }
 480
 481        set ls_others [list | git ls-files --others -z \
 482                --exclude-per-directory=.gitignore]
 483        set info_exclude [gitdir info exclude]
 484        if {[file readable $info_exclude]} {
 485                lappend ls_others "--exclude-from=$info_exclude"
 486        }
 487
 488        set buf_rdi {}
 489        set buf_rdf {}
 490        set buf_rlo {}
 491
 492        set rescan_active 3
 493        set ui_status_value {Scanning for modified files ...}
 494        set fd_di [open "| git diff-index --cached -z [PARENT]" r]
 495        set fd_df [open "| git diff-files -z" r]
 496        set fd_lo [open $ls_others r]
 497
 498        fconfigure $fd_di -blocking 0 -translation binary -encoding binary
 499        fconfigure $fd_df -blocking 0 -translation binary -encoding binary
 500        fconfigure $fd_lo -blocking 0 -translation binary -encoding binary
 501        fileevent $fd_di readable [list read_diff_index $fd_di $after]
 502        fileevent $fd_df readable [list read_diff_files $fd_df $after]
 503        fileevent $fd_lo readable [list read_ls_others $fd_lo $after]
 504}
 505
 506proc load_message {file} {
 507        global ui_comm
 508
 509        set f [gitdir $file]
 510        if {[file isfile $f]} {
 511                if {[catch {set fd [open $f r]}]} {
 512                        return 0
 513                }
 514                set content [string trim [read $fd]]
 515                close $fd
 516                regsub -all -line {[ \r\t]+$} $content {} content
 517                $ui_comm delete 0.0 end
 518                $ui_comm insert end $content
 519                return 1
 520        }
 521        return 0
 522}
 523
 524proc read_diff_index {fd after} {
 525        global buf_rdi
 526
 527        append buf_rdi [read $fd]
 528        set c 0
 529        set n [string length $buf_rdi]
 530        while {$c < $n} {
 531                set z1 [string first "\0" $buf_rdi $c]
 532                if {$z1 == -1} break
 533                incr z1
 534                set z2 [string first "\0" $buf_rdi $z1]
 535                if {$z2 == -1} break
 536
 537                incr c
 538                set i [split [string range $buf_rdi $c [expr {$z1 - 2}]] { }]
 539                set p [string range $buf_rdi $z1 [expr {$z2 - 1}]]
 540                merge_state \
 541                        [encoding convertfrom $p] \
 542                        [lindex $i 4]? \
 543                        [list [lindex $i 0] [lindex $i 2]] \
 544                        [list]
 545                set c $z2
 546                incr c
 547        }
 548        if {$c < $n} {
 549                set buf_rdi [string range $buf_rdi $c end]
 550        } else {
 551                set buf_rdi {}
 552        }
 553
 554        rescan_done $fd buf_rdi $after
 555}
 556
 557proc read_diff_files {fd after} {
 558        global buf_rdf
 559
 560        append buf_rdf [read $fd]
 561        set c 0
 562        set n [string length $buf_rdf]
 563        while {$c < $n} {
 564                set z1 [string first "\0" $buf_rdf $c]
 565                if {$z1 == -1} break
 566                incr z1
 567                set z2 [string first "\0" $buf_rdf $z1]
 568                if {$z2 == -1} break
 569
 570                incr c
 571                set i [split [string range $buf_rdf $c [expr {$z1 - 2}]] { }]
 572                set p [string range $buf_rdf $z1 [expr {$z2 - 1}]]
 573                merge_state \
 574                        [encoding convertfrom $p] \
 575                        ?[lindex $i 4] \
 576                        [list] \
 577                        [list [lindex $i 0] [lindex $i 2]]
 578                set c $z2
 579                incr c
 580        }
 581        if {$c < $n} {
 582                set buf_rdf [string range $buf_rdf $c end]
 583        } else {
 584                set buf_rdf {}
 585        }
 586
 587        rescan_done $fd buf_rdf $after
 588}
 589
 590proc read_ls_others {fd after} {
 591        global buf_rlo
 592
 593        append buf_rlo [read $fd]
 594        set pck [split $buf_rlo "\0"]
 595        set buf_rlo [lindex $pck end]
 596        foreach p [lrange $pck 0 end-1] {
 597                merge_state [encoding convertfrom $p] ?O
 598        }
 599        rescan_done $fd buf_rlo $after
 600}
 601
 602proc rescan_done {fd buf after} {
 603        global rescan_active
 604        global file_states repo_config
 605        upvar $buf to_clear
 606
 607        if {![eof $fd]} return
 608        set to_clear {}
 609        close $fd
 610        if {[incr rescan_active -1] > 0} return
 611
 612        prune_selection
 613        unlock_index
 614        display_all_files
 615        reshow_diff
 616        uplevel #0 $after
 617}
 618
 619proc prune_selection {} {
 620        global file_states selected_paths
 621
 622        foreach path [array names selected_paths] {
 623                if {[catch {set still_here $file_states($path)}]} {
 624                        unset selected_paths($path)
 625                }
 626        }
 627}
 628
 629######################################################################
 630##
 631## diff
 632
 633proc clear_diff {} {
 634        global ui_diff current_diff_path current_diff_header
 635        global ui_index ui_workdir
 636
 637        $ui_diff conf -state normal
 638        $ui_diff delete 0.0 end
 639        $ui_diff conf -state disabled
 640
 641        set current_diff_path {}
 642        set current_diff_header {}
 643
 644        $ui_index tag remove in_diff 0.0 end
 645        $ui_workdir tag remove in_diff 0.0 end
 646}
 647
 648proc reshow_diff {} {
 649        global ui_status_value file_states file_lists
 650        global current_diff_path current_diff_side
 651
 652        set p $current_diff_path
 653        if {$p eq {}
 654                || $current_diff_side eq {}
 655                || [catch {set s $file_states($p)}]
 656                || [lsearch -sorted -exact $file_lists($current_diff_side) $p] == -1} {
 657                clear_diff
 658        } else {
 659                show_diff $p $current_diff_side
 660        }
 661}
 662
 663proc handle_empty_diff {} {
 664        global current_diff_path file_states file_lists
 665
 666        set path $current_diff_path
 667        set s $file_states($path)
 668        if {[lindex $s 0] ne {_M}} return
 669
 670        info_popup "No differences detected.
 671
 672[short_path $path] has no changes.
 673
 674The modification date of this file was updated
 675by another application, but the content within
 676the file was not changed.
 677
 678A rescan will be automatically started to find
 679other files which may have the same state."
 680
 681        clear_diff
 682        display_file $path __
 683        rescan {set ui_status_value {Ready.}} 0
 684}
 685
 686proc show_diff {path w {lno {}}} {
 687        global file_states file_lists
 688        global is_3way_diff diff_active repo_config
 689        global ui_diff ui_status_value ui_index ui_workdir
 690        global current_diff_path current_diff_side current_diff_header
 691
 692        if {$diff_active || ![lock_index read]} return
 693
 694        clear_diff
 695        if {$lno == {}} {
 696                set lno [lsearch -sorted -exact $file_lists($w) $path]
 697                if {$lno >= 0} {
 698                        incr lno
 699                }
 700        }
 701        if {$lno >= 1} {
 702                $w tag add in_diff $lno.0 [expr {$lno + 1}].0
 703        }
 704
 705        set s $file_states($path)
 706        set m [lindex $s 0]
 707        set is_3way_diff 0
 708        set diff_active 1
 709        set current_diff_path $path
 710        set current_diff_side $w
 711        set current_diff_header {}
 712        set ui_status_value "Loading diff of [escape_path $path]..."
 713
 714        # - Git won't give us the diff, there's nothing to compare to!
 715        #
 716        if {$m eq {_O}} {
 717                set max_sz [expr {128 * 1024}]
 718                if {[catch {
 719                                set fd [open $path r]
 720                                set content [read $fd $max_sz]
 721                                close $fd
 722                                set sz [file size $path]
 723                        } err ]} {
 724                        set diff_active 0
 725                        unlock_index
 726                        set ui_status_value "Unable to display [escape_path $path]"
 727                        error_popup "Error loading file:\n\n$err"
 728                        return
 729                }
 730                $ui_diff conf -state normal
 731                if {![catch {set type [exec file $path]}]} {
 732                        set n [string length $path]
 733                        if {[string equal -length $n $path $type]} {
 734                                set type [string range $type $n end]
 735                                regsub {^:?\s*} $type {} type
 736                        }
 737                        $ui_diff insert end "* $type\n" d_@
 738                }
 739                if {[string first "\0" $content] != -1} {
 740                        $ui_diff insert end \
 741                                "* Binary file (not showing content)." \
 742                                d_@
 743                } else {
 744                        if {$sz > $max_sz} {
 745                                $ui_diff insert end \
 746"* Untracked file is $sz bytes.
 747* Showing only first $max_sz bytes.
 748" d_@
 749                        }
 750                        $ui_diff insert end $content
 751                        if {$sz > $max_sz} {
 752                                $ui_diff insert end "
 753* Untracked file clipped here by [appname].
 754* To see the entire file, use an external editor.
 755" d_@
 756                        }
 757                }
 758                $ui_diff conf -state disabled
 759                set diff_active 0
 760                unlock_index
 761                set ui_status_value {Ready.}
 762                return
 763        }
 764
 765        set cmd [list | git]
 766        if {$w eq $ui_index} {
 767                lappend cmd diff-index
 768                lappend cmd --cached
 769        } elseif {$w eq $ui_workdir} {
 770                if {[string index $m 0] eq {U}} {
 771                        lappend cmd diff
 772                } else {
 773                        lappend cmd diff-files
 774                }
 775        }
 776
 777        lappend cmd -p
 778        lappend cmd --no-color
 779        if {$repo_config(gui.diffcontext) > 0} {
 780                lappend cmd "-U$repo_config(gui.diffcontext)"
 781        }
 782        if {$w eq $ui_index} {
 783                lappend cmd [PARENT]
 784        }
 785        lappend cmd --
 786        lappend cmd $path
 787
 788        if {[catch {set fd [open $cmd r]} err]} {
 789                set diff_active 0
 790                unlock_index
 791                set ui_status_value "Unable to display [escape_path $path]"
 792                error_popup "Error loading diff:\n\n$err"
 793                return
 794        }
 795
 796        fconfigure $fd \
 797                -blocking 0 \
 798                -encoding binary \
 799                -translation binary
 800        fileevent $fd readable [list read_diff $fd]
 801}
 802
 803proc read_diff {fd} {
 804        global ui_diff ui_status_value diff_active
 805        global is_3way_diff current_diff_header
 806
 807        $ui_diff conf -state normal
 808        while {[gets $fd line] >= 0} {
 809                # -- Cleanup uninteresting diff header lines.
 810                #
 811                if {   [string match {diff --git *}      $line]
 812                        || [string match {diff --cc *}       $line]
 813                        || [string match {diff --combined *} $line]
 814                        || [string match {--- *}             $line]
 815                        || [string match {+++ *}             $line]} {
 816                        append current_diff_header $line "\n"
 817                        continue
 818                }
 819                if {[string match {index *} $line]} continue
 820                if {$line eq {deleted file mode 120000}} {
 821                        set line "deleted symlink"
 822                }
 823
 824                # -- Automatically detect if this is a 3 way diff.
 825                #
 826                if {[string match {@@@ *} $line]} {set is_3way_diff 1}
 827
 828                if {[string match {mode *} $line]
 829                        || [string match {new file *} $line]
 830                        || [string match {deleted file *} $line]
 831                        || [string match {Binary files * and * differ} $line]
 832                        || $line eq {\ No newline at end of file}
 833                        || [regexp {^\* Unmerged path } $line]} {
 834                        set tags {}
 835                } elseif {$is_3way_diff} {
 836                        set op [string range $line 0 1]
 837                        switch -- $op {
 838                        {  } {set tags {}}
 839                        {@@} {set tags d_@}
 840                        { +} {set tags d_s+}
 841                        { -} {set tags d_s-}
 842                        {+ } {set tags d_+s}
 843                        {- } {set tags d_-s}
 844                        {--} {set tags d_--}
 845                        {++} {
 846                                if {[regexp {^\+\+([<>]{7} |={7})} $line _g op]} {
 847                                        set line [string replace $line 0 1 {  }]
 848                                        set tags d$op
 849                                } else {
 850                                        set tags d_++
 851                                }
 852                        }
 853                        default {
 854                                puts "error: Unhandled 3 way diff marker: {$op}"
 855                                set tags {}
 856                        }
 857                        }
 858                } else {
 859                        set op [string index $line 0]
 860                        switch -- $op {
 861                        { } {set tags {}}
 862                        {@} {set tags d_@}
 863                        {-} {set tags d_-}
 864                        {+} {
 865                                if {[regexp {^\+([<>]{7} |={7})} $line _g op]} {
 866                                        set line [string replace $line 0 0 { }]
 867                                        set tags d$op
 868                                } else {
 869                                        set tags d_+
 870                                }
 871                        }
 872                        default {
 873                                puts "error: Unhandled 2 way diff marker: {$op}"
 874                                set tags {}
 875                        }
 876                        }
 877                }
 878                $ui_diff insert end $line $tags
 879                if {[string index $line end] eq "\r"} {
 880                        $ui_diff tag add d_cr {end - 2c}
 881                }
 882                $ui_diff insert end "\n" $tags
 883        }
 884        $ui_diff conf -state disabled
 885
 886        if {[eof $fd]} {
 887                close $fd
 888                set diff_active 0
 889                unlock_index
 890                set ui_status_value {Ready.}
 891
 892                if {[$ui_diff index end] eq {2.0}} {
 893                        handle_empty_diff
 894                }
 895        }
 896}
 897
 898proc apply_hunk {x y} {
 899        global current_diff_path current_diff_header current_diff_side
 900        global ui_diff ui_index file_states
 901
 902        if {$current_diff_path eq {} || $current_diff_header eq {}} return
 903        if {![lock_index apply_hunk]} return
 904
 905        set apply_cmd {git apply --cached --whitespace=nowarn}
 906        set mi [lindex $file_states($current_diff_path) 0]
 907        if {$current_diff_side eq $ui_index} {
 908                set mode unstage
 909                lappend apply_cmd --reverse
 910                if {[string index $mi 0] ne {M}} {
 911                        unlock_index
 912                        return
 913                }
 914        } else {
 915                set mode stage
 916                if {[string index $mi 1] ne {M}} {
 917                        unlock_index
 918                        return
 919                }
 920        }
 921
 922        set s_lno [lindex [split [$ui_diff index @$x,$y] .] 0]
 923        set s_lno [$ui_diff search -backwards -regexp ^@@ $s_lno.0 0.0]
 924        if {$s_lno eq {}} {
 925                unlock_index
 926                return
 927        }
 928
 929        set e_lno [$ui_diff search -forwards -regexp ^@@ "$s_lno + 1 lines" end]
 930        if {$e_lno eq {}} {
 931                set e_lno end
 932        }
 933
 934        if {[catch {
 935                set p [open "| $apply_cmd" w]
 936                fconfigure $p -translation binary -encoding binary
 937                puts -nonewline $p $current_diff_header
 938                puts -nonewline $p [$ui_diff get $s_lno $e_lno]
 939                close $p} err]} {
 940                error_popup "Failed to $mode selected hunk.\n\n$err"
 941                unlock_index
 942                return
 943        }
 944
 945        $ui_diff conf -state normal
 946        $ui_diff delete $s_lno $e_lno
 947        $ui_diff conf -state disabled
 948
 949        if {[$ui_diff get 1.0 end] eq "\n"} {
 950                set o _
 951        } else {
 952                set o ?
 953        }
 954
 955        if {$current_diff_side eq $ui_index} {
 956                set mi ${o}M
 957        } elseif {[string index $mi 0] eq {_}} {
 958                set mi M$o
 959        } else {
 960                set mi ?$o
 961        }
 962        unlock_index
 963        display_file $current_diff_path $mi
 964        if {$o eq {_}} {
 965                clear_diff
 966        }
 967}
 968
 969######################################################################
 970##
 971## commit
 972
 973proc load_last_commit {} {
 974        global HEAD PARENT MERGE_HEAD commit_type ui_comm
 975        global repo_config
 976
 977        if {[llength $PARENT] == 0} {
 978                error_popup {There is nothing to amend.
 979
 980You are about to create the initial commit.
 981There is no commit before this to amend.
 982}
 983                return
 984        }
 985
 986        repository_state curType curHEAD curMERGE_HEAD
 987        if {$curType eq {merge}} {
 988                error_popup {Cannot amend while merging.
 989
 990You are currently in the middle of a merge that
 991has not been fully completed.  You cannot amend
 992the prior commit unless you first abort the
 993current merge activity.
 994}
 995                return
 996        }
 997
 998        set msg {}
 999        set parents [list]
1000        if {[catch {
1001                        set fd [open "| git cat-file commit $curHEAD" r]
1002                        fconfigure $fd -encoding binary -translation lf
1003                        if {[catch {set enc $repo_config(i18n.commitencoding)}]} {
1004                                set enc utf-8
1005                        }
1006                        while {[gets $fd line] > 0} {
1007                                if {[string match {parent *} $line]} {
1008                                        lappend parents [string range $line 7 end]
1009                                } elseif {[string match {encoding *} $line]} {
1010                                        set enc [string tolower [string range $line 9 end]]
1011                                }
1012                        }
1013                        fconfigure $fd -encoding $enc
1014                        set msg [string trim [read $fd]]
1015                        close $fd
1016                } err]} {
1017                error_popup "Error loading commit data for amend:\n\n$err"
1018                return
1019        }
1020
1021        set HEAD $curHEAD
1022        set PARENT $parents
1023        set MERGE_HEAD [list]
1024        switch -- [llength $parents] {
1025        0       {set commit_type amend-initial}
1026        1       {set commit_type amend}
1027        default {set commit_type amend-merge}
1028        }
1029
1030        $ui_comm delete 0.0 end
1031        $ui_comm insert end $msg
1032        $ui_comm edit reset
1033        $ui_comm edit modified false
1034        rescan {set ui_status_value {Ready.}}
1035}
1036
1037proc create_new_commit {} {
1038        global commit_type ui_comm
1039
1040        set commit_type normal
1041        $ui_comm delete 0.0 end
1042        $ui_comm edit reset
1043        $ui_comm edit modified false
1044        rescan {set ui_status_value {Ready.}}
1045}
1046
1047set GIT_COMMITTER_IDENT {}
1048
1049proc committer_ident {} {
1050        global GIT_COMMITTER_IDENT
1051
1052        if {$GIT_COMMITTER_IDENT eq {}} {
1053                if {[catch {set me [git var GIT_COMMITTER_IDENT]} err]} {
1054                        error_popup "Unable to obtain your identity:\n\n$err"
1055                        return {}
1056                }
1057                if {![regexp {^(.*) [0-9]+ [-+0-9]+$} \
1058                        $me me GIT_COMMITTER_IDENT]} {
1059                        error_popup "Invalid GIT_COMMITTER_IDENT:\n\n$me"
1060                        return {}
1061                }
1062        }
1063
1064        return $GIT_COMMITTER_IDENT
1065}
1066
1067proc commit_tree {} {
1068        global HEAD commit_type file_states ui_comm repo_config
1069        global ui_status_value pch_error
1070
1071        if {[committer_ident] eq {}} return
1072        if {![lock_index update]} return
1073
1074        # -- Our in memory state should match the repository.
1075        #
1076        repository_state curType curHEAD curMERGE_HEAD
1077        if {[string match amend* $commit_type]
1078                && $curType eq {normal}
1079                && $curHEAD eq $HEAD} {
1080        } elseif {$commit_type ne $curType || $HEAD ne $curHEAD} {
1081                info_popup {Last scanned state does not match repository state.
1082
1083Another Git program has modified this repository
1084since the last scan.  A rescan must be performed
1085before another commit can be created.
1086
1087The rescan will be automatically started now.
1088}
1089                unlock_index
1090                rescan {set ui_status_value {Ready.}}
1091                return
1092        }
1093
1094        # -- At least one file should differ in the index.
1095        #
1096        set files_ready 0
1097        foreach path [array names file_states] {
1098                switch -glob -- [lindex $file_states($path) 0] {
1099                _? {continue}
1100                A? -
1101                D? -
1102                M? {set files_ready 1}
1103                U? {
1104                        error_popup "Unmerged files cannot be committed.
1105
1106File [short_path $path] has merge conflicts.
1107You must resolve them and add the file before committing.
1108"
1109                        unlock_index
1110                        return
1111                }
1112                default {
1113                        error_popup "Unknown file state [lindex $s 0] detected.
1114
1115File [short_path $path] cannot be committed by this program.
1116"
1117                }
1118                }
1119        }
1120        if {!$files_ready} {
1121                info_popup {No changes to commit.
1122
1123You must add at least 1 file before you can commit.
1124}
1125                unlock_index
1126                return
1127        }
1128
1129        # -- A message is required.
1130        #
1131        set msg [string trim [$ui_comm get 1.0 end]]
1132        regsub -all -line {[ \t\r]+$} $msg {} msg
1133        if {$msg eq {}} {
1134                error_popup {Please supply a commit message.
1135
1136A good commit message has the following format:
1137
1138- First line: Describe in one sentance what you did.
1139- Second line: Blank
1140- Remaining lines: Describe why this change is good.
1141}
1142                unlock_index
1143                return
1144        }
1145
1146        # -- Run the pre-commit hook.
1147        #
1148        set pchook [gitdir hooks pre-commit]
1149
1150        # On Cygwin [file executable] might lie so we need to ask
1151        # the shell if the hook is executable.  Yes that's annoying.
1152        #
1153        if {[is_Cygwin] && [file isfile $pchook]} {
1154                set pchook [list sh -c [concat \
1155                        "if test -x \"$pchook\";" \
1156                        "then exec \"$pchook\" 2>&1;" \
1157                        "fi"]]
1158        } elseif {[file executable $pchook]} {
1159                set pchook [list $pchook |& cat]
1160        } else {
1161                commit_writetree $curHEAD $msg
1162                return
1163        }
1164
1165        set ui_status_value {Calling pre-commit hook...}
1166        set pch_error {}
1167        set fd_ph [open "| $pchook" r]
1168        fconfigure $fd_ph -blocking 0 -translation binary
1169        fileevent $fd_ph readable \
1170                [list commit_prehook_wait $fd_ph $curHEAD $msg]
1171}
1172
1173proc commit_prehook_wait {fd_ph curHEAD msg} {
1174        global pch_error ui_status_value
1175
1176        append pch_error [read $fd_ph]
1177        fconfigure $fd_ph -blocking 1
1178        if {[eof $fd_ph]} {
1179                if {[catch {close $fd_ph}]} {
1180                        set ui_status_value {Commit declined by pre-commit hook.}
1181                        hook_failed_popup pre-commit $pch_error
1182                        unlock_index
1183                } else {
1184                        commit_writetree $curHEAD $msg
1185                }
1186                set pch_error {}
1187                return
1188        }
1189        fconfigure $fd_ph -blocking 0
1190}
1191
1192proc commit_writetree {curHEAD msg} {
1193        global ui_status_value
1194
1195        set ui_status_value {Committing changes...}
1196        set fd_wt [open "| git write-tree" r]
1197        fileevent $fd_wt readable \
1198                [list commit_committree $fd_wt $curHEAD $msg]
1199}
1200
1201proc commit_committree {fd_wt curHEAD msg} {
1202        global HEAD PARENT MERGE_HEAD commit_type
1203        global all_heads current_branch
1204        global ui_status_value ui_comm selected_commit_type
1205        global file_states selected_paths rescan_active
1206        global repo_config
1207
1208        gets $fd_wt tree_id
1209        if {$tree_id eq {} || [catch {close $fd_wt} err]} {
1210                error_popup "write-tree failed:\n\n$err"
1211                set ui_status_value {Commit failed.}
1212                unlock_index
1213                return
1214        }
1215
1216        # -- Build the message.
1217        #
1218        set msg_p [gitdir COMMIT_EDITMSG]
1219        set msg_wt [open $msg_p w]
1220        if {[catch {set enc $repo_config(i18n.commitencoding)}]} {
1221                set enc utf-8
1222        }
1223        fconfigure $msg_wt -encoding $enc -translation binary
1224        puts -nonewline $msg_wt $msg
1225        close $msg_wt
1226
1227        # -- Create the commit.
1228        #
1229        set cmd [list git commit-tree $tree_id]
1230        set parents [concat $PARENT $MERGE_HEAD]
1231        if {[llength $parents] > 0} {
1232                foreach p $parents {
1233                        lappend cmd -p $p
1234                }
1235        } else {
1236                # git commit-tree writes to stderr during initial commit.
1237                lappend cmd 2>/dev/null
1238        }
1239        lappend cmd <$msg_p
1240        if {[catch {set cmt_id [eval exec $cmd]} err]} {
1241                error_popup "commit-tree failed:\n\n$err"
1242                set ui_status_value {Commit failed.}
1243                unlock_index
1244                return
1245        }
1246
1247        # -- Update the HEAD ref.
1248        #
1249        set reflogm commit
1250        if {$commit_type ne {normal}} {
1251                append reflogm " ($commit_type)"
1252        }
1253        set i [string first "\n" $msg]
1254        if {$i >= 0} {
1255                append reflogm {: } [string range $msg 0 [expr {$i - 1}]]
1256        } else {
1257                append reflogm {: } $msg
1258        }
1259        set cmd [list git update-ref -m $reflogm HEAD $cmt_id $curHEAD]
1260        if {[catch {eval exec $cmd} err]} {
1261                error_popup "update-ref failed:\n\n$err"
1262                set ui_status_value {Commit failed.}
1263                unlock_index
1264                return
1265        }
1266
1267        # -- Make sure our current branch exists.
1268        #
1269        if {$commit_type eq {initial}} {
1270                lappend all_heads $current_branch
1271                set all_heads [lsort -unique $all_heads]
1272                populate_branch_menu
1273        }
1274
1275        # -- Cleanup after ourselves.
1276        #
1277        catch {file delete $msg_p}
1278        catch {file delete [gitdir MERGE_HEAD]}
1279        catch {file delete [gitdir MERGE_MSG]}
1280        catch {file delete [gitdir SQUASH_MSG]}
1281        catch {file delete [gitdir GITGUI_MSG]}
1282
1283        # -- Let rerere do its thing.
1284        #
1285        if {[file isdirectory [gitdir rr-cache]]} {
1286                catch {git rerere}
1287        }
1288
1289        # -- Run the post-commit hook.
1290        #
1291        set pchook [gitdir hooks post-commit]
1292        if {[is_Cygwin] && [file isfile $pchook]} {
1293                set pchook [list sh -c [concat \
1294                        "if test -x \"$pchook\";" \
1295                        "then exec \"$pchook\";" \
1296                        "fi"]]
1297        } elseif {![file executable $pchook]} {
1298                set pchook {}
1299        }
1300        if {$pchook ne {}} {
1301                catch {exec $pchook &}
1302        }
1303
1304        $ui_comm delete 0.0 end
1305        $ui_comm edit reset
1306        $ui_comm edit modified false
1307
1308        if {[is_enabled singlecommit]} do_quit
1309
1310        # -- Update in memory status
1311        #
1312        set selected_commit_type new
1313        set commit_type normal
1314        set HEAD $cmt_id
1315        set PARENT $cmt_id
1316        set MERGE_HEAD [list]
1317
1318        foreach path [array names file_states] {
1319                set s $file_states($path)
1320                set m [lindex $s 0]
1321                switch -glob -- $m {
1322                _O -
1323                _M -
1324                _D {continue}
1325                __ -
1326                A_ -
1327                M_ -
1328                D_ {
1329                        unset file_states($path)
1330                        catch {unset selected_paths($path)}
1331                }
1332                DO {
1333                        set file_states($path) [list _O [lindex $s 1] {} {}]
1334                }
1335                AM -
1336                AD -
1337                MM -
1338                MD {
1339                        set file_states($path) [list \
1340                                _[string index $m 1] \
1341                                [lindex $s 1] \
1342                                [lindex $s 3] \
1343                                {}]
1344                }
1345                }
1346        }
1347
1348        display_all_files
1349        unlock_index
1350        reshow_diff
1351        set ui_status_value \
1352                "Changes committed as [string range $cmt_id 0 7]."
1353}
1354
1355######################################################################
1356##
1357## fetch push
1358
1359proc fetch_from {remote} {
1360        set w [new_console \
1361                "fetch $remote" \
1362                "Fetching new changes from $remote"]
1363        set cmd [list git fetch]
1364        lappend cmd $remote
1365        console_exec $w $cmd console_done
1366}
1367
1368proc push_to {remote} {
1369        set w [new_console \
1370                "push $remote" \
1371                "Pushing changes to $remote"]
1372        set cmd [list git push]
1373        lappend cmd -v
1374        lappend cmd $remote
1375        console_exec $w $cmd console_done
1376}
1377
1378######################################################################
1379##
1380## ui helpers
1381
1382proc mapicon {w state path} {
1383        global all_icons
1384
1385        if {[catch {set r $all_icons($state$w)}]} {
1386                puts "error: no icon for $w state={$state} $path"
1387                return file_plain
1388        }
1389        return $r
1390}
1391
1392proc mapdesc {state path} {
1393        global all_descs
1394
1395        if {[catch {set r $all_descs($state)}]} {
1396                puts "error: no desc for state={$state} $path"
1397                return $state
1398        }
1399        return $r
1400}
1401
1402proc escape_path {path} {
1403        regsub -all {\\} $path "\\\\" path
1404        regsub -all "\n" $path "\\n" path
1405        return $path
1406}
1407
1408proc short_path {path} {
1409        return [escape_path [lindex [file split $path] end]]
1410}
1411
1412set next_icon_id 0
1413set null_sha1 [string repeat 0 40]
1414
1415proc merge_state {path new_state {head_info {}} {index_info {}}} {
1416        global file_states next_icon_id null_sha1
1417
1418        set s0 [string index $new_state 0]
1419        set s1 [string index $new_state 1]
1420
1421        if {[catch {set info $file_states($path)}]} {
1422                set state __
1423                set icon n[incr next_icon_id]
1424        } else {
1425                set state [lindex $info 0]
1426                set icon [lindex $info 1]
1427                if {$head_info eq {}}  {set head_info  [lindex $info 2]}
1428                if {$index_info eq {}} {set index_info [lindex $info 3]}
1429        }
1430
1431        if     {$s0 eq {?}} {set s0 [string index $state 0]} \
1432        elseif {$s0 eq {_}} {set s0 _}
1433
1434        if     {$s1 eq {?}} {set s1 [string index $state 1]} \
1435        elseif {$s1 eq {_}} {set s1 _}
1436
1437        if {$s0 eq {A} && $s1 eq {_} && $head_info eq {}} {
1438                set head_info [list 0 $null_sha1]
1439        } elseif {$s0 ne {_} && [string index $state 0] eq {_}
1440                && $head_info eq {}} {
1441                set head_info $index_info
1442        }
1443
1444        set file_states($path) [list $s0$s1 $icon \
1445                $head_info $index_info \
1446                ]
1447        return $state
1448}
1449
1450proc display_file_helper {w path icon_name old_m new_m} {
1451        global file_lists
1452
1453        if {$new_m eq {_}} {
1454                set lno [lsearch -sorted -exact $file_lists($w) $path]
1455                if {$lno >= 0} {
1456                        set file_lists($w) [lreplace $file_lists($w) $lno $lno]
1457                        incr lno
1458                        $w conf -state normal
1459                        $w delete $lno.0 [expr {$lno + 1}].0
1460                        $w conf -state disabled
1461                }
1462        } elseif {$old_m eq {_} && $new_m ne {_}} {
1463                lappend file_lists($w) $path
1464                set file_lists($w) [lsort -unique $file_lists($w)]
1465                set lno [lsearch -sorted -exact $file_lists($w) $path]
1466                incr lno
1467                $w conf -state normal
1468                $w image create $lno.0 \
1469                        -align center -padx 5 -pady 1 \
1470                        -name $icon_name \
1471                        -image [mapicon $w $new_m $path]
1472                $w insert $lno.1 "[escape_path $path]\n"
1473                $w conf -state disabled
1474        } elseif {$old_m ne $new_m} {
1475                $w conf -state normal
1476                $w image conf $icon_name -image [mapicon $w $new_m $path]
1477                $w conf -state disabled
1478        }
1479}
1480
1481proc display_file {path state} {
1482        global file_states selected_paths
1483        global ui_index ui_workdir
1484
1485        set old_m [merge_state $path $state]
1486        set s $file_states($path)
1487        set new_m [lindex $s 0]
1488        set icon_name [lindex $s 1]
1489
1490        set o [string index $old_m 0]
1491        set n [string index $new_m 0]
1492        if {$o eq {U}} {
1493                set o _
1494        }
1495        if {$n eq {U}} {
1496                set n _
1497        }
1498        display_file_helper     $ui_index $path $icon_name $o $n
1499
1500        if {[string index $old_m 0] eq {U}} {
1501                set o U
1502        } else {
1503                set o [string index $old_m 1]
1504        }
1505        if {[string index $new_m 0] eq {U}} {
1506                set n U
1507        } else {
1508                set n [string index $new_m 1]
1509        }
1510        display_file_helper     $ui_workdir $path $icon_name $o $n
1511
1512        if {$new_m eq {__}} {
1513                unset file_states($path)
1514                catch {unset selected_paths($path)}
1515        }
1516}
1517
1518proc display_all_files_helper {w path icon_name m} {
1519        global file_lists
1520
1521        lappend file_lists($w) $path
1522        set lno [expr {[lindex [split [$w index end] .] 0] - 1}]
1523        $w image create end \
1524                -align center -padx 5 -pady 1 \
1525                -name $icon_name \
1526                -image [mapicon $w $m $path]
1527        $w insert end "[escape_path $path]\n"
1528}
1529
1530proc display_all_files {} {
1531        global ui_index ui_workdir
1532        global file_states file_lists
1533        global last_clicked
1534
1535        $ui_index conf -state normal
1536        $ui_workdir conf -state normal
1537
1538        $ui_index delete 0.0 end
1539        $ui_workdir delete 0.0 end
1540        set last_clicked {}
1541
1542        set file_lists($ui_index) [list]
1543        set file_lists($ui_workdir) [list]
1544
1545        foreach path [lsort [array names file_states]] {
1546                set s $file_states($path)
1547                set m [lindex $s 0]
1548                set icon_name [lindex $s 1]
1549
1550                set s [string index $m 0]
1551                if {$s ne {U} && $s ne {_}} {
1552                        display_all_files_helper $ui_index $path \
1553                                $icon_name $s
1554                }
1555
1556                if {[string index $m 0] eq {U}} {
1557                        set s U
1558                } else {
1559                        set s [string index $m 1]
1560                }
1561                if {$s ne {_}} {
1562                        display_all_files_helper $ui_workdir $path \
1563                                $icon_name $s
1564                }
1565        }
1566
1567        $ui_index conf -state disabled
1568        $ui_workdir conf -state disabled
1569}
1570
1571proc update_indexinfo {msg pathList after} {
1572        global update_index_cp ui_status_value
1573
1574        if {![lock_index update]} return
1575
1576        set update_index_cp 0
1577        set pathList [lsort $pathList]
1578        set totalCnt [llength $pathList]
1579        set batch [expr {int($totalCnt * .01) + 1}]
1580        if {$batch > 25} {set batch 25}
1581
1582        set ui_status_value [format \
1583                "$msg... %i/%i files (%.2f%%)" \
1584                $update_index_cp \
1585                $totalCnt \
1586                0.0]
1587        set fd [open "| git update-index -z --index-info" w]
1588        fconfigure $fd \
1589                -blocking 0 \
1590                -buffering full \
1591                -buffersize 512 \
1592                -encoding binary \
1593                -translation binary
1594        fileevent $fd writable [list \
1595                write_update_indexinfo \
1596                $fd \
1597                $pathList \
1598                $totalCnt \
1599                $batch \
1600                $msg \
1601                $after \
1602                ]
1603}
1604
1605proc write_update_indexinfo {fd pathList totalCnt batch msg after} {
1606        global update_index_cp ui_status_value
1607        global file_states current_diff_path
1608
1609        if {$update_index_cp >= $totalCnt} {
1610                close $fd
1611                unlock_index
1612                uplevel #0 $after
1613                return
1614        }
1615
1616        for {set i $batch} \
1617                {$update_index_cp < $totalCnt && $i > 0} \
1618                {incr i -1} {
1619                set path [lindex $pathList $update_index_cp]
1620                incr update_index_cp
1621
1622                set s $file_states($path)
1623                switch -glob -- [lindex $s 0] {
1624                A? {set new _O}
1625                M? {set new _M}
1626                D_ {set new _D}
1627                D? {set new _?}
1628                ?? {continue}
1629                }
1630                set info [lindex $s 2]
1631                if {$info eq {}} continue
1632
1633                puts -nonewline $fd "$info\t[encoding convertto $path]\0"
1634                display_file $path $new
1635        }
1636
1637        set ui_status_value [format \
1638                "$msg... %i/%i files (%.2f%%)" \
1639                $update_index_cp \
1640                $totalCnt \
1641                [expr {100.0 * $update_index_cp / $totalCnt}]]
1642}
1643
1644proc update_index {msg pathList after} {
1645        global update_index_cp ui_status_value
1646
1647        if {![lock_index update]} return
1648
1649        set update_index_cp 0
1650        set pathList [lsort $pathList]
1651        set totalCnt [llength $pathList]
1652        set batch [expr {int($totalCnt * .01) + 1}]
1653        if {$batch > 25} {set batch 25}
1654
1655        set ui_status_value [format \
1656                "$msg... %i/%i files (%.2f%%)" \
1657                $update_index_cp \
1658                $totalCnt \
1659                0.0]
1660        set fd [open "| git update-index --add --remove -z --stdin" w]
1661        fconfigure $fd \
1662                -blocking 0 \
1663                -buffering full \
1664                -buffersize 512 \
1665                -encoding binary \
1666                -translation binary
1667        fileevent $fd writable [list \
1668                write_update_index \
1669                $fd \
1670                $pathList \
1671                $totalCnt \
1672                $batch \
1673                $msg \
1674                $after \
1675                ]
1676}
1677
1678proc write_update_index {fd pathList totalCnt batch msg after} {
1679        global update_index_cp ui_status_value
1680        global file_states current_diff_path
1681
1682        if {$update_index_cp >= $totalCnt} {
1683                close $fd
1684                unlock_index
1685                uplevel #0 $after
1686                return
1687        }
1688
1689        for {set i $batch} \
1690                {$update_index_cp < $totalCnt && $i > 0} \
1691                {incr i -1} {
1692                set path [lindex $pathList $update_index_cp]
1693                incr update_index_cp
1694
1695                switch -glob -- [lindex $file_states($path) 0] {
1696                AD {set new __}
1697                ?D {set new D_}
1698                _O -
1699                AM {set new A_}
1700                U? {
1701                        if {[file exists $path]} {
1702                                set new M_
1703                        } else {
1704                                set new D_
1705                        }
1706                }
1707                ?M {set new M_}
1708                ?? {continue}
1709                }
1710                puts -nonewline $fd "[encoding convertto $path]\0"
1711                display_file $path $new
1712        }
1713
1714        set ui_status_value [format \
1715                "$msg... %i/%i files (%.2f%%)" \
1716                $update_index_cp \
1717                $totalCnt \
1718                [expr {100.0 * $update_index_cp / $totalCnt}]]
1719}
1720
1721proc checkout_index {msg pathList after} {
1722        global update_index_cp ui_status_value
1723
1724        if {![lock_index update]} return
1725
1726        set update_index_cp 0
1727        set pathList [lsort $pathList]
1728        set totalCnt [llength $pathList]
1729        set batch [expr {int($totalCnt * .01) + 1}]
1730        if {$batch > 25} {set batch 25}
1731
1732        set ui_status_value [format \
1733                "$msg... %i/%i files (%.2f%%)" \
1734                $update_index_cp \
1735                $totalCnt \
1736                0.0]
1737        set cmd [list git checkout-index]
1738        lappend cmd --index
1739        lappend cmd --quiet
1740        lappend cmd --force
1741        lappend cmd -z
1742        lappend cmd --stdin
1743        set fd [open "| $cmd " w]
1744        fconfigure $fd \
1745                -blocking 0 \
1746                -buffering full \
1747                -buffersize 512 \
1748                -encoding binary \
1749                -translation binary
1750        fileevent $fd writable [list \
1751                write_checkout_index \
1752                $fd \
1753                $pathList \
1754                $totalCnt \
1755                $batch \
1756                $msg \
1757                $after \
1758                ]
1759}
1760
1761proc write_checkout_index {fd pathList totalCnt batch msg after} {
1762        global update_index_cp ui_status_value
1763        global file_states current_diff_path
1764
1765        if {$update_index_cp >= $totalCnt} {
1766                close $fd
1767                unlock_index
1768                uplevel #0 $after
1769                return
1770        }
1771
1772        for {set i $batch} \
1773                {$update_index_cp < $totalCnt && $i > 0} \
1774                {incr i -1} {
1775                set path [lindex $pathList $update_index_cp]
1776                incr update_index_cp
1777                switch -glob -- [lindex $file_states($path) 0] {
1778                U? {continue}
1779                ?M -
1780                ?D {
1781                        puts -nonewline $fd "[encoding convertto $path]\0"
1782                        display_file $path ?_
1783                }
1784                }
1785        }
1786
1787        set ui_status_value [format \
1788                "$msg... %i/%i files (%.2f%%)" \
1789                $update_index_cp \
1790                $totalCnt \
1791                [expr {100.0 * $update_index_cp / $totalCnt}]]
1792}
1793
1794######################################################################
1795##
1796## branch management
1797
1798proc is_tracking_branch {name} {
1799        global tracking_branches
1800
1801        if {![catch {set info $tracking_branches($name)}]} {
1802                return 1
1803        }
1804        foreach t [array names tracking_branches] {
1805                if {[string match {*/\*} $t] && [string match $t $name]} {
1806                        return 1
1807                }
1808        }
1809        return 0
1810}
1811
1812proc load_all_heads {} {
1813        global all_heads
1814
1815        set all_heads [list]
1816        set fd [open "| git for-each-ref --format=%(refname) refs/heads" r]
1817        while {[gets $fd line] > 0} {
1818                if {[is_tracking_branch $line]} continue
1819                if {![regsub ^refs/heads/ $line {} name]} continue
1820                lappend all_heads $name
1821        }
1822        close $fd
1823
1824        set all_heads [lsort $all_heads]
1825}
1826
1827proc populate_branch_menu {} {
1828        global all_heads disable_on_lock
1829
1830        set m .mbar.branch
1831        set last [$m index last]
1832        for {set i 0} {$i <= $last} {incr i} {
1833                if {[$m type $i] eq {separator}} {
1834                        $m delete $i last
1835                        set new_dol [list]
1836                        foreach a $disable_on_lock {
1837                                if {[lindex $a 0] ne $m || [lindex $a 2] < $i} {
1838                                        lappend new_dol $a
1839                                }
1840                        }
1841                        set disable_on_lock $new_dol
1842                        break
1843                }
1844        }
1845
1846        if {$all_heads ne {}} {
1847                $m add separator
1848        }
1849        foreach b $all_heads {
1850                $m add radiobutton \
1851                        -label $b \
1852                        -command [list switch_branch $b] \
1853                        -variable current_branch \
1854                        -value $b \
1855                        -font font_ui
1856                lappend disable_on_lock \
1857                        [list $m entryconf [$m index last] -state]
1858        }
1859}
1860
1861proc all_tracking_branches {} {
1862        global tracking_branches
1863
1864        set all_trackings {}
1865        set cmd {}
1866        foreach name [array names tracking_branches] {
1867                if {[regsub {/\*$} $name {} name]} {
1868                        lappend cmd $name
1869                } else {
1870                        regsub ^refs/(heads|remotes)/ $name {} name
1871                        lappend all_trackings $name
1872                }
1873        }
1874
1875        if {$cmd ne {}} {
1876                set fd [open "| git for-each-ref --format=%(refname) $cmd" r]
1877                while {[gets $fd name] > 0} {
1878                        regsub ^refs/(heads|remotes)/ $name {} name
1879                        lappend all_trackings $name
1880                }
1881                close $fd
1882        }
1883
1884        return [lsort -unique $all_trackings]
1885}
1886
1887proc do_create_branch_action {w} {
1888        global all_heads null_sha1 repo_config
1889        global create_branch_checkout create_branch_revtype
1890        global create_branch_head create_branch_trackinghead
1891        global create_branch_name create_branch_revexp
1892
1893        set newbranch $create_branch_name
1894        if {$newbranch eq {}
1895                || $newbranch eq $repo_config(gui.newbranchtemplate)} {
1896                tk_messageBox \
1897                        -icon error \
1898                        -type ok \
1899                        -title [wm title $w] \
1900                        -parent $w \
1901                        -message "Please supply a branch name."
1902                focus $w.desc.name_t
1903                return
1904        }
1905        if {![catch {git show-ref --verify -- "refs/heads/$newbranch"}]} {
1906                tk_messageBox \
1907                        -icon error \
1908                        -type ok \
1909                        -title [wm title $w] \
1910                        -parent $w \
1911                        -message "Branch '$newbranch' already exists."
1912                focus $w.desc.name_t
1913                return
1914        }
1915        if {[catch {git check-ref-format "heads/$newbranch"}]} {
1916                tk_messageBox \
1917                        -icon error \
1918                        -type ok \
1919                        -title [wm title $w] \
1920                        -parent $w \
1921                        -message "We do not like '$newbranch' as a branch name."
1922                focus $w.desc.name_t
1923                return
1924        }
1925
1926        set rev {}
1927        switch -- $create_branch_revtype {
1928        head {set rev $create_branch_head}
1929        tracking {set rev $create_branch_trackinghead}
1930        expression {set rev $create_branch_revexp}
1931        }
1932        if {[catch {set cmt [git rev-parse --verify "${rev}^0"]}]} {
1933                tk_messageBox \
1934                        -icon error \
1935                        -type ok \
1936                        -title [wm title $w] \
1937                        -parent $w \
1938                        -message "Invalid starting revision: $rev"
1939                return
1940        }
1941        set cmd [list git update-ref]
1942        lappend cmd -m
1943        lappend cmd "branch: Created from $rev"
1944        lappend cmd "refs/heads/$newbranch"
1945        lappend cmd $cmt
1946        lappend cmd $null_sha1
1947        if {[catch {eval exec $cmd} err]} {
1948                tk_messageBox \
1949                        -icon error \
1950                        -type ok \
1951                        -title [wm title $w] \
1952                        -parent $w \
1953                        -message "Failed to create '$newbranch'.\n\n$err"
1954                return
1955        }
1956
1957        lappend all_heads $newbranch
1958        set all_heads [lsort $all_heads]
1959        populate_branch_menu
1960        destroy $w
1961        if {$create_branch_checkout} {
1962                switch_branch $newbranch
1963        }
1964}
1965
1966proc radio_selector {varname value args} {
1967        upvar #0 $varname var
1968        set var $value
1969}
1970
1971trace add variable create_branch_head write \
1972        [list radio_selector create_branch_revtype head]
1973trace add variable create_branch_trackinghead write \
1974        [list radio_selector create_branch_revtype tracking]
1975
1976trace add variable delete_branch_head write \
1977        [list radio_selector delete_branch_checktype head]
1978trace add variable delete_branch_trackinghead write \
1979        [list radio_selector delete_branch_checktype tracking]
1980
1981proc do_create_branch {} {
1982        global all_heads current_branch repo_config
1983        global create_branch_checkout create_branch_revtype
1984        global create_branch_head create_branch_trackinghead
1985        global create_branch_name create_branch_revexp
1986
1987        set w .branch_editor
1988        toplevel $w
1989        wm geometry $w "+[winfo rootx .]+[winfo rooty .]"
1990
1991        label $w.header -text {Create New Branch} \
1992                -font font_uibold
1993        pack $w.header -side top -fill x
1994
1995        frame $w.buttons
1996        button $w.buttons.create -text Create \
1997                -font font_ui \
1998                -default active \
1999                -command [list do_create_branch_action $w]
2000        pack $w.buttons.create -side right
2001        button $w.buttons.cancel -text {Cancel} \
2002                -font font_ui \
2003                -command [list destroy $w]
2004        pack $w.buttons.cancel -side right -padx 5
2005        pack $w.buttons -side bottom -fill x -pady 10 -padx 10
2006
2007        labelframe $w.desc \
2008                -text {Branch Description} \
2009                -font font_ui
2010        label $w.desc.name_l -text {Name:} -font font_ui
2011        entry $w.desc.name_t \
2012                -borderwidth 1 \
2013                -relief sunken \
2014                -width 40 \
2015                -textvariable create_branch_name \
2016                -font font_ui \
2017                -validate key \
2018                -validatecommand {
2019                        if {%d == 1 && [regexp {[~^:?*\[\0- ]} %S]} {return 0}
2020                        return 1
2021                }
2022        grid $w.desc.name_l $w.desc.name_t -sticky we -padx {0 5}
2023        grid columnconfigure $w.desc 1 -weight 1
2024        pack $w.desc -anchor nw -fill x -pady 5 -padx 5
2025
2026        labelframe $w.from \
2027                -text {Starting Revision} \
2028                -font font_ui
2029        radiobutton $w.from.head_r \
2030                -text {Local Branch:} \
2031                -value head \
2032                -variable create_branch_revtype \
2033                -font font_ui
2034        eval tk_optionMenu $w.from.head_m create_branch_head $all_heads
2035        grid $w.from.head_r $w.from.head_m -sticky w
2036        set all_trackings [all_tracking_branches]
2037        if {$all_trackings ne {}} {
2038                set create_branch_trackinghead [lindex $all_trackings 0]
2039                radiobutton $w.from.tracking_r \
2040                        -text {Tracking Branch:} \
2041                        -value tracking \
2042                        -variable create_branch_revtype \
2043                        -font font_ui
2044                eval tk_optionMenu $w.from.tracking_m \
2045                        create_branch_trackinghead \
2046                        $all_trackings
2047                grid $w.from.tracking_r $w.from.tracking_m -sticky w
2048        }
2049        radiobutton $w.from.exp_r \
2050                -text {Revision Expression:} \
2051                -value expression \
2052                -variable create_branch_revtype \
2053                -font font_ui
2054        entry $w.from.exp_t \
2055                -borderwidth 1 \
2056                -relief sunken \
2057                -width 50 \
2058                -textvariable create_branch_revexp \
2059                -font font_ui \
2060                -validate key \
2061                -validatecommand {
2062                        if {%d == 1 && [regexp {\s} %S]} {return 0}
2063                        if {%d == 1 && [string length %S] > 0} {
2064                                set create_branch_revtype expression
2065                        }
2066                        return 1
2067                }
2068        grid $w.from.exp_r $w.from.exp_t -sticky we -padx {0 5}
2069        grid columnconfigure $w.from 1 -weight 1
2070        pack $w.from -anchor nw -fill x -pady 5 -padx 5
2071
2072        labelframe $w.postActions \
2073                -text {Post Creation Actions} \
2074                -font font_ui
2075        checkbutton $w.postActions.checkout \
2076                -text {Checkout after creation} \
2077                -variable create_branch_checkout \
2078                -font font_ui
2079        pack $w.postActions.checkout -anchor nw
2080        pack $w.postActions -anchor nw -fill x -pady 5 -padx 5
2081
2082        set create_branch_checkout 1
2083        set create_branch_head $current_branch
2084        set create_branch_revtype head
2085        set create_branch_name $repo_config(gui.newbranchtemplate)
2086        set create_branch_revexp {}
2087
2088        bind $w <Visibility> "
2089                grab $w
2090                $w.desc.name_t icursor end
2091                focus $w.desc.name_t
2092        "
2093        bind $w <Key-Escape> "destroy $w"
2094        bind $w <Key-Return> "do_create_branch_action $w;break"
2095        wm title $w "[appname] ([reponame]): Create Branch"
2096        tkwait window $w
2097}
2098
2099proc do_delete_branch_action {w} {
2100        global all_heads
2101        global delete_branch_checktype delete_branch_head delete_branch_trackinghead
2102
2103        set check_rev {}
2104        switch -- $delete_branch_checktype {
2105        head {set check_rev $delete_branch_head}
2106        tracking {set check_rev $delete_branch_trackinghead}
2107        always {set check_rev {:none}}
2108        }
2109        if {$check_rev eq {:none}} {
2110                set check_cmt {}
2111        } elseif {[catch {set check_cmt [git rev-parse --verify "${check_rev}^0"]}]} {
2112                tk_messageBox \
2113                        -icon error \
2114                        -type ok \
2115                        -title [wm title $w] \
2116                        -parent $w \
2117                        -message "Invalid check revision: $check_rev"
2118                return
2119        }
2120
2121        set to_delete [list]
2122        set not_merged [list]
2123        foreach i [$w.list.l curselection] {
2124                set b [$w.list.l get $i]
2125                if {[catch {set o [git rev-parse --verify $b]}]} continue
2126                if {$check_cmt ne {}} {
2127                        if {$b eq $check_rev} continue
2128                        if {[catch {set m [git merge-base $o $check_cmt]}]} continue
2129                        if {$o ne $m} {
2130                                lappend not_merged $b
2131                                continue
2132                        }
2133                }
2134                lappend to_delete [list $b $o]
2135        }
2136        if {$not_merged ne {}} {
2137                set msg "The following branches are not completely merged into $check_rev:
2138
2139 - [join $not_merged "\n - "]"
2140                tk_messageBox \
2141                        -icon info \
2142                        -type ok \
2143                        -title [wm title $w] \
2144                        -parent $w \
2145                        -message $msg
2146        }
2147        if {$to_delete eq {}} return
2148        if {$delete_branch_checktype eq {always}} {
2149                set msg {Recovering deleted branches is difficult.
2150
2151Delete the selected branches?}
2152                if {[tk_messageBox \
2153                        -icon warning \
2154                        -type yesno \
2155                        -title [wm title $w] \
2156                        -parent $w \
2157                        -message $msg] ne yes} {
2158                        return
2159                }
2160        }
2161
2162        set failed {}
2163        foreach i $to_delete {
2164                set b [lindex $i 0]
2165                set o [lindex $i 1]
2166                if {[catch {git update-ref -d "refs/heads/$b" $o} err]} {
2167                        append failed " - $b: $err\n"
2168                } else {
2169                        set x [lsearch -sorted -exact $all_heads $b]
2170                        if {$x >= 0} {
2171                                set all_heads [lreplace $all_heads $x $x]
2172                        }
2173                }
2174        }
2175
2176        if {$failed ne {}} {
2177                tk_messageBox \
2178                        -icon error \
2179                        -type ok \
2180                        -title [wm title $w] \
2181                        -parent $w \
2182                        -message "Failed to delete branches:\n$failed"
2183        }
2184
2185        set all_heads [lsort $all_heads]
2186        populate_branch_menu
2187        destroy $w
2188}
2189
2190proc do_delete_branch {} {
2191        global all_heads tracking_branches current_branch
2192        global delete_branch_checktype delete_branch_head delete_branch_trackinghead
2193
2194        set w .branch_editor
2195        toplevel $w
2196        wm geometry $w "+[winfo rootx .]+[winfo rooty .]"
2197
2198        label $w.header -text {Delete Local Branch} \
2199                -font font_uibold
2200        pack $w.header -side top -fill x
2201
2202        frame $w.buttons
2203        button $w.buttons.create -text Delete \
2204                -font font_ui \
2205                -command [list do_delete_branch_action $w]
2206        pack $w.buttons.create -side right
2207        button $w.buttons.cancel -text {Cancel} \
2208                -font font_ui \
2209                -command [list destroy $w]
2210        pack $w.buttons.cancel -side right -padx 5
2211        pack $w.buttons -side bottom -fill x -pady 10 -padx 10
2212
2213        labelframe $w.list \
2214                -text {Local Branches} \
2215                -font font_ui
2216        listbox $w.list.l \
2217                -height 10 \
2218                -width 70 \
2219                -selectmode extended \
2220                -yscrollcommand [list $w.list.sby set] \
2221                -font font_ui
2222        foreach h $all_heads {
2223                if {$h ne $current_branch} {
2224                        $w.list.l insert end $h
2225                }
2226        }
2227        scrollbar $w.list.sby -command [list $w.list.l yview]
2228        pack $w.list.sby -side right -fill y
2229        pack $w.list.l -side left -fill both -expand 1
2230        pack $w.list -fill both -expand 1 -pady 5 -padx 5
2231
2232        labelframe $w.validate \
2233                -text {Delete Only If} \
2234                -font font_ui
2235        radiobutton $w.validate.head_r \
2236                -text {Merged Into Local Branch:} \
2237                -value head \
2238                -variable delete_branch_checktype \
2239                -font font_ui
2240        eval tk_optionMenu $w.validate.head_m delete_branch_head $all_heads
2241        grid $w.validate.head_r $w.validate.head_m -sticky w
2242        set all_trackings [all_tracking_branches]
2243        if {$all_trackings ne {}} {
2244                set delete_branch_trackinghead [lindex $all_trackings 0]
2245                radiobutton $w.validate.tracking_r \
2246                        -text {Merged Into Tracking Branch:} \
2247                        -value tracking \
2248                        -variable delete_branch_checktype \
2249                        -font font_ui
2250                eval tk_optionMenu $w.validate.tracking_m \
2251                        delete_branch_trackinghead \
2252                        $all_trackings
2253                grid $w.validate.tracking_r $w.validate.tracking_m -sticky w
2254        }
2255        radiobutton $w.validate.always_r \
2256                -text {Always (Do not perform merge checks)} \
2257                -value always \
2258                -variable delete_branch_checktype \
2259                -font font_ui
2260        grid $w.validate.always_r -columnspan 2 -sticky w
2261        grid columnconfigure $w.validate 1 -weight 1
2262        pack $w.validate -anchor nw -fill x -pady 5 -padx 5
2263
2264        set delete_branch_head $current_branch
2265        set delete_branch_checktype head
2266
2267        bind $w <Visibility> "grab $w; focus $w"
2268        bind $w <Key-Escape> "destroy $w"
2269        wm title $w "[appname] ([reponame]): Delete Branch"
2270        tkwait window $w
2271}
2272
2273proc switch_branch {new_branch} {
2274        global HEAD commit_type current_branch repo_config
2275
2276        if {![lock_index switch]} return
2277
2278        # -- Our in memory state should match the repository.
2279        #
2280        repository_state curType curHEAD curMERGE_HEAD
2281        if {[string match amend* $commit_type]
2282                && $curType eq {normal}
2283                && $curHEAD eq $HEAD} {
2284        } elseif {$commit_type ne $curType || $HEAD ne $curHEAD} {
2285                info_popup {Last scanned state does not match repository state.
2286
2287Another Git program has modified this repository
2288since the last scan.  A rescan must be performed
2289before the current branch can be changed.
2290
2291The rescan will be automatically started now.
2292}
2293                unlock_index
2294                rescan {set ui_status_value {Ready.}}
2295                return
2296        }
2297
2298        # -- Don't do a pointless switch.
2299        #
2300        if {$current_branch eq $new_branch} {
2301                unlock_index
2302                return
2303        }
2304
2305        if {$repo_config(gui.trustmtime) eq {true}} {
2306                switch_branch_stage2 {} $new_branch
2307        } else {
2308                set ui_status_value {Refreshing file status...}
2309                set cmd [list git update-index]
2310                lappend cmd -q
2311                lappend cmd --unmerged
2312                lappend cmd --ignore-missing
2313                lappend cmd --refresh
2314                set fd_rf [open "| $cmd" r]
2315                fconfigure $fd_rf -blocking 0 -translation binary
2316                fileevent $fd_rf readable \
2317                        [list switch_branch_stage2 $fd_rf $new_branch]
2318        }
2319}
2320
2321proc switch_branch_stage2 {fd_rf new_branch} {
2322        global ui_status_value HEAD
2323
2324        if {$fd_rf ne {}} {
2325                read $fd_rf
2326                if {![eof $fd_rf]} return
2327                close $fd_rf
2328        }
2329
2330        set ui_status_value "Updating working directory to '$new_branch'..."
2331        set cmd [list git read-tree]
2332        lappend cmd -m
2333        lappend cmd -u
2334        lappend cmd --exclude-per-directory=.gitignore
2335        lappend cmd $HEAD
2336        lappend cmd $new_branch
2337        set fd_rt [open "| $cmd" r]
2338        fconfigure $fd_rt -blocking 0 -translation binary
2339        fileevent $fd_rt readable \
2340                [list switch_branch_readtree_wait $fd_rt $new_branch]
2341}
2342
2343proc switch_branch_readtree_wait {fd_rt new_branch} {
2344        global selected_commit_type commit_type HEAD MERGE_HEAD PARENT
2345        global current_branch
2346        global ui_comm ui_status_value
2347
2348        # -- We never get interesting output on stdout; only stderr.
2349        #
2350        read $fd_rt
2351        fconfigure $fd_rt -blocking 1
2352        if {![eof $fd_rt]} {
2353                fconfigure $fd_rt -blocking 0
2354                return
2355        }
2356
2357        # -- The working directory wasn't in sync with the index and
2358        #    we'd have to overwrite something to make the switch. A
2359        #    merge is required.
2360        #
2361        if {[catch {close $fd_rt} err]} {
2362                regsub {^fatal: } $err {} err
2363                warn_popup "File level merge required.
2364
2365$err
2366
2367Staying on branch '$current_branch'."
2368                set ui_status_value "Aborted checkout of '$new_branch' (file level merging is required)."
2369                unlock_index
2370                return
2371        }
2372
2373        # -- Update the symbolic ref.  Core git doesn't even check for failure
2374        #    here, it Just Works(tm).  If it doesn't we are in some really ugly
2375        #    state that is difficult to recover from within git-gui.
2376        #
2377        if {[catch {git symbolic-ref HEAD "refs/heads/$new_branch"} err]} {
2378                error_popup "Failed to set current branch.
2379
2380This working directory is only partially switched.
2381We successfully updated your files, but failed to
2382update an internal Git file.
2383
2384This should not have occurred.  [appname] will now
2385close and give up.
2386
2387$err"
2388                do_quit
2389                return
2390        }
2391
2392        # -- Update our repository state.  If we were previously in amend mode
2393        #    we need to toss the current buffer and do a full rescan to update
2394        #    our file lists.  If we weren't in amend mode our file lists are
2395        #    accurate and we can avoid the rescan.
2396        #
2397        unlock_index
2398        set selected_commit_type new
2399        if {[string match amend* $commit_type]} {
2400                $ui_comm delete 0.0 end
2401                $ui_comm edit reset
2402                $ui_comm edit modified false
2403                rescan {set ui_status_value "Checked out branch '$current_branch'."}
2404        } else {
2405                repository_state commit_type HEAD MERGE_HEAD
2406                set PARENT $HEAD
2407                set ui_status_value "Checked out branch '$current_branch'."
2408        }
2409}
2410
2411######################################################################
2412##
2413## remote management
2414
2415proc load_all_remotes {} {
2416        global repo_config
2417        global all_remotes tracking_branches
2418
2419        set all_remotes [list]
2420        array unset tracking_branches
2421
2422        set rm_dir [gitdir remotes]
2423        if {[file isdirectory $rm_dir]} {
2424                set all_remotes [glob \
2425                        -types f \
2426                        -tails \
2427                        -nocomplain \
2428                        -directory $rm_dir *]
2429
2430                foreach name $all_remotes {
2431                        catch {
2432                                set fd [open [file join $rm_dir $name] r]
2433                                while {[gets $fd line] >= 0} {
2434                                        if {![regexp {^Pull:[   ]*([^:]+):(.+)$} \
2435                                                $line line src dst]} continue
2436                                        if {![regexp ^refs/ $dst]} {
2437                                                set dst "refs/heads/$dst"
2438                                        }
2439                                        set tracking_branches($dst) [list $name $src]
2440                                }
2441                                close $fd
2442                        }
2443                }
2444        }
2445
2446        foreach line [array names repo_config remote.*.url] {
2447                if {![regexp ^remote\.(.*)\.url\$ $line line name]} continue
2448                lappend all_remotes $name
2449
2450                if {[catch {set fl $repo_config(remote.$name.fetch)}]} {
2451                        set fl {}
2452                }
2453                foreach line $fl {
2454                        if {![regexp {^([^:]+):(.+)$} $line line src dst]} continue
2455                        if {![regexp ^refs/ $dst]} {
2456                                set dst "refs/heads/$dst"
2457                        }
2458                        set tracking_branches($dst) [list $name $src]
2459                }
2460        }
2461
2462        set all_remotes [lsort -unique $all_remotes]
2463}
2464
2465proc populate_fetch_menu {} {
2466        global all_remotes repo_config
2467
2468        set m .mbar.fetch
2469        foreach r $all_remotes {
2470                set enable 0
2471                if {![catch {set a $repo_config(remote.$r.url)}]} {
2472                        if {![catch {set a $repo_config(remote.$r.fetch)}]} {
2473                                set enable 1
2474                        }
2475                } else {
2476                        catch {
2477                                set fd [open [gitdir remotes $r] r]
2478                                while {[gets $fd n] >= 0} {
2479                                        if {[regexp {^Pull:[ \t]*([^:]+):} $n]} {
2480                                                set enable 1
2481                                                break
2482                                        }
2483                                }
2484                                close $fd
2485                        }
2486                }
2487
2488                if {$enable} {
2489                        $m add command \
2490                                -label "Fetch from $r..." \
2491                                -command [list fetch_from $r] \
2492                                -font font_ui
2493                }
2494        }
2495}
2496
2497proc populate_push_menu {} {
2498        global all_remotes repo_config
2499
2500        set m .mbar.push
2501        set fast_count 0
2502        foreach r $all_remotes {
2503                set enable 0
2504                if {![catch {set a $repo_config(remote.$r.url)}]} {
2505                        if {![catch {set a $repo_config(remote.$r.push)}]} {
2506                                set enable 1
2507                        }
2508                } else {
2509                        catch {
2510                                set fd [open [gitdir remotes $r] r]
2511                                while {[gets $fd n] >= 0} {
2512                                        if {[regexp {^Push:[ \t]*([^:]+):} $n]} {
2513                                                set enable 1
2514                                                break
2515                                        }
2516                                }
2517                                close $fd
2518                        }
2519                }
2520
2521                if {$enable} {
2522                        if {!$fast_count} {
2523                                $m add separator
2524                        }
2525                        $m add command \
2526                                -label "Push to $r..." \
2527                                -command [list push_to $r] \
2528                                -font font_ui
2529                        incr fast_count
2530                }
2531        }
2532}
2533
2534proc start_push_anywhere_action {w} {
2535        global push_urltype push_remote push_url push_thin push_tags
2536
2537        set r_url {}
2538        switch -- $push_urltype {
2539        remote {set r_url $push_remote}
2540        url {set r_url $push_url}
2541        }
2542        if {$r_url eq {}} return
2543
2544        set cmd [list git push]
2545        lappend cmd -v
2546        if {$push_thin} {
2547                lappend cmd --thin
2548        }
2549        if {$push_tags} {
2550                lappend cmd --tags
2551        }
2552        lappend cmd $r_url
2553        set cnt 0
2554        foreach i [$w.source.l curselection] {
2555                set b [$w.source.l get $i]
2556                lappend cmd "refs/heads/$b:refs/heads/$b"
2557                incr cnt
2558        }
2559        if {$cnt == 0} {
2560                return
2561        } elseif {$cnt == 1} {
2562                set unit branch
2563        } else {
2564                set unit branches
2565        }
2566
2567        set cons [new_console "push $r_url" "Pushing $cnt $unit to $r_url"]
2568        console_exec $cons $cmd console_done
2569        destroy $w
2570}
2571
2572trace add variable push_remote write \
2573        [list radio_selector push_urltype remote]
2574
2575proc do_push_anywhere {} {
2576        global all_heads all_remotes current_branch
2577        global push_urltype push_remote push_url push_thin push_tags
2578
2579        set w .push_setup
2580        toplevel $w
2581        wm geometry $w "+[winfo rootx .]+[winfo rooty .]"
2582
2583        label $w.header -text {Push Branches} -font font_uibold
2584        pack $w.header -side top -fill x
2585
2586        frame $w.buttons
2587        button $w.buttons.create -text Push \
2588                -font font_ui \
2589                -command [list start_push_anywhere_action $w]
2590        pack $w.buttons.create -side right
2591        button $w.buttons.cancel -text {Cancel} \
2592                -font font_ui \
2593                -command [list destroy $w]
2594        pack $w.buttons.cancel -side right -padx 5
2595        pack $w.buttons -side bottom -fill x -pady 10 -padx 10
2596
2597        labelframe $w.source \
2598                -text {Source Branches} \
2599                -font font_ui
2600        listbox $w.source.l \
2601                -height 10 \
2602                -width 70 \
2603                -selectmode extended \
2604                -yscrollcommand [list $w.source.sby set] \
2605                -font font_ui
2606        foreach h $all_heads {
2607                $w.source.l insert end $h
2608                if {$h eq $current_branch} {
2609                        $w.source.l select set end
2610                }
2611        }
2612        scrollbar $w.source.sby -command [list $w.source.l yview]
2613        pack $w.source.sby -side right -fill y
2614        pack $w.source.l -side left -fill both -expand 1
2615        pack $w.source -fill both -expand 1 -pady 5 -padx 5
2616
2617        labelframe $w.dest \
2618                -text {Destination Repository} \
2619                -font font_ui
2620        if {$all_remotes ne {}} {
2621                radiobutton $w.dest.remote_r \
2622                        -text {Remote:} \
2623                        -value remote \
2624                        -variable push_urltype \
2625                        -font font_ui
2626                eval tk_optionMenu $w.dest.remote_m push_remote $all_remotes
2627                grid $w.dest.remote_r $w.dest.remote_m -sticky w
2628                if {[lsearch -sorted -exact $all_remotes origin] != -1} {
2629                        set push_remote origin
2630                } else {
2631                        set push_remote [lindex $all_remotes 0]
2632                }
2633                set push_urltype remote
2634        } else {
2635                set push_urltype url
2636        }
2637        radiobutton $w.dest.url_r \
2638                -text {Arbitrary URL:} \
2639                -value url \
2640                -variable push_urltype \
2641                -font font_ui
2642        entry $w.dest.url_t \
2643                -borderwidth 1 \
2644                -relief sunken \
2645                -width 50 \
2646                -textvariable push_url \
2647                -font font_ui \
2648                -validate key \
2649                -validatecommand {
2650                        if {%d == 1 && [regexp {\s} %S]} {return 0}
2651                        if {%d == 1 && [string length %S] > 0} {
2652                                set push_urltype url
2653                        }
2654                        return 1
2655                }
2656        grid $w.dest.url_r $w.dest.url_t -sticky we -padx {0 5}
2657        grid columnconfigure $w.dest 1 -weight 1
2658        pack $w.dest -anchor nw -fill x -pady 5 -padx 5
2659
2660        labelframe $w.options \
2661                -text {Transfer Options} \
2662                -font font_ui
2663        checkbutton $w.options.thin \
2664                -text {Use thin pack (for slow network connections)} \
2665                -variable push_thin \
2666                -font font_ui
2667        grid $w.options.thin -columnspan 2 -sticky w
2668        checkbutton $w.options.tags \
2669                -text {Include tags} \
2670                -variable push_tags \
2671                -font font_ui
2672        grid $w.options.tags -columnspan 2 -sticky w
2673        grid columnconfigure $w.options 1 -weight 1
2674        pack $w.options -anchor nw -fill x -pady 5 -padx 5
2675
2676        set push_url {}
2677        set push_thin 0
2678        set push_tags 0
2679
2680        bind $w <Visibility> "grab $w"
2681        bind $w <Key-Escape> "destroy $w"
2682        wm title $w "[appname] ([reponame]): Push"
2683        tkwait window $w
2684}
2685
2686######################################################################
2687##
2688## merge
2689
2690proc can_merge {} {
2691        global HEAD commit_type file_states
2692
2693        if {[string match amend* $commit_type]} {
2694                info_popup {Cannot merge while amending.
2695
2696You must finish amending this commit before
2697starting any type of merge.
2698}
2699                return 0
2700        }
2701
2702        if {[committer_ident] eq {}} {return 0}
2703        if {![lock_index merge]} {return 0}
2704
2705        # -- Our in memory state should match the repository.
2706        #
2707        repository_state curType curHEAD curMERGE_HEAD
2708        if {$commit_type ne $curType || $HEAD ne $curHEAD} {
2709                info_popup {Last scanned state does not match repository state.
2710
2711Another Git program has modified this repository
2712since the last scan.  A rescan must be performed
2713before a merge can be performed.
2714
2715The rescan will be automatically started now.
2716}
2717                unlock_index
2718                rescan {set ui_status_value {Ready.}}
2719                return 0
2720        }
2721
2722        foreach path [array names file_states] {
2723                switch -glob -- [lindex $file_states($path) 0] {
2724                _O {
2725                        continue; # and pray it works!
2726                }
2727                U? {
2728                        error_popup "You are in the middle of a conflicted merge.
2729
2730File [short_path $path] has merge conflicts.
2731
2732You must resolve them, add the file, and commit to
2733complete the current merge.  Only then can you
2734begin another merge.
2735"
2736                        unlock_index
2737                        return 0
2738                }
2739                ?? {
2740                        error_popup "You are in the middle of a change.
2741
2742File [short_path $path] is modified.
2743
2744You should complete the current commit before
2745starting a merge.  Doing so will help you abort
2746a failed merge, should the need arise.
2747"
2748                        unlock_index
2749                        return 0
2750                }
2751                }
2752        }
2753
2754        return 1
2755}
2756
2757proc visualize_local_merge {w} {
2758        set revs {}
2759        foreach i [$w.source.l curselection] {
2760                lappend revs [$w.source.l get $i]
2761        }
2762        if {$revs eq {}} return
2763        lappend revs --not HEAD
2764        do_gitk $revs
2765}
2766
2767proc start_local_merge_action {w} {
2768        global HEAD ui_status_value current_branch
2769
2770        set cmd [list git merge]
2771        set names {}
2772        set revcnt 0
2773        foreach i [$w.source.l curselection] {
2774                set b [$w.source.l get $i]
2775                lappend cmd $b
2776                lappend names $b
2777                incr revcnt
2778        }
2779
2780        if {$revcnt == 0} {
2781                return
2782        } elseif {$revcnt == 1} {
2783                set unit branch
2784        } elseif {$revcnt <= 15} {
2785                set unit branches
2786        } else {
2787                tk_messageBox \
2788                        -icon error \
2789                        -type ok \
2790                        -title [wm title $w] \
2791                        -parent $w \
2792                        -message "Too many branches selected.
2793
2794You have requested to merge $revcnt branches
2795in an octopus merge.  This exceeds Git's
2796internal limit of 15 branches per merge.
2797
2798Please select fewer branches.  To merge more
2799than 15 branches, merge the branches in batches.
2800"
2801                return
2802        }
2803
2804        set msg "Merging $current_branch, [join $names {, }]"
2805        set ui_status_value "$msg..."
2806        set cons [new_console "Merge" $msg]
2807        console_exec $cons $cmd [list finish_merge $revcnt]
2808        bind $w <Destroy> {}
2809        destroy $w
2810}
2811
2812proc finish_merge {revcnt w ok} {
2813        console_done $w $ok
2814        if {$ok} {
2815                set msg {Merge completed successfully.}
2816        } else {
2817                if {$revcnt != 1} {
2818                        info_popup "Octopus merge failed.
2819
2820Your merge of $revcnt branches has failed.
2821
2822There are file-level conflicts between the
2823branches which must be resolved manually.
2824
2825The working directory will now be reset.
2826
2827You can attempt this merge again
2828by merging only one branch at a time." $w
2829
2830                        set fd [open "| git read-tree --reset -u HEAD" r]
2831                        fconfigure $fd -blocking 0 -translation binary
2832                        fileevent $fd readable [list reset_hard_wait $fd]
2833                        set ui_status_value {Aborting... please wait...}
2834                        return
2835                }
2836
2837                set msg {Merge failed.  Conflict resolution is required.}
2838        }
2839        unlock_index
2840        rescan [list set ui_status_value $msg]
2841}
2842
2843proc do_local_merge {} {
2844        global current_branch
2845
2846        if {![can_merge]} return
2847
2848        set w .merge_setup
2849        toplevel $w
2850        wm geometry $w "+[winfo rootx .]+[winfo rooty .]"
2851
2852        label $w.header \
2853                -text "Merge Into $current_branch" \
2854                -font font_uibold
2855        pack $w.header -side top -fill x
2856
2857        frame $w.buttons
2858        button $w.buttons.visualize -text Visualize \
2859                -font font_ui \
2860                -command [list visualize_local_merge $w]
2861        pack $w.buttons.visualize -side left
2862        button $w.buttons.create -text Merge \
2863                -font font_ui \
2864                -command [list start_local_merge_action $w]
2865        pack $w.buttons.create -side right
2866        button $w.buttons.cancel -text {Cancel} \
2867                -font font_ui \
2868                -command [list destroy $w]
2869        pack $w.buttons.cancel -side right -padx 5
2870        pack $w.buttons -side bottom -fill x -pady 10 -padx 10
2871
2872        labelframe $w.source \
2873                -text {Source Branches} \
2874                -font font_ui
2875        listbox $w.source.l \
2876                -height 10 \
2877                -width 70 \
2878                -selectmode extended \
2879                -yscrollcommand [list $w.source.sby set] \
2880                -font font_ui
2881        scrollbar $w.source.sby -command [list $w.source.l yview]
2882        pack $w.source.sby -side right -fill y
2883        pack $w.source.l -side left -fill both -expand 1
2884        pack $w.source -fill both -expand 1 -pady 5 -padx 5
2885
2886        set cmd [list git for-each-ref]
2887        lappend cmd {--format=%(objectname) %(refname)}
2888        lappend cmd refs/heads
2889        lappend cmd refs/remotes
2890        set fr_fd [open "| $cmd" r]
2891        fconfigure $fr_fd -translation binary
2892        while {[gets $fr_fd line] > 0} {
2893                set line [split $line { }]
2894                set sha1([lindex $line 0]) [lindex $line 1]
2895        }
2896        close $fr_fd
2897
2898        set to_show {}
2899        set fr_fd [open "| git rev-list --all --not HEAD"]
2900        while {[gets $fr_fd line] > 0} {
2901                if {[catch {set ref $sha1($line)}]} continue
2902                regsub ^refs/(heads|remotes)/ $ref {} ref
2903                lappend to_show $ref
2904        }
2905        close $fr_fd
2906
2907        foreach ref [lsort -unique $to_show] {
2908                $w.source.l insert end $ref
2909        }
2910
2911        bind $w <Visibility> "grab $w"
2912        bind $w <Key-Escape> "unlock_index;destroy $w"
2913        bind $w <Destroy> unlock_index
2914        wm title $w "[appname] ([reponame]): Merge"
2915        tkwait window $w
2916}
2917
2918proc do_reset_hard {} {
2919        global HEAD commit_type file_states
2920
2921        if {[string match amend* $commit_type]} {
2922                info_popup {Cannot abort while amending.
2923
2924You must finish amending this commit.
2925}
2926                return
2927        }
2928
2929        if {![lock_index abort]} return
2930
2931        if {[string match *merge* $commit_type]} {
2932                set op merge
2933        } else {
2934                set op commit
2935        }
2936
2937        if {[ask_popup "Abort $op?
2938
2939Aborting the current $op will cause
2940*ALL* uncommitted changes to be lost.
2941
2942Continue with aborting the current $op?"] eq {yes}} {
2943                set fd [open "| git read-tree --reset -u HEAD" r]
2944                fconfigure $fd -blocking 0 -translation binary
2945                fileevent $fd readable [list reset_hard_wait $fd]
2946                set ui_status_value {Aborting... please wait...}
2947        } else {
2948                unlock_index
2949        }
2950}
2951
2952proc reset_hard_wait {fd} {
2953        global ui_comm
2954
2955        read $fd
2956        if {[eof $fd]} {
2957                close $fd
2958                unlock_index
2959
2960                $ui_comm delete 0.0 end
2961                $ui_comm edit modified false
2962
2963                catch {file delete [gitdir MERGE_HEAD]}
2964                catch {file delete [gitdir rr-cache MERGE_RR]}
2965                catch {file delete [gitdir SQUASH_MSG]}
2966                catch {file delete [gitdir MERGE_MSG]}
2967                catch {file delete [gitdir GITGUI_MSG]}
2968
2969                rescan {set ui_status_value {Abort completed.  Ready.}}
2970        }
2971}
2972
2973######################################################################
2974##
2975## browser
2976
2977set next_browser_id 0
2978
2979proc new_browser {commit} {
2980        global next_browser_id cursor_ptr M1B
2981        global browser_commit browser_status browser_stack browser_path browser_busy
2982
2983        set w .browser[incr next_browser_id]
2984        set w_list $w.list.l
2985        set browser_commit($w_list) $commit
2986        set browser_status($w_list) {Starting...}
2987        set browser_stack($w_list) {}
2988        set browser_path($w_list) $browser_commit($w_list):
2989        set browser_busy($w_list) 1
2990
2991        toplevel $w
2992        label $w.path -textvariable browser_path($w_list) \
2993                -anchor w \
2994                -justify left \
2995                -borderwidth 1 \
2996                -relief sunken \
2997                -font font_uibold
2998        pack $w.path -anchor w -side top -fill x
2999
3000        frame $w.list
3001        text $w_list -background white -borderwidth 0 \
3002                -cursor $cursor_ptr \
3003                -state disabled \
3004                -wrap none \
3005                -height 20 \
3006                -width 70 \
3007                -xscrollcommand [list $w.list.sbx set] \
3008                -yscrollcommand [list $w.list.sby set] \
3009                -font font_ui
3010        $w_list tag conf in_sel \
3011                -background [$w_list cget -foreground] \
3012                -foreground [$w_list cget -background]
3013        scrollbar $w.list.sbx -orient h -command [list $w_list xview]
3014        scrollbar $w.list.sby -orient v -command [list $w_list yview]
3015        pack $w.list.sbx -side bottom -fill x
3016        pack $w.list.sby -side right -fill y
3017        pack $w_list -side left -fill both -expand 1
3018        pack $w.list -side top -fill both -expand 1
3019
3020        label $w.status -textvariable browser_status($w_list) \
3021                -anchor w \
3022                -justify left \
3023                -borderwidth 1 \
3024                -relief sunken \
3025                -font font_ui
3026        pack $w.status -anchor w -side bottom -fill x
3027
3028        bind $w_list <Button-1>        "browser_click 0 $w_list @%x,%y;break"
3029        bind $w_list <Double-Button-1> "browser_click 1 $w_list @%x,%y;break"
3030        bind $w_list <$M1B-Up>         "browser_parent $w_list;break"
3031        bind $w_list <$M1B-Left>       "browser_parent $w_list;break"
3032        bind $w_list <Up>              "browser_move -1 $w_list;break"
3033        bind $w_list <Down>            "browser_move 1 $w_list;break"
3034        bind $w_list <$M1B-Right>      "browser_enter $w_list;break"
3035        bind $w_list <Return>          "browser_enter $w_list;break"
3036        bind $w_list <Prior>           "browser_page -1 $w_list;break"
3037        bind $w_list <Next>            "browser_page 1 $w_list;break"
3038        bind $w_list <Left>            break
3039        bind $w_list <Right>           break
3040
3041        bind $w <Visibility> "focus $w"
3042        bind $w <Destroy> "
3043                array unset browser_buffer $w_list
3044                array unset browser_files $w_list
3045                array unset browser_status $w_list
3046                array unset browser_stack $w_list
3047                array unset browser_path $w_list
3048                array unset browser_commit $w_list
3049                array unset browser_busy $w_list
3050        "
3051        wm title $w "[appname] ([reponame]): File Browser"
3052        ls_tree $w_list $browser_commit($w_list) {}
3053}
3054
3055proc browser_move {dir w} {
3056        global browser_files browser_busy
3057
3058        if {$browser_busy($w)} return
3059        set lno [lindex [split [$w index in_sel.first] .] 0]
3060        incr lno $dir
3061        if {[lindex $browser_files($w) [expr {$lno - 1}]] ne {}} {
3062                $w tag remove in_sel 0.0 end
3063                $w tag add in_sel $lno.0 [expr {$lno + 1}].0
3064                $w see $lno.0
3065        }
3066}
3067
3068proc browser_page {dir w} {
3069        global browser_files browser_busy
3070
3071        if {$browser_busy($w)} return
3072        $w yview scroll $dir pages
3073        set lno [expr {int(
3074                  [lindex [$w yview] 0]
3075                * [llength $browser_files($w)]
3076                + 1)}]
3077        if {[lindex $browser_files($w) [expr {$lno - 1}]] ne {}} {
3078                $w tag remove in_sel 0.0 end
3079                $w tag add in_sel $lno.0 [expr {$lno + 1}].0
3080                $w see $lno.0
3081        }
3082}
3083
3084proc browser_parent {w} {
3085        global browser_files browser_status browser_path
3086        global browser_stack browser_busy
3087
3088        if {$browser_busy($w)} return
3089        set info [lindex $browser_files($w) 0]
3090        if {[lindex $info 0] eq {parent}} {
3091                set parent [lindex $browser_stack($w) end-1]
3092                set browser_stack($w) [lrange $browser_stack($w) 0 end-2]
3093                if {$browser_stack($w) eq {}} {
3094                        regsub {:.*$} $browser_path($w) {:} browser_path($w)
3095                } else {
3096                        regsub {/[^/]+$} $browser_path($w) {} browser_path($w)
3097                }
3098                set browser_status($w) "Loading $browser_path($w)..."
3099                ls_tree $w [lindex $parent 0] [lindex $parent 1]
3100        }
3101}
3102
3103proc browser_enter {w} {
3104        global browser_files browser_status browser_path
3105        global browser_commit browser_stack browser_busy
3106
3107        if {$browser_busy($w)} return
3108        set lno [lindex [split [$w index in_sel.first] .] 0]
3109        set info [lindex $browser_files($w) [expr {$lno - 1}]]
3110        if {$info ne {}} {
3111                switch -- [lindex $info 0] {
3112                parent {
3113                        browser_parent $w
3114                }
3115                tree {
3116                        set name [lindex $info 2]
3117                        set escn [escape_path $name]
3118                        set browser_status($w) "Loading $escn..."
3119                        append browser_path($w) $escn
3120                        ls_tree $w [lindex $info 1] $name
3121                }
3122                blob {
3123                        set name [lindex $info 2]
3124                        set p {}
3125                        foreach n $browser_stack($w) {
3126                                append p [lindex $n 1]
3127                        }
3128                        append p $name
3129                        show_blame $browser_commit($w) $p
3130                }
3131                }
3132        }
3133}
3134
3135proc browser_click {was_double_click w pos} {
3136        global browser_files browser_busy
3137
3138        if {$browser_busy($w)} return
3139        set lno [lindex [split [$w index $pos] .] 0]
3140        focus $w
3141
3142        if {[lindex $browser_files($w) [expr {$lno - 1}]] ne {}} {
3143                $w tag remove in_sel 0.0 end
3144                $w tag add in_sel $lno.0 [expr {$lno + 1}].0
3145                if {$was_double_click} {
3146                        browser_enter $w
3147                }
3148        }
3149}
3150
3151proc ls_tree {w tree_id name} {
3152        global browser_buffer browser_files browser_stack browser_busy
3153
3154        set browser_buffer($w) {}
3155        set browser_files($w) {}
3156        set browser_busy($w) 1
3157
3158        $w conf -state normal
3159        $w tag remove in_sel 0.0 end
3160        $w delete 0.0 end
3161        if {$browser_stack($w) ne {}} {
3162                $w image create end \
3163                        -align center -padx 5 -pady 1 \
3164                        -name icon0 \
3165                        -image file_uplevel
3166                $w insert end {[Up To Parent]}
3167                lappend browser_files($w) parent
3168        }
3169        lappend browser_stack($w) [list $tree_id $name]
3170        $w conf -state disabled
3171
3172        set cmd [list git ls-tree -z $tree_id]
3173        set fd [open "| $cmd" r]
3174        fconfigure $fd -blocking 0 -translation binary -encoding binary
3175        fileevent $fd readable [list read_ls_tree $fd $w]
3176}
3177
3178proc read_ls_tree {fd w} {
3179        global browser_buffer browser_files browser_status browser_busy
3180
3181        if {![winfo exists $w]} {
3182                catch {close $fd}
3183                return
3184        }
3185
3186        append browser_buffer($w) [read $fd]
3187        set pck [split $browser_buffer($w) "\0"]
3188        set browser_buffer($w) [lindex $pck end]
3189
3190        set n [llength $browser_files($w)]
3191        $w conf -state normal
3192        foreach p [lrange $pck 0 end-1] {
3193                set info [split $p "\t"]
3194                set path [lindex $info 1]
3195                set info [split [lindex $info 0] { }]
3196                set type [lindex $info 1]
3197                set object [lindex $info 2]
3198
3199                switch -- $type {
3200                blob {
3201                        set image file_mod
3202                }
3203                tree {
3204                        set image file_dir
3205                        append path /
3206                }
3207                default {
3208                        set image file_question
3209                }
3210                }
3211
3212                if {$n > 0} {$w insert end "\n"}
3213                $w image create end \
3214                        -align center -padx 5 -pady 1 \
3215                        -name icon[incr n] \
3216                        -image $image
3217                $w insert end [escape_path $path]
3218                lappend browser_files($w) [list $type $object $path]
3219        }
3220        $w conf -state disabled
3221
3222        if {[eof $fd]} {
3223                close $fd
3224                set browser_status($w) Ready.
3225                set browser_busy($w) 0
3226                array unset browser_buffer $w
3227                if {$n > 0} {
3228                        $w tag add in_sel 1.0 2.0
3229                        focus -force $w
3230                }
3231        }
3232}
3233
3234proc show_blame {commit path} {
3235        global next_browser_id blame_status blame_data
3236
3237        if {[winfo ismapped .]} {
3238                set w .browser[incr next_browser_id]
3239                set tl $w
3240                toplevel $w
3241        } else {
3242                set w {}
3243                set tl .
3244        }
3245        set blame_status($w) {Loading current file content...}
3246
3247        label $w.path -text "$commit:$path" \
3248                -anchor w \
3249                -justify left \
3250                -borderwidth 1 \
3251                -relief sunken \
3252                -font font_uibold
3253        pack $w.path -side top -fill x
3254
3255        frame $w.out
3256        text $w.out.loaded_t \
3257                -background white -borderwidth 0 \
3258                -state disabled \
3259                -wrap none \
3260                -height 40 \
3261                -width 1 \
3262                -font font_diff
3263        $w.out.loaded_t tag conf annotated -background grey
3264
3265        text $w.out.linenumber_t \
3266                -background white -borderwidth 0 \
3267                -state disabled \
3268                -wrap none \
3269                -height 40 \
3270                -width 5 \
3271                -font font_diff
3272        $w.out.linenumber_t tag conf linenumber -justify right
3273
3274        text $w.out.file_t \
3275                -background white -borderwidth 0 \
3276                -state disabled \
3277                -wrap none \
3278                -height 40 \
3279                -width 80 \
3280                -xscrollcommand [list $w.out.sbx set] \
3281                -font font_diff
3282
3283        scrollbar $w.out.sbx -orient h -command [list $w.out.file_t xview]
3284        scrollbar $w.out.sby -orient v \
3285                -command [list scrollbar2many [list \
3286                $w.out.loaded_t \
3287                $w.out.linenumber_t \
3288                $w.out.file_t \
3289                ] yview]
3290        grid \
3291                $w.out.linenumber_t \
3292                $w.out.loaded_t \
3293                $w.out.file_t \
3294                $w.out.sby \
3295                -sticky nsew
3296        grid conf $w.out.sbx -column 2 -sticky we
3297        grid columnconfigure $w.out 2 -weight 1
3298        grid rowconfigure $w.out 0 -weight 1
3299        pack $w.out -fill both -expand 1
3300
3301        label $w.status -textvariable blame_status($w) \
3302                -anchor w \
3303                -justify left \
3304                -borderwidth 1 \
3305                -relief sunken \
3306                -font font_ui
3307        pack $w.status -side bottom -fill x
3308
3309        frame $w.cm
3310        text $w.cm.t \
3311                -background white -borderwidth 0 \
3312                -state disabled \
3313                -wrap none \
3314                -height 10 \
3315                -width 80 \
3316                -xscrollcommand [list $w.cm.sbx set] \
3317                -yscrollcommand [list $w.cm.sby set] \
3318                -font font_diff
3319        scrollbar $w.cm.sbx -orient h -command [list $w.cm.t xview]
3320        scrollbar $w.cm.sby -orient v -command [list $w.cm.t yview]
3321        pack $w.cm.sby -side right -fill y
3322        pack $w.cm.sbx -side bottom -fill x
3323        pack $w.cm.t -expand 1 -fill both
3324        pack $w.cm -side bottom -fill x
3325
3326        menu $w.ctxm -tearoff 0
3327        $w.ctxm add command -label "Copy Commit" \
3328                -font font_ui \
3329                -command "blame_copycommit $w \$cursorW @\$cursorX,\$cursorY"
3330
3331        foreach i [list \
3332                $w.out.loaded_t \
3333                $w.out.linenumber_t \
3334                $w.out.file_t] {
3335                $i tag conf in_sel \
3336                        -background [$i cget -foreground] \
3337                        -foreground [$i cget -background]
3338                $i conf -yscrollcommand \
3339                        [list many2scrollbar [list \
3340                        $w.out.loaded_t \
3341                        $w.out.linenumber_t \
3342                        $w.out.file_t \
3343                        ] yview $w.out.sby]
3344                bind $i <Button-1> "
3345                        blame_click {$w} \\
3346                                $w.cm.t \\
3347                                $w.out.linenumber_t \\
3348                                $w.out.file_t \\
3349                                $i @%x,%y
3350                        focus $i
3351                "
3352                bind_button3 $i "
3353                        set cursorX %x
3354                        set cursorY %y
3355                        set cursorW %W
3356                        tk_popup $w.ctxm %X %Y
3357                "
3358        }
3359
3360        bind $w.cm.t <Button-1> "focus $w.cm.t"
3361        bind $tl <Visibility> "focus $tl"
3362        bind $tl <Destroy> "
3363                array unset blame_status {$w}
3364                array unset blame_data $w,*
3365        "
3366        wm title $tl "[appname] ([reponame]): File Viewer"
3367
3368        set blame_data($w,commit_count) 0
3369        set blame_data($w,commit_list) {}
3370        set blame_data($w,total_lines) 0
3371        set blame_data($w,blame_lines) 0
3372        set blame_data($w,highlight_commit) {}
3373        set blame_data($w,highlight_line) -1
3374
3375        set cmd [list git cat-file blob "$commit:$path"]
3376        set fd [open "| $cmd" r]
3377        fconfigure $fd -blocking 0 -translation lf -encoding binary
3378        fileevent $fd readable [list read_blame_catfile \
3379                $fd $w $commit $path \
3380                $w.cm.t $w.out.loaded_t $w.out.linenumber_t $w.out.file_t]
3381}
3382
3383proc read_blame_catfile {fd w commit path w_cmit w_load w_line w_file} {
3384        global blame_status blame_data
3385
3386        if {![winfo exists $w_file]} {
3387                catch {close $fd}
3388                return
3389        }
3390
3391        set n $blame_data($w,total_lines)
3392        $w_load conf -state normal
3393        $w_line conf -state normal
3394        $w_file conf -state normal
3395        while {[gets $fd line] >= 0} {
3396                regsub "\r\$" $line {} line
3397                incr n
3398                $w_load insert end "\n"
3399                $w_line insert end "$n\n" linenumber
3400                $w_file insert end "$line\n"
3401        }
3402        $w_load conf -state disabled
3403        $w_line conf -state disabled
3404        $w_file conf -state disabled
3405        set blame_data($w,total_lines) $n
3406
3407        if {[eof $fd]} {
3408                close $fd
3409                blame_incremental_status $w
3410                set cmd [list git blame -M -C --incremental]
3411                lappend cmd $commit -- $path
3412                set fd [open "| $cmd" r]
3413                fconfigure $fd -blocking 0 -translation lf -encoding binary
3414                fileevent $fd readable [list read_blame_incremental $fd $w \
3415                        $w_load $w_cmit $w_line $w_file]
3416        }
3417}
3418
3419proc read_blame_incremental {fd w w_load w_cmit w_line w_file} {
3420        global blame_status blame_data
3421
3422        if {![winfo exists $w_file]} {
3423                catch {close $fd}
3424                return
3425        }
3426
3427        while {[gets $fd line] >= 0} {
3428                if {[regexp {^([a-z0-9]{40}) (\d+) (\d+) (\d+)$} $line line \
3429                        cmit original_line final_line line_count]} {
3430                        set blame_data($w,commit) $cmit
3431                        set blame_data($w,original_line) $original_line
3432                        set blame_data($w,final_line) $final_line
3433                        set blame_data($w,line_count) $line_count
3434
3435                        if {[catch {set g $blame_data($w,$cmit,order)}]} {
3436                                $w_line tag conf g$cmit
3437                                $w_file tag conf g$cmit
3438                                $w_line tag raise in_sel
3439                                $w_file tag raise in_sel
3440                                $w_file tag raise sel
3441                                set blame_data($w,$cmit,order) $blame_data($w,commit_count)
3442                                incr blame_data($w,commit_count)
3443                                lappend blame_data($w,commit_list) $cmit
3444                        }
3445                } elseif {[string match {filename *} $line]} {
3446                        set file [string range $line 9 end]
3447                        set n $blame_data($w,line_count)
3448                        set lno $blame_data($w,final_line)
3449                        set cmit $blame_data($w,commit)
3450
3451                        while {$n > 0} {
3452                                if {[catch {set g g$blame_data($w,line$lno,commit)}]} {
3453                                        $w_load tag add annotated $lno.0 "$lno.0 lineend + 1c"
3454                                } else {
3455                                        $w_line tag remove g$g $lno.0 "$lno.0 lineend + 1c"
3456                                        $w_file tag remove g$g $lno.0 "$lno.0 lineend + 1c"
3457                                }
3458
3459                                set blame_data($w,line$lno,commit) $cmit
3460                                set blame_data($w,line$lno,file) $file
3461                                $w_line tag add g$cmit $lno.0 "$lno.0 lineend + 1c"
3462                                $w_file tag add g$cmit $lno.0 "$lno.0 lineend + 1c"
3463
3464                                if {$blame_data($w,highlight_line) == -1} {
3465                                        if {[lindex [$w_file yview] 0] == 0} {
3466                                                $w_file see $lno.0
3467                                                blame_showcommit $w $w_cmit $w_line $w_file $lno
3468                                        }
3469                                } elseif {$blame_data($w,highlight_line) == $lno} {
3470                                        blame_showcommit $w $w_cmit $w_line $w_file $lno
3471                                }
3472
3473                                incr n -1
3474                                incr lno
3475                                incr blame_data($w,blame_lines)
3476                        }
3477
3478                        set hc $blame_data($w,highlight_commit)
3479                        if {$hc ne {}
3480                                && [expr {$blame_data($w,$hc,order) + 1}]
3481                                        == $blame_data($w,$cmit,order)} {
3482                                blame_showcommit $w $w_cmit $w_line $w_file \
3483                                        $blame_data($w,highlight_line)
3484                        }
3485                } elseif {[regexp {^([a-z-]+) (.*)$} $line line header data]} {
3486                        set blame_data($w,$blame_data($w,commit),$header) $data
3487                }
3488        }
3489
3490        if {[eof $fd]} {
3491                close $fd
3492                set blame_status($w) {Annotation complete.}
3493        } else {
3494                blame_incremental_status $w
3495        }
3496}
3497
3498proc blame_incremental_status {w} {
3499        global blame_status blame_data
3500
3501        set blame_status($w) [format \
3502                "Loading annotations... %i of %i lines annotated (%2i%%)" \
3503                $blame_data($w,blame_lines) \
3504                $blame_data($w,total_lines) \
3505                [expr {100 * $blame_data($w,blame_lines)
3506                        / $blame_data($w,total_lines)}]]
3507}
3508
3509proc blame_click {w w_cmit w_line w_file cur_w pos} {
3510        set lno [lindex [split [$cur_w index $pos] .] 0]
3511        if {$lno eq {}} return
3512
3513        $w_line tag remove in_sel 0.0 end
3514        $w_file tag remove in_sel 0.0 end
3515        $w_line tag add in_sel $lno.0 "$lno.0 + 1 line"
3516        $w_file tag add in_sel $lno.0 "$lno.0 + 1 line"
3517
3518        blame_showcommit $w $w_cmit $w_line $w_file $lno
3519}
3520
3521set blame_colors {
3522        #ff4040
3523        #ff40ff
3524        #4040ff
3525}
3526
3527proc blame_showcommit {w w_cmit w_line w_file lno} {
3528        global blame_colors blame_data repo_config
3529
3530        set cmit $blame_data($w,highlight_commit)
3531        if {$cmit ne {}} {
3532                set idx $blame_data($w,$cmit,order)
3533                set i 0
3534                foreach c $blame_colors {
3535                        set h [lindex $blame_data($w,commit_list) [expr {$idx - 1 + $i}]]
3536                        $w_line tag conf g$h -background white
3537                        $w_file tag conf g$h -background white
3538                        incr i
3539                }
3540        }
3541
3542        $w_cmit conf -state normal
3543        $w_cmit delete 0.0 end
3544        if {[catch {set cmit $blame_data($w,line$lno,commit)}]} {
3545                set cmit {}
3546                $w_cmit insert end "Loading annotation..."
3547        } else {
3548                set idx $blame_data($w,$cmit,order)
3549                set i 0
3550                foreach c $blame_colors {
3551                        set h [lindex $blame_data($w,commit_list) [expr {$idx - 1 + $i}]]
3552                        $w_line tag conf g$h -background $c
3553                        $w_file tag conf g$h -background $c
3554                        incr i
3555                }
3556
3557                if {[catch {set msg $blame_data($w,$cmit,message)}]} {
3558                        set msg {}
3559                        catch {
3560                                set fd [open "| git cat-file commit $cmit" r]
3561                                fconfigure $fd -encoding binary -translation lf
3562                                if {[catch {set enc $repo_config(i18n.commitencoding)}]} {
3563                                        set enc utf-8
3564                                }
3565                                while {[gets $fd line] > 0} {
3566                                        if {[string match {encoding *} $line]} {
3567                                                set enc [string tolower [string range $line 9 end]]
3568                                        }
3569                                }
3570                                fconfigure $fd -encoding $enc
3571                                set msg [string trim [read $fd]]
3572                                close $fd
3573                        }
3574                        set blame_data($w,$cmit,message) $msg
3575                }
3576
3577                set author_name {}
3578                set author_email {}
3579                set author_time {}
3580                catch {set author_name $blame_data($w,$cmit,author)}
3581                catch {set author_email $blame_data($w,$cmit,author-mail)}
3582                catch {set author_time [clock format $blame_data($w,$cmit,author-time)]}
3583
3584                set committer_name {}
3585                set committer_email {}
3586                set committer_time {}
3587                catch {set committer_name $blame_data($w,$cmit,committer)}
3588                catch {set committer_email $blame_data($w,$cmit,committer-mail)}
3589                catch {set committer_time [clock format $blame_data($w,$cmit,committer-time)]}
3590
3591                $w_cmit insert end "commit $cmit\n"
3592                $w_cmit insert end "Author: $author_name $author_email $author_time\n"
3593                $w_cmit insert end "Committer: $committer_name $committer_email $committer_time\n"
3594                $w_cmit insert end "Original File: [escape_path $blame_data($w,line$lno,file)]\n"
3595                $w_cmit insert end "\n"
3596                $w_cmit insert end $msg
3597        }
3598        $w_cmit conf -state disabled
3599
3600        set blame_data($w,highlight_line) $lno
3601        set blame_data($w,highlight_commit) $cmit
3602}
3603
3604proc blame_copycommit {w i pos} {
3605        global blame_data
3606        set lno [lindex [split [$i index $pos] .] 0]
3607        if {![catch {set commit $blame_data($w,line$lno,commit)}]} {
3608                clipboard clear
3609                clipboard append \
3610                        -format STRING \
3611                        -type STRING \
3612                        -- $commit
3613        }
3614}
3615
3616######################################################################
3617##
3618## icons
3619
3620set filemask {
3621#define mask_width 14
3622#define mask_height 15
3623static unsigned char mask_bits[] = {
3624   0xfe, 0x1f, 0xfe, 0x1f, 0xfe, 0x1f, 0xfe, 0x1f, 0xfe, 0x1f, 0xfe, 0x1f,
3625   0xfe, 0x1f, 0xfe, 0x1f, 0xfe, 0x1f, 0xfe, 0x1f, 0xfe, 0x1f, 0xfe, 0x1f,
3626   0xfe, 0x1f, 0xfe, 0x1f, 0xfe, 0x1f};
3627}
3628
3629image create bitmap file_plain -background white -foreground black -data {
3630#define plain_width 14
3631#define plain_height 15
3632static unsigned char plain_bits[] = {
3633   0xfe, 0x01, 0x02, 0x03, 0x02, 0x05, 0x02, 0x09, 0x02, 0x1f, 0x02, 0x10,
3634   0x02, 0x10, 0x02, 0x10, 0x02, 0x10, 0x02, 0x10, 0x02, 0x10, 0x02, 0x10,
3635   0x02, 0x10, 0x02, 0x10, 0xfe, 0x1f};
3636} -maskdata $filemask
3637
3638image create bitmap file_mod -background white -foreground blue -data {
3639#define mod_width 14
3640#define mod_height 15
3641static unsigned char mod_bits[] = {
3642   0xfe, 0x01, 0x02, 0x03, 0x7a, 0x05, 0x02, 0x09, 0x7a, 0x1f, 0x02, 0x10,
3643   0xfa, 0x17, 0x02, 0x10, 0xfa, 0x17, 0x02, 0x10, 0xfa, 0x17, 0x02, 0x10,
3644   0xfa, 0x17, 0x02, 0x10, 0xfe, 0x1f};
3645} -maskdata $filemask
3646
3647image create bitmap file_fulltick -background white -foreground "#007000" -data {
3648#define file_fulltick_width 14
3649#define file_fulltick_height 15
3650static unsigned char file_fulltick_bits[] = {
3651   0xfe, 0x01, 0x02, 0x1a, 0x02, 0x0c, 0x02, 0x0c, 0x02, 0x16, 0x02, 0x16,
3652   0x02, 0x13, 0x00, 0x13, 0x86, 0x11, 0x8c, 0x11, 0xd8, 0x10, 0xf2, 0x10,
3653   0x62, 0x10, 0x02, 0x10, 0xfe, 0x1f};
3654} -maskdata $filemask
3655
3656image create bitmap file_parttick -background white -foreground "#005050" -data {
3657#define parttick_width 14
3658#define parttick_height 15
3659static unsigned char parttick_bits[] = {
3660   0xfe, 0x01, 0x02, 0x03, 0x7a, 0x05, 0x02, 0x09, 0x7a, 0x1f, 0x02, 0x10,
3661   0x7a, 0x14, 0x02, 0x16, 0x02, 0x13, 0x8a, 0x11, 0xda, 0x10, 0x72, 0x10,
3662   0x22, 0x10, 0x02, 0x10, 0xfe, 0x1f};
3663} -maskdata $filemask
3664
3665image create bitmap file_question -background white -foreground black -data {
3666#define file_question_width 14
3667#define file_question_height 15
3668static unsigned char file_question_bits[] = {
3669   0xfe, 0x01, 0x02, 0x02, 0xe2, 0x04, 0xf2, 0x09, 0x1a, 0x1b, 0x0a, 0x13,
3670   0x82, 0x11, 0xc2, 0x10, 0x62, 0x10, 0x62, 0x10, 0x02, 0x10, 0x62, 0x10,
3671   0x62, 0x10, 0x02, 0x10, 0xfe, 0x1f};
3672} -maskdata $filemask
3673
3674image create bitmap file_removed -background white -foreground red -data {
3675#define file_removed_width 14
3676#define file_removed_height 15
3677static unsigned char file_removed_bits[] = {
3678   0xfe, 0x01, 0x02, 0x03, 0x02, 0x05, 0x02, 0x09, 0x02, 0x1f, 0x02, 0x10,
3679   0x1a, 0x16, 0x32, 0x13, 0xe2, 0x11, 0xc2, 0x10, 0xe2, 0x11, 0x32, 0x13,
3680   0x1a, 0x16, 0x02, 0x10, 0xfe, 0x1f};
3681} -maskdata $filemask
3682
3683image create bitmap file_merge -background white -foreground blue -data {
3684#define file_merge_width 14
3685#define file_merge_height 15
3686static unsigned char file_merge_bits[] = {
3687   0xfe, 0x01, 0x02, 0x03, 0x62, 0x05, 0x62, 0x09, 0x62, 0x1f, 0x62, 0x10,
3688   0xfa, 0x11, 0xf2, 0x10, 0x62, 0x10, 0x02, 0x10, 0xfa, 0x17, 0x02, 0x10,
3689   0xfa, 0x17, 0x02, 0x10, 0xfe, 0x1f};
3690} -maskdata $filemask
3691
3692set file_dir_data {
3693#define file_width 18
3694#define file_height 18
3695static unsigned char file_bits[] = {
3696  0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0xf8, 0x03, 0x00,
3697  0x0c, 0x03, 0x00, 0x04, 0xfe, 0x00, 0x06, 0x80, 0x00, 0xff, 0x9f, 0x00,
3698  0x03, 0x98, 0x00, 0x02, 0x90, 0x00, 0x06, 0xb0, 0x00, 0x04, 0xa0, 0x00,
3699  0x0c, 0xe0, 0x00, 0x08, 0xc0, 0x00, 0xf8, 0xff, 0x00, 0x00, 0x00, 0x00,
3700  0x00, 0x00, 0x00, 0x00, 0x00, 0x00};
3701}
3702image create bitmap file_dir -background white -foreground blue \
3703        -data $file_dir_data -maskdata $file_dir_data
3704unset file_dir_data
3705
3706set file_uplevel_data {
3707#define up_width 15
3708#define up_height 15
3709static unsigned char up_bits[] = {
3710  0x80, 0x00, 0xc0, 0x01, 0xe0, 0x03, 0xf0, 0x07, 0xf8, 0x0f, 0xfc, 0x1f,
3711  0xfe, 0x3f, 0xc0, 0x01, 0xc0, 0x01, 0xc0, 0x01, 0xc0, 0x01, 0xc0, 0x01,
3712  0xc0, 0x01, 0xc0, 0x01, 0x00, 0x00};
3713}
3714image create bitmap file_uplevel -background white -foreground red \
3715        -data $file_uplevel_data -maskdata $file_uplevel_data
3716unset file_uplevel_data
3717
3718set ui_index .vpane.files.index.list
3719set ui_workdir .vpane.files.workdir.list
3720
3721set all_icons(_$ui_index)   file_plain
3722set all_icons(A$ui_index)   file_fulltick
3723set all_icons(M$ui_index)   file_fulltick
3724set all_icons(D$ui_index)   file_removed
3725set all_icons(U$ui_index)   file_merge
3726
3727set all_icons(_$ui_workdir) file_plain
3728set all_icons(M$ui_workdir) file_mod
3729set all_icons(D$ui_workdir) file_question
3730set all_icons(U$ui_workdir) file_merge
3731set all_icons(O$ui_workdir) file_plain
3732
3733set max_status_desc 0
3734foreach i {
3735                {__ "Unmodified"}
3736
3737                {_M "Modified, not staged"}
3738                {M_ "Staged for commit"}
3739                {MM "Portions staged for commit"}
3740                {MD "Staged for commit, missing"}
3741
3742                {_O "Untracked, not staged"}
3743                {A_ "Staged for commit"}
3744                {AM "Portions staged for commit"}
3745                {AD "Staged for commit, missing"}
3746
3747                {_D "Missing"}
3748                {D_ "Staged for removal"}
3749                {DO "Staged for removal, still present"}
3750
3751                {U_ "Requires merge resolution"}
3752                {UU "Requires merge resolution"}
3753                {UM "Requires merge resolution"}
3754                {UD "Requires merge resolution"}
3755        } {
3756        if {$max_status_desc < [string length [lindex $i 1]]} {
3757                set max_status_desc [string length [lindex $i 1]]
3758        }
3759        set all_descs([lindex $i 0]) [lindex $i 1]
3760}
3761unset i
3762
3763######################################################################
3764##
3765## util
3766
3767proc bind_button3 {w cmd} {
3768        bind $w <Any-Button-3> $cmd
3769        if {[is_MacOSX]} {
3770                bind $w <Control-Button-1> $cmd
3771        }
3772}
3773
3774proc scrollbar2many {list mode args} {
3775        foreach w $list {eval $w $mode $args}
3776}
3777
3778proc many2scrollbar {list mode sb top bottom} {
3779        $sb set $top $bottom
3780        foreach w $list {$w $mode moveto $top}
3781}
3782
3783proc incr_font_size {font {amt 1}} {
3784        set sz [font configure $font -size]
3785        incr sz $amt
3786        font configure $font -size $sz
3787        font configure ${font}bold -size $sz
3788}
3789
3790proc hook_failed_popup {hook msg} {
3791        set w .hookfail
3792        toplevel $w
3793
3794        frame $w.m
3795        label $w.m.l1 -text "$hook hook failed:" \
3796                -anchor w \
3797                -justify left \
3798                -font font_uibold
3799        text $w.m.t \
3800                -background white -borderwidth 1 \
3801                -relief sunken \
3802                -width 80 -height 10 \
3803                -font font_diff \
3804                -yscrollcommand [list $w.m.sby set]
3805        label $w.m.l2 \
3806                -text {You must correct the above errors before committing.} \
3807                -anchor w \
3808                -justify left \
3809                -font font_uibold
3810        scrollbar $w.m.sby -command [list $w.m.t yview]
3811        pack $w.m.l1 -side top -fill x
3812        pack $w.m.l2 -side bottom -fill x
3813        pack $w.m.sby -side right -fill y
3814        pack $w.m.t -side left -fill both -expand 1
3815        pack $w.m -side top -fill both -expand 1 -padx 5 -pady 10
3816
3817        $w.m.t insert 1.0 $msg
3818        $w.m.t conf -state disabled
3819
3820        button $w.ok -text OK \
3821                -width 15 \
3822                -font font_ui \
3823                -command "destroy $w"
3824        pack $w.ok -side bottom -anchor e -pady 10 -padx 10
3825
3826        bind $w <Visibility> "grab $w; focus $w"
3827        bind $w <Key-Return> "destroy $w"
3828        wm title $w "[appname] ([reponame]): error"
3829        tkwait window $w
3830}
3831
3832set next_console_id 0
3833
3834proc new_console {short_title long_title} {
3835        global next_console_id console_data
3836        set w .console[incr next_console_id]
3837        set console_data($w) [list $short_title $long_title]
3838        return [console_init $w]
3839}
3840
3841proc console_init {w} {
3842        global console_cr console_data M1B
3843
3844        set console_cr($w) 1.0
3845        toplevel $w
3846        frame $w.m
3847        label $w.m.l1 -text "[lindex $console_data($w) 1]:" \
3848                -anchor w \
3849                -justify left \
3850                -font font_uibold
3851        text $w.m.t \
3852                -background white -borderwidth 1 \
3853                -relief sunken \
3854                -width 80 -height 10 \
3855                -font font_diff \
3856                -state disabled \
3857                -yscrollcommand [list $w.m.sby set]
3858        label $w.m.s -text {Working... please wait...} \
3859                -anchor w \
3860                -justify left \
3861                -font font_uibold
3862        scrollbar $w.m.sby -command [list $w.m.t yview]
3863        pack $w.m.l1 -side top -fill x
3864        pack $w.m.s -side bottom -fill x
3865        pack $w.m.sby -side right -fill y
3866        pack $w.m.t -side left -fill both -expand 1
3867        pack $w.m -side top -fill both -expand 1 -padx 5 -pady 10
3868
3869        menu $w.ctxm -tearoff 0
3870        $w.ctxm add command -label "Copy" \
3871                -font font_ui \
3872                -command "tk_textCopy $w.m.t"
3873        $w.ctxm add command -label "Select All" \
3874                -font font_ui \
3875                -command "focus $w.m.t;$w.m.t tag add sel 0.0 end"
3876        $w.ctxm add command -label "Copy All" \
3877                -font font_ui \
3878                -command "
3879                        $w.m.t tag add sel 0.0 end
3880                        tk_textCopy $w.m.t
3881                        $w.m.t tag remove sel 0.0 end
3882                "
3883
3884        button $w.ok -text {Close} \
3885                -font font_ui \
3886                -state disabled \
3887                -command "destroy $w"
3888        pack $w.ok -side bottom -anchor e -pady 10 -padx 10
3889
3890        bind_button3 $w.m.t "tk_popup $w.ctxm %X %Y"
3891        bind $w.m.t <$M1B-Key-a> "$w.m.t tag add sel 0.0 end;break"
3892        bind $w.m.t <$M1B-Key-A> "$w.m.t tag add sel 0.0 end;break"
3893        bind $w <Visibility> "focus $w"
3894        wm title $w "[appname] ([reponame]): [lindex $console_data($w) 0]"
3895        return $w
3896}
3897
3898proc console_exec {w cmd after} {
3899        # -- Cygwin's Tcl tosses the enviroment when we exec our child.
3900        #    But most users need that so we have to relogin. :-(
3901        #
3902        if {[is_Cygwin]} {
3903                set cmd [list sh --login -c "cd \"[pwd]\" && [join $cmd { }]"]
3904        }
3905
3906        # -- Tcl won't let us redirect both stdout and stderr to
3907        #    the same pipe.  So pass it through cat...
3908        #
3909        set cmd [concat | $cmd |& cat]
3910
3911        set fd_f [open $cmd r]
3912        fconfigure $fd_f -blocking 0 -translation binary
3913        fileevent $fd_f readable [list console_read $w $fd_f $after]
3914}
3915
3916proc console_read {w fd after} {
3917        global console_cr
3918
3919        set buf [read $fd]
3920        if {$buf ne {}} {
3921                if {![winfo exists $w]} {console_init $w}
3922                $w.m.t conf -state normal
3923                set c 0
3924                set n [string length $buf]
3925                while {$c < $n} {
3926                        set cr [string first "\r" $buf $c]
3927                        set lf [string first "\n" $buf $c]
3928                        if {$cr < 0} {set cr [expr {$n + 1}]}
3929                        if {$lf < 0} {set lf [expr {$n + 1}]}
3930
3931                        if {$lf < $cr} {
3932                                $w.m.t insert end [string range $buf $c $lf]
3933                                set console_cr($w) [$w.m.t index {end -1c}]
3934                                set c $lf
3935                                incr c
3936                        } else {
3937                                $w.m.t delete $console_cr($w) end
3938                                $w.m.t insert end "\n"
3939                                $w.m.t insert end [string range $buf $c $cr]
3940                                set c $cr
3941                                incr c
3942                        }
3943                }
3944                $w.m.t conf -state disabled
3945                $w.m.t see end
3946        }
3947
3948        fconfigure $fd -blocking 1
3949        if {[eof $fd]} {
3950                if {[catch {close $fd}]} {
3951                        set ok 0
3952                } else {
3953                        set ok 1
3954                }
3955                uplevel #0 $after $w $ok
3956                return
3957        }
3958        fconfigure $fd -blocking 0
3959}
3960
3961proc console_chain {cmdlist w {ok 1}} {
3962        if {$ok} {
3963                if {[llength $cmdlist] == 0} {
3964                        console_done $w $ok
3965                        return
3966                }
3967
3968                set cmd [lindex $cmdlist 0]
3969                set cmdlist [lrange $cmdlist 1 end]
3970
3971                if {[lindex $cmd 0] eq {console_exec}} {
3972                        console_exec $w \
3973                                [lindex $cmd 1] \
3974                                [list console_chain $cmdlist]
3975                } else {
3976                        uplevel #0 $cmd $cmdlist $w $ok
3977                }
3978        } else {
3979                console_done $w $ok
3980        }
3981}
3982
3983proc console_done {args} {
3984        global console_cr console_data
3985
3986        switch -- [llength $args] {
3987        2 {
3988                set w [lindex $args 0]
3989                set ok [lindex $args 1]
3990        }
3991        3 {
3992                set w [lindex $args 1]
3993                set ok [lindex $args 2]
3994        }
3995        default {
3996                error "wrong number of args: console_done ?ignored? w ok"
3997        }
3998        }
3999
4000        if {$ok} {
4001                if {[winfo exists $w]} {
4002                        $w.m.s conf -background green -text {Success}
4003                        $w.ok conf -state normal
4004                }
4005        } else {
4006                if {![winfo exists $w]} {
4007                        console_init $w
4008                }
4009                $w.m.s conf -background red -text {Error: Command Failed}
4010                $w.ok conf -state normal
4011        }
4012
4013        array unset console_cr $w
4014        array unset console_data $w
4015}
4016
4017######################################################################
4018##
4019## ui commands
4020
4021set starting_gitk_msg {Starting gitk... please wait...}
4022
4023proc do_gitk {revs} {
4024        global env ui_status_value starting_gitk_msg
4025
4026        # -- Always start gitk through whatever we were loaded with.  This
4027        #    lets us bypass using shell process on Windows systems.
4028        #
4029        set cmd [info nameofexecutable]
4030        lappend cmd [gitexec gitk]
4031        if {$revs ne {}} {
4032                append cmd { }
4033                append cmd $revs
4034        }
4035
4036        if {[catch {eval exec $cmd &} err]} {
4037                error_popup "Failed to start gitk:\n\n$err"
4038        } else {
4039                set ui_status_value $starting_gitk_msg
4040                after 10000 {
4041                        if {$ui_status_value eq $starting_gitk_msg} {
4042                                set ui_status_value {Ready.}
4043                        }
4044                }
4045        }
4046}
4047
4048proc do_stats {} {
4049        set fd [open "| git count-objects -v" r]
4050        while {[gets $fd line] > 0} {
4051                if {[regexp {^([^:]+): (\d+)$} $line _ name value]} {
4052                        set stats($name) $value
4053                }
4054        }
4055        close $fd
4056
4057        set packed_sz 0
4058        foreach p [glob -directory [gitdir objects pack] \
4059                -type f \
4060                -nocomplain -- *] {
4061                incr packed_sz [file size $p]
4062        }
4063        if {$packed_sz > 0} {
4064                set stats(size-pack) [expr {$packed_sz / 1024}]
4065        }
4066
4067        set w .stats_view
4068        toplevel $w
4069        wm geometry $w "+[winfo rootx .]+[winfo rooty .]"
4070
4071        label $w.header -text {Database Statistics} \
4072                -font font_uibold
4073        pack $w.header -side top -fill x
4074
4075        frame $w.buttons -border 1
4076        button $w.buttons.close -text Close \
4077                -font font_ui \
4078                -command [list destroy $w]
4079        button $w.buttons.gc -text {Compress Database} \
4080                -font font_ui \
4081                -command "destroy $w;do_gc"
4082        pack $w.buttons.close -side right
4083        pack $w.buttons.gc -side left
4084        pack $w.buttons -side bottom -fill x -pady 10 -padx 10
4085
4086        frame $w.stat -borderwidth 1 -relief solid
4087        foreach s {
4088                {count           {Number of loose objects}}
4089                {size            {Disk space used by loose objects} { KiB}}
4090                {in-pack         {Number of packed objects}}
4091                {packs           {Number of packs}}
4092                {size-pack       {Disk space used by packed objects} { KiB}}
4093                {prune-packable  {Packed objects waiting for pruning}}
4094                {garbage         {Garbage files}}
4095                } {
4096                set name [lindex $s 0]
4097                set label [lindex $s 1]
4098                if {[catch {set value $stats($name)}]} continue
4099                if {[llength $s] > 2} {
4100                        set value "$value[lindex $s 2]"
4101                }
4102
4103                label $w.stat.l_$name -text "$label:" -anchor w -font font_ui
4104                label $w.stat.v_$name -text $value -anchor w -font font_ui
4105                grid $w.stat.l_$name $w.stat.v_$name -sticky we -padx {0 5}
4106        }
4107        pack $w.stat -pady 10 -padx 10
4108
4109        bind $w <Visibility> "grab $w; focus $w"
4110        bind $w <Key-Escape> [list destroy $w]
4111        bind $w <Key-Return> [list destroy $w]
4112        wm title $w "[appname] ([reponame]): Database Statistics"
4113        tkwait window $w
4114}
4115
4116proc do_gc {} {
4117        set w [new_console {gc} {Compressing the object database}]
4118        console_chain {
4119                {console_exec {git pack-refs --prune}}
4120                {console_exec {git reflog expire --all}}
4121                {console_exec {git repack -a -d -l}}
4122                {console_exec {git rerere gc}}
4123        } $w
4124}
4125
4126proc do_fsck_objects {} {
4127        set w [new_console {fsck-objects} \
4128                {Verifying the object database with fsck-objects}]
4129        set cmd [list git fsck-objects]
4130        lappend cmd --full
4131        lappend cmd --cache
4132        lappend cmd --strict
4133        console_exec $w $cmd console_done
4134}
4135
4136set is_quitting 0
4137
4138proc do_quit {} {
4139        global ui_comm is_quitting repo_config commit_type
4140
4141        if {$is_quitting} return
4142        set is_quitting 1
4143
4144        if {[winfo exists $ui_comm]} {
4145                # -- Stash our current commit buffer.
4146                #
4147                set save [gitdir GITGUI_MSG]
4148                set msg [string trim [$ui_comm get 0.0 end]]
4149                regsub -all -line {[ \r\t]+$} $msg {} msg
4150                if {(![string match amend* $commit_type]
4151                        || [$ui_comm edit modified])
4152                        && $msg ne {}} {
4153                        catch {
4154                                set fd [open $save w]
4155                                puts -nonewline $fd $msg
4156                                close $fd
4157                        }
4158                } else {
4159                        catch {file delete $save}
4160                }
4161
4162                # -- Stash our current window geometry into this repository.
4163                #
4164                set cfg_geometry [list]
4165                lappend cfg_geometry [wm geometry .]
4166                lappend cfg_geometry [lindex [.vpane sash coord 0] 1]
4167                lappend cfg_geometry [lindex [.vpane.files sash coord 0] 0]
4168                if {[catch {set rc_geometry $repo_config(gui.geometry)}]} {
4169                        set rc_geometry {}
4170                }
4171                if {$cfg_geometry ne $rc_geometry} {
4172                        catch {git config gui.geometry $cfg_geometry}
4173                }
4174        }
4175
4176        destroy .
4177}
4178
4179proc do_rescan {} {
4180        rescan {set ui_status_value {Ready.}}
4181}
4182
4183proc unstage_helper {txt paths} {
4184        global file_states current_diff_path
4185
4186        if {![lock_index begin-update]} return
4187
4188        set pathList [list]
4189        set after {}
4190        foreach path $paths {
4191                switch -glob -- [lindex $file_states($path) 0] {
4192                A? -
4193                M? -
4194                D? {
4195                        lappend pathList $path
4196                        if {$path eq $current_diff_path} {
4197                                set after {reshow_diff;}
4198                        }
4199                }
4200                }
4201        }
4202        if {$pathList eq {}} {
4203                unlock_index
4204        } else {
4205                update_indexinfo \
4206                        $txt \
4207                        $pathList \
4208                        [concat $after {set ui_status_value {Ready.}}]
4209        }
4210}
4211
4212proc do_unstage_selection {} {
4213        global current_diff_path selected_paths
4214
4215        if {[array size selected_paths] > 0} {
4216                unstage_helper \
4217                        {Unstaging selected files from commit} \
4218                        [array names selected_paths]
4219        } elseif {$current_diff_path ne {}} {
4220                unstage_helper \
4221                        "Unstaging [short_path $current_diff_path] from commit" \
4222                        [list $current_diff_path]
4223        }
4224}
4225
4226proc add_helper {txt paths} {
4227        global file_states current_diff_path
4228
4229        if {![lock_index begin-update]} return
4230
4231        set pathList [list]
4232        set after {}
4233        foreach path $paths {
4234                switch -glob -- [lindex $file_states($path) 0] {
4235                _O -
4236                ?M -
4237                ?D -
4238                U? {
4239                        lappend pathList $path
4240                        if {$path eq $current_diff_path} {
4241                                set after {reshow_diff;}
4242                        }
4243                }
4244                }
4245        }
4246        if {$pathList eq {}} {
4247                unlock_index
4248        } else {
4249                update_index \
4250                        $txt \
4251                        $pathList \
4252                        [concat $after {set ui_status_value {Ready to commit.}}]
4253        }
4254}
4255
4256proc do_add_selection {} {
4257        global current_diff_path selected_paths
4258
4259        if {[array size selected_paths] > 0} {
4260                add_helper \
4261                        {Adding selected files} \
4262                        [array names selected_paths]
4263        } elseif {$current_diff_path ne {}} {
4264                add_helper \
4265                        "Adding [short_path $current_diff_path]" \
4266                        [list $current_diff_path]
4267        }
4268}
4269
4270proc do_add_all {} {
4271        global file_states
4272
4273        set paths [list]
4274        foreach path [array names file_states] {
4275                switch -glob -- [lindex $file_states($path) 0] {
4276                U? {continue}
4277                ?M -
4278                ?D {lappend paths $path}
4279                }
4280        }
4281        add_helper {Adding all changed files} $paths
4282}
4283
4284proc revert_helper {txt paths} {
4285        global file_states current_diff_path
4286
4287        if {![lock_index begin-update]} return
4288
4289        set pathList [list]
4290        set after {}
4291        foreach path $paths {
4292                switch -glob -- [lindex $file_states($path) 0] {
4293                U? {continue}
4294                ?M -
4295                ?D {
4296                        lappend pathList $path
4297                        if {$path eq $current_diff_path} {
4298                                set after {reshow_diff;}
4299                        }
4300                }
4301                }
4302        }
4303
4304        set n [llength $pathList]
4305        if {$n == 0} {
4306                unlock_index
4307                return
4308        } elseif {$n == 1} {
4309                set s "[short_path [lindex $pathList]]"
4310        } else {
4311                set s "these $n files"
4312        }
4313
4314        set reply [tk_dialog \
4315                .confirm_revert \
4316                "[appname] ([reponame])" \
4317                "Revert changes in $s?
4318
4319Any unadded changes will be permanently lost by the revert." \
4320                question \
4321                1 \
4322                {Do Nothing} \
4323                {Revert Changes} \
4324                ]
4325        if {$reply == 1} {
4326                checkout_index \
4327                        $txt \
4328                        $pathList \
4329                        [concat $after {set ui_status_value {Ready.}}]
4330        } else {
4331                unlock_index
4332        }
4333}
4334
4335proc do_revert_selection {} {
4336        global current_diff_path selected_paths
4337
4338        if {[array size selected_paths] > 0} {
4339                revert_helper \
4340                        {Reverting selected files} \
4341                        [array names selected_paths]
4342        } elseif {$current_diff_path ne {}} {
4343                revert_helper \
4344                        "Reverting [short_path $current_diff_path]" \
4345                        [list $current_diff_path]
4346        }
4347}
4348
4349proc do_signoff {} {
4350        global ui_comm
4351
4352        set me [committer_ident]
4353        if {$me eq {}} return
4354
4355        set sob "Signed-off-by: $me"
4356        set last [$ui_comm get {end -1c linestart} {end -1c}]
4357        if {$last ne $sob} {
4358                $ui_comm edit separator
4359                if {$last ne {}
4360                        && ![regexp {^[A-Z][A-Za-z]*-[A-Za-z-]+: *} $last]} {
4361                        $ui_comm insert end "\n"
4362                }
4363                $ui_comm insert end "\n$sob"
4364                $ui_comm edit separator
4365                $ui_comm see end
4366        }
4367}
4368
4369proc do_select_commit_type {} {
4370        global commit_type selected_commit_type
4371
4372        if {$selected_commit_type eq {new}
4373                && [string match amend* $commit_type]} {
4374                create_new_commit
4375        } elseif {$selected_commit_type eq {amend}
4376                && ![string match amend* $commit_type]} {
4377                load_last_commit
4378
4379                # The amend request was rejected...
4380                #
4381                if {![string match amend* $commit_type]} {
4382                        set selected_commit_type new
4383                }
4384        }
4385}
4386
4387proc do_commit {} {
4388        commit_tree
4389}
4390
4391proc do_about {} {
4392        global appvers copyright
4393        global tcl_patchLevel tk_patchLevel
4394
4395        set w .about_dialog
4396        toplevel $w
4397        wm geometry $w "+[winfo rootx .]+[winfo rooty .]"
4398
4399        label $w.header -text "About [appname]" \
4400                -font font_uibold
4401        pack $w.header -side top -fill x
4402
4403        frame $w.buttons
4404        button $w.buttons.close -text {Close} \
4405                -font font_ui \
4406                -command [list destroy $w]
4407        pack $w.buttons.close -side right
4408        pack $w.buttons -side bottom -fill x -pady 10 -padx 10
4409
4410        label $w.desc \
4411                -text "[appname] - a commit creation tool for Git.
4412$copyright" \
4413                -padx 5 -pady 5 \
4414                -justify left \
4415                -anchor w \
4416                -borderwidth 1 \
4417                -relief solid \
4418                -font font_ui
4419        pack $w.desc -side top -fill x -padx 5 -pady 5
4420
4421        set v {}
4422        append v "[appname] version $appvers\n"
4423        append v "[git version]\n"
4424        append v "\n"
4425        if {$tcl_patchLevel eq $tk_patchLevel} {
4426                append v "Tcl/Tk version $tcl_patchLevel"
4427        } else {
4428                append v "Tcl version $tcl_patchLevel"
4429                append v ", Tk version $tk_patchLevel"
4430        }
4431
4432        label $w.vers \
4433                -text $v \
4434                -padx 5 -pady 5 \
4435                -justify left \
4436                -anchor w \
4437                -borderwidth 1 \
4438                -relief solid \
4439                -font font_ui
4440        pack $w.vers -side top -fill x -padx 5 -pady 5
4441
4442        menu $w.ctxm -tearoff 0
4443        $w.ctxm add command \
4444                -label {Copy} \
4445                -font font_ui \
4446                -command "
4447                clipboard clear
4448                clipboard append -format STRING -type STRING -- \[$w.vers cget -text\]
4449        "
4450
4451        bind $w <Visibility> "grab $w; focus $w"
4452        bind $w <Key-Escape> "destroy $w"
4453        bind_button3 $w.vers "tk_popup $w.ctxm %X %Y; grab $w; focus $w"
4454        wm title $w "About [appname]"
4455        tkwait window $w
4456}
4457
4458proc do_options {} {
4459        global repo_config global_config font_descs
4460        global repo_config_new global_config_new
4461
4462        array unset repo_config_new
4463        array unset global_config_new
4464        foreach name [array names repo_config] {
4465                set repo_config_new($name) $repo_config($name)
4466        }
4467        load_config 1
4468        foreach name [array names repo_config] {
4469                switch -- $name {
4470                gui.diffcontext {continue}
4471                }
4472                set repo_config_new($name) $repo_config($name)
4473        }
4474        foreach name [array names global_config] {
4475                set global_config_new($name) $global_config($name)
4476        }
4477
4478        set w .options_editor
4479        toplevel $w
4480        wm geometry $w "+[winfo rootx .]+[winfo rooty .]"
4481
4482        label $w.header -text "[appname] Options" \
4483                -font font_uibold
4484        pack $w.header -side top -fill x
4485
4486        frame $w.buttons
4487        button $w.buttons.restore -text {Restore Defaults} \
4488                -font font_ui \
4489                -command do_restore_defaults
4490        pack $w.buttons.restore -side left
4491        button $w.buttons.save -text Save \
4492                -font font_ui \
4493                -command [list do_save_config $w]
4494        pack $w.buttons.save -side right
4495        button $w.buttons.cancel -text {Cancel} \
4496                -font font_ui \
4497                -command [list destroy $w]
4498        pack $w.buttons.cancel -side right -padx 5
4499        pack $w.buttons -side bottom -fill x -pady 10 -padx 10
4500
4501        labelframe $w.repo -text "[reponame] Repository" \
4502                -font font_ui
4503        labelframe $w.global -text {Global (All Repositories)} \
4504                -font font_ui
4505        pack $w.repo -side left -fill both -expand 1 -pady 5 -padx 5
4506        pack $w.global -side right -fill both -expand 1 -pady 5 -padx 5
4507
4508        set optid 0
4509        foreach option {
4510                {t user.name {User Name}}
4511                {t user.email {Email Address}}
4512
4513                {b merge.summary {Summarize Merge Commits}}
4514                {i-1..5 merge.verbosity {Merge Verbosity}}
4515
4516                {b gui.trustmtime  {Trust File Modification Timestamps}}
4517                {i-1..99 gui.diffcontext {Number of Diff Context Lines}}
4518                {t gui.newbranchtemplate {New Branch Name Template}}
4519                } {
4520                set type [lindex $option 0]
4521                set name [lindex $option 1]
4522                set text [lindex $option 2]
4523                incr optid
4524                foreach f {repo global} {
4525                        switch -glob -- $type {
4526                        b {
4527                                checkbutton $w.$f.$optid -text $text \
4528                                        -variable ${f}_config_new($name) \
4529                                        -onvalue true \
4530                                        -offvalue false \
4531                                        -font font_ui
4532                                pack $w.$f.$optid -side top -anchor w
4533                        }
4534                        i-* {
4535                                regexp -- {-(\d+)\.\.(\d+)$} $type _junk min max
4536                                frame $w.$f.$optid
4537                                label $w.$f.$optid.l -text "$text:" -font font_ui
4538                                pack $w.$f.$optid.l -side left -anchor w -fill x
4539                                spinbox $w.$f.$optid.v \
4540                                        -textvariable ${f}_config_new($name) \
4541                                        -from $min \
4542                                        -to $max \
4543                                        -increment 1 \
4544                                        -width [expr {1 + [string length $max]}] \
4545                                        -font font_ui
4546                                bind $w.$f.$optid.v <FocusIn> {%W selection range 0 end}
4547                                pack $w.$f.$optid.v -side right -anchor e -padx 5
4548                                pack $w.$f.$optid -side top -anchor w -fill x
4549                        }
4550                        t {
4551                                frame $w.$f.$optid
4552                                label $w.$f.$optid.l -text "$text:" -font font_ui
4553                                entry $w.$f.$optid.v \
4554                                        -borderwidth 1 \
4555                                        -relief sunken \
4556                                        -width 20 \
4557                                        -textvariable ${f}_config_new($name) \
4558                                        -font font_ui
4559                                pack $w.$f.$optid.l -side left -anchor w
4560                                pack $w.$f.$optid.v -side left -anchor w \
4561                                        -fill x -expand 1 \
4562                                        -padx 5
4563                                pack $w.$f.$optid -side top -anchor w -fill x
4564                        }
4565                        }
4566                }
4567        }
4568
4569        set all_fonts [lsort [font families]]
4570        foreach option $font_descs {
4571                set name [lindex $option 0]
4572                set font [lindex $option 1]
4573                set text [lindex $option 2]
4574
4575                set global_config_new(gui.$font^^family) \
4576                        [font configure $font -family]
4577                set global_config_new(gui.$font^^size) \
4578                        [font configure $font -size]
4579
4580                frame $w.global.$name
4581                label $w.global.$name.l -text "$text:" -font font_ui
4582                pack $w.global.$name.l -side left -anchor w -fill x
4583                eval tk_optionMenu $w.global.$name.family \
4584                        global_config_new(gui.$font^^family) \
4585                        $all_fonts
4586                spinbox $w.global.$name.size \
4587                        -textvariable global_config_new(gui.$font^^size) \
4588                        -from 2 -to 80 -increment 1 \
4589                        -width 3 \
4590                        -font font_ui
4591                bind $w.global.$name.size <FocusIn> {%W selection range 0 end}
4592                pack $w.global.$name.size -side right -anchor e
4593                pack $w.global.$name.family -side right -anchor e
4594                pack $w.global.$name -side top -anchor w -fill x
4595        }
4596
4597        bind $w <Visibility> "grab $w; focus $w"
4598        bind $w <Key-Escape> "destroy $w"
4599        wm title $w "[appname] ([reponame]): Options"
4600        tkwait window $w
4601}
4602
4603proc do_restore_defaults {} {
4604        global font_descs default_config repo_config
4605        global repo_config_new global_config_new
4606
4607        foreach name [array names default_config] {
4608                set repo_config_new($name) $default_config($name)
4609                set global_config_new($name) $default_config($name)
4610        }
4611
4612        foreach option $font_descs {
4613                set name [lindex $option 0]
4614                set repo_config(gui.$name) $default_config(gui.$name)
4615        }
4616        apply_config
4617
4618        foreach option $font_descs {
4619                set name [lindex $option 0]
4620                set font [lindex $option 1]
4621                set global_config_new(gui.$font^^family) \
4622                        [font configure $font -family]
4623                set global_config_new(gui.$font^^size) \
4624                        [font configure $font -size]
4625        }
4626}
4627
4628proc do_save_config {w} {
4629        if {[catch {save_config} err]} {
4630                error_popup "Failed to completely save options:\n\n$err"
4631        }
4632        reshow_diff
4633        destroy $w
4634}
4635
4636proc do_windows_shortcut {} {
4637        global argv0
4638
4639        set fn [tk_getSaveFile \
4640                -parent . \
4641                -title "[appname] ([reponame]): Create Desktop Icon" \
4642                -initialfile "Git [reponame].bat"]
4643        if {$fn != {}} {
4644                if {[catch {
4645                                set fd [open $fn w]
4646                                puts $fd "@ECHO Entering [reponame]"
4647                                puts $fd "@ECHO Starting git-gui... please wait..."
4648                                puts $fd "@SET PATH=[file normalize [gitexec]];%PATH%"
4649                                puts $fd "@SET GIT_DIR=[file normalize [gitdir]]"
4650                                puts -nonewline $fd "@\"[info nameofexecutable]\""
4651                                puts $fd " \"[file normalize $argv0]\""
4652                                close $fd
4653                        } err]} {
4654                        error_popup "Cannot write script:\n\n$err"
4655                }
4656        }
4657}
4658
4659proc do_cygwin_shortcut {} {
4660        global argv0
4661
4662        if {[catch {
4663                set desktop [exec cygpath \
4664                        --windows \
4665                        --absolute \
4666                        --long-name \
4667                        --desktop]
4668                }]} {
4669                        set desktop .
4670        }
4671        set fn [tk_getSaveFile \
4672                -parent . \
4673                -title "[appname] ([reponame]): Create Desktop Icon" \
4674                -initialdir $desktop \
4675                -initialfile "Git [reponame].bat"]
4676        if {$fn != {}} {
4677                if {[catch {
4678                                set fd [open $fn w]
4679                                set sh [exec cygpath \
4680                                        --windows \
4681                                        --absolute \
4682                                        /bin/sh]
4683                                set me [exec cygpath \
4684                                        --unix \
4685                                        --absolute \
4686                                        $argv0]
4687                                set gd [exec cygpath \
4688                                        --unix \
4689                                        --absolute \
4690                                        [gitdir]]
4691                                set gw [exec cygpath \
4692                                        --windows \
4693                                        --absolute \
4694                                        [file dirname [gitdir]]]
4695                                regsub -all ' $me "'\\''" me
4696                                regsub -all ' $gd "'\\''" gd
4697                                puts $fd "@ECHO Entering $gw"
4698                                puts $fd "@ECHO Starting git-gui... please wait..."
4699                                puts -nonewline $fd "@\"$sh\" --login -c \""
4700                                puts -nonewline $fd "GIT_DIR='$gd'"
4701                                puts -nonewline $fd " '$me'"
4702                                puts $fd "&\""
4703                                close $fd
4704                        } err]} {
4705                        error_popup "Cannot write script:\n\n$err"
4706                }
4707        }
4708}
4709
4710proc do_macosx_app {} {
4711        global argv0 env
4712
4713        set fn [tk_getSaveFile \
4714                -parent . \
4715                -title "[appname] ([reponame]): Create Desktop Icon" \
4716                -initialdir [file join $env(HOME) Desktop] \
4717                -initialfile "Git [reponame].app"]
4718        if {$fn != {}} {
4719                if {[catch {
4720                                set Contents [file join $fn Contents]
4721                                set MacOS [file join $Contents MacOS]
4722                                set exe [file join $MacOS git-gui]
4723
4724                                file mkdir $MacOS
4725
4726                                set fd [open [file join $Contents Info.plist] w]
4727                                puts $fd {<?xml version="1.0" encoding="UTF-8"?>
4728<!DOCTYPE plist PUBLIC "-//Apple Computer//DTD PLIST 1.0//EN" "http://www.apple.com/DTDs/PropertyList-1.0.dtd">
4729<plist version="1.0">
4730<dict>
4731        <key>CFBundleDevelopmentRegion</key>
4732        <string>English</string>
4733        <key>CFBundleExecutable</key>
4734        <string>git-gui</string>
4735        <key>CFBundleIdentifier</key>
4736        <string>org.spearce.git-gui</string>
4737        <key>CFBundleInfoDictionaryVersion</key>
4738        <string>6.0</string>
4739        <key>CFBundlePackageType</key>
4740        <string>APPL</string>
4741        <key>CFBundleSignature</key>
4742        <string>????</string>
4743        <key>CFBundleVersion</key>
4744        <string>1.0</string>
4745        <key>NSPrincipalClass</key>
4746        <string>NSApplication</string>
4747</dict>
4748</plist>}
4749                                close $fd
4750
4751                                set fd [open $exe w]
4752                                set gd [file normalize [gitdir]]
4753                                set ep [file normalize [gitexec]]
4754                                regsub -all ' $gd "'\\''" gd
4755                                regsub -all ' $ep "'\\''" ep
4756                                puts $fd "#!/bin/sh"
4757                                foreach name [array names env] {
4758                                        if {[string match GIT_* $name]} {
4759                                                regsub -all ' $env($name) "'\\''" v
4760                                                puts $fd "export $name='$v'"
4761                                        }
4762                                }
4763                                puts $fd "export PATH='$ep':\$PATH"
4764                                puts $fd "export GIT_DIR='$gd'"
4765                                puts $fd "exec [file normalize $argv0]"
4766                                close $fd
4767
4768                                file attributes $exe -permissions u+x,g+x,o+x
4769                        } err]} {
4770                        error_popup "Cannot write icon:\n\n$err"
4771                }
4772        }
4773}
4774
4775proc toggle_or_diff {w x y} {
4776        global file_states file_lists current_diff_path ui_index ui_workdir
4777        global last_clicked selected_paths
4778
4779        set pos [split [$w index @$x,$y] .]
4780        set lno [lindex $pos 0]
4781        set col [lindex $pos 1]
4782        set path [lindex $file_lists($w) [expr {$lno - 1}]]
4783        if {$path eq {}} {
4784                set last_clicked {}
4785                return
4786        }
4787
4788        set last_clicked [list $w $lno]
4789        array unset selected_paths
4790        $ui_index tag remove in_sel 0.0 end
4791        $ui_workdir tag remove in_sel 0.0 end
4792
4793        if {$col == 0} {
4794                if {$current_diff_path eq $path} {
4795                        set after {reshow_diff;}
4796                } else {
4797                        set after {}
4798                }
4799                if {$w eq $ui_index} {
4800                        update_indexinfo \
4801                                "Unstaging [short_path $path] from commit" \
4802                                [list $path] \
4803                                [concat $after {set ui_status_value {Ready.}}]
4804                } elseif {$w eq $ui_workdir} {
4805                        update_index \
4806                                "Adding [short_path $path]" \
4807                                [list $path] \
4808                                [concat $after {set ui_status_value {Ready.}}]
4809                }
4810        } else {
4811                show_diff $path $w $lno
4812        }
4813}
4814
4815proc add_one_to_selection {w x y} {
4816        global file_lists last_clicked selected_paths
4817
4818        set lno [lindex [split [$w index @$x,$y] .] 0]
4819        set path [lindex $file_lists($w) [expr {$lno - 1}]]
4820        if {$path eq {}} {
4821                set last_clicked {}
4822                return
4823        }
4824
4825        if {$last_clicked ne {}
4826                && [lindex $last_clicked 0] ne $w} {
4827                array unset selected_paths
4828                [lindex $last_clicked 0] tag remove in_sel 0.0 end
4829        }
4830
4831        set last_clicked [list $w $lno]
4832        if {[catch {set in_sel $selected_paths($path)}]} {
4833                set in_sel 0
4834        }
4835        if {$in_sel} {
4836                unset selected_paths($path)
4837                $w tag remove in_sel $lno.0 [expr {$lno + 1}].0
4838        } else {
4839                set selected_paths($path) 1
4840                $w tag add in_sel $lno.0 [expr {$lno + 1}].0
4841        }
4842}
4843
4844proc add_range_to_selection {w x y} {
4845        global file_lists last_clicked selected_paths
4846
4847        if {[lindex $last_clicked 0] ne $w} {
4848                toggle_or_diff $w $x $y
4849                return
4850        }
4851
4852        set lno [lindex [split [$w index @$x,$y] .] 0]
4853        set lc [lindex $last_clicked 1]
4854        if {$lc < $lno} {
4855                set begin $lc
4856                set end $lno
4857        } else {
4858                set begin $lno
4859                set end $lc
4860        }
4861
4862        foreach path [lrange $file_lists($w) \
4863                [expr {$begin - 1}] \
4864                [expr {$end - 1}]] {
4865                set selected_paths($path) 1
4866        }
4867        $w tag add in_sel $begin.0 [expr {$end + 1}].0
4868}
4869
4870######################################################################
4871##
4872## config defaults
4873
4874set cursor_ptr arrow
4875font create font_diff -family Courier -size 10
4876font create font_ui
4877catch {
4878        label .dummy
4879        eval font configure font_ui [font actual [.dummy cget -font]]
4880        destroy .dummy
4881}
4882
4883font create font_uibold
4884font create font_diffbold
4885
4886if {[is_Windows]} {
4887        set M1B Control
4888        set M1T Ctrl
4889} elseif {[is_MacOSX]} {
4890        set M1B M1
4891        set M1T Cmd
4892} else {
4893        set M1B M1
4894        set M1T M1
4895}
4896
4897proc apply_config {} {
4898        global repo_config font_descs
4899
4900        foreach option $font_descs {
4901                set name [lindex $option 0]
4902                set font [lindex $option 1]
4903                if {[catch {
4904                        foreach {cn cv} $repo_config(gui.$name) {
4905                                font configure $font $cn $cv
4906                        }
4907                        } err]} {
4908                        error_popup "Invalid font specified in gui.$name:\n\n$err"
4909                }
4910                foreach {cn cv} [font configure $font] {
4911                        font configure ${font}bold $cn $cv
4912                }
4913                font configure ${font}bold -weight bold
4914        }
4915}
4916
4917set default_config(merge.summary) false
4918set default_config(merge.verbosity) 2
4919set default_config(user.name) {}
4920set default_config(user.email) {}
4921
4922set default_config(gui.trustmtime) false
4923set default_config(gui.diffcontext) 5
4924set default_config(gui.newbranchtemplate) {}
4925set default_config(gui.fontui) [font configure font_ui]
4926set default_config(gui.fontdiff) [font configure font_diff]
4927set font_descs {
4928        {fontui   font_ui   {Main Font}}
4929        {fontdiff font_diff {Diff/Console Font}}
4930}
4931load_config 0
4932apply_config
4933
4934######################################################################
4935##
4936## feature option selection
4937
4938if {[regexp {^git-(.+)$} [appname] _junk subcommand]} {
4939        unset _junk
4940} else {
4941        set subcommand gui
4942}
4943if {$subcommand eq {gui.sh}} {
4944        set subcommand gui
4945}
4946if {$subcommand eq {gui} && [llength $argv] > 0} {
4947        set subcommand [lindex $argv 0]
4948        set argv [lrange $argv 1 end]
4949}
4950
4951enable_option multicommit
4952enable_option branch
4953enable_option transport
4954
4955switch -- $subcommand {
4956blame {
4957        disable_option multicommit
4958        disable_option branch
4959        disable_option transport
4960}
4961citool {
4962        enable_option singlecommit
4963
4964        disable_option multicommit
4965        disable_option branch
4966        disable_option transport
4967}
4968}
4969
4970######################################################################
4971##
4972## ui construction
4973
4974set ui_comm {}
4975
4976# -- Menu Bar
4977#
4978menu .mbar -tearoff 0
4979.mbar add cascade -label Repository -menu .mbar.repository
4980.mbar add cascade -label Edit -menu .mbar.edit
4981if {[is_enabled branch]} {
4982        .mbar add cascade -label Branch -menu .mbar.branch
4983}
4984if {[is_enabled multicommit] || [is_enabled singlecommit]} {
4985        .mbar add cascade -label Commit -menu .mbar.commit
4986}
4987if {[is_enabled transport]} {
4988        .mbar add cascade -label Merge -menu .mbar.merge
4989        .mbar add cascade -label Fetch -menu .mbar.fetch
4990        .mbar add cascade -label Push -menu .mbar.push
4991}
4992. configure -menu .mbar
4993
4994# -- Repository Menu
4995#
4996menu .mbar.repository
4997
4998.mbar.repository add command \
4999        -label {Browse Current Branch} \
5000        -command {new_browser $current_branch} \
5001        -font font_ui
5002trace add variable current_branch write ".mbar.repository entryconf [.mbar.repository index last] -label \"Browse \$current_branch\" ;#"
5003.mbar.repository add separator
5004
5005.mbar.repository add command \
5006        -label {Visualize Current Branch} \
5007        -command {do_gitk $current_branch} \
5008        -font font_ui
5009trace add variable current_branch write ".mbar.repository entryconf [.mbar.repository index last] -label \"Visualize \$current_branch\" ;#"
5010.mbar.repository add command \
5011        -label {Visualize All Branches} \
5012        -command {do_gitk --all} \
5013        -font font_ui
5014.mbar.repository add separator
5015
5016if {[is_enabled multicommit]} {
5017        .mbar.repository add command -label {Database Statistics} \
5018                -command do_stats \
5019                -font font_ui
5020
5021        .mbar.repository add command -label {Compress Database} \
5022                -command do_gc \
5023                -font font_ui
5024
5025        .mbar.repository add command -label {Verify Database} \
5026                -command do_fsck_objects \
5027                -font font_ui
5028
5029        .mbar.repository add separator
5030
5031        if {[is_Cygwin]} {
5032                .mbar.repository add command \
5033                        -label {Create Desktop Icon} \
5034                        -command do_cygwin_shortcut \
5035                        -font font_ui
5036        } elseif {[is_Windows]} {
5037                .mbar.repository add command \
5038                        -label {Create Desktop Icon} \
5039                        -command do_windows_shortcut \
5040                        -font font_ui
5041        } elseif {[is_MacOSX]} {
5042                .mbar.repository add command \
5043                        -label {Create Desktop Icon} \
5044                        -command do_macosx_app \
5045                        -font font_ui
5046        }
5047}
5048
5049.mbar.repository add command -label Quit \
5050        -command do_quit \
5051        -accelerator $M1T-Q \
5052        -font font_ui
5053
5054# -- Edit Menu
5055#
5056menu .mbar.edit
5057.mbar.edit add command -label Undo \
5058        -command {catch {[focus] edit undo}} \
5059        -accelerator $M1T-Z \
5060        -font font_ui
5061.mbar.edit add command -label Redo \
5062        -command {catch {[focus] edit redo}} \
5063        -accelerator $M1T-Y \
5064        -font font_ui
5065.mbar.edit add separator
5066.mbar.edit add command -label Cut \
5067        -command {catch {tk_textCut [focus]}} \
5068        -accelerator $M1T-X \
5069        -font font_ui
5070.mbar.edit add command -label Copy \
5071        -command {catch {tk_textCopy [focus]}} \
5072        -accelerator $M1T-C \
5073        -font font_ui
5074.mbar.edit add command -label Paste \
5075        -command {catch {tk_textPaste [focus]; [focus] see insert}} \
5076        -accelerator $M1T-V \
5077        -font font_ui
5078.mbar.edit add command -label Delete \
5079        -command {catch {[focus] delete sel.first sel.last}} \
5080        -accelerator Del \
5081        -font font_ui
5082.mbar.edit add separator
5083.mbar.edit add command -label {Select All} \
5084        -command {catch {[focus] tag add sel 0.0 end}} \
5085        -accelerator $M1T-A \
5086        -font font_ui
5087
5088# -- Branch Menu
5089#
5090if {[is_enabled branch]} {
5091        menu .mbar.branch
5092
5093        .mbar.branch add command -label {Create...} \
5094                -command do_create_branch \
5095                -accelerator $M1T-N \
5096                -font font_ui
5097        lappend disable_on_lock [list .mbar.branch entryconf \
5098                [.mbar.branch index last] -state]
5099
5100        .mbar.branch add command -label {Delete...} \
5101                -command do_delete_branch \
5102                -font font_ui
5103        lappend disable_on_lock [list .mbar.branch entryconf \
5104                [.mbar.branch index last] -state]
5105}
5106
5107# -- Commit Menu
5108#
5109if {[is_enabled multicommit] || [is_enabled singlecommit]} {
5110        menu .mbar.commit
5111
5112        .mbar.commit add radiobutton \
5113                -label {New Commit} \
5114                -command do_select_commit_type \
5115                -variable selected_commit_type \
5116                -value new \
5117                -font font_ui
5118        lappend disable_on_lock \
5119                [list .mbar.commit entryconf [.mbar.commit index last] -state]
5120
5121        .mbar.commit add radiobutton \
5122                -label {Amend Last Commit} \
5123                -command do_select_commit_type \
5124                -variable selected_commit_type \
5125                -value amend \
5126                -font font_ui
5127        lappend disable_on_lock \
5128                [list .mbar.commit entryconf [.mbar.commit index last] -state]
5129
5130        .mbar.commit add separator
5131
5132        .mbar.commit add command -label Rescan \
5133                -command do_rescan \
5134                -accelerator F5 \
5135                -font font_ui
5136        lappend disable_on_lock \
5137                [list .mbar.commit entryconf [.mbar.commit index last] -state]
5138
5139        .mbar.commit add command -label {Add To Commit} \
5140                -command do_add_selection \
5141                -font font_ui
5142        lappend disable_on_lock \
5143                [list .mbar.commit entryconf [.mbar.commit index last] -state]
5144
5145        .mbar.commit add command -label {Add Existing To Commit} \
5146                -command do_add_all \
5147                -accelerator $M1T-I \
5148                -font font_ui
5149        lappend disable_on_lock \
5150                [list .mbar.commit entryconf [.mbar.commit index last] -state]
5151
5152        .mbar.commit add command -label {Unstage From Commit} \
5153                -command do_unstage_selection \
5154                -font font_ui
5155        lappend disable_on_lock \
5156                [list .mbar.commit entryconf [.mbar.commit index last] -state]
5157
5158        .mbar.commit add command -label {Revert Changes} \
5159                -command do_revert_selection \
5160                -font font_ui
5161        lappend disable_on_lock \
5162                [list .mbar.commit entryconf [.mbar.commit index last] -state]
5163
5164        .mbar.commit add separator
5165
5166        .mbar.commit add command -label {Sign Off} \
5167                -command do_signoff \
5168                -accelerator $M1T-S \
5169                -font font_ui
5170
5171        .mbar.commit add command -label Commit \
5172                -command do_commit \
5173                -accelerator $M1T-Return \
5174                -font font_ui
5175        lappend disable_on_lock \
5176                [list .mbar.commit entryconf [.mbar.commit index last] -state]
5177}
5178
5179if {[is_MacOSX]} {
5180        # -- Apple Menu (Mac OS X only)
5181        #
5182        .mbar add cascade -label Apple -menu .mbar.apple
5183        menu .mbar.apple
5184
5185        .mbar.apple add command -label "About [appname]" \
5186                -command do_about \
5187                -font font_ui
5188        .mbar.apple add command -label "[appname] Options..." \
5189                -command do_options \
5190                -font font_ui
5191} else {
5192        # -- Edit Menu
5193        #
5194        .mbar.edit add separator
5195        .mbar.edit add command -label {Options...} \
5196                -command do_options \
5197                -font font_ui
5198
5199        # -- Tools Menu
5200        #
5201        if {[file exists /usr/local/miga/lib/gui-miga]
5202                && [file exists .pvcsrc]} {
5203        proc do_miga {} {
5204                global ui_status_value
5205                if {![lock_index update]} return
5206                set cmd [list sh --login -c "/usr/local/miga/lib/gui-miga \"[pwd]\""]
5207                set miga_fd [open "|$cmd" r]
5208                fconfigure $miga_fd -blocking 0
5209                fileevent $miga_fd readable [list miga_done $miga_fd]
5210                set ui_status_value {Running miga...}
5211        }
5212        proc miga_done {fd} {
5213                read $fd 512
5214                if {[eof $fd]} {
5215                        close $fd
5216                        unlock_index
5217                        rescan [list set ui_status_value {Ready.}]
5218                }
5219        }
5220        .mbar add cascade -label Tools -menu .mbar.tools
5221        menu .mbar.tools
5222        .mbar.tools add command -label "Migrate" \
5223                -command do_miga \
5224                -font font_ui
5225        lappend disable_on_lock \
5226                [list .mbar.tools entryconf [.mbar.tools index last] -state]
5227        }
5228}
5229
5230# -- Help Menu
5231#
5232.mbar add cascade -label Help -menu .mbar.help
5233menu .mbar.help
5234
5235if {![is_MacOSX]} {
5236        .mbar.help add command -label "About [appname]" \
5237                -command do_about \
5238                -font font_ui
5239}
5240
5241set browser {}
5242catch {set browser $repo_config(instaweb.browser)}
5243set doc_path [file dirname [gitexec]]
5244set doc_path [file join $doc_path Documentation index.html]
5245
5246if {[is_Cygwin]} {
5247        set doc_path [exec cygpath --windows $doc_path]
5248}
5249
5250if {$browser eq {}} {
5251        if {[is_MacOSX]} {
5252                set browser open
5253        } elseif {[is_Cygwin]} {
5254                set program_files [file dirname [exec cygpath --windir]]
5255                set program_files [file join $program_files {Program Files}]
5256                set firefox [file join $program_files {Mozilla Firefox} firefox.exe]
5257                set ie [file join $program_files {Internet Explorer} IEXPLORE.EXE]
5258                if {[file exists $firefox]} {
5259                        set browser $firefox
5260                } elseif {[file exists $ie]} {
5261                        set browser $ie
5262                }
5263                unset program_files firefox ie
5264        }
5265}
5266
5267if {[file isfile $doc_path]} {
5268        set doc_url "file:$doc_path"
5269} else {
5270        set doc_url {http://www.kernel.org/pub/software/scm/git/docs/}
5271}
5272
5273if {$browser ne {}} {
5274        .mbar.help add command -label {Online Documentation} \
5275                -command [list exec $browser $doc_url &] \
5276                -font font_ui
5277}
5278unset browser doc_path doc_url
5279
5280# -- Standard bindings
5281#
5282bind .   <Destroy> do_quit
5283bind all <$M1B-Key-q> do_quit
5284bind all <$M1B-Key-Q> do_quit
5285bind all <$M1B-Key-w> {destroy [winfo toplevel %W]}
5286bind all <$M1B-Key-W> {destroy [winfo toplevel %W]}
5287
5288# -- Not a normal commit type invocation?  Do that instead!
5289#
5290switch -- $subcommand {
5291blame {
5292        if {[llength $argv] != 2} {
5293                puts stderr "usage: $argv0 blame commit path"
5294                exit 1
5295        }
5296        set current_branch [lindex $argv 0]
5297        show_blame $current_branch [lindex $argv 1]
5298        return
5299}
5300citool -
5301gui {
5302        if {[llength $argv] != 0} {
5303                puts -nonewline stderr "usage: $argv0"
5304                if {$subcommand ne {gui} && [appname] ne "git-$subcommand"} {
5305                        puts -nonewline stderr " $subcommand"
5306                }
5307                puts stderr {}
5308                exit 1
5309        }
5310        # fall through to setup UI for commits
5311}
5312default {
5313        puts stderr "usage: $argv0 \[{blame|citool}\]"
5314        exit 1
5315}
5316}
5317
5318# -- Branch Control
5319#
5320frame .branch \
5321        -borderwidth 1 \
5322        -relief sunken
5323label .branch.l1 \
5324        -text {Current Branch:} \
5325        -anchor w \
5326        -justify left \
5327        -font font_ui
5328label .branch.cb \
5329        -textvariable current_branch \
5330        -anchor w \
5331        -justify left \
5332        -font font_ui
5333pack .branch.l1 -side left
5334pack .branch.cb -side left -fill x
5335pack .branch -side top -fill x
5336
5337if {[is_enabled branch]} {
5338        menu .mbar.merge
5339        .mbar.merge add command -label {Local Merge...} \
5340                -command do_local_merge \
5341                -font font_ui
5342        lappend disable_on_lock \
5343                [list .mbar.merge entryconf [.mbar.merge index last] -state]
5344        .mbar.merge add command -label {Abort Merge...} \
5345                -command do_reset_hard \
5346                -font font_ui
5347        lappend disable_on_lock \
5348                [list .mbar.merge entryconf [.mbar.merge index last] -state]
5349
5350
5351        menu .mbar.fetch
5352
5353        menu .mbar.push
5354        .mbar.push add command -label {Push...} \
5355                -command do_push_anywhere \
5356                -font font_ui
5357}
5358
5359# -- Main Window Layout
5360#
5361panedwindow .vpane -orient vertical
5362panedwindow .vpane.files -orient horizontal
5363.vpane add .vpane.files -sticky nsew -height 100 -width 200
5364pack .vpane -anchor n -side top -fill both -expand 1
5365
5366# -- Index File List
5367#
5368frame .vpane.files.index -height 100 -width 200
5369label .vpane.files.index.title -text {Changes To Be Committed} \
5370        -background green \
5371        -font font_ui
5372text $ui_index -background white -borderwidth 0 \
5373        -width 20 -height 10 \
5374        -wrap none \
5375        -font font_ui \
5376        -cursor $cursor_ptr \
5377        -xscrollcommand {.vpane.files.index.sx set} \
5378        -yscrollcommand {.vpane.files.index.sy set} \
5379        -state disabled
5380scrollbar .vpane.files.index.sx -orient h -command [list $ui_index xview]
5381scrollbar .vpane.files.index.sy -orient v -command [list $ui_index yview]
5382pack .vpane.files.index.title -side top -fill x
5383pack .vpane.files.index.sx -side bottom -fill x
5384pack .vpane.files.index.sy -side right -fill y
5385pack $ui_index -side left -fill both -expand 1
5386.vpane.files add .vpane.files.index -sticky nsew
5387
5388# -- Working Directory File List
5389#
5390frame .vpane.files.workdir -height 100 -width 200
5391label .vpane.files.workdir.title -text {Changed But Not Updated} \
5392        -background red \
5393        -font font_ui
5394text $ui_workdir -background white -borderwidth 0 \
5395        -width 20 -height 10 \
5396        -wrap none \
5397        -font font_ui \
5398        -cursor $cursor_ptr \
5399        -xscrollcommand {.vpane.files.workdir.sx set} \
5400        -yscrollcommand {.vpane.files.workdir.sy set} \
5401        -state disabled
5402scrollbar .vpane.files.workdir.sx -orient h -command [list $ui_workdir xview]
5403scrollbar .vpane.files.workdir.sy -orient v -command [list $ui_workdir yview]
5404pack .vpane.files.workdir.title -side top -fill x
5405pack .vpane.files.workdir.sx -side bottom -fill x
5406pack .vpane.files.workdir.sy -side right -fill y
5407pack $ui_workdir -side left -fill both -expand 1
5408.vpane.files add .vpane.files.workdir -sticky nsew
5409
5410foreach i [list $ui_index $ui_workdir] {
5411        $i tag conf in_diff -font font_uibold
5412        $i tag conf in_sel \
5413                -background [$i cget -foreground] \
5414                -foreground [$i cget -background]
5415}
5416unset i
5417
5418# -- Diff and Commit Area
5419#
5420frame .vpane.lower -height 300 -width 400
5421frame .vpane.lower.commarea
5422frame .vpane.lower.diff -relief sunken -borderwidth 1
5423pack .vpane.lower.commarea -side top -fill x
5424pack .vpane.lower.diff -side bottom -fill both -expand 1
5425.vpane add .vpane.lower -sticky nsew
5426
5427# -- Commit Area Buttons
5428#
5429frame .vpane.lower.commarea.buttons
5430label .vpane.lower.commarea.buttons.l -text {} \
5431        -anchor w \
5432        -justify left \
5433        -font font_ui
5434pack .vpane.lower.commarea.buttons.l -side top -fill x
5435pack .vpane.lower.commarea.buttons -side left -fill y
5436
5437button .vpane.lower.commarea.buttons.rescan -text {Rescan} \
5438        -command do_rescan \
5439        -font font_ui
5440pack .vpane.lower.commarea.buttons.rescan -side top -fill x
5441lappend disable_on_lock \
5442        {.vpane.lower.commarea.buttons.rescan conf -state}
5443
5444button .vpane.lower.commarea.buttons.incall -text {Add Existing} \
5445        -command do_add_all \
5446        -font font_ui
5447pack .vpane.lower.commarea.buttons.incall -side top -fill x
5448lappend disable_on_lock \
5449        {.vpane.lower.commarea.buttons.incall conf -state}
5450
5451button .vpane.lower.commarea.buttons.signoff -text {Sign Off} \
5452        -command do_signoff \
5453        -font font_ui
5454pack .vpane.lower.commarea.buttons.signoff -side top -fill x
5455
5456button .vpane.lower.commarea.buttons.commit -text {Commit} \
5457        -command do_commit \
5458        -font font_ui
5459pack .vpane.lower.commarea.buttons.commit -side top -fill x
5460lappend disable_on_lock \
5461        {.vpane.lower.commarea.buttons.commit conf -state}
5462
5463# -- Commit Message Buffer
5464#
5465frame .vpane.lower.commarea.buffer
5466frame .vpane.lower.commarea.buffer.header
5467set ui_comm .vpane.lower.commarea.buffer.t
5468set ui_coml .vpane.lower.commarea.buffer.header.l
5469radiobutton .vpane.lower.commarea.buffer.header.new \
5470        -text {New Commit} \
5471        -command do_select_commit_type \
5472        -variable selected_commit_type \
5473        -value new \
5474        -font font_ui
5475lappend disable_on_lock \
5476        [list .vpane.lower.commarea.buffer.header.new conf -state]
5477radiobutton .vpane.lower.commarea.buffer.header.amend \
5478        -text {Amend Last Commit} \
5479        -command do_select_commit_type \
5480        -variable selected_commit_type \
5481        -value amend \
5482        -font font_ui
5483lappend disable_on_lock \
5484        [list .vpane.lower.commarea.buffer.header.amend conf -state]
5485label $ui_coml \
5486        -anchor w \
5487        -justify left \
5488        -font font_ui
5489proc trace_commit_type {varname args} {
5490        global ui_coml commit_type
5491        switch -glob -- $commit_type {
5492        initial       {set txt {Initial Commit Message:}}
5493        amend         {set txt {Amended Commit Message:}}
5494        amend-initial {set txt {Amended Initial Commit Message:}}
5495        amend-merge   {set txt {Amended Merge Commit Message:}}
5496        merge         {set txt {Merge Commit Message:}}
5497        *             {set txt {Commit Message:}}
5498        }
5499        $ui_coml conf -text $txt
5500}
5501trace add variable commit_type write trace_commit_type
5502pack $ui_coml -side left -fill x
5503pack .vpane.lower.commarea.buffer.header.amend -side right
5504pack .vpane.lower.commarea.buffer.header.new -side right
5505
5506text $ui_comm -background white -borderwidth 1 \
5507        -undo true \
5508        -maxundo 20 \
5509        -autoseparators true \
5510        -relief sunken \
5511        -width 75 -height 9 -wrap none \
5512        -font font_diff \
5513        -yscrollcommand {.vpane.lower.commarea.buffer.sby set}
5514scrollbar .vpane.lower.commarea.buffer.sby \
5515        -command [list $ui_comm yview]
5516pack .vpane.lower.commarea.buffer.header -side top -fill x
5517pack .vpane.lower.commarea.buffer.sby -side right -fill y
5518pack $ui_comm -side left -fill y
5519pack .vpane.lower.commarea.buffer -side left -fill y
5520
5521# -- Commit Message Buffer Context Menu
5522#
5523set ctxm .vpane.lower.commarea.buffer.ctxm
5524menu $ctxm -tearoff 0
5525$ctxm add command \
5526        -label {Cut} \
5527        -font font_ui \
5528        -command {tk_textCut $ui_comm}
5529$ctxm add command \
5530        -label {Copy} \
5531        -font font_ui \
5532        -command {tk_textCopy $ui_comm}
5533$ctxm add command \
5534        -label {Paste} \
5535        -font font_ui \
5536        -command {tk_textPaste $ui_comm}
5537$ctxm add command \
5538        -label {Delete} \
5539        -font font_ui \
5540        -command {$ui_comm delete sel.first sel.last}
5541$ctxm add separator
5542$ctxm add command \
5543        -label {Select All} \
5544        -font font_ui \
5545        -command {focus $ui_comm;$ui_comm tag add sel 0.0 end}
5546$ctxm add command \
5547        -label {Copy All} \
5548        -font font_ui \
5549        -command {
5550                $ui_comm tag add sel 0.0 end
5551                tk_textCopy $ui_comm
5552                $ui_comm tag remove sel 0.0 end
5553        }
5554$ctxm add separator
5555$ctxm add command \
5556        -label {Sign Off} \
5557        -font font_ui \
5558        -command do_signoff
5559bind_button3 $ui_comm "tk_popup $ctxm %X %Y"
5560
5561# -- Diff Header
5562#
5563set current_diff_path {}
5564set current_diff_side {}
5565set diff_actions [list]
5566proc trace_current_diff_path {varname args} {
5567        global current_diff_path diff_actions file_states
5568        if {$current_diff_path eq {}} {
5569                set s {}
5570                set f {}
5571                set p {}
5572                set o disabled
5573        } else {
5574                set p $current_diff_path
5575                set s [mapdesc [lindex $file_states($p) 0] $p]
5576                set f {File:}
5577                set p [escape_path $p]
5578                set o normal
5579        }
5580
5581        .vpane.lower.diff.header.status configure -text $s
5582        .vpane.lower.diff.header.file configure -text $f
5583        .vpane.lower.diff.header.path configure -text $p
5584        foreach w $diff_actions {
5585                uplevel #0 $w $o
5586        }
5587}
5588trace add variable current_diff_path write trace_current_diff_path
5589
5590frame .vpane.lower.diff.header -background orange
5591label .vpane.lower.diff.header.status \
5592        -background orange \
5593        -width $max_status_desc \
5594        -anchor w \
5595        -justify left \
5596        -font font_ui
5597label .vpane.lower.diff.header.file \
5598        -background orange \
5599        -anchor w \
5600        -justify left \
5601        -font font_ui
5602label .vpane.lower.diff.header.path \
5603        -background orange \
5604        -anchor w \
5605        -justify left \
5606        -font font_ui
5607pack .vpane.lower.diff.header.status -side left
5608pack .vpane.lower.diff.header.file -side left
5609pack .vpane.lower.diff.header.path -fill x
5610set ctxm .vpane.lower.diff.header.ctxm
5611menu $ctxm -tearoff 0
5612$ctxm add command \
5613        -label {Copy} \
5614        -font font_ui \
5615        -command {
5616                clipboard clear
5617                clipboard append \
5618                        -format STRING \
5619                        -type STRING \
5620                        -- $current_diff_path
5621        }
5622lappend diff_actions [list $ctxm entryconf [$ctxm index last] -state]
5623bind_button3 .vpane.lower.diff.header.path "tk_popup $ctxm %X %Y"
5624
5625# -- Diff Body
5626#
5627frame .vpane.lower.diff.body
5628set ui_diff .vpane.lower.diff.body.t
5629text $ui_diff -background white -borderwidth 0 \
5630        -width 80 -height 15 -wrap none \
5631        -font font_diff \
5632        -xscrollcommand {.vpane.lower.diff.body.sbx set} \
5633        -yscrollcommand {.vpane.lower.diff.body.sby set} \
5634        -state disabled
5635scrollbar .vpane.lower.diff.body.sbx -orient horizontal \
5636        -command [list $ui_diff xview]
5637scrollbar .vpane.lower.diff.body.sby -orient vertical \
5638        -command [list $ui_diff yview]
5639pack .vpane.lower.diff.body.sbx -side bottom -fill x
5640pack .vpane.lower.diff.body.sby -side right -fill y
5641pack $ui_diff -side left -fill both -expand 1
5642pack .vpane.lower.diff.header -side top -fill x
5643pack .vpane.lower.diff.body -side bottom -fill both -expand 1
5644
5645$ui_diff tag conf d_cr -elide true
5646$ui_diff tag conf d_@ -foreground blue -font font_diffbold
5647$ui_diff tag conf d_+ -foreground {#00a000}
5648$ui_diff tag conf d_- -foreground red
5649
5650$ui_diff tag conf d_++ -foreground {#00a000}
5651$ui_diff tag conf d_-- -foreground red
5652$ui_diff tag conf d_+s \
5653        -foreground {#00a000} \
5654        -background {#e2effa}
5655$ui_diff tag conf d_-s \
5656        -foreground red \
5657        -background {#e2effa}
5658$ui_diff tag conf d_s+ \
5659        -foreground {#00a000} \
5660        -background ivory1
5661$ui_diff tag conf d_s- \
5662        -foreground red \
5663        -background ivory1
5664
5665$ui_diff tag conf d<<<<<<< \
5666        -foreground orange \
5667        -font font_diffbold
5668$ui_diff tag conf d======= \
5669        -foreground orange \
5670        -font font_diffbold
5671$ui_diff tag conf d>>>>>>> \
5672        -foreground orange \
5673        -font font_diffbold
5674
5675$ui_diff tag raise sel
5676
5677# -- Diff Body Context Menu
5678#
5679set ctxm .vpane.lower.diff.body.ctxm
5680menu $ctxm -tearoff 0
5681$ctxm add command \
5682        -label {Refresh} \
5683        -font font_ui \
5684        -command reshow_diff
5685lappend diff_actions [list $ctxm entryconf [$ctxm index last] -state]
5686$ctxm add command \
5687        -label {Copy} \
5688        -font font_ui \
5689        -command {tk_textCopy $ui_diff}
5690lappend diff_actions [list $ctxm entryconf [$ctxm index last] -state]
5691$ctxm add command \
5692        -label {Select All} \
5693        -font font_ui \
5694        -command {focus $ui_diff;$ui_diff tag add sel 0.0 end}
5695lappend diff_actions [list $ctxm entryconf [$ctxm index last] -state]
5696$ctxm add command \
5697        -label {Copy All} \
5698        -font font_ui \
5699        -command {
5700                $ui_diff tag add sel 0.0 end
5701                tk_textCopy $ui_diff
5702                $ui_diff tag remove sel 0.0 end
5703        }
5704lappend diff_actions [list $ctxm entryconf [$ctxm index last] -state]
5705$ctxm add separator
5706$ctxm add command \
5707        -label {Apply/Reverse Hunk} \
5708        -font font_ui \
5709        -command {apply_hunk $cursorX $cursorY}
5710set ui_diff_applyhunk [$ctxm index last]
5711lappend diff_actions [list $ctxm entryconf $ui_diff_applyhunk -state]
5712$ctxm add separator
5713$ctxm add command \
5714        -label {Decrease Font Size} \
5715        -font font_ui \
5716        -command {incr_font_size font_diff -1}
5717lappend diff_actions [list $ctxm entryconf [$ctxm index last] -state]
5718$ctxm add command \
5719        -label {Increase Font Size} \
5720        -font font_ui \
5721        -command {incr_font_size font_diff 1}
5722lappend diff_actions [list $ctxm entryconf [$ctxm index last] -state]
5723$ctxm add separator
5724$ctxm add command \
5725        -label {Show Less Context} \
5726        -font font_ui \
5727        -command {if {$repo_config(gui.diffcontext) >= 2} {
5728                incr repo_config(gui.diffcontext) -1
5729                reshow_diff
5730        }}
5731lappend diff_actions [list $ctxm entryconf [$ctxm index last] -state]
5732$ctxm add command \
5733        -label {Show More Context} \
5734        -font font_ui \
5735        -command {
5736                incr repo_config(gui.diffcontext)
5737                reshow_diff
5738        }
5739lappend diff_actions [list $ctxm entryconf [$ctxm index last] -state]
5740$ctxm add separator
5741$ctxm add command -label {Options...} \
5742        -font font_ui \
5743        -command do_options
5744bind_button3 $ui_diff "
5745        set cursorX %x
5746        set cursorY %y
5747        if {\$ui_index eq \$current_diff_side} {
5748                $ctxm entryconf $ui_diff_applyhunk -label {Unstage Hunk From Commit}
5749        } else {
5750                $ctxm entryconf $ui_diff_applyhunk -label {Stage Hunk For Commit}
5751        }
5752        tk_popup $ctxm %X %Y
5753"
5754unset ui_diff_applyhunk
5755
5756# -- Status Bar
5757#
5758set ui_status_value {Initializing...}
5759label .status -textvariable ui_status_value \
5760        -anchor w \
5761        -justify left \
5762        -borderwidth 1 \
5763        -relief sunken \
5764        -font font_ui
5765pack .status -anchor w -side bottom -fill x
5766
5767# -- Load geometry
5768#
5769catch {
5770set gm $repo_config(gui.geometry)
5771wm geometry . [lindex $gm 0]
5772.vpane sash place 0 \
5773        [lindex [.vpane sash coord 0] 0] \
5774        [lindex $gm 1]
5775.vpane.files sash place 0 \
5776        [lindex $gm 2] \
5777        [lindex [.vpane.files sash coord 0] 1]
5778unset gm
5779}
5780
5781# -- Key Bindings
5782#
5783bind $ui_comm <$M1B-Key-Return> {do_commit;break}
5784bind $ui_comm <$M1B-Key-i> {do_add_all;break}
5785bind $ui_comm <$M1B-Key-I> {do_add_all;break}
5786bind $ui_comm <$M1B-Key-x> {tk_textCut %W;break}
5787bind $ui_comm <$M1B-Key-X> {tk_textCut %W;break}
5788bind $ui_comm <$M1B-Key-c> {tk_textCopy %W;break}
5789bind $ui_comm <$M1B-Key-C> {tk_textCopy %W;break}
5790bind $ui_comm <$M1B-Key-v> {tk_textPaste %W; %W see insert; break}
5791bind $ui_comm <$M1B-Key-V> {tk_textPaste %W; %W see insert; break}
5792bind $ui_comm <$M1B-Key-a> {%W tag add sel 0.0 end;break}
5793bind $ui_comm <$M1B-Key-A> {%W tag add sel 0.0 end;break}
5794
5795bind $ui_diff <$M1B-Key-x> {tk_textCopy %W;break}
5796bind $ui_diff <$M1B-Key-X> {tk_textCopy %W;break}
5797bind $ui_diff <$M1B-Key-c> {tk_textCopy %W;break}
5798bind $ui_diff <$M1B-Key-C> {tk_textCopy %W;break}
5799bind $ui_diff <$M1B-Key-v> {break}
5800bind $ui_diff <$M1B-Key-V> {break}
5801bind $ui_diff <$M1B-Key-a> {%W tag add sel 0.0 end;break}
5802bind $ui_diff <$M1B-Key-A> {%W tag add sel 0.0 end;break}
5803bind $ui_diff <Key-Up>     {catch {%W yview scroll -1 units};break}
5804bind $ui_diff <Key-Down>   {catch {%W yview scroll  1 units};break}
5805bind $ui_diff <Key-Left>   {catch {%W xview scroll -1 units};break}
5806bind $ui_diff <Key-Right>  {catch {%W xview scroll  1 units};break}
5807bind $ui_diff <Button-1>   {focus %W}
5808
5809if {[is_enabled branch]} {
5810        bind . <$M1B-Key-n> do_create_branch
5811        bind . <$M1B-Key-N> do_create_branch
5812}
5813
5814bind all <Key-F5> do_rescan
5815bind all <$M1B-Key-r> do_rescan
5816bind all <$M1B-Key-R> do_rescan
5817bind .   <$M1B-Key-s> do_signoff
5818bind .   <$M1B-Key-S> do_signoff
5819bind .   <$M1B-Key-i> do_add_all
5820bind .   <$M1B-Key-I> do_add_all
5821bind .   <$M1B-Key-Return> do_commit
5822foreach i [list $ui_index $ui_workdir] {
5823        bind $i <Button-1>       "toggle_or_diff         $i %x %y; break"
5824        bind $i <$M1B-Button-1>  "add_one_to_selection   $i %x %y; break"
5825        bind $i <Shift-Button-1> "add_range_to_selection $i %x %y; break"
5826}
5827unset i
5828
5829set file_lists($ui_index) [list]
5830set file_lists($ui_workdir) [list]
5831
5832set HEAD {}
5833set PARENT {}
5834set MERGE_HEAD [list]
5835set commit_type {}
5836set empty_tree {}
5837set current_branch {}
5838set current_diff_path {}
5839set selected_commit_type new
5840
5841wm title . "[appname] ([file normalize [file dirname [gitdir]]])"
5842focus -force $ui_comm
5843
5844# -- Warn the user about environmental problems.  Cygwin's Tcl
5845#    does *not* pass its env array onto any processes it spawns.
5846#    This means that git processes get none of our environment.
5847#
5848if {[is_Cygwin]} {
5849        set ignored_env 0
5850        set suggest_user {}
5851        set msg "Possible environment issues exist.
5852
5853The following environment variables are probably
5854going to be ignored by any Git subprocess run
5855by [appname]:
5856
5857"
5858        foreach name [array names env] {
5859                switch -regexp -- $name {
5860                {^GIT_INDEX_FILE$} -
5861                {^GIT_OBJECT_DIRECTORY$} -
5862                {^GIT_ALTERNATE_OBJECT_DIRECTORIES$} -
5863                {^GIT_DIFF_OPTS$} -
5864                {^GIT_EXTERNAL_DIFF$} -
5865                {^GIT_PAGER$} -
5866                {^GIT_TRACE$} -
5867                {^GIT_CONFIG$} -
5868                {^GIT_CONFIG_LOCAL$} -
5869                {^GIT_(AUTHOR|COMMITTER)_DATE$} {
5870                        append msg " - $name\n"
5871                        incr ignored_env
5872                }
5873                {^GIT_(AUTHOR|COMMITTER)_(NAME|EMAIL)$} {
5874                        append msg " - $name\n"
5875                        incr ignored_env
5876                        set suggest_user $name
5877                }
5878                }
5879        }
5880        if {$ignored_env > 0} {
5881                append msg "
5882This is due to a known issue with the
5883Tcl binary distributed by Cygwin."
5884
5885                if {$suggest_user ne {}} {
5886                        append msg "
5887
5888A good replacement for $suggest_user
5889is placing values for the user.name and
5890user.email settings into your personal
5891~/.gitconfig file.
5892"
5893                }
5894                warn_popup $msg
5895        }
5896        unset ignored_env msg suggest_user name
5897}
5898
5899# -- Only initialize complex UI if we are going to stay running.
5900#
5901if {[is_enabled transport]} {
5902        load_all_remotes
5903        load_all_heads
5904
5905        populate_branch_menu
5906        populate_fetch_menu
5907        populate_push_menu
5908}
5909
5910# -- Only suggest a gc run if we are going to stay running.
5911#
5912if {[is_enabled multicommit]} {
5913        set object_limit 2000
5914        if {[is_Windows]} {set object_limit 200}
5915        regexp {^([0-9]+) objects,} [git count-objects] _junk objects_current
5916        if {$objects_current >= $object_limit} {
5917                if {[ask_popup \
5918                        "This repository currently has $objects_current loose objects.
5919
5920To maintain optimal performance it is strongly
5921recommended that you compress the database
5922when more than $object_limit loose objects exist.
5923
5924Compress the database now?"] eq yes} {
5925                        do_gc
5926                }
5927        }
5928        unset object_limit _junk objects_current
5929}
5930
5931lock_index begin-read
5932after 1 do_rescan