Code

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