Merge commit 'git-gui/master'
[git] / git-gui / git-gui.sh
1 #!/bin/sh
2 # Tcl ignores the next line -*- tcl -*- \
3  if test "z$*" = zversion \
4  || test "z$*" = z--version; \
5  then \
6         echo 'git-gui version @@GITGUI_VERSION@@'; \
7         exit; \
8  fi; \
9  exec wish "$0" -- "$@"
10
11 set appvers {@@GITGUI_VERSION@@}
12 set copyright {
13 Copyright © 2006, 2007 Shawn Pearce, et. al.
14
15 This program is free software; you can redistribute it and/or modify
16 it under the terms of the GNU General Public License as published by
17 the Free Software Foundation; either version 2 of the License, or
18 (at your option) any later version.
19
20 This program is distributed in the hope that it will be useful,
21 but WITHOUT ANY WARRANTY; without even the implied warranty of
22 MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
23 GNU General Public License for more details.
24
25 You should have received a copy of the GNU General Public License
26 along with this program; if not, write to the Free Software
27 Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA  02111-1307  USA}
28
29 ######################################################################
30 ##
31 ## Tcl/Tk sanity check
32
33 if {[catch {package require Tcl 8.4} err]
34  || [catch {package require Tk  8.4} err]
35 } {
36         catch {wm withdraw .}
37         tk_messageBox \
38                 -icon error \
39                 -type ok \
40                 -title "git-gui: fatal error" \
41                 -message $err
42         exit 1
43 }
44
45 ######################################################################
46 ##
47 ## enable verbose loading?
48
49 if {![catch {set _verbose $env(GITGUI_VERBOSE)}]} {
50         unset _verbose
51         rename auto_load real__auto_load
52         proc auto_load {name args} {
53                 puts stderr "auto_load $name"
54                 return [uplevel 1 real__auto_load $name $args]
55         }
56         rename source real__source
57         proc source {name} {
58                 puts stderr "source    $name"
59                 uplevel 1 real__source $name
60         }
61 }
62
63 ######################################################################
64 ##
65 ## configure our library
66
67 set oguilib {@@GITGUI_LIBDIR@@}
68 set oguirel {@@GITGUI_RELATIVE@@}
69 if {$oguirel eq {1}} {
70         set oguilib [file dirname [file dirname [file normalize $argv0]]]
71         set oguilib [file join $oguilib share git-gui lib]
72 } elseif {[string match @@* $oguirel]} {
73         set oguilib [file join [file dirname [file normalize $argv0]] lib]
74 }
75
76 set idx [file join $oguilib tclIndex]
77 if {[catch {set fd [open $idx r]} err]} {
78         catch {wm withdraw .}
79         tk_messageBox \
80                 -icon error \
81                 -type ok \
82                 -title "git-gui: fatal error" \
83                 -message $err
84         exit 1
85 }
86 if {[gets $fd] eq {# Autogenerated by git-gui Makefile}} {
87         set idx [list]
88         while {[gets $fd n] >= 0} {
89                 if {$n ne {} && ![string match #* $n]} {
90                         lappend idx $n
91                 }
92         }
93 } else {
94         set idx {}
95 }
96 close $fd
97
98 if {$idx ne {}} {
99         set loaded [list]
100         foreach p $idx {
101                 if {[lsearch -exact $loaded $p] >= 0} continue
102                 source [file join $oguilib $p]
103                 lappend loaded $p
104         }
105         unset loaded p
106 } else {
107         set auto_path [concat [list $oguilib] $auto_path]
108 }
109 unset -nocomplain oguirel idx fd
110
111 ######################################################################
112 ##
113 ## read only globals
114
115 set _appname [lindex [file split $argv0] end]
116 set _gitdir {}
117 set _gitexec {}
118 set _reponame {}
119 set _iscygwin {}
120 set _search_path {}
121
122 proc appname {} {
123         global _appname
124         return $_appname
125 }
126
127 proc gitdir {args} {
128         global _gitdir
129         if {$args eq {}} {
130                 return $_gitdir
131         }
132         return [eval [list file join $_gitdir] $args]
133 }
134
135 proc gitexec {args} {
136         global _gitexec
137         if {$_gitexec eq {}} {
138                 if {[catch {set _gitexec [git --exec-path]} err]} {
139                         error "Git not installed?\n\n$err"
140                 }
141                 if {[is_Cygwin]} {
142                         set _gitexec [exec cygpath \
143                                 --windows \
144                                 --absolute \
145                                 $_gitexec]
146                 } else {
147                         set _gitexec [file normalize $_gitexec]
148                 }
149         }
150         if {$args eq {}} {
151                 return $_gitexec
152         }
153         return [eval [list file join $_gitexec] $args]
154 }
155
156 proc reponame {} {
157         global _reponame
158         return $_reponame
159 }
160
161 proc is_MacOSX {} {
162         global tcl_platform tk_library
163         if {[tk windowingsystem] eq {aqua}} {
164                 return 1
165         }
166         return 0
167 }
168
169 proc is_Windows {} {
170         global tcl_platform
171         if {$tcl_platform(platform) eq {windows}} {
172                 return 1
173         }
174         return 0
175 }
176
177 proc is_Cygwin {} {
178         global tcl_platform _iscygwin
179         if {$_iscygwin eq {}} {
180                 if {$tcl_platform(platform) eq {windows}} {
181                         if {[catch {set p [exec cygpath --windir]} err]} {
182                                 set _iscygwin 0
183                         } else {
184                                 set _iscygwin 1
185                         }
186                 } else {
187                         set _iscygwin 0
188                 }
189         }
190         return $_iscygwin
191 }
192
193 proc is_enabled {option} {
194         global enabled_options
195         if {[catch {set on $enabled_options($option)}]} {return 0}
196         return $on
197 }
198
199 proc enable_option {option} {
200         global enabled_options
201         set enabled_options($option) 1
202 }
203
204 proc disable_option {option} {
205         global enabled_options
206         set enabled_options($option) 0
207 }
208
209 ######################################################################
210 ##
211 ## config
212
213 proc is_many_config {name} {
214         switch -glob -- $name {
215         remote.*.fetch -
216         remote.*.push
217                 {return 1}
218         *
219                 {return 0}
220         }
221 }
222
223 proc is_config_true {name} {
224         global repo_config
225         if {[catch {set v $repo_config($name)}]} {
226                 return 0
227         } elseif {$v eq {true} || $v eq {1} || $v eq {yes}} {
228                 return 1
229         } else {
230                 return 0
231         }
232 }
233
234 proc get_config {name} {
235         global repo_config
236         if {[catch {set v $repo_config($name)}]} {
237                 return {}
238         } else {
239                 return $v
240         }
241 }
242
243 proc load_config {include_global} {
244         global repo_config global_config default_config
245
246         array unset global_config
247         if {$include_global} {
248                 catch {
249                         set fd_rc [git_read config --global --list]
250                         while {[gets $fd_rc line] >= 0} {
251                                 if {[regexp {^([^=]+)=(.*)$} $line line name value]} {
252                                         if {[is_many_config $name]} {
253                                                 lappend global_config($name) $value
254                                         } else {
255                                                 set global_config($name) $value
256                                         }
257                                 }
258                         }
259                         close $fd_rc
260                 }
261         }
262
263         array unset repo_config
264         catch {
265                 set fd_rc [git_read config --list]
266                 while {[gets $fd_rc line] >= 0} {
267                         if {[regexp {^([^=]+)=(.*)$} $line line name value]} {
268                                 if {[is_many_config $name]} {
269                                         lappend repo_config($name) $value
270                                 } else {
271                                         set repo_config($name) $value
272                                 }
273                         }
274                 }
275                 close $fd_rc
276         }
277
278         foreach name [array names default_config] {
279                 if {[catch {set v $global_config($name)}]} {
280                         set global_config($name) $default_config($name)
281                 }
282                 if {[catch {set v $repo_config($name)}]} {
283                         set repo_config($name) $default_config($name)
284                 }
285         }
286 }
287
288 ######################################################################
289 ##
290 ## handy utils
291
292 proc _git_cmd {name} {
293         global _git_cmd_path
294
295         if {[catch {set v $_git_cmd_path($name)}]} {
296                 switch -- $name {
297                   version   -
298                 --version   -
299                 --exec-path { return [list $::_git $name] }
300                 }
301
302                 set p [gitexec git-$name$::_search_exe]
303                 if {[file exists $p]} {
304                         set v [list $p]
305                 } elseif {[is_Windows] && [file exists [gitexec git-$name]]} {
306                         # Try to determine what sort of magic will make
307                         # git-$name go and do its thing, because native
308                         # Tcl on Windows doesn't know it.
309                         #
310                         set p [gitexec git-$name]
311                         set f [open $p r]
312                         set s [gets $f]
313                         close $f
314
315                         switch -glob -- $s {
316                         #!*sh     { set i sh     }
317                         #!*perl   { set i perl   }
318                         #!*python { set i python }
319                         default   { error "git-$name is not supported: $s" }
320                         }
321
322                         upvar #0 _$i interp
323                         if {![info exists interp]} {
324                                 set interp [_which $i]
325                         }
326                         if {$interp eq {}} {
327                                 error "git-$name requires $i (not in PATH)"
328                         }
329                         set v [list $interp $p]
330                 } else {
331                         # Assume it is builtin to git somehow and we
332                         # aren't actually able to see a file for it.
333                         #
334                         set v [list $::_git $name]
335                 }
336                 set _git_cmd_path($name) $v
337         }
338         return $v
339 }
340
341 proc _which {what} {
342         global env _search_exe _search_path
343
344         if {$_search_path eq {}} {
345                 if {[is_Cygwin]} {
346                         set _search_path [split [exec cygpath \
347                                 --windows \
348                                 --path \
349                                 --absolute \
350                                 $env(PATH)] {;}]
351                         set _search_exe .exe
352                 } elseif {[is_Windows]} {
353                         set _search_path [split $env(PATH) {;}]
354                         set _search_exe .exe
355                 } else {
356                         set _search_path [split $env(PATH) :]
357                         set _search_exe {}
358                 }
359         }
360
361         foreach p $_search_path {
362                 set p [file join $p $what$_search_exe]
363                 if {[file exists $p]} {
364                         return [file normalize $p]
365                 }
366         }
367         return {}
368 }
369
370 proc git {args} {
371         set opt [list exec]
372
373         while {1} {
374                 switch -- [lindex $args 0] {
375                 --nice {
376                         global _nice
377                         if {$_nice ne {}} {
378                                 lappend opt $_nice
379                         }
380                 }
381
382                 default {
383                         break
384                 }
385
386                 }
387
388                 set args [lrange $args 1 end]
389         }
390
391         set cmdp [_git_cmd [lindex $args 0]]
392         set args [lrange $args 1 end]
393
394         return [eval $opt $cmdp $args]
395 }
396
397 proc _open_stdout_stderr {cmd} {
398         if {[catch {
399                         set fd [open $cmd r]
400                 } err]} {
401                 if {   [lindex $cmd end] eq {2>@1}
402                     && $err eq {can not find channel named "1"}
403                         } {
404                         # Older versions of Tcl 8.4 don't have this 2>@1 IO
405                         # redirect operator.  Fallback to |& cat for those.
406                         # The command was not actually started, so its safe
407                         # to try to start it a second time.
408                         #
409                         set fd [open [concat \
410                                 [lrange $cmd 0 end-1] \
411                                 [list |& cat] \
412                                 ] r]
413                 } else {
414                         error $err
415                 }
416         }
417         return $fd
418 }
419
420 proc git_read {args} {
421         set opt [list |]
422
423         while {1} {
424                 switch -- [lindex $args 0] {
425                 --nice {
426                         global _nice
427                         if {$_nice ne {}} {
428                                 lappend opt $_nice
429                         }
430                 }
431
432                 --stderr {
433                         lappend args 2>@1
434                 }
435
436                 default {
437                         break
438                 }
439
440                 }
441
442                 set args [lrange $args 1 end]
443         }
444
445         set cmdp [_git_cmd [lindex $args 0]]
446         set args [lrange $args 1 end]
447
448         return [_open_stdout_stderr [concat $opt $cmdp $args]]
449 }
450
451 proc git_write {args} {
452         set opt [list |]
453
454         while {1} {
455                 switch -- [lindex $args 0] {
456                 --nice {
457                         global _nice
458                         if {$_nice ne {}} {
459                                 lappend opt $_nice
460                         }
461                 }
462
463                 default {
464                         break
465                 }
466
467                 }
468
469                 set args [lrange $args 1 end]
470         }
471
472         set cmdp [_git_cmd [lindex $args 0]]
473         set args [lrange $args 1 end]
474
475         return [open [concat $opt $cmdp $args] w]
476 }
477
478 proc sq {value} {
479         regsub -all ' $value "'\\''" value
480         return "'$value'"
481 }
482
483 proc load_current_branch {} {
484         global current_branch is_detached
485
486         set fd [open [gitdir HEAD] r]
487         if {[gets $fd ref] < 1} {
488                 set ref {}
489         }
490         close $fd
491
492         set pfx {ref: refs/heads/}
493         set len [string length $pfx]
494         if {[string equal -length $len $pfx $ref]} {
495                 # We're on a branch.  It might not exist.  But
496                 # HEAD looks good enough to be a branch.
497                 #
498                 set current_branch [string range $ref $len end]
499                 set is_detached 0
500         } else {
501                 # Assume this is a detached head.
502                 #
503                 set current_branch HEAD
504                 set is_detached 1
505         }
506 }
507
508 auto_load tk_optionMenu
509 rename tk_optionMenu real__tkOptionMenu
510 proc tk_optionMenu {w varName args} {
511         set m [eval real__tkOptionMenu $w $varName $args]
512         $m configure -font font_ui
513         $w configure -font font_ui
514         return $m
515 }
516
517 ######################################################################
518 ##
519 ## find git
520
521 set _git  [_which git]
522 if {$_git eq {}} {
523         catch {wm withdraw .}
524         error_popup "Cannot find git in PATH."
525         exit 1
526 }
527 set _nice [_which nice]
528
529 ######################################################################
530 ##
531 ## version check
532
533 if {[catch {set _git_version [git --version]} err]} {
534         catch {wm withdraw .}
535         error_popup "Cannot determine Git version:
536
537 $err
538
539 [appname] requires Git 1.5.0 or later."
540         exit 1
541 }
542 if {![regsub {^git version } $_git_version {} _git_version]} {
543         catch {wm withdraw .}
544         error_popup "Cannot parse Git version string:\n\n$_git_version"
545         exit 1
546 }
547 regsub {\.[0-9]+\.g[0-9a-f]+$} $_git_version {} _git_version
548 regsub {\.rc[0-9]+$} $_git_version {} _git_version
549
550 proc git-version {args} {
551         global _git_version
552
553         switch [llength $args] {
554         0 {
555                 return $_git_version
556         }
557
558         2 {
559                 set op [lindex $args 0]
560                 set vr [lindex $args 1]
561                 set cm [package vcompare $_git_version $vr]
562                 return [expr $cm $op 0]
563         }
564
565         4 {
566                 set type [lindex $args 0]
567                 set name [lindex $args 1]
568                 set parm [lindex $args 2]
569                 set body [lindex $args 3]
570
571                 if {($type ne {proc} && $type ne {method})} {
572                         error "Invalid arguments to git-version"
573                 }
574                 if {[llength $body] < 2 || [lindex $body end-1] ne {default}} {
575                         error "Last arm of $type $name must be default"
576                 }
577
578                 foreach {op vr cb} [lrange $body 0 end-2] {
579                         if {[git-version $op $vr]} {
580                                 return [uplevel [list $type $name $parm $cb]]
581                         }
582                 }
583
584                 return [uplevel [list $type $name $parm [lindex $body end]]]
585         }
586
587         default {
588                 error "git-version >= x"
589         }
590
591         }
592 }
593
594 if {[git-version < 1.5]} {
595         catch {wm withdraw .}
596         error_popup "[appname] requires Git 1.5.0 or later.
597
598 You are using [git-version]:
599
600 [git --version]"
601         exit 1
602 }
603
604 ######################################################################
605 ##
606 ## repository setup
607
608 if {[catch {
609                 set _gitdir $env(GIT_DIR)
610                 set _prefix {}
611                 }]
612         && [catch {
613                 set _gitdir [git rev-parse --git-dir]
614                 set _prefix [git rev-parse --show-prefix]
615         } err]} {
616         catch {wm withdraw .}
617         error_popup "Cannot find the git directory:\n\n$err"
618         exit 1
619 }
620 if {![file isdirectory $_gitdir] && [is_Cygwin]} {
621         catch {set _gitdir [exec cygpath --unix $_gitdir]}
622 }
623 if {![file isdirectory $_gitdir]} {
624         catch {wm withdraw .}
625         error_popup "Git directory not found:\n\n$_gitdir"
626         exit 1
627 }
628 if {[lindex [file split $_gitdir] end] ne {.git}} {
629         catch {wm withdraw .}
630         error_popup "Cannot use funny .git directory:\n\n$_gitdir"
631         exit 1
632 }
633 if {[catch {cd [file dirname $_gitdir]} err]} {
634         catch {wm withdraw .}
635         error_popup "No working directory [file dirname $_gitdir]:\n\n$err"
636         exit 1
637 }
638 set _reponame [lindex [file split \
639         [file normalize [file dirname $_gitdir]]] \
640         end]
641
642 ######################################################################
643 ##
644 ## global init
645
646 set current_diff_path {}
647 set current_diff_side {}
648 set diff_actions [list]
649
650 set HEAD {}
651 set PARENT {}
652 set MERGE_HEAD [list]
653 set commit_type {}
654 set empty_tree {}
655 set current_branch {}
656 set is_detached 0
657 set current_diff_path {}
658 set selected_commit_type new
659
660 ######################################################################
661 ##
662 ## task management
663
664 set rescan_active 0
665 set diff_active 0
666 set last_clicked {}
667
668 set disable_on_lock [list]
669 set index_lock_type none
670
671 proc lock_index {type} {
672         global index_lock_type disable_on_lock
673
674         if {$index_lock_type eq {none}} {
675                 set index_lock_type $type
676                 foreach w $disable_on_lock {
677                         uplevel #0 $w disabled
678                 }
679                 return 1
680         } elseif {$index_lock_type eq "begin-$type"} {
681                 set index_lock_type $type
682                 return 1
683         }
684         return 0
685 }
686
687 proc unlock_index {} {
688         global index_lock_type disable_on_lock
689
690         set index_lock_type none
691         foreach w $disable_on_lock {
692                 uplevel #0 $w normal
693         }
694 }
695
696 ######################################################################
697 ##
698 ## status
699
700 proc repository_state {ctvar hdvar mhvar} {
701         global current_branch
702         upvar $ctvar ct $hdvar hd $mhvar mh
703
704         set mh [list]
705
706         load_current_branch
707         if {[catch {set hd [git rev-parse --verify HEAD]}]} {
708                 set hd {}
709                 set ct initial
710                 return
711         }
712
713         set merge_head [gitdir MERGE_HEAD]
714         if {[file exists $merge_head]} {
715                 set ct merge
716                 set fd_mh [open $merge_head r]
717                 while {[gets $fd_mh line] >= 0} {
718                         lappend mh $line
719                 }
720                 close $fd_mh
721                 return
722         }
723
724         set ct normal
725 }
726
727 proc PARENT {} {
728         global PARENT empty_tree
729
730         set p [lindex $PARENT 0]
731         if {$p ne {}} {
732                 return $p
733         }
734         if {$empty_tree eq {}} {
735                 set empty_tree [git mktree << {}]
736         }
737         return $empty_tree
738 }
739
740 proc rescan {after {honor_trustmtime 1}} {
741         global HEAD PARENT MERGE_HEAD commit_type
742         global ui_index ui_workdir ui_comm
743         global rescan_active file_states
744         global repo_config
745
746         if {$rescan_active > 0 || ![lock_index read]} return
747
748         repository_state newType newHEAD newMERGE_HEAD
749         if {[string match amend* $commit_type]
750                 && $newType eq {normal}
751                 && $newHEAD eq $HEAD} {
752         } else {
753                 set HEAD $newHEAD
754                 set PARENT $newHEAD
755                 set MERGE_HEAD $newMERGE_HEAD
756                 set commit_type $newType
757         }
758
759         array unset file_states
760
761         if {![$ui_comm edit modified]
762                 || [string trim [$ui_comm get 0.0 end]] eq {}} {
763                 if {[string match amend* $commit_type]} {
764                 } elseif {[load_message GITGUI_MSG]} {
765                 } elseif {[load_message MERGE_MSG]} {
766                 } elseif {[load_message SQUASH_MSG]} {
767                 }
768                 $ui_comm edit reset
769                 $ui_comm edit modified false
770         }
771
772         if {$honor_trustmtime && $repo_config(gui.trustmtime) eq {true}} {
773                 rescan_stage2 {} $after
774         } else {
775                 set rescan_active 1
776                 ui_status {Refreshing file status...}
777                 set fd_rf [git_read update-index \
778                         -q \
779                         --unmerged \
780                         --ignore-missing \
781                         --refresh \
782                         ]
783                 fconfigure $fd_rf -blocking 0 -translation binary
784                 fileevent $fd_rf readable \
785                         [list rescan_stage2 $fd_rf $after]
786         }
787 }
788
789 proc rescan_stage2 {fd after} {
790         global rescan_active buf_rdi buf_rdf buf_rlo
791
792         if {$fd ne {}} {
793                 read $fd
794                 if {![eof $fd]} return
795                 close $fd
796         }
797
798         set ls_others [list --exclude-per-directory=.gitignore]
799         set info_exclude [gitdir info exclude]
800         if {[file readable $info_exclude]} {
801                 lappend ls_others "--exclude-from=$info_exclude"
802         }
803
804         set buf_rdi {}
805         set buf_rdf {}
806         set buf_rlo {}
807
808         set rescan_active 3
809         ui_status {Scanning for modified files ...}
810         set fd_di [git_read diff-index --cached -z [PARENT]]
811         set fd_df [git_read diff-files -z]
812         set fd_lo [eval git_read ls-files --others -z $ls_others]
813
814         fconfigure $fd_di -blocking 0 -translation binary -encoding binary
815         fconfigure $fd_df -blocking 0 -translation binary -encoding binary
816         fconfigure $fd_lo -blocking 0 -translation binary -encoding binary
817         fileevent $fd_di readable [list read_diff_index $fd_di $after]
818         fileevent $fd_df readable [list read_diff_files $fd_df $after]
819         fileevent $fd_lo readable [list read_ls_others $fd_lo $after]
820 }
821
822 proc load_message {file} {
823         global ui_comm
824
825         set f [gitdir $file]
826         if {[file isfile $f]} {
827                 if {[catch {set fd [open $f r]}]} {
828                         return 0
829                 }
830                 set content [string trim [read $fd]]
831                 close $fd
832                 regsub -all -line {[ \r\t]+$} $content {} content
833                 $ui_comm delete 0.0 end
834                 $ui_comm insert end $content
835                 return 1
836         }
837         return 0
838 }
839
840 proc read_diff_index {fd after} {
841         global buf_rdi
842
843         append buf_rdi [read $fd]
844         set c 0
845         set n [string length $buf_rdi]
846         while {$c < $n} {
847                 set z1 [string first "\0" $buf_rdi $c]
848                 if {$z1 == -1} break
849                 incr z1
850                 set z2 [string first "\0" $buf_rdi $z1]
851                 if {$z2 == -1} break
852
853                 incr c
854                 set i [split [string range $buf_rdi $c [expr {$z1 - 2}]] { }]
855                 set p [string range $buf_rdi $z1 [expr {$z2 - 1}]]
856                 merge_state \
857                         [encoding convertfrom $p] \
858                         [lindex $i 4]? \
859                         [list [lindex $i 0] [lindex $i 2]] \
860                         [list]
861                 set c $z2
862                 incr c
863         }
864         if {$c < $n} {
865                 set buf_rdi [string range $buf_rdi $c end]
866         } else {
867                 set buf_rdi {}
868         }
869
870         rescan_done $fd buf_rdi $after
871 }
872
873 proc read_diff_files {fd after} {
874         global buf_rdf
875
876         append buf_rdf [read $fd]
877         set c 0
878         set n [string length $buf_rdf]
879         while {$c < $n} {
880                 set z1 [string first "\0" $buf_rdf $c]
881                 if {$z1 == -1} break
882                 incr z1
883                 set z2 [string first "\0" $buf_rdf $z1]
884                 if {$z2 == -1} break
885
886                 incr c
887                 set i [split [string range $buf_rdf $c [expr {$z1 - 2}]] { }]
888                 set p [string range $buf_rdf $z1 [expr {$z2 - 1}]]
889                 merge_state \
890                         [encoding convertfrom $p] \
891                         ?[lindex $i 4] \
892                         [list] \
893                         [list [lindex $i 0] [lindex $i 2]]
894                 set c $z2
895                 incr c
896         }
897         if {$c < $n} {
898                 set buf_rdf [string range $buf_rdf $c end]
899         } else {
900                 set buf_rdf {}
901         }
902
903         rescan_done $fd buf_rdf $after
904 }
905
906 proc read_ls_others {fd after} {
907         global buf_rlo
908
909         append buf_rlo [read $fd]
910         set pck [split $buf_rlo "\0"]
911         set buf_rlo [lindex $pck end]
912         foreach p [lrange $pck 0 end-1] {
913                 merge_state [encoding convertfrom $p] ?O
914         }
915         rescan_done $fd buf_rlo $after
916 }
917
918 proc rescan_done {fd buf after} {
919         global rescan_active current_diff_path
920         global file_states repo_config
921         upvar $buf to_clear
922
923         if {![eof $fd]} return
924         set to_clear {}
925         close $fd
926         if {[incr rescan_active -1] > 0} return
927
928         prune_selection
929         unlock_index
930         display_all_files
931         if {$current_diff_path ne {}} reshow_diff
932         uplevel #0 $after
933 }
934
935 proc prune_selection {} {
936         global file_states selected_paths
937
938         foreach path [array names selected_paths] {
939                 if {[catch {set still_here $file_states($path)}]} {
940                         unset selected_paths($path)
941                 }
942         }
943 }
944
945 ######################################################################
946 ##
947 ## ui helpers
948
949 proc mapicon {w state path} {
950         global all_icons
951
952         if {[catch {set r $all_icons($state$w)}]} {
953                 puts "error: no icon for $w state={$state} $path"
954                 return file_plain
955         }
956         return $r
957 }
958
959 proc mapdesc {state path} {
960         global all_descs
961
962         if {[catch {set r $all_descs($state)}]} {
963                 puts "error: no desc for state={$state} $path"
964                 return $state
965         }
966         return $r
967 }
968
969 proc ui_status {msg} {
970         $::main_status show $msg
971 }
972
973 proc ui_ready {{test {}}} {
974         $::main_status show {Ready.} $test
975 }
976
977 proc escape_path {path} {
978         regsub -all {\\} $path "\\\\" path
979         regsub -all "\n" $path "\\n" path
980         return $path
981 }
982
983 proc short_path {path} {
984         return [escape_path [lindex [file split $path] end]]
985 }
986
987 set next_icon_id 0
988 set null_sha1 [string repeat 0 40]
989
990 proc merge_state {path new_state {head_info {}} {index_info {}}} {
991         global file_states next_icon_id null_sha1
992
993         set s0 [string index $new_state 0]
994         set s1 [string index $new_state 1]
995
996         if {[catch {set info $file_states($path)}]} {
997                 set state __
998                 set icon n[incr next_icon_id]
999         } else {
1000                 set state [lindex $info 0]
1001                 set icon [lindex $info 1]
1002                 if {$head_info eq {}}  {set head_info  [lindex $info 2]}
1003                 if {$index_info eq {}} {set index_info [lindex $info 3]}
1004         }
1005
1006         if     {$s0 eq {?}} {set s0 [string index $state 0]} \
1007         elseif {$s0 eq {_}} {set s0 _}
1008
1009         if     {$s1 eq {?}} {set s1 [string index $state 1]} \
1010         elseif {$s1 eq {_}} {set s1 _}
1011
1012         if {$s0 eq {A} && $s1 eq {_} && $head_info eq {}} {
1013                 set head_info [list 0 $null_sha1]
1014         } elseif {$s0 ne {_} && [string index $state 0] eq {_}
1015                 && $head_info eq {}} {
1016                 set head_info $index_info
1017         }
1018
1019         set file_states($path) [list $s0$s1 $icon \
1020                 $head_info $index_info \
1021                 ]
1022         return $state
1023 }
1024
1025 proc display_file_helper {w path icon_name old_m new_m} {
1026         global file_lists
1027
1028         if {$new_m eq {_}} {
1029                 set lno [lsearch -sorted -exact $file_lists($w) $path]
1030                 if {$lno >= 0} {
1031                         set file_lists($w) [lreplace $file_lists($w) $lno $lno]
1032                         incr lno
1033                         $w conf -state normal
1034                         $w delete $lno.0 [expr {$lno + 1}].0
1035                         $w conf -state disabled
1036                 }
1037         } elseif {$old_m eq {_} && $new_m ne {_}} {
1038                 lappend file_lists($w) $path
1039                 set file_lists($w) [lsort -unique $file_lists($w)]
1040                 set lno [lsearch -sorted -exact $file_lists($w) $path]
1041                 incr lno
1042                 $w conf -state normal
1043                 $w image create $lno.0 \
1044                         -align center -padx 5 -pady 1 \
1045                         -name $icon_name \
1046                         -image [mapicon $w $new_m $path]
1047                 $w insert $lno.1 "[escape_path $path]\n"
1048                 $w conf -state disabled
1049         } elseif {$old_m ne $new_m} {
1050                 $w conf -state normal
1051                 $w image conf $icon_name -image [mapicon $w $new_m $path]
1052                 $w conf -state disabled
1053         }
1054 }
1055
1056 proc display_file {path state} {
1057         global file_states selected_paths
1058         global ui_index ui_workdir
1059
1060         set old_m [merge_state $path $state]
1061         set s $file_states($path)
1062         set new_m [lindex $s 0]
1063         set icon_name [lindex $s 1]
1064
1065         set o [string index $old_m 0]
1066         set n [string index $new_m 0]
1067         if {$o eq {U}} {
1068                 set o _
1069         }
1070         if {$n eq {U}} {
1071                 set n _
1072         }
1073         display_file_helper     $ui_index $path $icon_name $o $n
1074
1075         if {[string index $old_m 0] eq {U}} {
1076                 set o U
1077         } else {
1078                 set o [string index $old_m 1]
1079         }
1080         if {[string index $new_m 0] eq {U}} {
1081                 set n U
1082         } else {
1083                 set n [string index $new_m 1]
1084         }
1085         display_file_helper     $ui_workdir $path $icon_name $o $n
1086
1087         if {$new_m eq {__}} {
1088                 unset file_states($path)
1089                 catch {unset selected_paths($path)}
1090         }
1091 }
1092
1093 proc display_all_files_helper {w path icon_name m} {
1094         global file_lists
1095
1096         lappend file_lists($w) $path
1097         set lno [expr {[lindex [split [$w index end] .] 0] - 1}]
1098         $w image create end \
1099                 -align center -padx 5 -pady 1 \
1100                 -name $icon_name \
1101                 -image [mapicon $w $m $path]
1102         $w insert end "[escape_path $path]\n"
1103 }
1104
1105 proc display_all_files {} {
1106         global ui_index ui_workdir
1107         global file_states file_lists
1108         global last_clicked
1109
1110         $ui_index conf -state normal
1111         $ui_workdir conf -state normal
1112
1113         $ui_index delete 0.0 end
1114         $ui_workdir delete 0.0 end
1115         set last_clicked {}
1116
1117         set file_lists($ui_index) [list]
1118         set file_lists($ui_workdir) [list]
1119
1120         foreach path [lsort [array names file_states]] {
1121                 set s $file_states($path)
1122                 set m [lindex $s 0]
1123                 set icon_name [lindex $s 1]
1124
1125                 set s [string index $m 0]
1126                 if {$s ne {U} && $s ne {_}} {
1127                         display_all_files_helper $ui_index $path \
1128                                 $icon_name $s
1129                 }
1130
1131                 if {[string index $m 0] eq {U}} {
1132                         set s U
1133                 } else {
1134                         set s [string index $m 1]
1135                 }
1136                 if {$s ne {_}} {
1137                         display_all_files_helper $ui_workdir $path \
1138                                 $icon_name $s
1139                 }
1140         }
1141
1142         $ui_index conf -state disabled
1143         $ui_workdir conf -state disabled
1144 }
1145
1146 ######################################################################
1147 ##
1148 ## icons
1149
1150 set filemask {
1151 #define mask_width 14
1152 #define mask_height 15
1153 static unsigned char mask_bits[] = {
1154    0xfe, 0x1f, 0xfe, 0x1f, 0xfe, 0x1f, 0xfe, 0x1f, 0xfe, 0x1f, 0xfe, 0x1f,
1155    0xfe, 0x1f, 0xfe, 0x1f, 0xfe, 0x1f, 0xfe, 0x1f, 0xfe, 0x1f, 0xfe, 0x1f,
1156    0xfe, 0x1f, 0xfe, 0x1f, 0xfe, 0x1f};
1157 }
1158
1159 image create bitmap file_plain -background white -foreground black -data {
1160 #define plain_width 14
1161 #define plain_height 15
1162 static unsigned char plain_bits[] = {
1163    0xfe, 0x01, 0x02, 0x03, 0x02, 0x05, 0x02, 0x09, 0x02, 0x1f, 0x02, 0x10,
1164    0x02, 0x10, 0x02, 0x10, 0x02, 0x10, 0x02, 0x10, 0x02, 0x10, 0x02, 0x10,
1165    0x02, 0x10, 0x02, 0x10, 0xfe, 0x1f};
1166 } -maskdata $filemask
1167
1168 image create bitmap file_mod -background white -foreground blue -data {
1169 #define mod_width 14
1170 #define mod_height 15
1171 static unsigned char mod_bits[] = {
1172    0xfe, 0x01, 0x02, 0x03, 0x7a, 0x05, 0x02, 0x09, 0x7a, 0x1f, 0x02, 0x10,
1173    0xfa, 0x17, 0x02, 0x10, 0xfa, 0x17, 0x02, 0x10, 0xfa, 0x17, 0x02, 0x10,
1174    0xfa, 0x17, 0x02, 0x10, 0xfe, 0x1f};
1175 } -maskdata $filemask
1176
1177 image create bitmap file_fulltick -background white -foreground "#007000" -data {
1178 #define file_fulltick_width 14
1179 #define file_fulltick_height 15
1180 static unsigned char file_fulltick_bits[] = {
1181    0xfe, 0x01, 0x02, 0x1a, 0x02, 0x0c, 0x02, 0x0c, 0x02, 0x16, 0x02, 0x16,
1182    0x02, 0x13, 0x00, 0x13, 0x86, 0x11, 0x8c, 0x11, 0xd8, 0x10, 0xf2, 0x10,
1183    0x62, 0x10, 0x02, 0x10, 0xfe, 0x1f};
1184 } -maskdata $filemask
1185
1186 image create bitmap file_parttick -background white -foreground "#005050" -data {
1187 #define parttick_width 14
1188 #define parttick_height 15
1189 static unsigned char parttick_bits[] = {
1190    0xfe, 0x01, 0x02, 0x03, 0x7a, 0x05, 0x02, 0x09, 0x7a, 0x1f, 0x02, 0x10,
1191    0x7a, 0x14, 0x02, 0x16, 0x02, 0x13, 0x8a, 0x11, 0xda, 0x10, 0x72, 0x10,
1192    0x22, 0x10, 0x02, 0x10, 0xfe, 0x1f};
1193 } -maskdata $filemask
1194
1195 image create bitmap file_question -background white -foreground black -data {
1196 #define file_question_width 14
1197 #define file_question_height 15
1198 static unsigned char file_question_bits[] = {
1199    0xfe, 0x01, 0x02, 0x02, 0xe2, 0x04, 0xf2, 0x09, 0x1a, 0x1b, 0x0a, 0x13,
1200    0x82, 0x11, 0xc2, 0x10, 0x62, 0x10, 0x62, 0x10, 0x02, 0x10, 0x62, 0x10,
1201    0x62, 0x10, 0x02, 0x10, 0xfe, 0x1f};
1202 } -maskdata $filemask
1203
1204 image create bitmap file_removed -background white -foreground red -data {
1205 #define file_removed_width 14
1206 #define file_removed_height 15
1207 static unsigned char file_removed_bits[] = {
1208    0xfe, 0x01, 0x02, 0x03, 0x02, 0x05, 0x02, 0x09, 0x02, 0x1f, 0x02, 0x10,
1209    0x1a, 0x16, 0x32, 0x13, 0xe2, 0x11, 0xc2, 0x10, 0xe2, 0x11, 0x32, 0x13,
1210    0x1a, 0x16, 0x02, 0x10, 0xfe, 0x1f};
1211 } -maskdata $filemask
1212
1213 image create bitmap file_merge -background white -foreground blue -data {
1214 #define file_merge_width 14
1215 #define file_merge_height 15
1216 static unsigned char file_merge_bits[] = {
1217    0xfe, 0x01, 0x02, 0x03, 0x62, 0x05, 0x62, 0x09, 0x62, 0x1f, 0x62, 0x10,
1218    0xfa, 0x11, 0xf2, 0x10, 0x62, 0x10, 0x02, 0x10, 0xfa, 0x17, 0x02, 0x10,
1219    0xfa, 0x17, 0x02, 0x10, 0xfe, 0x1f};
1220 } -maskdata $filemask
1221
1222 set file_dir_data {
1223 #define file_width 18
1224 #define file_height 18
1225 static unsigned char file_bits[] = {
1226   0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0xf8, 0x03, 0x00,
1227   0x0c, 0x03, 0x00, 0x04, 0xfe, 0x00, 0x06, 0x80, 0x00, 0xff, 0x9f, 0x00,
1228   0x03, 0x98, 0x00, 0x02, 0x90, 0x00, 0x06, 0xb0, 0x00, 0x04, 0xa0, 0x00,
1229   0x0c, 0xe0, 0x00, 0x08, 0xc0, 0x00, 0xf8, 0xff, 0x00, 0x00, 0x00, 0x00,
1230   0x00, 0x00, 0x00, 0x00, 0x00, 0x00};
1231 }
1232 image create bitmap file_dir -background white -foreground blue \
1233         -data $file_dir_data -maskdata $file_dir_data
1234 unset file_dir_data
1235
1236 set file_uplevel_data {
1237 #define up_width 15
1238 #define up_height 15
1239 static unsigned char up_bits[] = {
1240   0x80, 0x00, 0xc0, 0x01, 0xe0, 0x03, 0xf0, 0x07, 0xf8, 0x0f, 0xfc, 0x1f,
1241   0xfe, 0x3f, 0xc0, 0x01, 0xc0, 0x01, 0xc0, 0x01, 0xc0, 0x01, 0xc0, 0x01,
1242   0xc0, 0x01, 0xc0, 0x01, 0x00, 0x00};
1243 }
1244 image create bitmap file_uplevel -background white -foreground red \
1245         -data $file_uplevel_data -maskdata $file_uplevel_data
1246 unset file_uplevel_data
1247
1248 set ui_index .vpane.files.index.list
1249 set ui_workdir .vpane.files.workdir.list
1250
1251 set all_icons(_$ui_index)   file_plain
1252 set all_icons(A$ui_index)   file_fulltick
1253 set all_icons(M$ui_index)   file_fulltick
1254 set all_icons(D$ui_index)   file_removed
1255 set all_icons(U$ui_index)   file_merge
1256
1257 set all_icons(_$ui_workdir) file_plain
1258 set all_icons(M$ui_workdir) file_mod
1259 set all_icons(D$ui_workdir) file_question
1260 set all_icons(U$ui_workdir) file_merge
1261 set all_icons(O$ui_workdir) file_plain
1262
1263 set max_status_desc 0
1264 foreach i {
1265                 {__ "Unmodified"}
1266
1267                 {_M "Modified, not staged"}
1268                 {M_ "Staged for commit"}
1269                 {MM "Portions staged for commit"}
1270                 {MD "Staged for commit, missing"}
1271
1272                 {_O "Untracked, not staged"}
1273                 {A_ "Staged for commit"}
1274                 {AM "Portions staged for commit"}
1275                 {AD "Staged for commit, missing"}
1276
1277                 {_D "Missing"}
1278                 {D_ "Staged for removal"}
1279                 {DO "Staged for removal, still present"}
1280
1281                 {U_ "Requires merge resolution"}
1282                 {UU "Requires merge resolution"}
1283                 {UM "Requires merge resolution"}
1284                 {UD "Requires merge resolution"}
1285         } {
1286         if {$max_status_desc < [string length [lindex $i 1]]} {
1287                 set max_status_desc [string length [lindex $i 1]]
1288         }
1289         set all_descs([lindex $i 0]) [lindex $i 1]
1290 }
1291 unset i
1292
1293 ######################################################################
1294 ##
1295 ## util
1296
1297 proc bind_button3 {w cmd} {
1298         bind $w <Any-Button-3> $cmd
1299         if {[is_MacOSX]} {
1300                 bind $w <Control-Button-1> $cmd
1301         }
1302 }
1303
1304 proc scrollbar2many {list mode args} {
1305         foreach w $list {eval $w $mode $args}
1306 }
1307
1308 proc many2scrollbar {list mode sb top bottom} {
1309         $sb set $top $bottom
1310         foreach w $list {$w $mode moveto $top}
1311 }
1312
1313 proc incr_font_size {font {amt 1}} {
1314         set sz [font configure $font -size]
1315         incr sz $amt
1316         font configure $font -size $sz
1317         font configure ${font}bold -size $sz
1318         font configure ${font}italic -size $sz
1319 }
1320
1321 ######################################################################
1322 ##
1323 ## ui commands
1324
1325 set starting_gitk_msg {Starting gitk... please wait...}
1326
1327 proc do_gitk {revs} {
1328         # -- Always start gitk through whatever we were loaded with.  This
1329         #    lets us bypass using shell process on Windows systems.
1330         #
1331         set exe [file join [file dirname $::_git] gitk]
1332         set cmd [list [info nameofexecutable] $exe]
1333         if {! [file exists $exe]} {
1334                 error_popup "Unable to start gitk:\n\n$exe does not exist"
1335         } else {
1336                 eval exec $cmd $revs &
1337                 ui_status $::starting_gitk_msg
1338                 after 10000 {
1339                         ui_ready $starting_gitk_msg
1340                 }
1341         }
1342 }
1343
1344 set is_quitting 0
1345
1346 proc do_quit {} {
1347         global ui_comm is_quitting repo_config commit_type
1348
1349         if {$is_quitting} return
1350         set is_quitting 1
1351
1352         if {[winfo exists $ui_comm]} {
1353                 # -- Stash our current commit buffer.
1354                 #
1355                 set save [gitdir GITGUI_MSG]
1356                 set msg [string trim [$ui_comm get 0.0 end]]
1357                 regsub -all -line {[ \r\t]+$} $msg {} msg
1358                 if {(![string match amend* $commit_type]
1359                         || [$ui_comm edit modified])
1360                         && $msg ne {}} {
1361                         catch {
1362                                 set fd [open $save w]
1363                                 puts -nonewline $fd $msg
1364                                 close $fd
1365                         }
1366                 } else {
1367                         catch {file delete $save}
1368                 }
1369
1370                 # -- Stash our current window geometry into this repository.
1371                 #
1372                 set cfg_geometry [list]
1373                 lappend cfg_geometry [wm geometry .]
1374                 lappend cfg_geometry [lindex [.vpane sash coord 0] 1]
1375                 lappend cfg_geometry [lindex [.vpane.files sash coord 0] 0]
1376                 if {[catch {set rc_geometry $repo_config(gui.geometry)}]} {
1377                         set rc_geometry {}
1378                 }
1379                 if {$cfg_geometry ne $rc_geometry} {
1380                         catch {git config gui.geometry $cfg_geometry}
1381                 }
1382         }
1383
1384         destroy .
1385 }
1386
1387 proc do_rescan {} {
1388         rescan ui_ready
1389 }
1390
1391 proc do_commit {} {
1392         commit_tree
1393 }
1394
1395 proc toggle_or_diff {w x y} {
1396         global file_states file_lists current_diff_path ui_index ui_workdir
1397         global last_clicked selected_paths
1398
1399         set pos [split [$w index @$x,$y] .]
1400         set lno [lindex $pos 0]
1401         set col [lindex $pos 1]
1402         set path [lindex $file_lists($w) [expr {$lno - 1}]]
1403         if {$path eq {}} {
1404                 set last_clicked {}
1405                 return
1406         }
1407
1408         set last_clicked [list $w $lno]
1409         array unset selected_paths
1410         $ui_index tag remove in_sel 0.0 end
1411         $ui_workdir tag remove in_sel 0.0 end
1412
1413         if {$col == 0} {
1414                 if {$current_diff_path eq $path} {
1415                         set after {reshow_diff;}
1416                 } else {
1417                         set after {}
1418                 }
1419                 if {$w eq $ui_index} {
1420                         update_indexinfo \
1421                                 "Unstaging [short_path $path] from commit" \
1422                                 [list $path] \
1423                                 [concat $after [list ui_ready]]
1424                 } elseif {$w eq $ui_workdir} {
1425                         update_index \
1426                                 "Adding [short_path $path]" \
1427                                 [list $path] \
1428                                 [concat $after [list ui_ready]]
1429                 }
1430         } else {
1431                 show_diff $path $w $lno
1432         }
1433 }
1434
1435 proc add_one_to_selection {w x y} {
1436         global file_lists last_clicked selected_paths
1437
1438         set lno [lindex [split [$w index @$x,$y] .] 0]
1439         set path [lindex $file_lists($w) [expr {$lno - 1}]]
1440         if {$path eq {}} {
1441                 set last_clicked {}
1442                 return
1443         }
1444
1445         if {$last_clicked ne {}
1446                 && [lindex $last_clicked 0] ne $w} {
1447                 array unset selected_paths
1448                 [lindex $last_clicked 0] tag remove in_sel 0.0 end
1449         }
1450
1451         set last_clicked [list $w $lno]
1452         if {[catch {set in_sel $selected_paths($path)}]} {
1453                 set in_sel 0
1454         }
1455         if {$in_sel} {
1456                 unset selected_paths($path)
1457                 $w tag remove in_sel $lno.0 [expr {$lno + 1}].0
1458         } else {
1459                 set selected_paths($path) 1
1460                 $w tag add in_sel $lno.0 [expr {$lno + 1}].0
1461         }
1462 }
1463
1464 proc add_range_to_selection {w x y} {
1465         global file_lists last_clicked selected_paths
1466
1467         if {[lindex $last_clicked 0] ne $w} {
1468                 toggle_or_diff $w $x $y
1469                 return
1470         }
1471
1472         set lno [lindex [split [$w index @$x,$y] .] 0]
1473         set lc [lindex $last_clicked 1]
1474         if {$lc < $lno} {
1475                 set begin $lc
1476                 set end $lno
1477         } else {
1478                 set begin $lno
1479                 set end $lc
1480         }
1481
1482         foreach path [lrange $file_lists($w) \
1483                 [expr {$begin - 1}] \
1484                 [expr {$end - 1}]] {
1485                 set selected_paths($path) 1
1486         }
1487         $w tag add in_sel $begin.0 [expr {$end + 1}].0
1488 }
1489
1490 ######################################################################
1491 ##
1492 ## config defaults
1493
1494 set cursor_ptr arrow
1495 font create font_diff -family Courier -size 10
1496 font create font_ui
1497 catch {
1498         label .dummy
1499         eval font configure font_ui [font actual [.dummy cget -font]]
1500         destroy .dummy
1501 }
1502
1503 font create font_uiitalic
1504 font create font_uibold
1505 font create font_diffbold
1506 font create font_diffitalic
1507
1508 foreach class {Button Checkbutton Entry Label
1509                 Labelframe Listbox Menu Message
1510                 Radiobutton Spinbox Text} {
1511         option add *$class.font font_ui
1512 }
1513 unset class
1514
1515 if {[is_Windows] || [is_MacOSX]} {
1516         option add *Menu.tearOff 0
1517 }
1518
1519 if {[is_MacOSX]} {
1520         set M1B M1
1521         set M1T Cmd
1522 } else {
1523         set M1B Control
1524         set M1T Ctrl
1525 }
1526
1527 proc apply_config {} {
1528         global repo_config font_descs
1529
1530         foreach option $font_descs {
1531                 set name [lindex $option 0]
1532                 set font [lindex $option 1]
1533                 if {[catch {
1534                         foreach {cn cv} $repo_config(gui.$name) {
1535                                 font configure $font $cn $cv
1536                         }
1537                         } err]} {
1538                         error_popup "Invalid font specified in gui.$name:\n\n$err"
1539                 }
1540                 foreach {cn cv} [font configure $font] {
1541                         font configure ${font}bold $cn $cv
1542                         font configure ${font}italic $cn $cv
1543                 }
1544                 font configure ${font}bold -weight bold
1545                 font configure ${font}italic -slant italic
1546         }
1547 }
1548
1549 set default_config(merge.diffstat) true
1550 set default_config(merge.summary) false
1551 set default_config(merge.verbosity) 2
1552 set default_config(user.name) {}
1553 set default_config(user.email) {}
1554
1555 set default_config(gui.matchtrackingbranch) false
1556 set default_config(gui.pruneduringfetch) false
1557 set default_config(gui.trustmtime) false
1558 set default_config(gui.diffcontext) 5
1559 set default_config(gui.newbranchtemplate) {}
1560 set default_config(gui.fontui) [font configure font_ui]
1561 set default_config(gui.fontdiff) [font configure font_diff]
1562 set font_descs {
1563         {fontui   font_ui   {Main Font}}
1564         {fontdiff font_diff {Diff/Console Font}}
1565 }
1566 load_config 0
1567 apply_config
1568
1569 ######################################################################
1570 ##
1571 ## feature option selection
1572
1573 if {[regexp {^git-(.+)$} [appname] _junk subcommand]} {
1574         unset _junk
1575 } else {
1576         set subcommand gui
1577 }
1578 if {$subcommand eq {gui.sh}} {
1579         set subcommand gui
1580 }
1581 if {$subcommand eq {gui} && [llength $argv] > 0} {
1582         set subcommand [lindex $argv 0]
1583         set argv [lrange $argv 1 end]
1584 }
1585
1586 enable_option multicommit
1587 enable_option branch
1588 enable_option transport
1589
1590 switch -- $subcommand {
1591 browser -
1592 blame {
1593         disable_option multicommit
1594         disable_option branch
1595         disable_option transport
1596 }
1597 citool {
1598         enable_option singlecommit
1599
1600         disable_option multicommit
1601         disable_option branch
1602         disable_option transport
1603 }
1604 }
1605
1606 ######################################################################
1607 ##
1608 ## ui construction
1609
1610 set ui_comm {}
1611
1612 # -- Menu Bar
1613 #
1614 menu .mbar -tearoff 0
1615 .mbar add cascade -label Repository -menu .mbar.repository
1616 .mbar add cascade -label Edit -menu .mbar.edit
1617 if {[is_enabled branch]} {
1618         .mbar add cascade -label Branch -menu .mbar.branch
1619 }
1620 if {[is_enabled multicommit] || [is_enabled singlecommit]} {
1621         .mbar add cascade -label Commit -menu .mbar.commit
1622 }
1623 if {[is_enabled transport]} {
1624         .mbar add cascade -label Merge -menu .mbar.merge
1625         .mbar add cascade -label Fetch -menu .mbar.fetch
1626         .mbar add cascade -label Push -menu .mbar.push
1627 }
1628 . configure -menu .mbar
1629
1630 # -- Repository Menu
1631 #
1632 menu .mbar.repository
1633
1634 .mbar.repository add command \
1635         -label {Browse Current Branch} \
1636         -command {browser::new $current_branch}
1637 trace add variable current_branch write ".mbar.repository entryconf [.mbar.repository index last] -label \"Browse \$current_branch\" ;#"
1638 .mbar.repository add separator
1639
1640 .mbar.repository add command \
1641         -label {Visualize Current Branch} \
1642         -command {do_gitk $current_branch}
1643 trace add variable current_branch write ".mbar.repository entryconf [.mbar.repository index last] -label \"Visualize \$current_branch\" ;#"
1644 .mbar.repository add command \
1645         -label {Visualize All Branches} \
1646         -command {do_gitk --all}
1647 .mbar.repository add separator
1648
1649 if {[is_enabled multicommit]} {
1650         .mbar.repository add command -label {Database Statistics} \
1651                 -command do_stats
1652
1653         .mbar.repository add command -label {Compress Database} \
1654                 -command do_gc
1655
1656         .mbar.repository add command -label {Verify Database} \
1657                 -command do_fsck_objects
1658
1659         .mbar.repository add separator
1660
1661         if {[is_Cygwin]} {
1662                 .mbar.repository add command \
1663                         -label {Create Desktop Icon} \
1664                         -command do_cygwin_shortcut
1665         } elseif {[is_Windows]} {
1666                 .mbar.repository add command \
1667                         -label {Create Desktop Icon} \
1668                         -command do_windows_shortcut
1669         } elseif {[is_MacOSX]} {
1670                 .mbar.repository add command \
1671                         -label {Create Desktop Icon} \
1672                         -command do_macosx_app
1673         }
1674 }
1675
1676 .mbar.repository add command -label Quit \
1677         -command do_quit \
1678         -accelerator $M1T-Q
1679
1680 # -- Edit Menu
1681 #
1682 menu .mbar.edit
1683 .mbar.edit add command -label Undo \
1684         -command {catch {[focus] edit undo}} \
1685         -accelerator $M1T-Z
1686 .mbar.edit add command -label Redo \
1687         -command {catch {[focus] edit redo}} \
1688         -accelerator $M1T-Y
1689 .mbar.edit add separator
1690 .mbar.edit add command -label Cut \
1691         -command {catch {tk_textCut [focus]}} \
1692         -accelerator $M1T-X
1693 .mbar.edit add command -label Copy \
1694         -command {catch {tk_textCopy [focus]}} \
1695         -accelerator $M1T-C
1696 .mbar.edit add command -label Paste \
1697         -command {catch {tk_textPaste [focus]; [focus] see insert}} \
1698         -accelerator $M1T-V
1699 .mbar.edit add command -label Delete \
1700         -command {catch {[focus] delete sel.first sel.last}} \
1701         -accelerator Del
1702 .mbar.edit add separator
1703 .mbar.edit add command -label {Select All} \
1704         -command {catch {[focus] tag add sel 0.0 end}} \
1705         -accelerator $M1T-A
1706
1707 # -- Branch Menu
1708 #
1709 if {[is_enabled branch]} {
1710         menu .mbar.branch
1711
1712         .mbar.branch add command -label {Create...} \
1713                 -command branch_create::dialog \
1714                 -accelerator $M1T-N
1715         lappend disable_on_lock [list .mbar.branch entryconf \
1716                 [.mbar.branch index last] -state]
1717
1718         .mbar.branch add command -label {Checkout...} \
1719                 -command branch_checkout::dialog \
1720                 -accelerator $M1T-O
1721         lappend disable_on_lock [list .mbar.branch entryconf \
1722                 [.mbar.branch index last] -state]
1723
1724         .mbar.branch add command -label {Rename...} \
1725                 -command branch_rename::dialog
1726         lappend disable_on_lock [list .mbar.branch entryconf \
1727                 [.mbar.branch index last] -state]
1728
1729         .mbar.branch add command -label {Delete...} \
1730                 -command branch_delete::dialog
1731         lappend disable_on_lock [list .mbar.branch entryconf \
1732                 [.mbar.branch index last] -state]
1733
1734         .mbar.branch add command -label {Reset...} \
1735                 -command merge::reset_hard
1736         lappend disable_on_lock [list .mbar.branch entryconf \
1737                 [.mbar.branch index last] -state]
1738 }
1739
1740 # -- Commit Menu
1741 #
1742 if {[is_enabled multicommit] || [is_enabled singlecommit]} {
1743         menu .mbar.commit
1744
1745         .mbar.commit add radiobutton \
1746                 -label {New Commit} \
1747                 -command do_select_commit_type \
1748                 -variable selected_commit_type \
1749                 -value new
1750         lappend disable_on_lock \
1751                 [list .mbar.commit entryconf [.mbar.commit index last] -state]
1752
1753         .mbar.commit add radiobutton \
1754                 -label {Amend Last Commit} \
1755                 -command do_select_commit_type \
1756                 -variable selected_commit_type \
1757                 -value amend
1758         lappend disable_on_lock \
1759                 [list .mbar.commit entryconf [.mbar.commit index last] -state]
1760
1761         .mbar.commit add separator
1762
1763         .mbar.commit add command -label Rescan \
1764                 -command do_rescan \
1765                 -accelerator F5
1766         lappend disable_on_lock \
1767                 [list .mbar.commit entryconf [.mbar.commit index last] -state]
1768
1769         .mbar.commit add command -label {Add To Commit} \
1770                 -command do_add_selection
1771         lappend disable_on_lock \
1772                 [list .mbar.commit entryconf [.mbar.commit index last] -state]
1773
1774         .mbar.commit add command -label {Add Existing To Commit} \
1775                 -command do_add_all \
1776                 -accelerator $M1T-I
1777         lappend disable_on_lock \
1778                 [list .mbar.commit entryconf [.mbar.commit index last] -state]
1779
1780         .mbar.commit add command -label {Unstage From Commit} \
1781                 -command do_unstage_selection
1782         lappend disable_on_lock \
1783                 [list .mbar.commit entryconf [.mbar.commit index last] -state]
1784
1785         .mbar.commit add command -label {Revert Changes} \
1786                 -command do_revert_selection
1787         lappend disable_on_lock \
1788                 [list .mbar.commit entryconf [.mbar.commit index last] -state]
1789
1790         .mbar.commit add separator
1791
1792         .mbar.commit add command -label {Sign Off} \
1793                 -command do_signoff \
1794                 -accelerator $M1T-S
1795
1796         .mbar.commit add command -label Commit \
1797                 -command do_commit \
1798                 -accelerator $M1T-Return
1799         lappend disable_on_lock \
1800                 [list .mbar.commit entryconf [.mbar.commit index last] -state]
1801 }
1802
1803 # -- Merge Menu
1804 #
1805 if {[is_enabled branch]} {
1806         menu .mbar.merge
1807         .mbar.merge add command -label {Local Merge...} \
1808                 -command merge::dialog
1809         lappend disable_on_lock \
1810                 [list .mbar.merge entryconf [.mbar.merge index last] -state]
1811         .mbar.merge add command -label {Abort Merge...} \
1812                 -command merge::reset_hard
1813         lappend disable_on_lock \
1814                 [list .mbar.merge entryconf [.mbar.merge index last] -state]
1815
1816 }
1817
1818 # -- Transport Menu
1819 #
1820 if {[is_enabled transport]} {
1821         menu .mbar.fetch
1822
1823         menu .mbar.push
1824         .mbar.push add command -label {Push...} \
1825                 -command do_push_anywhere \
1826                 -accelerator $M1T-P
1827         .mbar.push add command -label {Delete...} \
1828                 -command remote_branch_delete::dialog
1829 }
1830
1831 if {[is_MacOSX]} {
1832         # -- Apple Menu (Mac OS X only)
1833         #
1834         .mbar add cascade -label Apple -menu .mbar.apple
1835         menu .mbar.apple
1836
1837         .mbar.apple add command -label "About [appname]" \
1838                 -command do_about
1839         .mbar.apple add command -label "Options..." \
1840                 -command do_options
1841 } else {
1842         # -- Edit Menu
1843         #
1844         .mbar.edit add separator
1845         .mbar.edit add command -label {Options...} \
1846                 -command do_options
1847
1848         # -- Tools Menu
1849         #
1850         if {[is_Cygwin] && [file exists /usr/local/miga/lib/gui-miga]} {
1851         proc do_miga {} {
1852                 if {![lock_index update]} return
1853                 set cmd [list sh --login -c "/usr/local/miga/lib/gui-miga \"[pwd]\""]
1854                 set miga_fd [open "|$cmd" r]
1855                 fconfigure $miga_fd -blocking 0
1856                 fileevent $miga_fd readable [list miga_done $miga_fd]
1857                 ui_status {Running miga...}
1858         }
1859         proc miga_done {fd} {
1860                 read $fd 512
1861                 if {[eof $fd]} {
1862                         close $fd
1863                         unlock_index
1864                         rescan ui_ready
1865                 }
1866         }
1867         .mbar add cascade -label Tools -menu .mbar.tools
1868         menu .mbar.tools
1869         .mbar.tools add command -label "Migrate" \
1870                 -command do_miga
1871         lappend disable_on_lock \
1872                 [list .mbar.tools entryconf [.mbar.tools index last] -state]
1873         }
1874 }
1875
1876 # -- Help Menu
1877 #
1878 .mbar add cascade -label Help -menu .mbar.help
1879 menu .mbar.help
1880
1881 if {![is_MacOSX]} {
1882         .mbar.help add command -label "About [appname]" \
1883                 -command do_about
1884 }
1885
1886 set browser {}
1887 catch {set browser $repo_config(instaweb.browser)}
1888 set doc_path [file dirname [gitexec]]
1889 set doc_path [file join $doc_path Documentation index.html]
1890
1891 if {[is_Cygwin]} {
1892         set doc_path [exec cygpath --mixed $doc_path]
1893 }
1894
1895 if {$browser eq {}} {
1896         if {[is_MacOSX]} {
1897                 set browser open
1898         } elseif {[is_Cygwin]} {
1899                 set program_files [file dirname [exec cygpath --windir]]
1900                 set program_files [file join $program_files {Program Files}]
1901                 set firefox [file join $program_files {Mozilla Firefox} firefox.exe]
1902                 set ie [file join $program_files {Internet Explorer} IEXPLORE.EXE]
1903                 if {[file exists $firefox]} {
1904                         set browser $firefox
1905                 } elseif {[file exists $ie]} {
1906                         set browser $ie
1907                 }
1908                 unset program_files firefox ie
1909         }
1910 }
1911
1912 if {[file isfile $doc_path]} {
1913         set doc_url "file:$doc_path"
1914 } else {
1915         set doc_url {http://www.kernel.org/pub/software/scm/git/docs/}
1916 }
1917
1918 if {$browser ne {}} {
1919         .mbar.help add command -label {Online Documentation} \
1920                 -command [list exec $browser $doc_url &]
1921 }
1922 unset browser doc_path doc_url
1923
1924 # -- Standard bindings
1925 #
1926 wm protocol . WM_DELETE_WINDOW do_quit
1927 bind all <$M1B-Key-q> do_quit
1928 bind all <$M1B-Key-Q> do_quit
1929 bind all <$M1B-Key-w> {destroy [winfo toplevel %W]}
1930 bind all <$M1B-Key-W> {destroy [winfo toplevel %W]}
1931
1932 set subcommand_args {}
1933 proc usage {} {
1934         puts stderr "usage: $::argv0 $::subcommand $::subcommand_args"
1935         exit 1
1936 }
1937
1938 # -- Not a normal commit type invocation?  Do that instead!
1939 #
1940 switch -- $subcommand {
1941 browser {
1942         set subcommand_args {rev?}
1943         switch [llength $argv] {
1944         0 { load_current_branch }
1945         1 {
1946                 set current_branch [lindex $argv 0]
1947                 if {[regexp {^[0-9a-f]{1,39}$} $current_branch]} {
1948                         if {[catch {
1949                                         set current_branch \
1950                                         [git rev-parse --verify $current_branch]
1951                                 } err]} {
1952                                 puts stderr $err
1953                                 exit 1
1954                         }
1955                 }
1956         }
1957         default usage
1958         }
1959         browser::new $current_branch
1960         return
1961 }
1962 blame {
1963         set subcommand_args {rev? path?}
1964         set head {}
1965         set path {}
1966         set is_path 0
1967         foreach a $argv {
1968                 if {$is_path || [file exists $_prefix$a]} {
1969                         if {$path ne {}} usage
1970                         set path $_prefix$a
1971                         break
1972                 } elseif {$a eq {--}} {
1973                         if {$path ne {}} {
1974                                 if {$head ne {}} usage
1975                                 set head $path
1976                                 set path {}
1977                         }
1978                         set is_path 1
1979                 } elseif {$head eq {}} {
1980                         if {$head ne {}} usage
1981                         set head $a
1982                 } else {
1983                         usage
1984                 }
1985         }
1986         unset is_path
1987
1988         if {$head eq {}} {
1989                 load_current_branch
1990         } else {
1991                 if {[regexp {^[0-9a-f]{1,39}$} $head]} {
1992                         if {[catch {
1993                                         set head [git rev-parse --verify $head]
1994                                 } err]} {
1995                                 puts stderr $err
1996                                 exit 1
1997                         }
1998                 }
1999                 set current_branch $head
2000         }
2001
2002         if {$path eq {}} usage
2003         blame::new $head $path
2004         return
2005 }
2006 citool -
2007 gui {
2008         if {[llength $argv] != 0} {
2009                 puts -nonewline stderr "usage: $argv0"
2010                 if {$subcommand ne {gui} && [appname] ne "git-$subcommand"} {
2011                         puts -nonewline stderr " $subcommand"
2012                 }
2013                 puts stderr {}
2014                 exit 1
2015         }
2016         # fall through to setup UI for commits
2017 }
2018 default {
2019         puts stderr "usage: $argv0 \[{blame|browser|citool}\]"
2020         exit 1
2021 }
2022 }
2023
2024 # -- Branch Control
2025 #
2026 frame .branch \
2027         -borderwidth 1 \
2028         -relief sunken
2029 label .branch.l1 \
2030         -text {Current Branch:} \
2031         -anchor w \
2032         -justify left
2033 label .branch.cb \
2034         -textvariable current_branch \
2035         -anchor w \
2036         -justify left
2037 pack .branch.l1 -side left
2038 pack .branch.cb -side left -fill x
2039 pack .branch -side top -fill x
2040
2041 # -- Main Window Layout
2042 #
2043 panedwindow .vpane -orient vertical
2044 panedwindow .vpane.files -orient horizontal
2045 .vpane add .vpane.files -sticky nsew -height 100 -width 200
2046 pack .vpane -anchor n -side top -fill both -expand 1
2047
2048 # -- Index File List
2049 #
2050 frame .vpane.files.index -height 100 -width 200
2051 label .vpane.files.index.title -text {Staged Changes (Will Be Committed)} \
2052         -background lightgreen
2053 text $ui_index -background white -borderwidth 0 \
2054         -width 20 -height 10 \
2055         -wrap none \
2056         -cursor $cursor_ptr \
2057         -xscrollcommand {.vpane.files.index.sx set} \
2058         -yscrollcommand {.vpane.files.index.sy set} \
2059         -state disabled
2060 scrollbar .vpane.files.index.sx -orient h -command [list $ui_index xview]
2061 scrollbar .vpane.files.index.sy -orient v -command [list $ui_index yview]
2062 pack .vpane.files.index.title -side top -fill x
2063 pack .vpane.files.index.sx -side bottom -fill x
2064 pack .vpane.files.index.sy -side right -fill y
2065 pack $ui_index -side left -fill both -expand 1
2066 .vpane.files add .vpane.files.index -sticky nsew
2067
2068 # -- Working Directory File List
2069 #
2070 frame .vpane.files.workdir -height 100 -width 200
2071 label .vpane.files.workdir.title -text {Unstaged Changes (Will Not Be Committed)} \
2072         -background lightsalmon
2073 text $ui_workdir -background white -borderwidth 0 \
2074         -width 20 -height 10 \
2075         -wrap none \
2076         -cursor $cursor_ptr \
2077         -xscrollcommand {.vpane.files.workdir.sx set} \
2078         -yscrollcommand {.vpane.files.workdir.sy set} \
2079         -state disabled
2080 scrollbar .vpane.files.workdir.sx -orient h -command [list $ui_workdir xview]
2081 scrollbar .vpane.files.workdir.sy -orient v -command [list $ui_workdir yview]
2082 pack .vpane.files.workdir.title -side top -fill x
2083 pack .vpane.files.workdir.sx -side bottom -fill x
2084 pack .vpane.files.workdir.sy -side right -fill y
2085 pack $ui_workdir -side left -fill both -expand 1
2086 .vpane.files add .vpane.files.workdir -sticky nsew
2087
2088 foreach i [list $ui_index $ui_workdir] {
2089         $i tag conf in_diff -background lightgray
2090         $i tag conf in_sel  -background lightgray
2091 }
2092 unset i
2093
2094 # -- Diff and Commit Area
2095 #
2096 frame .vpane.lower -height 300 -width 400
2097 frame .vpane.lower.commarea
2098 frame .vpane.lower.diff -relief sunken -borderwidth 1
2099 pack .vpane.lower.commarea -side top -fill x
2100 pack .vpane.lower.diff -side bottom -fill both -expand 1
2101 .vpane add .vpane.lower -sticky nsew
2102
2103 # -- Commit Area Buttons
2104 #
2105 frame .vpane.lower.commarea.buttons
2106 label .vpane.lower.commarea.buttons.l -text {} \
2107         -anchor w \
2108         -justify left
2109 pack .vpane.lower.commarea.buttons.l -side top -fill x
2110 pack .vpane.lower.commarea.buttons -side left -fill y
2111
2112 button .vpane.lower.commarea.buttons.rescan -text {Rescan} \
2113         -command do_rescan
2114 pack .vpane.lower.commarea.buttons.rescan -side top -fill x
2115 lappend disable_on_lock \
2116         {.vpane.lower.commarea.buttons.rescan conf -state}
2117
2118 button .vpane.lower.commarea.buttons.incall -text {Add Existing} \
2119         -command do_add_all
2120 pack .vpane.lower.commarea.buttons.incall -side top -fill x
2121 lappend disable_on_lock \
2122         {.vpane.lower.commarea.buttons.incall conf -state}
2123
2124 button .vpane.lower.commarea.buttons.signoff -text {Sign Off} \
2125         -command do_signoff
2126 pack .vpane.lower.commarea.buttons.signoff -side top -fill x
2127
2128 button .vpane.lower.commarea.buttons.commit -text {Commit} \
2129         -command do_commit
2130 pack .vpane.lower.commarea.buttons.commit -side top -fill x
2131 lappend disable_on_lock \
2132         {.vpane.lower.commarea.buttons.commit conf -state}
2133
2134 button .vpane.lower.commarea.buttons.push -text {Push} \
2135         -command do_push_anywhere
2136 pack .vpane.lower.commarea.buttons.push -side top -fill x
2137
2138 # -- Commit Message Buffer
2139 #
2140 frame .vpane.lower.commarea.buffer
2141 frame .vpane.lower.commarea.buffer.header
2142 set ui_comm .vpane.lower.commarea.buffer.t
2143 set ui_coml .vpane.lower.commarea.buffer.header.l
2144 radiobutton .vpane.lower.commarea.buffer.header.new \
2145         -text {New Commit} \
2146         -command do_select_commit_type \
2147         -variable selected_commit_type \
2148         -value new
2149 lappend disable_on_lock \
2150         [list .vpane.lower.commarea.buffer.header.new conf -state]
2151 radiobutton .vpane.lower.commarea.buffer.header.amend \
2152         -text {Amend Last Commit} \
2153         -command do_select_commit_type \
2154         -variable selected_commit_type \
2155         -value amend
2156 lappend disable_on_lock \
2157         [list .vpane.lower.commarea.buffer.header.amend conf -state]
2158 label $ui_coml \
2159         -anchor w \
2160         -justify left
2161 proc trace_commit_type {varname args} {
2162         global ui_coml commit_type
2163         switch -glob -- $commit_type {
2164         initial       {set txt {Initial Commit Message:}}
2165         amend         {set txt {Amended Commit Message:}}
2166         amend-initial {set txt {Amended Initial Commit Message:}}
2167         amend-merge   {set txt {Amended Merge Commit Message:}}
2168         merge         {set txt {Merge Commit Message:}}
2169         *             {set txt {Commit Message:}}
2170         }
2171         $ui_coml conf -text $txt
2172 }
2173 trace add variable commit_type write trace_commit_type
2174 pack $ui_coml -side left -fill x
2175 pack .vpane.lower.commarea.buffer.header.amend -side right
2176 pack .vpane.lower.commarea.buffer.header.new -side right
2177
2178 text $ui_comm -background white -borderwidth 1 \
2179         -undo true \
2180         -maxundo 20 \
2181         -autoseparators true \
2182         -relief sunken \
2183         -width 75 -height 9 -wrap none \
2184         -font font_diff \
2185         -yscrollcommand {.vpane.lower.commarea.buffer.sby set}
2186 scrollbar .vpane.lower.commarea.buffer.sby \
2187         -command [list $ui_comm yview]
2188 pack .vpane.lower.commarea.buffer.header -side top -fill x
2189 pack .vpane.lower.commarea.buffer.sby -side right -fill y
2190 pack $ui_comm -side left -fill y
2191 pack .vpane.lower.commarea.buffer -side left -fill y
2192
2193 # -- Commit Message Buffer Context Menu
2194 #
2195 set ctxm .vpane.lower.commarea.buffer.ctxm
2196 menu $ctxm -tearoff 0
2197 $ctxm add command \
2198         -label {Cut} \
2199         -command {tk_textCut $ui_comm}
2200 $ctxm add command \
2201         -label {Copy} \
2202         -command {tk_textCopy $ui_comm}
2203 $ctxm add command \
2204         -label {Paste} \
2205         -command {tk_textPaste $ui_comm}
2206 $ctxm add command \
2207         -label {Delete} \
2208         -command {$ui_comm delete sel.first sel.last}
2209 $ctxm add separator
2210 $ctxm add command \
2211         -label {Select All} \
2212         -command {focus $ui_comm;$ui_comm tag add sel 0.0 end}
2213 $ctxm add command \
2214         -label {Copy All} \
2215         -command {
2216                 $ui_comm tag add sel 0.0 end
2217                 tk_textCopy $ui_comm
2218                 $ui_comm tag remove sel 0.0 end
2219         }
2220 $ctxm add separator
2221 $ctxm add command \
2222         -label {Sign Off} \
2223         -command do_signoff
2224 bind_button3 $ui_comm "tk_popup $ctxm %X %Y"
2225
2226 # -- Diff Header
2227 #
2228 proc trace_current_diff_path {varname args} {
2229         global current_diff_path diff_actions file_states
2230         if {$current_diff_path eq {}} {
2231                 set s {}
2232                 set f {}
2233                 set p {}
2234                 set o disabled
2235         } else {
2236                 set p $current_diff_path
2237                 set s [mapdesc [lindex $file_states($p) 0] $p]
2238                 set f {File:}
2239                 set p [escape_path $p]
2240                 set o normal
2241         }
2242
2243         .vpane.lower.diff.header.status configure -text $s
2244         .vpane.lower.diff.header.file configure -text $f
2245         .vpane.lower.diff.header.path configure -text $p
2246         foreach w $diff_actions {
2247                 uplevel #0 $w $o
2248         }
2249 }
2250 trace add variable current_diff_path write trace_current_diff_path
2251
2252 frame .vpane.lower.diff.header -background gold
2253 label .vpane.lower.diff.header.status \
2254         -background gold \
2255         -width $max_status_desc \
2256         -anchor w \
2257         -justify left
2258 label .vpane.lower.diff.header.file \
2259         -background gold \
2260         -anchor w \
2261         -justify left
2262 label .vpane.lower.diff.header.path \
2263         -background gold \
2264         -anchor w \
2265         -justify left
2266 pack .vpane.lower.diff.header.status -side left
2267 pack .vpane.lower.diff.header.file -side left
2268 pack .vpane.lower.diff.header.path -fill x
2269 set ctxm .vpane.lower.diff.header.ctxm
2270 menu $ctxm -tearoff 0
2271 $ctxm add command \
2272         -label {Copy} \
2273         -command {
2274                 clipboard clear
2275                 clipboard append \
2276                         -format STRING \
2277                         -type STRING \
2278                         -- $current_diff_path
2279         }
2280 lappend diff_actions [list $ctxm entryconf [$ctxm index last] -state]
2281 bind_button3 .vpane.lower.diff.header.path "tk_popup $ctxm %X %Y"
2282
2283 # -- Diff Body
2284 #
2285 frame .vpane.lower.diff.body
2286 set ui_diff .vpane.lower.diff.body.t
2287 text $ui_diff -background white -borderwidth 0 \
2288         -width 80 -height 15 -wrap none \
2289         -font font_diff \
2290         -xscrollcommand {.vpane.lower.diff.body.sbx set} \
2291         -yscrollcommand {.vpane.lower.diff.body.sby set} \
2292         -state disabled
2293 scrollbar .vpane.lower.diff.body.sbx -orient horizontal \
2294         -command [list $ui_diff xview]
2295 scrollbar .vpane.lower.diff.body.sby -orient vertical \
2296         -command [list $ui_diff yview]
2297 pack .vpane.lower.diff.body.sbx -side bottom -fill x
2298 pack .vpane.lower.diff.body.sby -side right -fill y
2299 pack $ui_diff -side left -fill both -expand 1
2300 pack .vpane.lower.diff.header -side top -fill x
2301 pack .vpane.lower.diff.body -side bottom -fill both -expand 1
2302
2303 $ui_diff tag conf d_cr -elide true
2304 $ui_diff tag conf d_@ -foreground blue -font font_diffbold
2305 $ui_diff tag conf d_+ -foreground {#00a000}
2306 $ui_diff tag conf d_- -foreground red
2307
2308 $ui_diff tag conf d_++ -foreground {#00a000}
2309 $ui_diff tag conf d_-- -foreground red
2310 $ui_diff tag conf d_+s \
2311         -foreground {#00a000} \
2312         -background {#e2effa}
2313 $ui_diff tag conf d_-s \
2314         -foreground red \
2315         -background {#e2effa}
2316 $ui_diff tag conf d_s+ \
2317         -foreground {#00a000} \
2318         -background ivory1
2319 $ui_diff tag conf d_s- \
2320         -foreground red \
2321         -background ivory1
2322
2323 $ui_diff tag conf d<<<<<<< \
2324         -foreground orange \
2325         -font font_diffbold
2326 $ui_diff tag conf d======= \
2327         -foreground orange \
2328         -font font_diffbold
2329 $ui_diff tag conf d>>>>>>> \
2330         -foreground orange \
2331         -font font_diffbold
2332
2333 $ui_diff tag raise sel
2334
2335 # -- Diff Body Context Menu
2336 #
2337 set ctxm .vpane.lower.diff.body.ctxm
2338 menu $ctxm -tearoff 0
2339 $ctxm add command \
2340         -label {Refresh} \
2341         -command reshow_diff
2342 lappend diff_actions [list $ctxm entryconf [$ctxm index last] -state]
2343 $ctxm add command \
2344         -label {Copy} \
2345         -command {tk_textCopy $ui_diff}
2346 lappend diff_actions [list $ctxm entryconf [$ctxm index last] -state]
2347 $ctxm add command \
2348         -label {Select All} \
2349         -command {focus $ui_diff;$ui_diff tag add sel 0.0 end}
2350 lappend diff_actions [list $ctxm entryconf [$ctxm index last] -state]
2351 $ctxm add command \
2352         -label {Copy All} \
2353         -command {
2354                 $ui_diff tag add sel 0.0 end
2355                 tk_textCopy $ui_diff
2356                 $ui_diff tag remove sel 0.0 end
2357         }
2358 lappend diff_actions [list $ctxm entryconf [$ctxm index last] -state]
2359 $ctxm add separator
2360 $ctxm add command \
2361         -label {Apply/Reverse Hunk} \
2362         -command {apply_hunk $cursorX $cursorY}
2363 set ui_diff_applyhunk [$ctxm index last]
2364 lappend diff_actions [list $ctxm entryconf $ui_diff_applyhunk -state]
2365 $ctxm add separator
2366 $ctxm add command \
2367         -label {Decrease Font Size} \
2368         -command {incr_font_size font_diff -1}
2369 lappend diff_actions [list $ctxm entryconf [$ctxm index last] -state]
2370 $ctxm add command \
2371         -label {Increase Font Size} \
2372         -command {incr_font_size font_diff 1}
2373 lappend diff_actions [list $ctxm entryconf [$ctxm index last] -state]
2374 $ctxm add separator
2375 $ctxm add command \
2376         -label {Show Less Context} \
2377         -command {if {$repo_config(gui.diffcontext) >= 1} {
2378                 incr repo_config(gui.diffcontext) -1
2379                 reshow_diff
2380         }}
2381 lappend diff_actions [list $ctxm entryconf [$ctxm index last] -state]
2382 $ctxm add command \
2383         -label {Show More Context} \
2384         -command {if {$repo_config(gui.diffcontext) < 99} {
2385                 incr repo_config(gui.diffcontext)
2386                 reshow_diff
2387         }}
2388 lappend diff_actions [list $ctxm entryconf [$ctxm index last] -state]
2389 $ctxm add separator
2390 $ctxm add command -label {Options...} \
2391         -command do_options
2392 bind_button3 $ui_diff "
2393         set cursorX %x
2394         set cursorY %y
2395         if {\$ui_index eq \$current_diff_side} {
2396                 $ctxm entryconf $ui_diff_applyhunk -label {Unstage Hunk From Commit}
2397         } else {
2398                 $ctxm entryconf $ui_diff_applyhunk -label {Stage Hunk For Commit}
2399         }
2400         tk_popup $ctxm %X %Y
2401 "
2402 unset ui_diff_applyhunk
2403
2404 # -- Status Bar
2405 #
2406 set main_status [::status_bar::new .status]
2407 pack .status -anchor w -side bottom -fill x
2408 $main_status show {Initializing...}
2409
2410 # -- Load geometry
2411 #
2412 catch {
2413 set gm $repo_config(gui.geometry)
2414 wm geometry . [lindex $gm 0]
2415 .vpane sash place 0 \
2416         [lindex [.vpane sash coord 0] 0] \
2417         [lindex $gm 1]
2418 .vpane.files sash place 0 \
2419         [lindex $gm 2] \
2420         [lindex [.vpane.files sash coord 0] 1]
2421 unset gm
2422 }
2423
2424 # -- Key Bindings
2425 #
2426 bind $ui_comm <$M1B-Key-Return> {do_commit;break}
2427 bind $ui_comm <$M1B-Key-i> {do_add_all;break}
2428 bind $ui_comm <$M1B-Key-I> {do_add_all;break}
2429 bind $ui_comm <$M1B-Key-x> {tk_textCut %W;break}
2430 bind $ui_comm <$M1B-Key-X> {tk_textCut %W;break}
2431 bind $ui_comm <$M1B-Key-c> {tk_textCopy %W;break}
2432 bind $ui_comm <$M1B-Key-C> {tk_textCopy %W;break}
2433 bind $ui_comm <$M1B-Key-v> {tk_textPaste %W; %W see insert; break}
2434 bind $ui_comm <$M1B-Key-V> {tk_textPaste %W; %W see insert; break}
2435 bind $ui_comm <$M1B-Key-a> {%W tag add sel 0.0 end;break}
2436 bind $ui_comm <$M1B-Key-A> {%W tag add sel 0.0 end;break}
2437
2438 bind $ui_diff <$M1B-Key-x> {tk_textCopy %W;break}
2439 bind $ui_diff <$M1B-Key-X> {tk_textCopy %W;break}
2440 bind $ui_diff <$M1B-Key-c> {tk_textCopy %W;break}
2441 bind $ui_diff <$M1B-Key-C> {tk_textCopy %W;break}
2442 bind $ui_diff <$M1B-Key-v> {break}
2443 bind $ui_diff <$M1B-Key-V> {break}
2444 bind $ui_diff <$M1B-Key-a> {%W tag add sel 0.0 end;break}
2445 bind $ui_diff <$M1B-Key-A> {%W tag add sel 0.0 end;break}
2446 bind $ui_diff <Key-Up>     {catch {%W yview scroll -1 units};break}
2447 bind $ui_diff <Key-Down>   {catch {%W yview scroll  1 units};break}
2448 bind $ui_diff <Key-Left>   {catch {%W xview scroll -1 units};break}
2449 bind $ui_diff <Key-Right>  {catch {%W xview scroll  1 units};break}
2450 bind $ui_diff <Key-k>         {catch {%W yview scroll -1 units};break}
2451 bind $ui_diff <Key-j>         {catch {%W yview scroll  1 units};break}
2452 bind $ui_diff <Key-h>         {catch {%W xview scroll -1 units};break}
2453 bind $ui_diff <Key-l>         {catch {%W xview scroll  1 units};break}
2454 bind $ui_diff <Control-Key-b> {catch {%W yview scroll -1 pages};break}
2455 bind $ui_diff <Control-Key-f> {catch {%W yview scroll  1 pages};break}
2456 bind $ui_diff <Button-1>   {focus %W}
2457
2458 if {[is_enabled branch]} {
2459         bind . <$M1B-Key-n> branch_create::dialog
2460         bind . <$M1B-Key-N> branch_create::dialog
2461         bind . <$M1B-Key-o> branch_checkout::dialog
2462         bind . <$M1B-Key-O> branch_checkout::dialog
2463 }
2464 if {[is_enabled transport]} {
2465         bind . <$M1B-Key-p> do_push_anywhere
2466         bind . <$M1B-Key-P> do_push_anywhere
2467 }
2468
2469 bind .   <Key-F5>     do_rescan
2470 bind .   <$M1B-Key-r> do_rescan
2471 bind .   <$M1B-Key-R> do_rescan
2472 bind .   <$M1B-Key-s> do_signoff
2473 bind .   <$M1B-Key-S> do_signoff
2474 bind .   <$M1B-Key-i> do_add_all
2475 bind .   <$M1B-Key-I> do_add_all
2476 bind .   <$M1B-Key-Return> do_commit
2477 foreach i [list $ui_index $ui_workdir] {
2478         bind $i <Button-1>       "toggle_or_diff         $i %x %y; break"
2479         bind $i <$M1B-Button-1>  "add_one_to_selection   $i %x %y; break"
2480         bind $i <Shift-Button-1> "add_range_to_selection $i %x %y; break"
2481 }
2482 unset i
2483
2484 set file_lists($ui_index) [list]
2485 set file_lists($ui_workdir) [list]
2486
2487 wm title . "[appname] ([reponame]) [file normalize [file dirname [gitdir]]]"
2488 focus -force $ui_comm
2489
2490 # -- Warn the user about environmental problems.  Cygwin's Tcl
2491 #    does *not* pass its env array onto any processes it spawns.
2492 #    This means that git processes get none of our environment.
2493 #
2494 if {[is_Cygwin]} {
2495         set ignored_env 0
2496         set suggest_user {}
2497         set msg "Possible environment issues exist.
2498
2499 The following environment variables are probably
2500 going to be ignored by any Git subprocess run
2501 by [appname]:
2502
2503 "
2504         foreach name [array names env] {
2505                 switch -regexp -- $name {
2506                 {^GIT_INDEX_FILE$} -
2507                 {^GIT_OBJECT_DIRECTORY$} -
2508                 {^GIT_ALTERNATE_OBJECT_DIRECTORIES$} -
2509                 {^GIT_DIFF_OPTS$} -
2510                 {^GIT_EXTERNAL_DIFF$} -
2511                 {^GIT_PAGER$} -
2512                 {^GIT_TRACE$} -
2513                 {^GIT_CONFIG$} -
2514                 {^GIT_CONFIG_LOCAL$} -
2515                 {^GIT_(AUTHOR|COMMITTER)_DATE$} {
2516                         append msg " - $name\n"
2517                         incr ignored_env
2518                 }
2519                 {^GIT_(AUTHOR|COMMITTER)_(NAME|EMAIL)$} {
2520                         append msg " - $name\n"
2521                         incr ignored_env
2522                         set suggest_user $name
2523                 }
2524                 }
2525         }
2526         if {$ignored_env > 0} {
2527                 append msg "
2528 This is due to a known issue with the
2529 Tcl binary distributed by Cygwin."
2530
2531                 if {$suggest_user ne {}} {
2532                         append msg "
2533
2534 A good replacement for $suggest_user
2535 is placing values for the user.name and
2536 user.email settings into your personal
2537 ~/.gitconfig file.
2538 "
2539                 }
2540                 warn_popup $msg
2541         }
2542         unset ignored_env msg suggest_user name
2543 }
2544
2545 # -- Only initialize complex UI if we are going to stay running.
2546 #
2547 if {[is_enabled transport]} {
2548         load_all_remotes
2549
2550         populate_fetch_menu
2551         populate_push_menu
2552 }
2553
2554 # -- Only suggest a gc run if we are going to stay running.
2555 #
2556 if {[is_enabled multicommit]} {
2557         set object_limit 2000
2558         if {[is_Windows]} {set object_limit 200}
2559         regexp {^([0-9]+) objects,} [git count-objects] _junk objects_current
2560         if {$objects_current >= $object_limit} {
2561                 if {[ask_popup \
2562                         "This repository currently has $objects_current loose objects.
2563
2564 To maintain optimal performance it is strongly recommended that you compress the database when more than $object_limit loose objects exist.
2565
2566 Compress the database now?"] eq yes} {
2567                         do_gc
2568                 }
2569         }
2570         unset object_limit _junk objects_current
2571 }
2572
2573 lock_index begin-read
2574 after 1 do_rescan