Code

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