Code

git-gui: Localize commit/author dates when displaying them
[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 "git-gui: fatal error" \
41                 -message $err
42         exit 1
43 }
45 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
89 namespace import ::msgcat::mc
90 ::msgcat::mcload $oguimsg
91 unset oguimsg
93 ######################################################################
94 ##
95 ## read only globals
97 set _appname [lindex [file split $argv0] end]
98 set _gitdir {}
99 set _gitexec {}
100 set _reponame {}
101 set _iscygwin {}
102 set _search_path {}
104 proc appname {} {
105         global _appname
106         return $_appname
109 proc gitdir {args} {
110         global _gitdir
111         if {$args eq {}} {
112                 return $_gitdir
113         }
114         return [eval [list file join $_gitdir] $args]
117 proc gitexec {args} {
118         global _gitexec
119         if {$_gitexec eq {}} {
120                 if {[catch {set _gitexec [git --exec-path]} err]} {
121                         error "Git not installed?\n\n$err"
122                 }
123                 if {[is_Cygwin]} {
124                         set _gitexec [exec cygpath \
125                                 --windows \
126                                 --absolute \
127                                 $_gitexec]
128                 } else {
129                         set _gitexec [file normalize $_gitexec]
130                 }
131         }
132         if {$args eq {}} {
133                 return $_gitexec
134         }
135         return [eval [list file join $_gitexec] $args]
138 proc reponame {} {
139         return $::_reponame
142 proc is_MacOSX {} {
143         if {[tk windowingsystem] eq {aqua}} {
144                 return 1
145         }
146         return 0
149 proc is_Windows {} {
150         if {$::tcl_platform(platform) eq {windows}} {
151                 return 1
152         }
153         return 0
156 proc is_Cygwin {} {
157         global _iscygwin
158         if {$_iscygwin eq {}} {
159                 if {$::tcl_platform(platform) eq {windows}} {
160                         if {[catch {set p [exec cygpath --windir]} err]} {
161                                 set _iscygwin 0
162                         } else {
163                                 set _iscygwin 1
164                         }
165                 } else {
166                         set _iscygwin 0
167                 }
168         }
169         return $_iscygwin
172 proc is_enabled {option} {
173         global enabled_options
174         if {[catch {set on $enabled_options($option)}]} {return 0}
175         return $on
178 proc enable_option {option} {
179         global enabled_options
180         set enabled_options($option) 1
183 proc disable_option {option} {
184         global enabled_options
185         set enabled_options($option) 0
188 ######################################################################
189 ##
190 ## config
192 proc is_many_config {name} {
193         switch -glob -- $name {
194         remote.*.fetch -
195         remote.*.push
196                 {return 1}
197         *
198                 {return 0}
199         }
202 proc is_config_true {name} {
203         global repo_config
204         if {[catch {set v $repo_config($name)}]} {
205                 return 0
206         } elseif {$v eq {true} || $v eq {1} || $v eq {yes}} {
207                 return 1
208         } else {
209                 return 0
210         }
213 proc get_config {name} {
214         global repo_config
215         if {[catch {set v $repo_config($name)}]} {
216                 return {}
217         } else {
218                 return $v
219         }
222 proc load_config {include_global} {
223         global repo_config global_config default_config
225         array unset global_config
226         if {$include_global} {
227                 catch {
228                         set fd_rc [git_read config --global --list]
229                         while {[gets $fd_rc line] >= 0} {
230                                 if {[regexp {^([^=]+)=(.*)$} $line line name value]} {
231                                         if {[is_many_config $name]} {
232                                                 lappend global_config($name) $value
233                                         } else {
234                                                 set global_config($name) $value
235                                         }
236                                 }
237                         }
238                         close $fd_rc
239                 }
240         }
242         array unset repo_config
243         catch {
244                 set fd_rc [git_read config --list]
245                 while {[gets $fd_rc line] >= 0} {
246                         if {[regexp {^([^=]+)=(.*)$} $line line name value]} {
247                                 if {[is_many_config $name]} {
248                                         lappend repo_config($name) $value
249                                 } else {
250                                         set repo_config($name) $value
251                                 }
252                         }
253                 }
254                 close $fd_rc
255         }
257         foreach name [array names default_config] {
258                 if {[catch {set v $global_config($name)}]} {
259                         set global_config($name) $default_config($name)
260                 }
261                 if {[catch {set v $repo_config($name)}]} {
262                         set repo_config($name) $default_config($name)
263                 }
264         }
267 ######################################################################
268 ##
269 ## handy utils
271 proc _git_cmd {name} {
272         global _git_cmd_path
274         if {[catch {set v $_git_cmd_path($name)}]} {
275                 switch -- $name {
276                   version   -
277                 --version   -
278                 --exec-path { return [list $::_git $name] }
279                 }
281                 set p [gitexec git-$name$::_search_exe]
282                 if {[file exists $p]} {
283                         set v [list $p]
284                 } elseif {[is_Windows] && [file exists [gitexec git-$name]]} {
285                         # Try to determine what sort of magic will make
286                         # git-$name go and do its thing, because native
287                         # Tcl on Windows doesn't know it.
288                         #
289                         set p [gitexec git-$name]
290                         set f [open $p r]
291                         set s [gets $f]
292                         close $f
294                         switch -glob -- [lindex $s 0] {
295                         #!*sh     { set i sh     }
296                         #!*perl   { set i perl   }
297                         #!*python { set i python }
298                         default   { error "git-$name is not supported: $s" }
299                         }
301                         upvar #0 _$i interp
302                         if {![info exists interp]} {
303                                 set interp [_which $i]
304                         }
305                         if {$interp eq {}} {
306                                 error "git-$name requires $i (not in PATH)"
307                         }
308                         set v [concat [list $interp] [lrange $s 1 end] [list $p]]
309                 } else {
310                         # Assume it is builtin to git somehow and we
311                         # aren't actually able to see a file for it.
312                         #
313                         set v [list $::_git $name]
314                 }
315                 set _git_cmd_path($name) $v
316         }
317         return $v
320 proc _which {what} {
321         global env _search_exe _search_path
323         if {$_search_path eq {}} {
324                 if {[is_Cygwin]} {
325                         set _search_path [split [exec cygpath \
326                                 --windows \
327                                 --path \
328                                 --absolute \
329                                 $env(PATH)] {;}]
330                         set _search_exe .exe
331                 } elseif {[is_Windows]} {
332                         set _search_path [split $env(PATH) {;}]
333                         set _search_exe .exe
334                 } else {
335                         set _search_path [split $env(PATH) :]
336                         set _search_exe {}
337                 }
338         }
340         foreach p $_search_path {
341                 set p [file join $p $what$_search_exe]
342                 if {[file exists $p]} {
343                         return [file normalize $p]
344                 }
345         }
346         return {}
349 proc _lappend_nice {cmd_var} {
350         global _nice
351         upvar $cmd_var cmd
353         if {![info exists _nice]} {
354                 set _nice [_which nice]
355         }
356         if {$_nice ne {}} {
357                 lappend cmd $_nice
358         }
361 proc git {args} {
362         set opt [list exec]
364         while {1} {
365                 switch -- [lindex $args 0] {
366                 --nice {
367                         _lappend_nice opt
368                 }
370                 default {
371                         break
372                 }
374                 }
376                 set args [lrange $args 1 end]
377         }
379         set cmdp [_git_cmd [lindex $args 0]]
380         set args [lrange $args 1 end]
382         return [eval $opt $cmdp $args]
385 proc _open_stdout_stderr {cmd} {
386         if {[catch {
387                         set fd [open $cmd r]
388                 } err]} {
389                 if {   [lindex $cmd end] eq {2>@1}
390                     && $err eq {can not find channel named "1"}
391                         } {
392                         # Older versions of Tcl 8.4 don't have this 2>@1 IO
393                         # redirect operator.  Fallback to |& cat for those.
394                         # The command was not actually started, so its safe
395                         # to try to start it a second time.
396                         #
397                         set fd [open [concat \
398                                 [lrange $cmd 0 end-1] \
399                                 [list |& cat] \
400                                 ] r]
401                 } else {
402                         error $err
403                 }
404         }
405         fconfigure $fd -eofchar {}
406         return $fd
409 proc git_read {args} {
410         set opt [list |]
412         while {1} {
413                 switch -- [lindex $args 0] {
414                 --nice {
415                         _lappend_nice opt
416                 }
418                 --stderr {
419                         lappend args 2>@1
420                 }
422                 default {
423                         break
424                 }
426                 }
428                 set args [lrange $args 1 end]
429         }
431         set cmdp [_git_cmd [lindex $args 0]]
432         set args [lrange $args 1 end]
434         return [_open_stdout_stderr [concat $opt $cmdp $args]]
437 proc git_write {args} {
438         set opt [list |]
440         while {1} {
441                 switch -- [lindex $args 0] {
442                 --nice {
443                         _lappend_nice opt
444                 }
446                 default {
447                         break
448                 }
450                 }
452                 set args [lrange $args 1 end]
453         }
455         set cmdp [_git_cmd [lindex $args 0]]
456         set args [lrange $args 1 end]
458         return [open [concat $opt $cmdp $args] w]
461 proc sq {value} {
462         regsub -all ' $value "'\\''" value
463         return "'$value'"
466 proc load_current_branch {} {
467         global current_branch is_detached
469         set fd [open [gitdir HEAD] r]
470         if {[gets $fd ref] < 1} {
471                 set ref {}
472         }
473         close $fd
475         set pfx {ref: refs/heads/}
476         set len [string length $pfx]
477         if {[string equal -length $len $pfx $ref]} {
478                 # We're on a branch.  It might not exist.  But
479                 # HEAD looks good enough to be a branch.
480                 #
481                 set current_branch [string range $ref $len end]
482                 set is_detached 0
483         } else {
484                 # Assume this is a detached head.
485                 #
486                 set current_branch HEAD
487                 set is_detached 1
488         }
491 auto_load tk_optionMenu
492 rename tk_optionMenu real__tkOptionMenu
493 proc tk_optionMenu {w varName args} {
494         set m [eval real__tkOptionMenu $w $varName $args]
495         $m configure -font font_ui
496         $w configure -font font_ui
497         return $m
500 ######################################################################
501 ##
502 ## find git
504 set _git  [_which git]
505 if {$_git eq {}} {
506         catch {wm withdraw .}
507         error_popup [mc "Cannot find git in PATH."]
508         exit 1
511 ######################################################################
512 ##
513 ## version check
515 if {[catch {set _git_version [git --version]} err]} {
516         catch {wm withdraw .}
517         tk_messageBox \
518                 -icon error \
519                 -type ok \
520                 -title "git-gui: fatal error" \
521                 -message "Cannot determine Git version:
523 $err
525 [appname] requires Git 1.5.0 or later."
526         exit 1
528 if {![regsub {^git version } $_git_version {} _git_version]} {
529         catch {wm withdraw .}
530         tk_messageBox \
531                 -icon error \
532                 -type ok \
533                 -title "git-gui: fatal error" \
534                 -message [append [mc "Cannot parse Git version string:"] "\n\n$_git_version"]
535         exit 1
538 set _real_git_version $_git_version
539 regsub -- {-dirty$} $_git_version {} _git_version
540 regsub {\.[0-9]+\.g[0-9a-f]+$} $_git_version {} _git_version
541 regsub {\.rc[0-9]+$} $_git_version {} _git_version
542 regsub {\.GIT$} $_git_version {} _git_version
544 if {![regexp {^[1-9]+(\.[0-9]+)+$} $_git_version]} {
545         catch {wm withdraw .}
546         if {[tk_messageBox \
547                 -icon warning \
548                 -type yesno \
549                 -default no \
550                 -title "[appname]: warning" \
551                  -message [mc "Git version cannot be determined.
553 %s claims it is version '%s'.
555 %s requires at least Git 1.5.0 or later.
557 Assume '%s' is version 1.5.0?
558 " $_git $_real_git_version [appname] $_real_git_version]] eq {yes}} {
559                 set _git_version 1.5.0
560         } else {
561                 exit 1
562         }
564 unset _real_git_version
566 proc git-version {args} {
567         global _git_version
569         switch [llength $args] {
570         0 {
571                 return $_git_version
572         }
574         2 {
575                 set op [lindex $args 0]
576                 set vr [lindex $args 1]
577                 set cm [package vcompare $_git_version $vr]
578                 return [expr $cm $op 0]
579         }
581         4 {
582                 set type [lindex $args 0]
583                 set name [lindex $args 1]
584                 set parm [lindex $args 2]
585                 set body [lindex $args 3]
587                 if {($type ne {proc} && $type ne {method})} {
588                         error "Invalid arguments to git-version"
589                 }
590                 if {[llength $body] < 2 || [lindex $body end-1] ne {default}} {
591                         error "Last arm of $type $name must be default"
592                 }
594                 foreach {op vr cb} [lrange $body 0 end-2] {
595                         if {[git-version $op $vr]} {
596                                 return [uplevel [list $type $name $parm $cb]]
597                         }
598                 }
600                 return [uplevel [list $type $name $parm [lindex $body end]]]
601         }
603         default {
604                 error "git-version >= x"
605         }
607         }
610 if {[git-version < 1.5]} {
611         catch {wm withdraw .}
612         tk_messageBox \
613                 -icon error \
614                 -type ok \
615                 -title "git-gui: fatal error" \
616                 -message "[appname] requires Git 1.5.0 or later.
618 You are using [git-version]:
620 [git --version]"
621         exit 1
624 ######################################################################
625 ##
626 ## configure our library
628 set idx [file join $oguilib tclIndex]
629 if {[catch {set fd [open $idx r]} err]} {
630         catch {wm withdraw .}
631         tk_messageBox \
632                 -icon error \
633                 -type ok \
634                 -title "git-gui: fatal error" \
635                 -message $err
636         exit 1
638 if {[gets $fd] eq {# Autogenerated by git-gui Makefile}} {
639         set idx [list]
640         while {[gets $fd n] >= 0} {
641                 if {$n ne {} && ![string match #* $n]} {
642                         lappend idx $n
643                 }
644         }
645 } else {
646         set idx {}
648 close $fd
650 if {$idx ne {}} {
651         set loaded [list]
652         foreach p $idx {
653                 if {[lsearch -exact $loaded $p] >= 0} continue
654                 source [file join $oguilib $p]
655                 lappend loaded $p
656         }
657         unset loaded p
658 } else {
659         set auto_path [concat [list $oguilib] $auto_path]
661 unset -nocomplain idx fd
663 ######################################################################
664 ##
665 ## feature option selection
667 if {[regexp {^git-(.+)$} [appname] _junk subcommand]} {
668         unset _junk
669 } else {
670         set subcommand gui
672 if {$subcommand eq {gui.sh}} {
673         set subcommand gui
675 if {$subcommand eq {gui} && [llength $argv] > 0} {
676         set subcommand [lindex $argv 0]
677         set argv [lrange $argv 1 end]
680 enable_option multicommit
681 enable_option branch
682 enable_option transport
683 disable_option bare
685 switch -- $subcommand {
686 browser -
687 blame {
688         enable_option bare
690         disable_option multicommit
691         disable_option branch
692         disable_option transport
694 citool {
695         enable_option singlecommit
697         disable_option multicommit
698         disable_option branch
699         disable_option transport
703 ######################################################################
704 ##
705 ## repository setup
707 if {[catch {
708                 set _gitdir $env(GIT_DIR)
709                 set _prefix {}
710                 }]
711         && [catch {
712                 set _gitdir [git rev-parse --git-dir]
713                 set _prefix [git rev-parse --show-prefix]
714         } err]} {
715         catch {wm withdraw .}
716         error_popup [append [mc "Cannot find the git directory:"] "\n\n$err"]
717         exit 1
719 if {![file isdirectory $_gitdir] && [is_Cygwin]} {
720         catch {set _gitdir [exec cygpath --unix $_gitdir]}
722 if {![file isdirectory $_gitdir]} {
723         catch {wm withdraw .}
724         error_popup [append [mc "Git directory not found:"] "\n\n$_gitdir"]
725         exit 1
727 if {$_prefix ne {}} {
728         regsub -all {[^/]+/} $_prefix ../ cdup
729         if {[catch {cd $cdup} err]} {
730                 catch {wm withdraw .}
731                 error_popup "Cannot move to top of working directory:\n\n$err"
732                 exit 1
733         }
734         unset cdup
735 } elseif {![is_enabled bare]} {
736         if {[lindex [file split $_gitdir] end] ne {.git}} {
737                 catch {wm withdraw .}
738                 error_popup [append [mc "Cannot use funny .git directory:"] "\n\n$_gitdir"]
739                 exit 1
740         }
741         if {[catch {cd [file dirname $_gitdir]} err]} {
742                 catch {wm withdraw .}
743                 error_popup [append [mc "No working directory"] " [file dirname $_gitdir]:\n\n$err"]
744                 exit 1
745         }
747 set _reponame [file split [file normalize $_gitdir]]
748 if {[lindex $_reponame end] eq {.git}} {
749         set _reponame [lindex $_reponame end-1]
750 } else {
751         set _reponame [lindex $_reponame end]
754 ######################################################################
755 ##
756 ## global init
758 set current_diff_path {}
759 set current_diff_side {}
760 set diff_actions [list]
762 set HEAD {}
763 set PARENT {}
764 set MERGE_HEAD [list]
765 set commit_type {}
766 set empty_tree {}
767 set current_branch {}
768 set is_detached 0
769 set current_diff_path {}
770 set is_3way_diff 0
771 set selected_commit_type new
773 ######################################################################
774 ##
775 ## task management
777 set rescan_active 0
778 set diff_active 0
779 set last_clicked {}
781 set disable_on_lock [list]
782 set index_lock_type none
784 proc lock_index {type} {
785         global index_lock_type disable_on_lock
787         if {$index_lock_type eq {none}} {
788                 set index_lock_type $type
789                 foreach w $disable_on_lock {
790                         uplevel #0 $w disabled
791                 }
792                 return 1
793         } elseif {$index_lock_type eq "begin-$type"} {
794                 set index_lock_type $type
795                 return 1
796         }
797         return 0
800 proc unlock_index {} {
801         global index_lock_type disable_on_lock
803         set index_lock_type none
804         foreach w $disable_on_lock {
805                 uplevel #0 $w normal
806         }
809 ######################################################################
810 ##
811 ## status
813 proc repository_state {ctvar hdvar mhvar} {
814         global current_branch
815         upvar $ctvar ct $hdvar hd $mhvar mh
817         set mh [list]
819         load_current_branch
820         if {[catch {set hd [git rev-parse --verify HEAD]}]} {
821                 set hd {}
822                 set ct initial
823                 return
824         }
826         set merge_head [gitdir MERGE_HEAD]
827         if {[file exists $merge_head]} {
828                 set ct merge
829                 set fd_mh [open $merge_head r]
830                 while {[gets $fd_mh line] >= 0} {
831                         lappend mh $line
832                 }
833                 close $fd_mh
834                 return
835         }
837         set ct normal
840 proc PARENT {} {
841         global PARENT empty_tree
843         set p [lindex $PARENT 0]
844         if {$p ne {}} {
845                 return $p
846         }
847         if {$empty_tree eq {}} {
848                 set empty_tree [git mktree << {}]
849         }
850         return $empty_tree
853 proc rescan {after {honor_trustmtime 1}} {
854         global HEAD PARENT MERGE_HEAD commit_type
855         global ui_index ui_workdir ui_comm
856         global rescan_active file_states
857         global repo_config
859         if {$rescan_active > 0 || ![lock_index read]} return
861         repository_state newType newHEAD newMERGE_HEAD
862         if {[string match amend* $commit_type]
863                 && $newType eq {normal}
864                 && $newHEAD eq $HEAD} {
865         } else {
866                 set HEAD $newHEAD
867                 set PARENT $newHEAD
868                 set MERGE_HEAD $newMERGE_HEAD
869                 set commit_type $newType
870         }
872         array unset file_states
874         if {!$::GITGUI_BCK_exists &&
875                 (![$ui_comm edit modified]
876                 || [string trim [$ui_comm get 0.0 end]] eq {})} {
877                 if {[string match amend* $commit_type]} {
878                 } elseif {[load_message GITGUI_MSG]} {
879                 } elseif {[load_message MERGE_MSG]} {
880                 } elseif {[load_message SQUASH_MSG]} {
881                 }
882                 $ui_comm edit reset
883                 $ui_comm edit modified false
884         }
886         if {$honor_trustmtime && $repo_config(gui.trustmtime) eq {true}} {
887                 rescan_stage2 {} $after
888         } else {
889                 set rescan_active 1
890                 ui_status [mc "Refreshing file status..."]
891                 set fd_rf [git_read update-index \
892                         -q \
893                         --unmerged \
894                         --ignore-missing \
895                         --refresh \
896                         ]
897                 fconfigure $fd_rf -blocking 0 -translation binary
898                 fileevent $fd_rf readable \
899                         [list rescan_stage2 $fd_rf $after]
900         }
903 proc rescan_stage2 {fd after} {
904         global rescan_active buf_rdi buf_rdf buf_rlo
906         if {$fd ne {}} {
907                 read $fd
908                 if {![eof $fd]} return
909                 close $fd
910         }
912         set ls_others [list --exclude-per-directory=.gitignore]
913         set info_exclude [gitdir info exclude]
914         if {[file readable $info_exclude]} {
915                 lappend ls_others "--exclude-from=$info_exclude"
916         }
917         set user_exclude [get_config core.excludesfile]
918         if {$user_exclude ne {} && [file readable $user_exclude]} {
919                 lappend ls_others "--exclude-from=$user_exclude"
920         }
922         set buf_rdi {}
923         set buf_rdf {}
924         set buf_rlo {}
926         set rescan_active 3
927         ui_status [mc "Scanning for modified files ..."]
928         set fd_di [git_read diff-index --cached -z [PARENT]]
929         set fd_df [git_read diff-files -z]
930         set fd_lo [eval git_read ls-files --others -z $ls_others]
932         fconfigure $fd_di -blocking 0 -translation binary -encoding binary
933         fconfigure $fd_df -blocking 0 -translation binary -encoding binary
934         fconfigure $fd_lo -blocking 0 -translation binary -encoding binary
935         fileevent $fd_di readable [list read_diff_index $fd_di $after]
936         fileevent $fd_df readable [list read_diff_files $fd_df $after]
937         fileevent $fd_lo readable [list read_ls_others $fd_lo $after]
940 proc load_message {file} {
941         global ui_comm
943         set f [gitdir $file]
944         if {[file isfile $f]} {
945                 if {[catch {set fd [open $f r]}]} {
946                         return 0
947                 }
948                 fconfigure $fd -eofchar {}
949                 set content [string trim [read $fd]]
950                 close $fd
951                 regsub -all -line {[ \r\t]+$} $content {} content
952                 $ui_comm delete 0.0 end
953                 $ui_comm insert end $content
954                 return 1
955         }
956         return 0
959 proc read_diff_index {fd after} {
960         global buf_rdi
962         append buf_rdi [read $fd]
963         set c 0
964         set n [string length $buf_rdi]
965         while {$c < $n} {
966                 set z1 [string first "\0" $buf_rdi $c]
967                 if {$z1 == -1} break
968                 incr z1
969                 set z2 [string first "\0" $buf_rdi $z1]
970                 if {$z2 == -1} break
972                 incr c
973                 set i [split [string range $buf_rdi $c [expr {$z1 - 2}]] { }]
974                 set p [string range $buf_rdi $z1 [expr {$z2 - 1}]]
975                 merge_state \
976                         [encoding convertfrom $p] \
977                         [lindex $i 4]? \
978                         [list [lindex $i 0] [lindex $i 2]] \
979                         [list]
980                 set c $z2
981                 incr c
982         }
983         if {$c < $n} {
984                 set buf_rdi [string range $buf_rdi $c end]
985         } else {
986                 set buf_rdi {}
987         }
989         rescan_done $fd buf_rdi $after
992 proc read_diff_files {fd after} {
993         global buf_rdf
995         append buf_rdf [read $fd]
996         set c 0
997         set n [string length $buf_rdf]
998         while {$c < $n} {
999                 set z1 [string first "\0" $buf_rdf $c]
1000                 if {$z1 == -1} break
1001                 incr z1
1002                 set z2 [string first "\0" $buf_rdf $z1]
1003                 if {$z2 == -1} break
1005                 incr c
1006                 set i [split [string range $buf_rdf $c [expr {$z1 - 2}]] { }]
1007                 set p [string range $buf_rdf $z1 [expr {$z2 - 1}]]
1008                 merge_state \
1009                         [encoding convertfrom $p] \
1010                         ?[lindex $i 4] \
1011                         [list] \
1012                         [list [lindex $i 0] [lindex $i 2]]
1013                 set c $z2
1014                 incr c
1015         }
1016         if {$c < $n} {
1017                 set buf_rdf [string range $buf_rdf $c end]
1018         } else {
1019                 set buf_rdf {}
1020         }
1022         rescan_done $fd buf_rdf $after
1025 proc read_ls_others {fd after} {
1026         global buf_rlo
1028         append buf_rlo [read $fd]
1029         set pck [split $buf_rlo "\0"]
1030         set buf_rlo [lindex $pck end]
1031         foreach p [lrange $pck 0 end-1] {
1032                 set p [encoding convertfrom $p]
1033                 if {[string index $p end] eq {/}} {
1034                         set p [string range $p 0 end-1]
1035                 }
1036                 merge_state $p ?O
1037         }
1038         rescan_done $fd buf_rlo $after
1041 proc rescan_done {fd buf after} {
1042         global rescan_active current_diff_path
1043         global file_states repo_config
1044         upvar $buf to_clear
1046         if {![eof $fd]} return
1047         set to_clear {}
1048         close $fd
1049         if {[incr rescan_active -1] > 0} return
1051         prune_selection
1052         unlock_index
1053         display_all_files
1054         if {$current_diff_path ne {}} reshow_diff
1055         uplevel #0 $after
1058 proc prune_selection {} {
1059         global file_states selected_paths
1061         foreach path [array names selected_paths] {
1062                 if {[catch {set still_here $file_states($path)}]} {
1063                         unset selected_paths($path)
1064                 }
1065         }
1068 ######################################################################
1069 ##
1070 ## ui helpers
1072 proc mapicon {w state path} {
1073         global all_icons
1075         if {[catch {set r $all_icons($state$w)}]} {
1076                 puts "error: no icon for $w state={$state} $path"
1077                 return file_plain
1078         }
1079         return $r
1082 proc mapdesc {state path} {
1083         global all_descs
1085         if {[catch {set r $all_descs($state)}]} {
1086                 puts "error: no desc for state={$state} $path"
1087                 return $state
1088         }
1089         return $r
1092 proc ui_status {msg} {
1093         $::main_status show $msg
1096 proc ui_ready {{test {}}} {
1097         $::main_status show [mc "Ready."] $test
1100 proc escape_path {path} {
1101         regsub -all {\\} $path "\\\\" path
1102         regsub -all "\n" $path "\\n" path
1103         return $path
1106 proc short_path {path} {
1107         return [escape_path [lindex [file split $path] end]]
1110 set next_icon_id 0
1111 set null_sha1 [string repeat 0 40]
1113 proc merge_state {path new_state {head_info {}} {index_info {}}} {
1114         global file_states next_icon_id null_sha1
1116         set s0 [string index $new_state 0]
1117         set s1 [string index $new_state 1]
1119         if {[catch {set info $file_states($path)}]} {
1120                 set state __
1121                 set icon n[incr next_icon_id]
1122         } else {
1123                 set state [lindex $info 0]
1124                 set icon [lindex $info 1]
1125                 if {$head_info eq {}}  {set head_info  [lindex $info 2]}
1126                 if {$index_info eq {}} {set index_info [lindex $info 3]}
1127         }
1129         if     {$s0 eq {?}} {set s0 [string index $state 0]} \
1130         elseif {$s0 eq {_}} {set s0 _}
1132         if     {$s1 eq {?}} {set s1 [string index $state 1]} \
1133         elseif {$s1 eq {_}} {set s1 _}
1135         if {$s0 eq {A} && $s1 eq {_} && $head_info eq {}} {
1136                 set head_info [list 0 $null_sha1]
1137         } elseif {$s0 ne {_} && [string index $state 0] eq {_}
1138                 && $head_info eq {}} {
1139                 set head_info $index_info
1140         }
1142         set file_states($path) [list $s0$s1 $icon \
1143                 $head_info $index_info \
1144                 ]
1145         return $state
1148 proc display_file_helper {w path icon_name old_m new_m} {
1149         global file_lists
1151         if {$new_m eq {_}} {
1152                 set lno [lsearch -sorted -exact $file_lists($w) $path]
1153                 if {$lno >= 0} {
1154                         set file_lists($w) [lreplace $file_lists($w) $lno $lno]
1155                         incr lno
1156                         $w conf -state normal
1157                         $w delete $lno.0 [expr {$lno + 1}].0
1158                         $w conf -state disabled
1159                 }
1160         } elseif {$old_m eq {_} && $new_m ne {_}} {
1161                 lappend file_lists($w) $path
1162                 set file_lists($w) [lsort -unique $file_lists($w)]
1163                 set lno [lsearch -sorted -exact $file_lists($w) $path]
1164                 incr lno
1165                 $w conf -state normal
1166                 $w image create $lno.0 \
1167                         -align center -padx 5 -pady 1 \
1168                         -name $icon_name \
1169                         -image [mapicon $w $new_m $path]
1170                 $w insert $lno.1 "[escape_path $path]\n"
1171                 $w conf -state disabled
1172         } elseif {$old_m ne $new_m} {
1173                 $w conf -state normal
1174                 $w image conf $icon_name -image [mapicon $w $new_m $path]
1175                 $w conf -state disabled
1176         }
1179 proc display_file {path state} {
1180         global file_states selected_paths
1181         global ui_index ui_workdir
1183         set old_m [merge_state $path $state]
1184         set s $file_states($path)
1185         set new_m [lindex $s 0]
1186         set icon_name [lindex $s 1]
1188         set o [string index $old_m 0]
1189         set n [string index $new_m 0]
1190         if {$o eq {U}} {
1191                 set o _
1192         }
1193         if {$n eq {U}} {
1194                 set n _
1195         }
1196         display_file_helper     $ui_index $path $icon_name $o $n
1198         if {[string index $old_m 0] eq {U}} {
1199                 set o U
1200         } else {
1201                 set o [string index $old_m 1]
1202         }
1203         if {[string index $new_m 0] eq {U}} {
1204                 set n U
1205         } else {
1206                 set n [string index $new_m 1]
1207         }
1208         display_file_helper     $ui_workdir $path $icon_name $o $n
1210         if {$new_m eq {__}} {
1211                 unset file_states($path)
1212                 catch {unset selected_paths($path)}
1213         }
1216 proc display_all_files_helper {w path icon_name m} {
1217         global file_lists
1219         lappend file_lists($w) $path
1220         set lno [expr {[lindex [split [$w index end] .] 0] - 1}]
1221         $w image create end \
1222                 -align center -padx 5 -pady 1 \
1223                 -name $icon_name \
1224                 -image [mapicon $w $m $path]
1225         $w insert end "[escape_path $path]\n"
1228 proc display_all_files {} {
1229         global ui_index ui_workdir
1230         global file_states file_lists
1231         global last_clicked
1233         $ui_index conf -state normal
1234         $ui_workdir conf -state normal
1236         $ui_index delete 0.0 end
1237         $ui_workdir delete 0.0 end
1238         set last_clicked {}
1240         set file_lists($ui_index) [list]
1241         set file_lists($ui_workdir) [list]
1243         foreach path [lsort [array names file_states]] {
1244                 set s $file_states($path)
1245                 set m [lindex $s 0]
1246                 set icon_name [lindex $s 1]
1248                 set s [string index $m 0]
1249                 if {$s ne {U} && $s ne {_}} {
1250                         display_all_files_helper $ui_index $path \
1251                                 $icon_name $s
1252                 }
1254                 if {[string index $m 0] eq {U}} {
1255                         set s U
1256                 } else {
1257                         set s [string index $m 1]
1258                 }
1259                 if {$s ne {_}} {
1260                         display_all_files_helper $ui_workdir $path \
1261                                 $icon_name $s
1262                 }
1263         }
1265         $ui_index conf -state disabled
1266         $ui_workdir conf -state disabled
1269 ######################################################################
1270 ##
1271 ## icons
1273 set filemask {
1274 #define mask_width 14
1275 #define mask_height 15
1276 static unsigned char mask_bits[] = {
1277    0xfe, 0x1f, 0xfe, 0x1f, 0xfe, 0x1f, 0xfe, 0x1f, 0xfe, 0x1f, 0xfe, 0x1f,
1278    0xfe, 0x1f, 0xfe, 0x1f, 0xfe, 0x1f, 0xfe, 0x1f, 0xfe, 0x1f, 0xfe, 0x1f,
1279    0xfe, 0x1f, 0xfe, 0x1f, 0xfe, 0x1f};
1282 image create bitmap file_plain -background white -foreground black -data {
1283 #define plain_width 14
1284 #define plain_height 15
1285 static unsigned char plain_bits[] = {
1286    0xfe, 0x01, 0x02, 0x03, 0x02, 0x05, 0x02, 0x09, 0x02, 0x1f, 0x02, 0x10,
1287    0x02, 0x10, 0x02, 0x10, 0x02, 0x10, 0x02, 0x10, 0x02, 0x10, 0x02, 0x10,
1288    0x02, 0x10, 0x02, 0x10, 0xfe, 0x1f};
1289 } -maskdata $filemask
1291 image create bitmap file_mod -background white -foreground blue -data {
1292 #define mod_width 14
1293 #define mod_height 15
1294 static unsigned char mod_bits[] = {
1295    0xfe, 0x01, 0x02, 0x03, 0x7a, 0x05, 0x02, 0x09, 0x7a, 0x1f, 0x02, 0x10,
1296    0xfa, 0x17, 0x02, 0x10, 0xfa, 0x17, 0x02, 0x10, 0xfa, 0x17, 0x02, 0x10,
1297    0xfa, 0x17, 0x02, 0x10, 0xfe, 0x1f};
1298 } -maskdata $filemask
1300 image create bitmap file_fulltick -background white -foreground "#007000" -data {
1301 #define file_fulltick_width 14
1302 #define file_fulltick_height 15
1303 static unsigned char file_fulltick_bits[] = {
1304    0xfe, 0x01, 0x02, 0x1a, 0x02, 0x0c, 0x02, 0x0c, 0x02, 0x16, 0x02, 0x16,
1305    0x02, 0x13, 0x00, 0x13, 0x86, 0x11, 0x8c, 0x11, 0xd8, 0x10, 0xf2, 0x10,
1306    0x62, 0x10, 0x02, 0x10, 0xfe, 0x1f};
1307 } -maskdata $filemask
1309 image create bitmap file_parttick -background white -foreground "#005050" -data {
1310 #define parttick_width 14
1311 #define parttick_height 15
1312 static unsigned char parttick_bits[] = {
1313    0xfe, 0x01, 0x02, 0x03, 0x7a, 0x05, 0x02, 0x09, 0x7a, 0x1f, 0x02, 0x10,
1314    0x7a, 0x14, 0x02, 0x16, 0x02, 0x13, 0x8a, 0x11, 0xda, 0x10, 0x72, 0x10,
1315    0x22, 0x10, 0x02, 0x10, 0xfe, 0x1f};
1316 } -maskdata $filemask
1318 image create bitmap file_question -background white -foreground black -data {
1319 #define file_question_width 14
1320 #define file_question_height 15
1321 static unsigned char file_question_bits[] = {
1322    0xfe, 0x01, 0x02, 0x02, 0xe2, 0x04, 0xf2, 0x09, 0x1a, 0x1b, 0x0a, 0x13,
1323    0x82, 0x11, 0xc2, 0x10, 0x62, 0x10, 0x62, 0x10, 0x02, 0x10, 0x62, 0x10,
1324    0x62, 0x10, 0x02, 0x10, 0xfe, 0x1f};
1325 } -maskdata $filemask
1327 image create bitmap file_removed -background white -foreground red -data {
1328 #define file_removed_width 14
1329 #define file_removed_height 15
1330 static unsigned char file_removed_bits[] = {
1331    0xfe, 0x01, 0x02, 0x03, 0x02, 0x05, 0x02, 0x09, 0x02, 0x1f, 0x02, 0x10,
1332    0x1a, 0x16, 0x32, 0x13, 0xe2, 0x11, 0xc2, 0x10, 0xe2, 0x11, 0x32, 0x13,
1333    0x1a, 0x16, 0x02, 0x10, 0xfe, 0x1f};
1334 } -maskdata $filemask
1336 image create bitmap file_merge -background white -foreground blue -data {
1337 #define file_merge_width 14
1338 #define file_merge_height 15
1339 static unsigned char file_merge_bits[] = {
1340    0xfe, 0x01, 0x02, 0x03, 0x62, 0x05, 0x62, 0x09, 0x62, 0x1f, 0x62, 0x10,
1341    0xfa, 0x11, 0xf2, 0x10, 0x62, 0x10, 0x02, 0x10, 0xfa, 0x17, 0x02, 0x10,
1342    0xfa, 0x17, 0x02, 0x10, 0xfe, 0x1f};
1343 } -maskdata $filemask
1345 set ui_index .vpane.files.index.list
1346 set ui_workdir .vpane.files.workdir.list
1348 set all_icons(_$ui_index)   file_plain
1349 set all_icons(A$ui_index)   file_fulltick
1350 set all_icons(M$ui_index)   file_fulltick
1351 set all_icons(D$ui_index)   file_removed
1352 set all_icons(U$ui_index)   file_merge
1354 set all_icons(_$ui_workdir) file_plain
1355 set all_icons(M$ui_workdir) file_mod
1356 set all_icons(D$ui_workdir) file_question
1357 set all_icons(U$ui_workdir) file_merge
1358 set all_icons(O$ui_workdir) file_plain
1360 set max_status_desc 0
1361 foreach i {
1362                 {__ {mc "Unmodified"}}
1364                 {_M {mc "Modified, not staged"}}
1365                 {M_ {mc "Staged for commit"}}
1366                 {MM {mc "Portions staged for commit"}}
1367                 {MD {mc "Staged for commit, missing"}}
1369                 {_O {mc "Untracked, not staged"}}
1370                 {A_ {mc "Staged for commit"}}
1371                 {AM {mc "Portions staged for commit"}}
1372                 {AD {mc "Staged for commit, missing"}}
1374                 {_D {mc "Missing"}}
1375                 {D_ {mc "Staged for removal"}}
1376                 {DO {mc "Staged for removal, still present"}}
1378                 {U_ {mc "Requires merge resolution"}}
1379                 {UU {mc "Requires merge resolution"}}
1380                 {UM {mc "Requires merge resolution"}}
1381                 {UD {mc "Requires merge resolution"}}
1382         } {
1383         set text [eval [lindex $i 1]]
1384         if {$max_status_desc < [string length $text]} {
1385                 set max_status_desc [string length $text]
1386         }
1387         set all_descs([lindex $i 0]) $text
1389 unset i
1391 ######################################################################
1392 ##
1393 ## util
1395 proc bind_button3 {w cmd} {
1396         bind $w <Any-Button-3> $cmd
1397         if {[is_MacOSX]} {
1398                 # Mac OS X sends Button-2 on right click through three-button mouse,
1399                 # or through trackpad right-clicking (two-finger touch + click).
1400                 bind $w <Any-Button-2> $cmd
1401                 bind $w <Control-Button-1> $cmd
1402         }
1405 proc scrollbar2many {list mode args} {
1406         foreach w $list {eval $w $mode $args}
1409 proc many2scrollbar {list mode sb top bottom} {
1410         $sb set $top $bottom
1411         foreach w $list {$w $mode moveto $top}
1414 proc incr_font_size {font {amt 1}} {
1415         set sz [font configure $font -size]
1416         incr sz $amt
1417         font configure $font -size $sz
1418         font configure ${font}bold -size $sz
1419         font configure ${font}italic -size $sz
1422 ######################################################################
1423 ##
1424 ## ui commands
1426 set starting_gitk_msg [mc "Starting gitk... please wait..."]
1428 proc do_gitk {revs} {
1429         # -- Always start gitk through whatever we were loaded with.  This
1430         #    lets us bypass using shell process on Windows systems.
1431         #
1432         set exe [file join [file dirname $::_git] gitk]
1433         set cmd [list [info nameofexecutable] $exe]
1434         if {! [file exists $exe]} {
1435                 error_popup [mc "Unable to start gitk:\n\n%s does not exist" $exe]
1436         } else {
1437                 eval exec $cmd $revs &
1438                 ui_status $::starting_gitk_msg
1439                 after 10000 {
1440                         ui_ready $starting_gitk_msg
1441                 }
1442         }
1445 set is_quitting 0
1447 proc do_quit {} {
1448         global ui_comm is_quitting repo_config commit_type
1449         global GITGUI_BCK_exists GITGUI_BCK_i
1451         if {$is_quitting} return
1452         set is_quitting 1
1454         if {[winfo exists $ui_comm]} {
1455                 # -- Stash our current commit buffer.
1456                 #
1457                 set save [gitdir GITGUI_MSG]
1458                 if {$GITGUI_BCK_exists && ![$ui_comm edit modified]} {
1459                         file rename -force [gitdir GITGUI_BCK] $save
1460                         set GITGUI_BCK_exists 0
1461                 } else {
1462                         set msg [string trim [$ui_comm get 0.0 end]]
1463                         regsub -all -line {[ \r\t]+$} $msg {} msg
1464                         if {(![string match amend* $commit_type]
1465                                 || [$ui_comm edit modified])
1466                                 && $msg ne {}} {
1467                                 catch {
1468                                         set fd [open $save w]
1469                                         puts -nonewline $fd $msg
1470                                         close $fd
1471                                 }
1472                         } else {
1473                                 catch {file delete $save}
1474                         }
1475                 }
1477                 # -- Remove our editor backup, its not needed.
1478                 #
1479                 after cancel $GITGUI_BCK_i
1480                 if {$GITGUI_BCK_exists} {
1481                         catch {file delete [gitdir GITGUI_BCK]}
1482                 }
1484                 # -- Stash our current window geometry into this repository.
1485                 #
1486                 set cfg_geometry [list]
1487                 lappend cfg_geometry [wm geometry .]
1488                 lappend cfg_geometry [lindex [.vpane sash coord 0] 1]
1489                 lappend cfg_geometry [lindex [.vpane.files sash coord 0] 0]
1490                 if {[catch {set rc_geometry $repo_config(gui.geometry)}]} {
1491                         set rc_geometry {}
1492                 }
1493                 if {$cfg_geometry ne $rc_geometry} {
1494                         catch {git config gui.geometry $cfg_geometry}
1495                 }
1496         }
1498         destroy .
1501 proc do_rescan {} {
1502         rescan ui_ready
1505 proc do_commit {} {
1506         commit_tree
1509 proc toggle_or_diff {w x y} {
1510         global file_states file_lists current_diff_path ui_index ui_workdir
1511         global last_clicked selected_paths
1513         set pos [split [$w index @$x,$y] .]
1514         set lno [lindex $pos 0]
1515         set col [lindex $pos 1]
1516         set path [lindex $file_lists($w) [expr {$lno - 1}]]
1517         if {$path eq {}} {
1518                 set last_clicked {}
1519                 return
1520         }
1522         set last_clicked [list $w $lno]
1523         array unset selected_paths
1524         $ui_index tag remove in_sel 0.0 end
1525         $ui_workdir tag remove in_sel 0.0 end
1527         if {$col == 0} {
1528                 if {$current_diff_path eq $path} {
1529                         set after {reshow_diff;}
1530                 } else {
1531                         set after {}
1532                 }
1533                 if {$w eq $ui_index} {
1534                         update_indexinfo \
1535                                 "Unstaging [short_path $path] from commit" \
1536                                 [list $path] \
1537                                 [concat $after [list ui_ready]]
1538                 } elseif {$w eq $ui_workdir} {
1539                         update_index \
1540                                 "Adding [short_path $path]" \
1541                                 [list $path] \
1542                                 [concat $after [list ui_ready]]
1543                 }
1544         } else {
1545                 show_diff $path $w $lno
1546         }
1549 proc add_one_to_selection {w x y} {
1550         global file_lists last_clicked selected_paths
1552         set lno [lindex [split [$w index @$x,$y] .] 0]
1553         set path [lindex $file_lists($w) [expr {$lno - 1}]]
1554         if {$path eq {}} {
1555                 set last_clicked {}
1556                 return
1557         }
1559         if {$last_clicked ne {}
1560                 && [lindex $last_clicked 0] ne $w} {
1561                 array unset selected_paths
1562                 [lindex $last_clicked 0] tag remove in_sel 0.0 end
1563         }
1565         set last_clicked [list $w $lno]
1566         if {[catch {set in_sel $selected_paths($path)}]} {
1567                 set in_sel 0
1568         }
1569         if {$in_sel} {
1570                 unset selected_paths($path)
1571                 $w tag remove in_sel $lno.0 [expr {$lno + 1}].0
1572         } else {
1573                 set selected_paths($path) 1
1574                 $w tag add in_sel $lno.0 [expr {$lno + 1}].0
1575         }
1578 proc add_range_to_selection {w x y} {
1579         global file_lists last_clicked selected_paths
1581         if {[lindex $last_clicked 0] ne $w} {
1582                 toggle_or_diff $w $x $y
1583                 return
1584         }
1586         set lno [lindex [split [$w index @$x,$y] .] 0]
1587         set lc [lindex $last_clicked 1]
1588         if {$lc < $lno} {
1589                 set begin $lc
1590                 set end $lno
1591         } else {
1592                 set begin $lno
1593                 set end $lc
1594         }
1596         foreach path [lrange $file_lists($w) \
1597                 [expr {$begin - 1}] \
1598                 [expr {$end - 1}]] {
1599                 set selected_paths($path) 1
1600         }
1601         $w tag add in_sel $begin.0 [expr {$end + 1}].0
1604 ######################################################################
1605 ##
1606 ## config defaults
1608 set cursor_ptr arrow
1609 font create font_diff -family Courier -size 10
1610 font create font_ui
1611 catch {
1612         label .dummy
1613         eval font configure font_ui [font actual [.dummy cget -font]]
1614         destroy .dummy
1617 font create font_uiitalic
1618 font create font_uibold
1619 font create font_diffbold
1620 font create font_diffitalic
1622 foreach class {Button Checkbutton Entry Label
1623                 Labelframe Listbox Menu Message
1624                 Radiobutton Spinbox Text} {
1625         option add *$class.font font_ui
1627 unset class
1629 if {[is_Windows] || [is_MacOSX]} {
1630         option add *Menu.tearOff 0
1633 if {[is_MacOSX]} {
1634         set M1B M1
1635         set M1T Cmd
1636 } else {
1637         set M1B Control
1638         set M1T Ctrl
1641 proc apply_config {} {
1642         global repo_config font_descs
1644         foreach option $font_descs {
1645                 set name [lindex $option 0]
1646                 set font [lindex $option 1]
1647                 if {[catch {
1648                         foreach {cn cv} $repo_config(gui.$name) {
1649                                 font configure $font $cn $cv
1650                         }
1651                         } err]} {
1652                         error_popup [append [mc "Invalid font specified in %s:" "gui.$name"] "\n\n$err"]
1653                 }
1654                 foreach {cn cv} [font configure $font] {
1655                         font configure ${font}bold $cn $cv
1656                         font configure ${font}italic $cn $cv
1657                 }
1658                 font configure ${font}bold -weight bold
1659                 font configure ${font}italic -slant italic
1660         }
1663 set default_config(merge.diffstat) true
1664 set default_config(merge.summary) false
1665 set default_config(merge.verbosity) 2
1666 set default_config(user.name) {}
1667 set default_config(user.email) {}
1669 set default_config(gui.matchtrackingbranch) false
1670 set default_config(gui.pruneduringfetch) false
1671 set default_config(gui.trustmtime) false
1672 set default_config(gui.diffcontext) 5
1673 set default_config(gui.newbranchtemplate) {}
1674 set default_config(gui.fontui) [font configure font_ui]
1675 set default_config(gui.fontdiff) [font configure font_diff]
1676 set font_descs {
1677         {fontui   font_ui   {mc "Main Font"}}
1678         {fontdiff font_diff {mc "Diff/Console Font"}}
1680 load_config 0
1681 apply_config
1683 ######################################################################
1684 ##
1685 ## ui construction
1687 set ui_comm {}
1689 # -- Menu Bar
1691 menu .mbar -tearoff 0
1692 .mbar add cascade -label [mc Repository] -menu .mbar.repository
1693 .mbar add cascade -label [mc Edit] -menu .mbar.edit
1694 if {[is_enabled branch]} {
1695         .mbar add cascade -label [mc Branch] -menu .mbar.branch
1697 if {[is_enabled multicommit] || [is_enabled singlecommit]} {
1698         .mbar add cascade -label [mc Commit] -menu .mbar.commit
1700 if {[is_enabled transport]} {
1701         .mbar add cascade -label [mc Merge] -menu .mbar.merge
1702         .mbar add cascade -label [mc Fetch] -menu .mbar.fetch
1703         .mbar add cascade -label [mc Push] -menu .mbar.push
1705 . configure -menu .mbar
1707 # -- Repository Menu
1709 menu .mbar.repository
1711 .mbar.repository add command \
1712         -label [mc "Browse Current Branch's Files"] \
1713         -command {browser::new $current_branch}
1714 set ui_browse_current [.mbar.repository index last]
1715 .mbar.repository add command \
1716         -label [mc "Browse Branch Files..."] \
1717         -command browser_open::dialog
1718 .mbar.repository add separator
1720 .mbar.repository add command \
1721         -label [mc "Visualize Current Branch's History"] \
1722         -command {do_gitk $current_branch}
1723 set ui_visualize_current [.mbar.repository index last]
1724 .mbar.repository add command \
1725         -label [mc "Visualize All Branch History"] \
1726         -command {do_gitk --all}
1727 .mbar.repository add separator
1729 proc current_branch_write {args} {
1730         global current_branch
1731         .mbar.repository entryconf $::ui_browse_current \
1732                 -label [mc "Browse %s's Files" $current_branch]
1733         .mbar.repository entryconf $::ui_visualize_current \
1734                 -label [mc "Visualize %s's History" $current_branch]
1736 trace add variable current_branch write current_branch_write
1738 if {[is_enabled multicommit]} {
1739         .mbar.repository add command -label [mc "Database Statistics"] \
1740                 -command do_stats
1742         .mbar.repository add command -label [mc "Compress Database"] \
1743                 -command do_gc
1745         .mbar.repository add command -label [mc "Verify Database"] \
1746                 -command do_fsck_objects
1748         .mbar.repository add separator
1750         if {[is_Cygwin]} {
1751                 .mbar.repository add command \
1752                         -label [mc "Create Desktop Icon"] \
1753                         -command do_cygwin_shortcut
1754         } elseif {[is_Windows]} {
1755                 .mbar.repository add command \
1756                         -label [mc "Create Desktop Icon"] \
1757                         -command do_windows_shortcut
1758         } elseif {[is_MacOSX]} {
1759                 .mbar.repository add command \
1760                         -label [mc "Create Desktop Icon"] \
1761                         -command do_macosx_app
1762         }
1765 .mbar.repository add command -label [mc Quit] \
1766         -command do_quit \
1767         -accelerator $M1T-Q
1769 # -- Edit Menu
1771 menu .mbar.edit
1772 .mbar.edit add command -label [mc Undo] \
1773         -command {catch {[focus] edit undo}} \
1774         -accelerator $M1T-Z
1775 .mbar.edit add command -label [mc Redo] \
1776         -command {catch {[focus] edit redo}} \
1777         -accelerator $M1T-Y
1778 .mbar.edit add separator
1779 .mbar.edit add command -label [mc Cut] \
1780         -command {catch {tk_textCut [focus]}} \
1781         -accelerator $M1T-X
1782 .mbar.edit add command -label [mc Copy] \
1783         -command {catch {tk_textCopy [focus]}} \
1784         -accelerator $M1T-C
1785 .mbar.edit add command -label [mc Paste] \
1786         -command {catch {tk_textPaste [focus]; [focus] see insert}} \
1787         -accelerator $M1T-V
1788 .mbar.edit add command -label [mc Delete] \
1789         -command {catch {[focus] delete sel.first sel.last}} \
1790         -accelerator Del
1791 .mbar.edit add separator
1792 .mbar.edit add command -label [mc "Select All"] \
1793         -command {catch {[focus] tag add sel 0.0 end}} \
1794         -accelerator $M1T-A
1796 # -- Branch Menu
1798 if {[is_enabled branch]} {
1799         menu .mbar.branch
1801         .mbar.branch add command -label [mc "Create..."] \
1802                 -command branch_create::dialog \
1803                 -accelerator $M1T-N
1804         lappend disable_on_lock [list .mbar.branch entryconf \
1805                 [.mbar.branch index last] -state]
1807         .mbar.branch add command -label [mc "Checkout..."] \
1808                 -command branch_checkout::dialog \
1809                 -accelerator $M1T-O
1810         lappend disable_on_lock [list .mbar.branch entryconf \
1811                 [.mbar.branch index last] -state]
1813         .mbar.branch add command -label [mc "Rename..."] \
1814                 -command branch_rename::dialog
1815         lappend disable_on_lock [list .mbar.branch entryconf \
1816                 [.mbar.branch index last] -state]
1818         .mbar.branch add command -label [mc "Delete..."] \
1819                 -command branch_delete::dialog
1820         lappend disable_on_lock [list .mbar.branch entryconf \
1821                 [.mbar.branch index last] -state]
1823         .mbar.branch add command -label [mc "Reset..."] \
1824                 -command merge::reset_hard
1825         lappend disable_on_lock [list .mbar.branch entryconf \
1826                 [.mbar.branch index last] -state]
1829 # -- Commit Menu
1831 if {[is_enabled multicommit] || [is_enabled singlecommit]} {
1832         menu .mbar.commit
1834         .mbar.commit add radiobutton \
1835                 -label [mc "New Commit"] \
1836                 -command do_select_commit_type \
1837                 -variable selected_commit_type \
1838                 -value new
1839         lappend disable_on_lock \
1840                 [list .mbar.commit entryconf [.mbar.commit index last] -state]
1842         .mbar.commit add radiobutton \
1843                 -label [mc "Amend Last Commit"] \
1844                 -command do_select_commit_type \
1845                 -variable selected_commit_type \
1846                 -value amend
1847         lappend disable_on_lock \
1848                 [list .mbar.commit entryconf [.mbar.commit index last] -state]
1850         .mbar.commit add separator
1852         .mbar.commit add command -label [mc Rescan] \
1853                 -command do_rescan \
1854                 -accelerator F5
1855         lappend disable_on_lock \
1856                 [list .mbar.commit entryconf [.mbar.commit index last] -state]
1858         .mbar.commit add command -label [mc "Stage To Commit"] \
1859                 -command do_add_selection
1860         lappend disable_on_lock \
1861                 [list .mbar.commit entryconf [.mbar.commit index last] -state]
1863         .mbar.commit add command -label [mc "Stage Changed Files To Commit"] \
1864                 -command do_add_all \
1865                 -accelerator $M1T-I
1866         lappend disable_on_lock \
1867                 [list .mbar.commit entryconf [.mbar.commit index last] -state]
1869         .mbar.commit add command -label [mc "Unstage From Commit"] \
1870                 -command do_unstage_selection
1871         lappend disable_on_lock \
1872                 [list .mbar.commit entryconf [.mbar.commit index last] -state]
1874         .mbar.commit add command -label [mc "Revert Changes"] \
1875                 -command do_revert_selection
1876         lappend disable_on_lock \
1877                 [list .mbar.commit entryconf [.mbar.commit index last] -state]
1879         .mbar.commit add separator
1881         .mbar.commit add command -label [mc "Sign Off"] \
1882                 -command do_signoff \
1883                 -accelerator $M1T-S
1885         .mbar.commit add command -label [mc Commit] \
1886                 -command do_commit \
1887                 -accelerator $M1T-Return
1888         lappend disable_on_lock \
1889                 [list .mbar.commit entryconf [.mbar.commit index last] -state]
1892 # -- Merge Menu
1894 if {[is_enabled branch]} {
1895         menu .mbar.merge
1896         .mbar.merge add command -label [mc "Local Merge..."] \
1897                 -command merge::dialog \
1898                 -accelerator $M1T-M
1899         lappend disable_on_lock \
1900                 [list .mbar.merge entryconf [.mbar.merge index last] -state]
1901         .mbar.merge add command -label [mc "Abort Merge..."] \
1902                 -command merge::reset_hard
1903         lappend disable_on_lock \
1904                 [list .mbar.merge entryconf [.mbar.merge index last] -state]
1907 # -- Transport Menu
1909 if {[is_enabled transport]} {
1910         menu .mbar.fetch
1912         menu .mbar.push
1913         .mbar.push add command -label [mc "Push..."] \
1914                 -command do_push_anywhere \
1915                 -accelerator $M1T-P
1916         .mbar.push add command -label [mc "Delete..."] \
1917                 -command remote_branch_delete::dialog
1920 if {[is_MacOSX]} {
1921         # -- Apple Menu (Mac OS X only)
1922         #
1923         .mbar add cascade -label [mc Apple] -menu .mbar.apple
1924         menu .mbar.apple
1926         .mbar.apple add command -label [mc "About %s" [appname]] \
1927                 -command do_about
1928         .mbar.apple add command -label [mc "Options..."] \
1929                 -command do_options
1930 } else {
1931         # -- Edit Menu
1932         #
1933         .mbar.edit add separator
1934         .mbar.edit add command -label [mc "Options..."] \
1935                 -command do_options
1938 # -- Help Menu
1940 .mbar add cascade -label [mc Help] -menu .mbar.help
1941 menu .mbar.help
1943 if {![is_MacOSX]} {
1944         .mbar.help add command -label [mc "About %s" [appname]] \
1945                 -command do_about
1948 set browser {}
1949 catch {set browser $repo_config(instaweb.browser)}
1950 set doc_path [file dirname [gitexec]]
1951 set doc_path [file join $doc_path Documentation index.html]
1953 if {[is_Cygwin]} {
1954         set doc_path [exec cygpath --mixed $doc_path]
1957 if {$browser eq {}} {
1958         if {[is_MacOSX]} {
1959                 set browser open
1960         } elseif {[is_Cygwin]} {
1961                 set program_files [file dirname [exec cygpath --windir]]
1962                 set program_files [file join $program_files {Program Files}]
1963                 set firefox [file join $program_files {Mozilla Firefox} firefox.exe]
1964                 set ie [file join $program_files {Internet Explorer} IEXPLORE.EXE]
1965                 if {[file exists $firefox]} {
1966                         set browser $firefox
1967                 } elseif {[file exists $ie]} {
1968                         set browser $ie
1969                 }
1970                 unset program_files firefox ie
1971         }
1974 if {[file isfile $doc_path]} {
1975         set doc_url "file:$doc_path"
1976 } else {
1977         set doc_url {http://www.kernel.org/pub/software/scm/git/docs/}
1980 if {$browser ne {}} {
1981         .mbar.help add command -label [mc "Online Documentation"] \
1982                 -command [list exec $browser $doc_url &]
1984 unset browser doc_path doc_url
1986 set root_exists 0
1987 bind . <Visibility> {
1988         bind . <Visibility> {}
1989         set root_exists 1
1992 # -- Standard bindings
1994 wm protocol . WM_DELETE_WINDOW do_quit
1995 bind all <$M1B-Key-q> do_quit
1996 bind all <$M1B-Key-Q> do_quit
1997 bind all <$M1B-Key-w> {destroy [winfo toplevel %W]}
1998 bind all <$M1B-Key-W> {destroy [winfo toplevel %W]}
2000 set subcommand_args {}
2001 proc usage {} {
2002         puts stderr "usage: $::argv0 $::subcommand $::subcommand_args"
2003         exit 1
2006 # -- Not a normal commit type invocation?  Do that instead!
2008 switch -- $subcommand {
2009 browser -
2010 blame {
2011         set subcommand_args {rev? path}
2012         if {$argv eq {}} usage
2013         set head {}
2014         set path {}
2015         set is_path 0
2016         foreach a $argv {
2017                 if {$is_path || [file exists $_prefix$a]} {
2018                         if {$path ne {}} usage
2019                         set path $_prefix$a
2020                         break
2021                 } elseif {$a eq {--}} {
2022                         if {$path ne {}} {
2023                                 if {$head ne {}} usage
2024                                 set head $path
2025                                 set path {}
2026                         }
2027                         set is_path 1
2028                 } elseif {$head eq {}} {
2029                         if {$head ne {}} usage
2030                         set head $a
2031                         set is_path 1
2032                 } else {
2033                         usage
2034                 }
2035         }
2036         unset is_path
2038         if {$head ne {} && $path eq {}} {
2039                 set path $_prefix$head
2040                 set head {}
2041         }
2043         if {$head eq {}} {
2044                 load_current_branch
2045         } else {
2046                 if {[regexp {^[0-9a-f]{1,39}$} $head]} {
2047                         if {[catch {
2048                                         set head [git rev-parse --verify $head]
2049                                 } err]} {
2050                                 puts stderr $err
2051                                 exit 1
2052                         }
2053                 }
2054                 set current_branch $head
2055         }
2057         switch -- $subcommand {
2058         browser {
2059                 if {$head eq {}} {
2060                         if {$path ne {} && [file isdirectory $path]} {
2061                                 set head $current_branch
2062                         } else {
2063                                 set head $path
2064                                 set path {}
2065                         }
2066                 }
2067                 browser::new $head $path
2068         }
2069         blame   {
2070                 if {$head eq {} && ![file exists $path]} {
2071                         puts stderr "fatal: cannot stat path $path: No such file or directory"
2072                         exit 1
2073                 }
2074                 blame::new $head $path
2075         }
2076         }
2077         return
2079 citool -
2080 gui {
2081         if {[llength $argv] != 0} {
2082                 puts -nonewline stderr "usage: $argv0"
2083                 if {$subcommand ne {gui} && [appname] ne "git-$subcommand"} {
2084                         puts -nonewline stderr " $subcommand"
2085                 }
2086                 puts stderr {}
2087                 exit 1
2088         }
2089         # fall through to setup UI for commits
2091 default {
2092         puts stderr "usage: $argv0 \[{blame|browser|citool}\]"
2093         exit 1
2097 # -- Branch Control
2099 frame .branch \
2100         -borderwidth 1 \
2101         -relief sunken
2102 label .branch.l1 \
2103         -text [mc "Current Branch:"] \
2104         -anchor w \
2105         -justify left
2106 label .branch.cb \
2107         -textvariable current_branch \
2108         -anchor w \
2109         -justify left
2110 pack .branch.l1 -side left
2111 pack .branch.cb -side left -fill x
2112 pack .branch -side top -fill x
2114 # -- Main Window Layout
2116 panedwindow .vpane -orient vertical
2117 panedwindow .vpane.files -orient horizontal
2118 .vpane add .vpane.files -sticky nsew -height 100 -width 200
2119 pack .vpane -anchor n -side top -fill both -expand 1
2121 # -- Index File List
2123 frame .vpane.files.index -height 100 -width 200
2124 label .vpane.files.index.title -text [mc "Staged Changes (Will Be Committed)"] \
2125         -background lightgreen
2126 text $ui_index -background white -borderwidth 0 \
2127         -width 20 -height 10 \
2128         -wrap none \
2129         -cursor $cursor_ptr \
2130         -xscrollcommand {.vpane.files.index.sx set} \
2131         -yscrollcommand {.vpane.files.index.sy set} \
2132         -state disabled
2133 scrollbar .vpane.files.index.sx -orient h -command [list $ui_index xview]
2134 scrollbar .vpane.files.index.sy -orient v -command [list $ui_index yview]
2135 pack .vpane.files.index.title -side top -fill x
2136 pack .vpane.files.index.sx -side bottom -fill x
2137 pack .vpane.files.index.sy -side right -fill y
2138 pack $ui_index -side left -fill both -expand 1
2139 .vpane.files add .vpane.files.index -sticky nsew
2141 # -- Working Directory File List
2143 frame .vpane.files.workdir -height 100 -width 200
2144 label .vpane.files.workdir.title -text [mc "Unstaged Changes (Will Not Be Committed)"] \
2145         -background lightsalmon
2146 text $ui_workdir -background white -borderwidth 0 \
2147         -width 20 -height 10 \
2148         -wrap none \
2149         -cursor $cursor_ptr \
2150         -xscrollcommand {.vpane.files.workdir.sx set} \
2151         -yscrollcommand {.vpane.files.workdir.sy set} \
2152         -state disabled
2153 scrollbar .vpane.files.workdir.sx -orient h -command [list $ui_workdir xview]
2154 scrollbar .vpane.files.workdir.sy -orient v -command [list $ui_workdir yview]
2155 pack .vpane.files.workdir.title -side top -fill x
2156 pack .vpane.files.workdir.sx -side bottom -fill x
2157 pack .vpane.files.workdir.sy -side right -fill y
2158 pack $ui_workdir -side left -fill both -expand 1
2159 .vpane.files add .vpane.files.workdir -sticky nsew
2161 foreach i [list $ui_index $ui_workdir] {
2162         $i tag conf in_diff -background lightgray
2163         $i tag conf in_sel  -background lightgray
2165 unset i
2167 # -- Diff and Commit Area
2169 frame .vpane.lower -height 300 -width 400
2170 frame .vpane.lower.commarea
2171 frame .vpane.lower.diff -relief sunken -borderwidth 1
2172 pack .vpane.lower.commarea -side top -fill x
2173 pack .vpane.lower.diff -side bottom -fill both -expand 1
2174 .vpane add .vpane.lower -sticky nsew
2176 # -- Commit Area Buttons
2178 frame .vpane.lower.commarea.buttons
2179 label .vpane.lower.commarea.buttons.l -text {} \
2180         -anchor w \
2181         -justify left
2182 pack .vpane.lower.commarea.buttons.l -side top -fill x
2183 pack .vpane.lower.commarea.buttons -side left -fill y
2185 button .vpane.lower.commarea.buttons.rescan -text [mc Rescan] \
2186         -command do_rescan
2187 pack .vpane.lower.commarea.buttons.rescan -side top -fill x
2188 lappend disable_on_lock \
2189         {.vpane.lower.commarea.buttons.rescan conf -state}
2191 button .vpane.lower.commarea.buttons.incall -text [mc "Stage Changed"] \
2192         -command do_add_all
2193 pack .vpane.lower.commarea.buttons.incall -side top -fill x
2194 lappend disable_on_lock \
2195         {.vpane.lower.commarea.buttons.incall conf -state}
2197 button .vpane.lower.commarea.buttons.signoff -text [mc "Sign Off"] \
2198         -command do_signoff
2199 pack .vpane.lower.commarea.buttons.signoff -side top -fill x
2201 button .vpane.lower.commarea.buttons.commit -text [mc Commit] \
2202         -command do_commit
2203 pack .vpane.lower.commarea.buttons.commit -side top -fill x
2204 lappend disable_on_lock \
2205         {.vpane.lower.commarea.buttons.commit conf -state}
2207 button .vpane.lower.commarea.buttons.push -text [mc Push] \
2208         -command do_push_anywhere
2209 pack .vpane.lower.commarea.buttons.push -side top -fill x
2211 # -- Commit Message Buffer
2213 frame .vpane.lower.commarea.buffer
2214 frame .vpane.lower.commarea.buffer.header
2215 set ui_comm .vpane.lower.commarea.buffer.t
2216 set ui_coml .vpane.lower.commarea.buffer.header.l
2217 radiobutton .vpane.lower.commarea.buffer.header.new \
2218         -text [mc "New Commit"] \
2219         -command do_select_commit_type \
2220         -variable selected_commit_type \
2221         -value new
2222 lappend disable_on_lock \
2223         [list .vpane.lower.commarea.buffer.header.new conf -state]
2224 radiobutton .vpane.lower.commarea.buffer.header.amend \
2225         -text [mc "Amend Last Commit"] \
2226         -command do_select_commit_type \
2227         -variable selected_commit_type \
2228         -value amend
2229 lappend disable_on_lock \
2230         [list .vpane.lower.commarea.buffer.header.amend conf -state]
2231 label $ui_coml \
2232         -anchor w \
2233         -justify left
2234 proc trace_commit_type {varname args} {
2235         global ui_coml commit_type
2236         switch -glob -- $commit_type {
2237         initial       {set txt [mc "Initial Commit Message:"]}
2238         amend         {set txt [mc "Amended Commit Message:"]}
2239         amend-initial {set txt [mc "Amended Initial Commit Message:"]}
2240         amend-merge   {set txt [mc "Amended Merge Commit Message:"]}
2241         merge         {set txt [mc "Merge Commit Message:"]}
2242         *             {set txt [mc "Commit Message:"]}
2243         }
2244         $ui_coml conf -text $txt
2246 trace add variable commit_type write trace_commit_type
2247 pack $ui_coml -side left -fill x
2248 pack .vpane.lower.commarea.buffer.header.amend -side right
2249 pack .vpane.lower.commarea.buffer.header.new -side right
2251 text $ui_comm -background white -borderwidth 1 \
2252         -undo true \
2253         -maxundo 20 \
2254         -autoseparators true \
2255         -relief sunken \
2256         -width 75 -height 9 -wrap none \
2257         -font font_diff \
2258         -yscrollcommand {.vpane.lower.commarea.buffer.sby set}
2259 scrollbar .vpane.lower.commarea.buffer.sby \
2260         -command [list $ui_comm yview]
2261 pack .vpane.lower.commarea.buffer.header -side top -fill x
2262 pack .vpane.lower.commarea.buffer.sby -side right -fill y
2263 pack $ui_comm -side left -fill y
2264 pack .vpane.lower.commarea.buffer -side left -fill y
2266 # -- Commit Message Buffer Context Menu
2268 set ctxm .vpane.lower.commarea.buffer.ctxm
2269 menu $ctxm -tearoff 0
2270 $ctxm add command \
2271         -label [mc Cut] \
2272         -command {tk_textCut $ui_comm}
2273 $ctxm add command \
2274         -label [mc Copy] \
2275         -command {tk_textCopy $ui_comm}
2276 $ctxm add command \
2277         -label [mc Paste] \
2278         -command {tk_textPaste $ui_comm}
2279 $ctxm add command \
2280         -label [mc Delete] \
2281         -command {$ui_comm delete sel.first sel.last}
2282 $ctxm add separator
2283 $ctxm add command \
2284         -label [mc "Select All"] \
2285         -command {focus $ui_comm;$ui_comm tag add sel 0.0 end}
2286 $ctxm add command \
2287         -label [mc "Copy All"] \
2288         -command {
2289                 $ui_comm tag add sel 0.0 end
2290                 tk_textCopy $ui_comm
2291                 $ui_comm tag remove sel 0.0 end
2292         }
2293 $ctxm add separator
2294 $ctxm add command \
2295         -label [mc "Sign Off"] \
2296         -command do_signoff
2297 bind_button3 $ui_comm "tk_popup $ctxm %X %Y"
2299 # -- Diff Header
2301 proc trace_current_diff_path {varname args} {
2302         global current_diff_path diff_actions file_states
2303         if {$current_diff_path eq {}} {
2304                 set s {}
2305                 set f {}
2306                 set p {}
2307                 set o disabled
2308         } else {
2309                 set p $current_diff_path
2310                 set s [mapdesc [lindex $file_states($p) 0] $p]
2311                 set f [mc "File:"]
2312                 set p [escape_path $p]
2313                 set o normal
2314         }
2316         .vpane.lower.diff.header.status configure -text $s
2317         .vpane.lower.diff.header.file configure -text $f
2318         .vpane.lower.diff.header.path configure -text $p
2319         foreach w $diff_actions {
2320                 uplevel #0 $w $o
2321         }
2323 trace add variable current_diff_path write trace_current_diff_path
2325 frame .vpane.lower.diff.header -background gold
2326 label .vpane.lower.diff.header.status \
2327         -background gold \
2328         -width $max_status_desc \
2329         -anchor w \
2330         -justify left
2331 label .vpane.lower.diff.header.file \
2332         -background gold \
2333         -anchor w \
2334         -justify left
2335 label .vpane.lower.diff.header.path \
2336         -background gold \
2337         -anchor w \
2338         -justify left
2339 pack .vpane.lower.diff.header.status -side left
2340 pack .vpane.lower.diff.header.file -side left
2341 pack .vpane.lower.diff.header.path -fill x
2342 set ctxm .vpane.lower.diff.header.ctxm
2343 menu $ctxm -tearoff 0
2344 $ctxm add command \
2345         -label [mc Copy] \
2346         -command {
2347                 clipboard clear
2348                 clipboard append \
2349                         -format STRING \
2350                         -type STRING \
2351                         -- $current_diff_path
2352         }
2353 lappend diff_actions [list $ctxm entryconf [$ctxm index last] -state]
2354 bind_button3 .vpane.lower.diff.header.path "tk_popup $ctxm %X %Y"
2356 # -- Diff Body
2358 frame .vpane.lower.diff.body
2359 set ui_diff .vpane.lower.diff.body.t
2360 text $ui_diff -background white -borderwidth 0 \
2361         -width 80 -height 15 -wrap none \
2362         -font font_diff \
2363         -xscrollcommand {.vpane.lower.diff.body.sbx set} \
2364         -yscrollcommand {.vpane.lower.diff.body.sby set} \
2365         -state disabled
2366 scrollbar .vpane.lower.diff.body.sbx -orient horizontal \
2367         -command [list $ui_diff xview]
2368 scrollbar .vpane.lower.diff.body.sby -orient vertical \
2369         -command [list $ui_diff yview]
2370 pack .vpane.lower.diff.body.sbx -side bottom -fill x
2371 pack .vpane.lower.diff.body.sby -side right -fill y
2372 pack $ui_diff -side left -fill both -expand 1
2373 pack .vpane.lower.diff.header -side top -fill x
2374 pack .vpane.lower.diff.body -side bottom -fill both -expand 1
2376 $ui_diff tag conf d_cr -elide true
2377 $ui_diff tag conf d_@ -foreground blue -font font_diffbold
2378 $ui_diff tag conf d_+ -foreground {#00a000}
2379 $ui_diff tag conf d_- -foreground red
2381 $ui_diff tag conf d_++ -foreground {#00a000}
2382 $ui_diff tag conf d_-- -foreground red
2383 $ui_diff tag conf d_+s \
2384         -foreground {#00a000} \
2385         -background {#e2effa}
2386 $ui_diff tag conf d_-s \
2387         -foreground red \
2388         -background {#e2effa}
2389 $ui_diff tag conf d_s+ \
2390         -foreground {#00a000} \
2391         -background ivory1
2392 $ui_diff tag conf d_s- \
2393         -foreground red \
2394         -background ivory1
2396 $ui_diff tag conf d<<<<<<< \
2397         -foreground orange \
2398         -font font_diffbold
2399 $ui_diff tag conf d======= \
2400         -foreground orange \
2401         -font font_diffbold
2402 $ui_diff tag conf d>>>>>>> \
2403         -foreground orange \
2404         -font font_diffbold
2406 $ui_diff tag raise sel
2408 # -- Diff Body Context Menu
2410 set ctxm .vpane.lower.diff.body.ctxm
2411 menu $ctxm -tearoff 0
2412 $ctxm add command \
2413         -label [mc Refresh] \
2414         -command reshow_diff
2415 lappend diff_actions [list $ctxm entryconf [$ctxm index last] -state]
2416 $ctxm add command \
2417         -label [mc Copy] \
2418         -command {tk_textCopy $ui_diff}
2419 lappend diff_actions [list $ctxm entryconf [$ctxm index last] -state]
2420 $ctxm add command \
2421         -label [mc "Select All"] \
2422         -command {focus $ui_diff;$ui_diff tag add sel 0.0 end}
2423 lappend diff_actions [list $ctxm entryconf [$ctxm index last] -state]
2424 $ctxm add command \
2425         -label [mc "Copy All"] \
2426         -command {
2427                 $ui_diff tag add sel 0.0 end
2428                 tk_textCopy $ui_diff
2429                 $ui_diff tag remove sel 0.0 end
2430         }
2431 lappend diff_actions [list $ctxm entryconf [$ctxm index last] -state]
2432 $ctxm add separator
2433 $ctxm add command \
2434         -label [mc "Apply/Reverse Hunk"] \
2435         -command {apply_hunk $cursorX $cursorY}
2436 set ui_diff_applyhunk [$ctxm index last]
2437 lappend diff_actions [list $ctxm entryconf $ui_diff_applyhunk -state]
2438 $ctxm add separator
2439 $ctxm add command \
2440         -label [mc "Decrease Font Size"] \
2441         -command {incr_font_size font_diff -1}
2442 lappend diff_actions [list $ctxm entryconf [$ctxm index last] -state]
2443 $ctxm add command \
2444         -label [mc "Increase Font Size"] \
2445         -command {incr_font_size font_diff 1}
2446 lappend diff_actions [list $ctxm entryconf [$ctxm index last] -state]
2447 $ctxm add separator
2448 $ctxm add command \
2449         -label [mc "Show Less Context"] \
2450         -command {if {$repo_config(gui.diffcontext) >= 1} {
2451                 incr repo_config(gui.diffcontext) -1
2452                 reshow_diff
2453         }}
2454 lappend diff_actions [list $ctxm entryconf [$ctxm index last] -state]
2455 $ctxm add command \
2456         -label [mc "Show More Context"] \
2457         -command {if {$repo_config(gui.diffcontext) < 99} {
2458                 incr repo_config(gui.diffcontext)
2459                 reshow_diff
2460         }}
2461 lappend diff_actions [list $ctxm entryconf [$ctxm index last] -state]
2462 $ctxm add separator
2463 $ctxm add command -label [mc "Options..."] \
2464         -command do_options
2465 proc popup_diff_menu {ctxm x y X Y} {
2466         global current_diff_path file_states
2467         set ::cursorX $x
2468         set ::cursorY $y
2469         if {$::ui_index eq $::current_diff_side} {
2470                 set l [mc "Unstage Hunk From Commit"]
2471         } else {
2472                 set l [mc "Stage Hunk For Commit"]
2473         }
2474         if {$::is_3way_diff
2475                 || $current_diff_path eq {}
2476                 || ![info exists file_states($current_diff_path)]
2477                 || {_O} eq [lindex $file_states($current_diff_path) 0]} {
2478                 set s disabled
2479         } else {
2480                 set s normal
2481         }
2482         $ctxm entryconf $::ui_diff_applyhunk -state $s -label $l
2483         tk_popup $ctxm $X $Y
2485 bind_button3 $ui_diff [list popup_diff_menu $ctxm %x %y %X %Y]
2487 # -- Status Bar
2489 set main_status [::status_bar::new .status]
2490 pack .status -anchor w -side bottom -fill x
2491 $main_status show [mc "Initializing..."]
2493 # -- Load geometry
2495 catch {
2496 set gm $repo_config(gui.geometry)
2497 wm geometry . [lindex $gm 0]
2498 .vpane sash place 0 \
2499         [lindex [.vpane sash coord 0] 0] \
2500         [lindex $gm 1]
2501 .vpane.files sash place 0 \
2502         [lindex $gm 2] \
2503         [lindex [.vpane.files sash coord 0] 1]
2504 unset gm
2507 # -- Key Bindings
2509 bind $ui_comm <$M1B-Key-Return> {do_commit;break}
2510 bind $ui_comm <$M1B-Key-i> {do_add_all;break}
2511 bind $ui_comm <$M1B-Key-I> {do_add_all;break}
2512 bind $ui_comm <$M1B-Key-x> {tk_textCut %W;break}
2513 bind $ui_comm <$M1B-Key-X> {tk_textCut %W;break}
2514 bind $ui_comm <$M1B-Key-c> {tk_textCopy %W;break}
2515 bind $ui_comm <$M1B-Key-C> {tk_textCopy %W;break}
2516 bind $ui_comm <$M1B-Key-v> {tk_textPaste %W; %W see insert; break}
2517 bind $ui_comm <$M1B-Key-V> {tk_textPaste %W; %W see insert; break}
2518 bind $ui_comm <$M1B-Key-a> {%W tag add sel 0.0 end;break}
2519 bind $ui_comm <$M1B-Key-A> {%W tag add sel 0.0 end;break}
2521 bind $ui_diff <$M1B-Key-x> {tk_textCopy %W;break}
2522 bind $ui_diff <$M1B-Key-X> {tk_textCopy %W;break}
2523 bind $ui_diff <$M1B-Key-c> {tk_textCopy %W;break}
2524 bind $ui_diff <$M1B-Key-C> {tk_textCopy %W;break}
2525 bind $ui_diff <$M1B-Key-v> {break}
2526 bind $ui_diff <$M1B-Key-V> {break}
2527 bind $ui_diff <$M1B-Key-a> {%W tag add sel 0.0 end;break}
2528 bind $ui_diff <$M1B-Key-A> {%W tag add sel 0.0 end;break}
2529 bind $ui_diff <Key-Up>     {catch {%W yview scroll -1 units};break}
2530 bind $ui_diff <Key-Down>   {catch {%W yview scroll  1 units};break}
2531 bind $ui_diff <Key-Left>   {catch {%W xview scroll -1 units};break}
2532 bind $ui_diff <Key-Right>  {catch {%W xview scroll  1 units};break}
2533 bind $ui_diff <Key-k>         {catch {%W yview scroll -1 units};break}
2534 bind $ui_diff <Key-j>         {catch {%W yview scroll  1 units};break}
2535 bind $ui_diff <Key-h>         {catch {%W xview scroll -1 units};break}
2536 bind $ui_diff <Key-l>         {catch {%W xview scroll  1 units};break}
2537 bind $ui_diff <Control-Key-b> {catch {%W yview scroll -1 pages};break}
2538 bind $ui_diff <Control-Key-f> {catch {%W yview scroll  1 pages};break}
2539 bind $ui_diff <Button-1>   {focus %W}
2541 if {[is_enabled branch]} {
2542         bind . <$M1B-Key-n> branch_create::dialog
2543         bind . <$M1B-Key-N> branch_create::dialog
2544         bind . <$M1B-Key-o> branch_checkout::dialog
2545         bind . <$M1B-Key-O> branch_checkout::dialog
2546         bind . <$M1B-Key-m> merge::dialog
2547         bind . <$M1B-Key-M> merge::dialog
2549 if {[is_enabled transport]} {
2550         bind . <$M1B-Key-p> do_push_anywhere
2551         bind . <$M1B-Key-P> do_push_anywhere
2554 bind .   <Key-F5>     do_rescan
2555 bind .   <$M1B-Key-r> do_rescan
2556 bind .   <$M1B-Key-R> do_rescan
2557 bind .   <$M1B-Key-s> do_signoff
2558 bind .   <$M1B-Key-S> do_signoff
2559 bind .   <$M1B-Key-i> do_add_all
2560 bind .   <$M1B-Key-I> do_add_all
2561 bind .   <$M1B-Key-Return> do_commit
2562 foreach i [list $ui_index $ui_workdir] {
2563         bind $i <Button-1>       "toggle_or_diff         $i %x %y; break"
2564         bind $i <$M1B-Button-1>  "add_one_to_selection   $i %x %y; break"
2565         bind $i <Shift-Button-1> "add_range_to_selection $i %x %y; break"
2567 unset i
2569 set file_lists($ui_index) [list]
2570 set file_lists($ui_workdir) [list]
2572 wm title . "[appname] ([reponame]) [file normalize [file dirname [gitdir]]]"
2573 focus -force $ui_comm
2575 # -- Warn the user about environmental problems.  Cygwin's Tcl
2576 #    does *not* pass its env array onto any processes it spawns.
2577 #    This means that git processes get none of our environment.
2579 if {[is_Cygwin]} {
2580         set ignored_env 0
2581         set suggest_user {}
2582         set msg "Possible environment issues exist.
2584 The following environment variables are probably
2585 going to be ignored by any Git subprocess run
2586 by [appname]:
2589         foreach name [array names env] {
2590                 switch -regexp -- $name {
2591                 {^GIT_INDEX_FILE$} -
2592                 {^GIT_OBJECT_DIRECTORY$} -
2593                 {^GIT_ALTERNATE_OBJECT_DIRECTORIES$} -
2594                 {^GIT_DIFF_OPTS$} -
2595                 {^GIT_EXTERNAL_DIFF$} -
2596                 {^GIT_PAGER$} -
2597                 {^GIT_TRACE$} -
2598                 {^GIT_CONFIG$} -
2599                 {^GIT_CONFIG_LOCAL$} -
2600                 {^GIT_(AUTHOR|COMMITTER)_DATE$} {
2601                         append msg " - $name\n"
2602                         incr ignored_env
2603                 }
2604                 {^GIT_(AUTHOR|COMMITTER)_(NAME|EMAIL)$} {
2605                         append msg " - $name\n"
2606                         incr ignored_env
2607                         set suggest_user $name
2608                 }
2609                 }
2610         }
2611         if {$ignored_env > 0} {
2612                 append msg "
2613 This is due to a known issue with the
2614 Tcl binary distributed by Cygwin."
2616                 if {$suggest_user ne {}} {
2617                         append msg "
2619 A good replacement for $suggest_user
2620 is placing values for the user.name and
2621 user.email settings into your personal
2622 ~/.gitconfig file.
2624                 }
2625                 warn_popup $msg
2626         }
2627         unset ignored_env msg suggest_user name
2630 # -- Only initialize complex UI if we are going to stay running.
2632 if {[is_enabled transport]} {
2633         load_all_remotes
2635         populate_fetch_menu
2636         populate_push_menu
2639 if {[winfo exists $ui_comm]} {
2640         set GITGUI_BCK_exists [load_message GITGUI_BCK]
2642         # -- If both our backup and message files exist use the
2643         #    newer of the two files to initialize the buffer.
2644         #
2645         if {$GITGUI_BCK_exists} {
2646                 set m [gitdir GITGUI_MSG]
2647                 if {[file isfile $m]} {
2648                         if {[file mtime [gitdir GITGUI_BCK]] > [file mtime $m]} {
2649                                 catch {file delete [gitdir GITGUI_MSG]}
2650                         } else {
2651                                 $ui_comm delete 0.0 end
2652                                 $ui_comm edit reset
2653                                 $ui_comm edit modified false
2654                                 catch {file delete [gitdir GITGUI_BCK]}
2655                                 set GITGUI_BCK_exists 0
2656                         }
2657                 }
2658                 unset m
2659         }
2661         proc backup_commit_buffer {} {
2662                 global ui_comm GITGUI_BCK_exists
2664                 set m [$ui_comm edit modified]
2665                 if {$m || $GITGUI_BCK_exists} {
2666                         set msg [string trim [$ui_comm get 0.0 end]]
2667                         regsub -all -line {[ \r\t]+$} $msg {} msg
2669                         if {$msg eq {}} {
2670                                 if {$GITGUI_BCK_exists} {
2671                                         catch {file delete [gitdir GITGUI_BCK]}
2672                                         set GITGUI_BCK_exists 0
2673                                 }
2674                         } elseif {$m} {
2675                                 catch {
2676                                         set fd [open [gitdir GITGUI_BCK] w]
2677                                         puts -nonewline $fd $msg
2678                                         close $fd
2679                                         set GITGUI_BCK_exists 1
2680                                 }
2681                         }
2683                         $ui_comm edit modified false
2684                 }
2686                 set ::GITGUI_BCK_i [after 2000 backup_commit_buffer]
2687         }
2689         backup_commit_buffer
2692 lock_index begin-read
2693 if {![winfo ismapped .]} {
2694         wm deiconify .
2696 after 1 do_rescan
2697 if {[is_enabled multicommit]} {
2698         after 1000 hint_gc