Code

Do not break git-gui messages into multiple lines.
[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         wm title $w "[appname] ([reponame]): Push"
2791         tkwait window $w
2794 ######################################################################
2795 ##
2796 ## merge
2798 proc can_merge {} {
2799         global HEAD commit_type file_states
2801         if {[string match amend* $commit_type]} {
2802                 info_popup {Cannot merge while amending.
2804 You must finish amending this commit before starting any type of merge.
2806                 return 0
2807         }
2809         if {[committer_ident] eq {}} {return 0}
2810         if {![lock_index merge]} {return 0}
2812         # -- Our in memory state should match the repository.
2813         #
2814         repository_state curType curHEAD curMERGE_HEAD
2815         if {$commit_type ne $curType || $HEAD ne $curHEAD} {
2816                 info_popup {Last scanned state does not match repository state.
2818 Another Git program has modified this repository since the last scan.  A rescan must be performed before a merge can be performed.
2820 The rescan will be automatically started now.
2822                 unlock_index
2823                 rescan {set ui_status_value {Ready.}}
2824                 return 0
2825         }
2827         foreach path [array names file_states] {
2828                 switch -glob -- [lindex $file_states($path) 0] {
2829                 _O {
2830                         continue; # and pray it works!
2831                 }
2832                 U? {
2833                         error_popup "You are in the middle of a conflicted merge.
2835 File [short_path $path] has merge conflicts.
2837 You must resolve them, add the file, and commit to complete the current merge.  Only then can you begin another merge.
2839                         unlock_index
2840                         return 0
2841                 }
2842                 ?? {
2843                         error_popup "You are in the middle of a change.
2845 File [short_path $path] is modified.
2847 You should complete the current commit before starting a merge.  Doing so will help you abort a failed merge, should the need arise.
2849                         unlock_index
2850                         return 0
2851                 }
2852                 }
2853         }
2855         return 1
2858 proc visualize_local_merge {w} {
2859         set revs {}
2860         foreach i [$w.source.l curselection] {
2861                 lappend revs [$w.source.l get $i]
2862         }
2863         if {$revs eq {}} return
2864         lappend revs --not HEAD
2865         do_gitk $revs
2868 proc start_local_merge_action {w} {
2869         global HEAD ui_status_value current_branch
2871         set cmd [list git merge]
2872         set names {}
2873         set revcnt 0
2874         foreach i [$w.source.l curselection] {
2875                 set b [$w.source.l get $i]
2876                 lappend cmd $b
2877                 lappend names $b
2878                 incr revcnt
2879         }
2881         if {$revcnt == 0} {
2882                 return
2883         } elseif {$revcnt == 1} {
2884                 set unit branch
2885         } elseif {$revcnt <= 15} {
2886                 set unit branches
2887         } else {
2888                 tk_messageBox \
2889                         -icon error \
2890                         -type ok \
2891                         -title [wm title $w] \
2892                         -parent $w \
2893                         -message "Too many branches selected.
2895 You have requested to merge $revcnt branches
2896 in an octopus merge.  This exceeds Git's
2897 internal limit of 15 branches per merge.
2899 Please select fewer branches.  To merge more
2900 than 15 branches, merge the branches in batches.
2902                 return
2903         }
2905         set msg "Merging $current_branch, [join $names {, }]"
2906         set ui_status_value "$msg..."
2907         set cons [new_console "Merge" $msg]
2908         console_exec $cons $cmd [list finish_merge $revcnt]
2909         bind $w <Destroy> {}
2910         destroy $w
2913 proc finish_merge {revcnt w ok} {
2914         console_done $w $ok
2915         if {$ok} {
2916                 set msg {Merge completed successfully.}
2917         } else {
2918                 if {$revcnt != 1} {
2919                         info_popup "Octopus merge failed.
2921 Your merge of $revcnt branches has failed.
2923 There are file-level conflicts between the branches which must be resolved manually.
2925 The working directory will now be reset.
2927 You can attempt this merge again by merging only one branch at a time." $w
2929                         set fd [open "| git read-tree --reset -u HEAD" r]
2930                         fconfigure $fd -blocking 0 -translation binary
2931                         fileevent $fd readable [list reset_hard_wait $fd]
2932                         set ui_status_value {Aborting... please wait...}
2933                         return
2934                 }
2936                 set msg {Merge failed.  Conflict resolution is required.}
2937         }
2938         unlock_index
2939         rescan [list set ui_status_value $msg]
2942 proc do_local_merge {} {
2943         global current_branch
2945         if {![can_merge]} return
2947         set w .merge_setup
2948         toplevel $w
2949         wm geometry $w "+[winfo rootx .]+[winfo rooty .]"
2951         label $w.header \
2952                 -text "Merge Into $current_branch" \
2953                 -font font_uibold
2954         pack $w.header -side top -fill x
2956         frame $w.buttons
2957         button $w.buttons.visualize -text Visualize \
2958                 -font font_ui \
2959                 -command [list visualize_local_merge $w]
2960         pack $w.buttons.visualize -side left
2961         button $w.buttons.create -text Merge \
2962                 -font font_ui \
2963                 -command [list start_local_merge_action $w]
2964         pack $w.buttons.create -side right
2965         button $w.buttons.cancel -text {Cancel} \
2966                 -font font_ui \
2967                 -command [list destroy $w]
2968         pack $w.buttons.cancel -side right -padx 5
2969         pack $w.buttons -side bottom -fill x -pady 10 -padx 10
2971         labelframe $w.source \
2972                 -text {Source Branches} \
2973                 -font font_ui
2974         listbox $w.source.l \
2975                 -height 10 \
2976                 -width 70 \
2977                 -selectmode extended \
2978                 -yscrollcommand [list $w.source.sby set] \
2979                 -font font_ui
2980         scrollbar $w.source.sby -command [list $w.source.l yview]
2981         pack $w.source.sby -side right -fill y
2982         pack $w.source.l -side left -fill both -expand 1
2983         pack $w.source -fill both -expand 1 -pady 5 -padx 5
2985         set cmd [list git for-each-ref]
2986         lappend cmd {--format=%(objectname) %(*objectname) %(refname)}
2987         lappend cmd refs/heads
2988         lappend cmd refs/remotes
2989         lappend cmd refs/tags
2990         set fr_fd [open "| $cmd" r]
2991         fconfigure $fr_fd -translation binary
2992         while {[gets $fr_fd line] > 0} {
2993                 set line [split $line { }]
2994                 set sha1([lindex $line 0]) [lindex $line 2]
2995                 set sha1([lindex $line 1]) [lindex $line 2]
2996         }
2997         close $fr_fd
2999         set to_show {}
3000         set fr_fd [open "| git rev-list --all --not HEAD"]
3001         while {[gets $fr_fd line] > 0} {
3002                 if {[catch {set ref $sha1($line)}]} continue
3003                 regsub ^refs/(heads|remotes|tags)/ $ref {} ref
3004                 lappend to_show $ref
3005         }
3006         close $fr_fd
3008         foreach ref [lsort -unique $to_show] {
3009                 $w.source.l insert end $ref
3010         }
3012         bind $w <Visibility> "grab $w"
3013         bind $w <Key-Escape> "unlock_index;destroy $w"
3014         bind $w <Destroy> unlock_index
3015         wm title $w "[appname] ([reponame]): Merge"
3016         tkwait window $w
3019 proc do_reset_hard {} {
3020         global HEAD commit_type file_states
3022         if {[string match amend* $commit_type]} {
3023                 info_popup {Cannot abort while amending.
3025 You must finish amending this commit.
3027                 return
3028         }
3030         if {![lock_index abort]} return
3032         if {[string match *merge* $commit_type]} {
3033                 set op merge
3034         } else {
3035                 set op commit
3036         }
3038         if {[ask_popup "Abort $op?
3040 Aborting the current $op will cause *ALL* uncommitted changes to be lost.
3042 Continue with aborting the current $op?"] eq {yes}} {
3043                 set fd [open "| git read-tree --reset -u HEAD" r]
3044                 fconfigure $fd -blocking 0 -translation binary
3045                 fileevent $fd readable [list reset_hard_wait $fd]
3046                 set ui_status_value {Aborting... please wait...}
3047         } else {
3048                 unlock_index
3049         }
3052 proc reset_hard_wait {fd} {
3053         global ui_comm
3055         read $fd
3056         if {[eof $fd]} {
3057                 close $fd
3058                 unlock_index
3060                 $ui_comm delete 0.0 end
3061                 $ui_comm edit modified false
3063                 catch {file delete [gitdir MERGE_HEAD]}
3064                 catch {file delete [gitdir rr-cache MERGE_RR]}
3065                 catch {file delete [gitdir SQUASH_MSG]}
3066                 catch {file delete [gitdir MERGE_MSG]}
3067                 catch {file delete [gitdir GITGUI_MSG]}
3069                 rescan {set ui_status_value {Abort completed.  Ready.}}
3070         }
3073 ######################################################################
3074 ##
3075 ## browser
3077 set next_browser_id 0
3079 proc new_browser {commit} {
3080         global next_browser_id cursor_ptr M1B
3081         global browser_commit browser_status browser_stack browser_path browser_busy
3083         if {[winfo ismapped .]} {
3084                 set w .browser[incr next_browser_id]
3085                 set tl $w
3086                 toplevel $w
3087         } else {
3088                 set w {}
3089                 set tl .
3090         }
3091         set w_list $w.list.l
3092         set browser_commit($w_list) $commit
3093         set browser_status($w_list) {Starting...}
3094         set browser_stack($w_list) {}
3095         set browser_path($w_list) $browser_commit($w_list):
3096         set browser_busy($w_list) 1
3098         label $w.path -textvariable browser_path($w_list) \
3099                 -anchor w \
3100                 -justify left \
3101                 -borderwidth 1 \
3102                 -relief sunken \
3103                 -font font_uibold
3104         pack $w.path -anchor w -side top -fill x
3106         frame $w.list
3107         text $w_list -background white -borderwidth 0 \
3108                 -cursor $cursor_ptr \
3109                 -state disabled \
3110                 -wrap none \
3111                 -height 20 \
3112                 -width 70 \
3113                 -xscrollcommand [list $w.list.sbx set] \
3114                 -yscrollcommand [list $w.list.sby set] \
3115                 -font font_ui
3116         $w_list tag conf in_sel \
3117                 -background [$w_list cget -foreground] \
3118                 -foreground [$w_list cget -background]
3119         scrollbar $w.list.sbx -orient h -command [list $w_list xview]
3120         scrollbar $w.list.sby -orient v -command [list $w_list yview]
3121         pack $w.list.sbx -side bottom -fill x
3122         pack $w.list.sby -side right -fill y
3123         pack $w_list -side left -fill both -expand 1
3124         pack $w.list -side top -fill both -expand 1
3126         label $w.status -textvariable browser_status($w_list) \
3127                 -anchor w \
3128                 -justify left \
3129                 -borderwidth 1 \
3130                 -relief sunken \
3131                 -font font_ui
3132         pack $w.status -anchor w -side bottom -fill x
3134         bind $w_list <Button-1>        "browser_click 0 $w_list @%x,%y;break"
3135         bind $w_list <Double-Button-1> "browser_click 1 $w_list @%x,%y;break"
3136         bind $w_list <$M1B-Up>         "browser_parent $w_list;break"
3137         bind $w_list <$M1B-Left>       "browser_parent $w_list;break"
3138         bind $w_list <Up>              "browser_move -1 $w_list;break"
3139         bind $w_list <Down>            "browser_move 1 $w_list;break"
3140         bind $w_list <$M1B-Right>      "browser_enter $w_list;break"
3141         bind $w_list <Return>          "browser_enter $w_list;break"
3142         bind $w_list <Prior>           "browser_page -1 $w_list;break"
3143         bind $w_list <Next>            "browser_page 1 $w_list;break"
3144         bind $w_list <Left>            break
3145         bind $w_list <Right>           break
3147         bind $tl <Visibility> "focus $w"
3148         bind $tl <Destroy> "
3149                 array unset browser_buffer $w_list
3150                 array unset browser_files $w_list
3151                 array unset browser_status $w_list
3152                 array unset browser_stack $w_list
3153                 array unset browser_path $w_list
3154                 array unset browser_commit $w_list
3155                 array unset browser_busy $w_list
3156         "
3157         wm title $tl "[appname] ([reponame]): File Browser"
3158         ls_tree $w_list $browser_commit($w_list) {}
3161 proc browser_move {dir w} {
3162         global browser_files browser_busy
3164         if {$browser_busy($w)} return
3165         set lno [lindex [split [$w index in_sel.first] .] 0]
3166         incr lno $dir
3167         if {[lindex $browser_files($w) [expr {$lno - 1}]] ne {}} {
3168                 $w tag remove in_sel 0.0 end
3169                 $w tag add in_sel $lno.0 [expr {$lno + 1}].0
3170                 $w see $lno.0
3171         }
3174 proc browser_page {dir w} {
3175         global browser_files browser_busy
3177         if {$browser_busy($w)} return
3178         $w yview scroll $dir pages
3179         set lno [expr {int(
3180                   [lindex [$w yview] 0]
3181                 * [llength $browser_files($w)]
3182                 + 1)}]
3183         if {[lindex $browser_files($w) [expr {$lno - 1}]] ne {}} {
3184                 $w tag remove in_sel 0.0 end
3185                 $w tag add in_sel $lno.0 [expr {$lno + 1}].0
3186                 $w see $lno.0
3187         }
3190 proc browser_parent {w} {
3191         global browser_files browser_status browser_path
3192         global browser_stack browser_busy
3194         if {$browser_busy($w)} return
3195         set info [lindex $browser_files($w) 0]
3196         if {[lindex $info 0] eq {parent}} {
3197                 set parent [lindex $browser_stack($w) end-1]
3198                 set browser_stack($w) [lrange $browser_stack($w) 0 end-2]
3199                 if {$browser_stack($w) eq {}} {
3200                         regsub {:.*$} $browser_path($w) {:} browser_path($w)
3201                 } else {
3202                         regsub {/[^/]+$} $browser_path($w) {} browser_path($w)
3203                 }
3204                 set browser_status($w) "Loading $browser_path($w)..."
3205                 ls_tree $w [lindex $parent 0] [lindex $parent 1]
3206         }
3209 proc browser_enter {w} {
3210         global browser_files browser_status browser_path
3211         global browser_commit browser_stack browser_busy
3213         if {$browser_busy($w)} return
3214         set lno [lindex [split [$w index in_sel.first] .] 0]
3215         set info [lindex $browser_files($w) [expr {$lno - 1}]]
3216         if {$info ne {}} {
3217                 switch -- [lindex $info 0] {
3218                 parent {
3219                         browser_parent $w
3220                 }
3221                 tree {
3222                         set name [lindex $info 2]
3223                         set escn [escape_path $name]
3224                         set browser_status($w) "Loading $escn..."
3225                         append browser_path($w) $escn
3226                         ls_tree $w [lindex $info 1] $name
3227                 }
3228                 blob {
3229                         set name [lindex $info 2]
3230                         set p {}
3231                         foreach n $browser_stack($w) {
3232                                 append p [lindex $n 1]
3233                         }
3234                         append p $name
3235                         show_blame $browser_commit($w) $p
3236                 }
3237                 }
3238         }
3241 proc browser_click {was_double_click w pos} {
3242         global browser_files browser_busy
3244         if {$browser_busy($w)} return
3245         set lno [lindex [split [$w index $pos] .] 0]
3246         focus $w
3248         if {[lindex $browser_files($w) [expr {$lno - 1}]] ne {}} {
3249                 $w tag remove in_sel 0.0 end
3250                 $w tag add in_sel $lno.0 [expr {$lno + 1}].0
3251                 if {$was_double_click} {
3252                         browser_enter $w
3253                 }
3254         }
3257 proc ls_tree {w tree_id name} {
3258         global browser_buffer browser_files browser_stack browser_busy
3260         set browser_buffer($w) {}
3261         set browser_files($w) {}
3262         set browser_busy($w) 1
3264         $w conf -state normal
3265         $w tag remove in_sel 0.0 end
3266         $w delete 0.0 end
3267         if {$browser_stack($w) ne {}} {
3268                 $w image create end \
3269                         -align center -padx 5 -pady 1 \
3270                         -name icon0 \
3271                         -image file_uplevel
3272                 $w insert end {[Up To Parent]}
3273                 lappend browser_files($w) parent
3274         }
3275         lappend browser_stack($w) [list $tree_id $name]
3276         $w conf -state disabled
3278         set cmd [list git ls-tree -z $tree_id]
3279         set fd [open "| $cmd" r]
3280         fconfigure $fd -blocking 0 -translation binary -encoding binary
3281         fileevent $fd readable [list read_ls_tree $fd $w]
3284 proc read_ls_tree {fd w} {
3285         global browser_buffer browser_files browser_status browser_busy
3287         if {![winfo exists $w]} {
3288                 catch {close $fd}
3289                 return
3290         }
3292         append browser_buffer($w) [read $fd]
3293         set pck [split $browser_buffer($w) "\0"]
3294         set browser_buffer($w) [lindex $pck end]
3296         set n [llength $browser_files($w)]
3297         $w conf -state normal
3298         foreach p [lrange $pck 0 end-1] {
3299                 set info [split $p "\t"]
3300                 set path [lindex $info 1]
3301                 set info [split [lindex $info 0] { }]
3302                 set type [lindex $info 1]
3303                 set object [lindex $info 2]
3305                 switch -- $type {
3306                 blob {
3307                         set image file_mod
3308                 }
3309                 tree {
3310                         set image file_dir
3311                         append path /
3312                 }
3313                 default {
3314                         set image file_question
3315                 }
3316                 }
3318                 if {$n > 0} {$w insert end "\n"}
3319                 $w image create end \
3320                         -align center -padx 5 -pady 1 \
3321                         -name icon[incr n] \
3322                         -image $image
3323                 $w insert end [escape_path $path]
3324                 lappend browser_files($w) [list $type $object $path]
3325         }
3326         $w conf -state disabled
3328         if {[eof $fd]} {
3329                 close $fd
3330                 set browser_status($w) Ready.
3331                 set browser_busy($w) 0
3332                 array unset browser_buffer $w
3333                 if {$n > 0} {
3334                         $w tag add in_sel 1.0 2.0
3335                         focus -force $w
3336                 }
3337         }
3340 proc show_blame {commit path} {
3341         global next_browser_id blame_status blame_data
3343         if {[winfo ismapped .]} {
3344                 set w .browser[incr next_browser_id]
3345                 set tl $w
3346                 toplevel $w
3347         } else {
3348                 set w {}
3349                 set tl .
3350         }
3351         set blame_status($w) {Loading current file content...}
3353         label $w.path -text "$commit:$path" \
3354                 -anchor w \
3355                 -justify left \
3356                 -borderwidth 1 \
3357                 -relief sunken \
3358                 -font font_uibold
3359         pack $w.path -side top -fill x
3361         frame $w.out
3362         text $w.out.loaded_t \
3363                 -background white -borderwidth 0 \
3364                 -state disabled \
3365                 -wrap none \
3366                 -height 40 \
3367                 -width 1 \
3368                 -font font_diff
3369         $w.out.loaded_t tag conf annotated -background grey
3371         text $w.out.linenumber_t \
3372                 -background white -borderwidth 0 \
3373                 -state disabled \
3374                 -wrap none \
3375                 -height 40 \
3376                 -width 5 \
3377                 -font font_diff
3378         $w.out.linenumber_t tag conf linenumber -justify right
3380         text $w.out.file_t \
3381                 -background white -borderwidth 0 \
3382                 -state disabled \
3383                 -wrap none \
3384                 -height 40 \
3385                 -width 80 \
3386                 -xscrollcommand [list $w.out.sbx set] \
3387                 -font font_diff
3389         scrollbar $w.out.sbx -orient h -command [list $w.out.file_t xview]
3390         scrollbar $w.out.sby -orient v \
3391                 -command [list scrollbar2many [list \
3392                 $w.out.loaded_t \
3393                 $w.out.linenumber_t \
3394                 $w.out.file_t \
3395                 ] yview]
3396         grid \
3397                 $w.out.linenumber_t \
3398                 $w.out.loaded_t \
3399                 $w.out.file_t \
3400                 $w.out.sby \
3401                 -sticky nsew
3402         grid conf $w.out.sbx -column 2 -sticky we
3403         grid columnconfigure $w.out 2 -weight 1
3404         grid rowconfigure $w.out 0 -weight 1
3405         pack $w.out -fill both -expand 1
3407         label $w.status -textvariable blame_status($w) \
3408                 -anchor w \
3409                 -justify left \
3410                 -borderwidth 1 \
3411                 -relief sunken \
3412                 -font font_ui
3413         pack $w.status -side bottom -fill x
3415         frame $w.cm
3416         text $w.cm.t \
3417                 -background white -borderwidth 0 \
3418                 -state disabled \
3419                 -wrap none \
3420                 -height 10 \
3421                 -width 80 \
3422                 -xscrollcommand [list $w.cm.sbx set] \
3423                 -yscrollcommand [list $w.cm.sby set] \
3424                 -font font_diff
3425         scrollbar $w.cm.sbx -orient h -command [list $w.cm.t xview]
3426         scrollbar $w.cm.sby -orient v -command [list $w.cm.t yview]
3427         pack $w.cm.sby -side right -fill y
3428         pack $w.cm.sbx -side bottom -fill x
3429         pack $w.cm.t -expand 1 -fill both
3430         pack $w.cm -side bottom -fill x
3432         menu $w.ctxm -tearoff 0
3433         $w.ctxm add command -label "Copy Commit" \
3434                 -font font_ui \
3435                 -command "blame_copycommit $w \$cursorW @\$cursorX,\$cursorY"
3437         foreach i [list \
3438                 $w.out.loaded_t \
3439                 $w.out.linenumber_t \
3440                 $w.out.file_t] {
3441                 $i tag conf in_sel \
3442                         -background [$i cget -foreground] \
3443                         -foreground [$i cget -background]
3444                 $i conf -yscrollcommand \
3445                         [list many2scrollbar [list \
3446                         $w.out.loaded_t \
3447                         $w.out.linenumber_t \
3448                         $w.out.file_t \
3449                         ] yview $w.out.sby]
3450                 bind $i <Button-1> "
3451                         blame_click {$w} \\
3452                                 $w.cm.t \\
3453                                 $w.out.linenumber_t \\
3454                                 $w.out.file_t \\
3455                                 $i @%x,%y
3456                         focus $i
3457                 "
3458                 bind_button3 $i "
3459                         set cursorX %x
3460                         set cursorY %y
3461                         set cursorW %W
3462                         tk_popup $w.ctxm %X %Y
3463                 "
3464         }
3466         bind $w.cm.t <Button-1> "focus $w.cm.t"
3467         bind $tl <Visibility> "focus $tl"
3468         bind $tl <Destroy> "
3469                 array unset blame_status {$w}
3470                 array unset blame_data $w,*
3471         "
3472         wm title $tl "[appname] ([reponame]): File Viewer"
3474         set blame_data($w,commit_count) 0
3475         set blame_data($w,commit_list) {}
3476         set blame_data($w,total_lines) 0
3477         set blame_data($w,blame_lines) 0
3478         set blame_data($w,highlight_commit) {}
3479         set blame_data($w,highlight_line) -1
3481         set cmd [list git cat-file blob "$commit:$path"]
3482         set fd [open "| $cmd" r]
3483         fconfigure $fd -blocking 0 -translation lf -encoding binary
3484         fileevent $fd readable [list read_blame_catfile \
3485                 $fd $w $commit $path \
3486                 $w.cm.t $w.out.loaded_t $w.out.linenumber_t $w.out.file_t]
3489 proc read_blame_catfile {fd w commit path w_cmit w_load w_line w_file} {
3490         global blame_status blame_data
3492         if {![winfo exists $w_file]} {
3493                 catch {close $fd}
3494                 return
3495         }
3497         set n $blame_data($w,total_lines)
3498         $w_load conf -state normal
3499         $w_line conf -state normal
3500         $w_file conf -state normal
3501         while {[gets $fd line] >= 0} {
3502                 regsub "\r\$" $line {} line
3503                 incr n
3504                 $w_load insert end "\n"
3505                 $w_line insert end "$n\n" linenumber
3506                 $w_file insert end "$line\n"
3507         }
3508         $w_load conf -state disabled
3509         $w_line conf -state disabled
3510         $w_file conf -state disabled
3511         set blame_data($w,total_lines) $n
3513         if {[eof $fd]} {
3514                 close $fd
3515                 blame_incremental_status $w
3516                 set cmd [list git blame -M -C --incremental]
3517                 lappend cmd $commit -- $path
3518                 set fd [open "| $cmd" r]
3519                 fconfigure $fd -blocking 0 -translation lf -encoding binary
3520                 fileevent $fd readable [list read_blame_incremental $fd $w \
3521                         $w_load $w_cmit $w_line $w_file]
3522         }
3525 proc read_blame_incremental {fd w w_load w_cmit w_line w_file} {
3526         global blame_status blame_data
3528         if {![winfo exists $w_file]} {
3529                 catch {close $fd}
3530                 return
3531         }
3533         while {[gets $fd line] >= 0} {
3534                 if {[regexp {^([a-z0-9]{40}) (\d+) (\d+) (\d+)$} $line line \
3535                         cmit original_line final_line line_count]} {
3536                         set blame_data($w,commit) $cmit
3537                         set blame_data($w,original_line) $original_line
3538                         set blame_data($w,final_line) $final_line
3539                         set blame_data($w,line_count) $line_count
3541                         if {[catch {set g $blame_data($w,$cmit,order)}]} {
3542                                 $w_line tag conf g$cmit
3543                                 $w_file tag conf g$cmit
3544                                 $w_line tag raise in_sel
3545                                 $w_file tag raise in_sel
3546                                 $w_file tag raise sel
3547                                 set blame_data($w,$cmit,order) $blame_data($w,commit_count)
3548                                 incr blame_data($w,commit_count)
3549                                 lappend blame_data($w,commit_list) $cmit
3550                         }
3551                 } elseif {[string match {filename *} $line]} {
3552                         set file [string range $line 9 end]
3553                         set n $blame_data($w,line_count)
3554                         set lno $blame_data($w,final_line)
3555                         set cmit $blame_data($w,commit)
3557                         while {$n > 0} {
3558                                 if {[catch {set g g$blame_data($w,line$lno,commit)}]} {
3559                                         $w_load tag add annotated $lno.0 "$lno.0 lineend + 1c"
3560                                 } else {
3561                                         $w_line tag remove g$g $lno.0 "$lno.0 lineend + 1c"
3562                                         $w_file tag remove g$g $lno.0 "$lno.0 lineend + 1c"
3563                                 }
3565                                 set blame_data($w,line$lno,commit) $cmit
3566                                 set blame_data($w,line$lno,file) $file
3567                                 $w_line tag add g$cmit $lno.0 "$lno.0 lineend + 1c"
3568                                 $w_file tag add g$cmit $lno.0 "$lno.0 lineend + 1c"
3570                                 if {$blame_data($w,highlight_line) == -1} {
3571                                         if {[lindex [$w_file yview] 0] == 0} {
3572                                                 $w_file see $lno.0
3573                                                 blame_showcommit $w $w_cmit $w_line $w_file $lno
3574                                         }
3575                                 } elseif {$blame_data($w,highlight_line) == $lno} {
3576                                         blame_showcommit $w $w_cmit $w_line $w_file $lno
3577                                 }
3579                                 incr n -1
3580                                 incr lno
3581                                 incr blame_data($w,blame_lines)
3582                         }
3584                         set hc $blame_data($w,highlight_commit)
3585                         if {$hc ne {}
3586                                 && [expr {$blame_data($w,$hc,order) + 1}]
3587                                         == $blame_data($w,$cmit,order)} {
3588                                 blame_showcommit $w $w_cmit $w_line $w_file \
3589                                         $blame_data($w,highlight_line)
3590                         }
3591                 } elseif {[regexp {^([a-z-]+) (.*)$} $line line header data]} {
3592                         set blame_data($w,$blame_data($w,commit),$header) $data
3593                 }
3594         }
3596         if {[eof $fd]} {
3597                 close $fd
3598                 set blame_status($w) {Annotation complete.}
3599         } else {
3600                 blame_incremental_status $w
3601         }
3604 proc blame_incremental_status {w} {
3605         global blame_status blame_data
3607         set blame_status($w) [format \
3608                 "Loading annotations... %i of %i lines annotated (%2i%%)" \
3609                 $blame_data($w,blame_lines) \
3610                 $blame_data($w,total_lines) \
3611                 [expr {100 * $blame_data($w,blame_lines)
3612                         / $blame_data($w,total_lines)}]]
3615 proc blame_click {w w_cmit w_line w_file cur_w pos} {
3616         set lno [lindex [split [$cur_w index $pos] .] 0]
3617         if {$lno eq {}} return
3619         $w_line tag remove in_sel 0.0 end
3620         $w_file tag remove in_sel 0.0 end
3621         $w_line tag add in_sel $lno.0 "$lno.0 + 1 line"
3622         $w_file tag add in_sel $lno.0 "$lno.0 + 1 line"
3624         blame_showcommit $w $w_cmit $w_line $w_file $lno
3627 set blame_colors {
3628         #ff4040
3629         #ff40ff
3630         #4040ff
3633 proc blame_showcommit {w w_cmit w_line w_file lno} {
3634         global blame_colors blame_data repo_config
3636         set cmit $blame_data($w,highlight_commit)
3637         if {$cmit ne {}} {
3638                 set idx $blame_data($w,$cmit,order)
3639                 set i 0
3640                 foreach c $blame_colors {
3641                         set h [lindex $blame_data($w,commit_list) [expr {$idx - 1 + $i}]]
3642                         $w_line tag conf g$h -background white
3643                         $w_file tag conf g$h -background white
3644                         incr i
3645                 }
3646         }
3648         $w_cmit conf -state normal
3649         $w_cmit delete 0.0 end
3650         if {[catch {set cmit $blame_data($w,line$lno,commit)}]} {
3651                 set cmit {}
3652                 $w_cmit insert end "Loading annotation..."
3653         } else {
3654                 set idx $blame_data($w,$cmit,order)
3655                 set i 0
3656                 foreach c $blame_colors {
3657                         set h [lindex $blame_data($w,commit_list) [expr {$idx - 1 + $i}]]
3658                         $w_line tag conf g$h -background $c
3659                         $w_file tag conf g$h -background $c
3660                         incr i
3661                 }
3663                 if {[catch {set msg $blame_data($w,$cmit,message)}]} {
3664                         set msg {}
3665                         catch {
3666                                 set fd [open "| git cat-file commit $cmit" r]
3667                                 fconfigure $fd -encoding binary -translation lf
3668                                 if {[catch {set enc $repo_config(i18n.commitencoding)}]} {
3669                                         set enc utf-8
3670                                 }
3671                                 while {[gets $fd line] > 0} {
3672                                         if {[string match {encoding *} $line]} {
3673                                                 set enc [string tolower [string range $line 9 end]]
3674                                         }
3675                                 }
3676                                 fconfigure $fd -encoding $enc
3677                                 set msg [string trim [read $fd]]
3678                                 close $fd
3679                         }
3680                         set blame_data($w,$cmit,message) $msg
3681                 }
3683                 set author_name {}
3684                 set author_email {}
3685                 set author_time {}
3686                 catch {set author_name $blame_data($w,$cmit,author)}
3687                 catch {set author_email $blame_data($w,$cmit,author-mail)}
3688                 catch {set author_time [clock format $blame_data($w,$cmit,author-time)]}
3690                 set committer_name {}
3691                 set committer_email {}
3692                 set committer_time {}
3693                 catch {set committer_name $blame_data($w,$cmit,committer)}
3694                 catch {set committer_email $blame_data($w,$cmit,committer-mail)}
3695                 catch {set committer_time [clock format $blame_data($w,$cmit,committer-time)]}
3697                 $w_cmit insert end "commit $cmit\n"
3698                 $w_cmit insert end "Author: $author_name $author_email $author_time\n"
3699                 $w_cmit insert end "Committer: $committer_name $committer_email $committer_time\n"
3700                 $w_cmit insert end "Original File: [escape_path $blame_data($w,line$lno,file)]\n"
3701                 $w_cmit insert end "\n"
3702                 $w_cmit insert end $msg
3703         }
3704         $w_cmit conf -state disabled
3706         set blame_data($w,highlight_line) $lno
3707         set blame_data($w,highlight_commit) $cmit
3710 proc blame_copycommit {w i pos} {
3711         global blame_data
3712         set lno [lindex [split [$i index $pos] .] 0]
3713         if {![catch {set commit $blame_data($w,line$lno,commit)}]} {
3714                 clipboard clear
3715                 clipboard append \
3716                         -format STRING \
3717                         -type STRING \
3718                         -- $commit
3719         }
3722 ######################################################################
3723 ##
3724 ## icons
3726 set filemask {
3727 #define mask_width 14
3728 #define mask_height 15
3729 static unsigned char mask_bits[] = {
3730    0xfe, 0x1f, 0xfe, 0x1f, 0xfe, 0x1f, 0xfe, 0x1f, 0xfe, 0x1f, 0xfe, 0x1f,
3731    0xfe, 0x1f, 0xfe, 0x1f, 0xfe, 0x1f, 0xfe, 0x1f, 0xfe, 0x1f, 0xfe, 0x1f,
3732    0xfe, 0x1f, 0xfe, 0x1f, 0xfe, 0x1f};
3735 image create bitmap file_plain -background white -foreground black -data {
3736 #define plain_width 14
3737 #define plain_height 15
3738 static unsigned char plain_bits[] = {
3739    0xfe, 0x01, 0x02, 0x03, 0x02, 0x05, 0x02, 0x09, 0x02, 0x1f, 0x02, 0x10,
3740    0x02, 0x10, 0x02, 0x10, 0x02, 0x10, 0x02, 0x10, 0x02, 0x10, 0x02, 0x10,
3741    0x02, 0x10, 0x02, 0x10, 0xfe, 0x1f};
3742 } -maskdata $filemask
3744 image create bitmap file_mod -background white -foreground blue -data {
3745 #define mod_width 14
3746 #define mod_height 15
3747 static unsigned char mod_bits[] = {
3748    0xfe, 0x01, 0x02, 0x03, 0x7a, 0x05, 0x02, 0x09, 0x7a, 0x1f, 0x02, 0x10,
3749    0xfa, 0x17, 0x02, 0x10, 0xfa, 0x17, 0x02, 0x10, 0xfa, 0x17, 0x02, 0x10,
3750    0xfa, 0x17, 0x02, 0x10, 0xfe, 0x1f};
3751 } -maskdata $filemask
3753 image create bitmap file_fulltick -background white -foreground "#007000" -data {
3754 #define file_fulltick_width 14
3755 #define file_fulltick_height 15
3756 static unsigned char file_fulltick_bits[] = {
3757    0xfe, 0x01, 0x02, 0x1a, 0x02, 0x0c, 0x02, 0x0c, 0x02, 0x16, 0x02, 0x16,
3758    0x02, 0x13, 0x00, 0x13, 0x86, 0x11, 0x8c, 0x11, 0xd8, 0x10, 0xf2, 0x10,
3759    0x62, 0x10, 0x02, 0x10, 0xfe, 0x1f};
3760 } -maskdata $filemask
3762 image create bitmap file_parttick -background white -foreground "#005050" -data {
3763 #define parttick_width 14
3764 #define parttick_height 15
3765 static unsigned char parttick_bits[] = {
3766    0xfe, 0x01, 0x02, 0x03, 0x7a, 0x05, 0x02, 0x09, 0x7a, 0x1f, 0x02, 0x10,
3767    0x7a, 0x14, 0x02, 0x16, 0x02, 0x13, 0x8a, 0x11, 0xda, 0x10, 0x72, 0x10,
3768    0x22, 0x10, 0x02, 0x10, 0xfe, 0x1f};
3769 } -maskdata $filemask
3771 image create bitmap file_question -background white -foreground black -data {
3772 #define file_question_width 14
3773 #define file_question_height 15
3774 static unsigned char file_question_bits[] = {
3775    0xfe, 0x01, 0x02, 0x02, 0xe2, 0x04, 0xf2, 0x09, 0x1a, 0x1b, 0x0a, 0x13,
3776    0x82, 0x11, 0xc2, 0x10, 0x62, 0x10, 0x62, 0x10, 0x02, 0x10, 0x62, 0x10,
3777    0x62, 0x10, 0x02, 0x10, 0xfe, 0x1f};
3778 } -maskdata $filemask
3780 image create bitmap file_removed -background white -foreground red -data {
3781 #define file_removed_width 14
3782 #define file_removed_height 15
3783 static unsigned char file_removed_bits[] = {
3784    0xfe, 0x01, 0x02, 0x03, 0x02, 0x05, 0x02, 0x09, 0x02, 0x1f, 0x02, 0x10,
3785    0x1a, 0x16, 0x32, 0x13, 0xe2, 0x11, 0xc2, 0x10, 0xe2, 0x11, 0x32, 0x13,
3786    0x1a, 0x16, 0x02, 0x10, 0xfe, 0x1f};
3787 } -maskdata $filemask
3789 image create bitmap file_merge -background white -foreground blue -data {
3790 #define file_merge_width 14
3791 #define file_merge_height 15
3792 static unsigned char file_merge_bits[] = {
3793    0xfe, 0x01, 0x02, 0x03, 0x62, 0x05, 0x62, 0x09, 0x62, 0x1f, 0x62, 0x10,
3794    0xfa, 0x11, 0xf2, 0x10, 0x62, 0x10, 0x02, 0x10, 0xfa, 0x17, 0x02, 0x10,
3795    0xfa, 0x17, 0x02, 0x10, 0xfe, 0x1f};
3796 } -maskdata $filemask
3798 set file_dir_data {
3799 #define file_width 18
3800 #define file_height 18
3801 static unsigned char file_bits[] = {
3802   0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0xf8, 0x03, 0x00,
3803   0x0c, 0x03, 0x00, 0x04, 0xfe, 0x00, 0x06, 0x80, 0x00, 0xff, 0x9f, 0x00,
3804   0x03, 0x98, 0x00, 0x02, 0x90, 0x00, 0x06, 0xb0, 0x00, 0x04, 0xa0, 0x00,
3805   0x0c, 0xe0, 0x00, 0x08, 0xc0, 0x00, 0xf8, 0xff, 0x00, 0x00, 0x00, 0x00,
3806   0x00, 0x00, 0x00, 0x00, 0x00, 0x00};
3808 image create bitmap file_dir -background white -foreground blue \
3809         -data $file_dir_data -maskdata $file_dir_data
3810 unset file_dir_data
3812 set file_uplevel_data {
3813 #define up_width 15
3814 #define up_height 15
3815 static unsigned char up_bits[] = {
3816   0x80, 0x00, 0xc0, 0x01, 0xe0, 0x03, 0xf0, 0x07, 0xf8, 0x0f, 0xfc, 0x1f,
3817   0xfe, 0x3f, 0xc0, 0x01, 0xc0, 0x01, 0xc0, 0x01, 0xc0, 0x01, 0xc0, 0x01,
3818   0xc0, 0x01, 0xc0, 0x01, 0x00, 0x00};
3820 image create bitmap file_uplevel -background white -foreground red \
3821         -data $file_uplevel_data -maskdata $file_uplevel_data
3822 unset file_uplevel_data
3824 set ui_index .vpane.files.index.list
3825 set ui_workdir .vpane.files.workdir.list
3827 set all_icons(_$ui_index)   file_plain
3828 set all_icons(A$ui_index)   file_fulltick
3829 set all_icons(M$ui_index)   file_fulltick
3830 set all_icons(D$ui_index)   file_removed
3831 set all_icons(U$ui_index)   file_merge
3833 set all_icons(_$ui_workdir) file_plain
3834 set all_icons(M$ui_workdir) file_mod
3835 set all_icons(D$ui_workdir) file_question
3836 set all_icons(U$ui_workdir) file_merge
3837 set all_icons(O$ui_workdir) file_plain
3839 set max_status_desc 0
3840 foreach i {
3841                 {__ "Unmodified"}
3843                 {_M "Modified, not staged"}
3844                 {M_ "Staged for commit"}
3845                 {MM "Portions staged for commit"}
3846                 {MD "Staged for commit, missing"}
3848                 {_O "Untracked, not staged"}
3849                 {A_ "Staged for commit"}
3850                 {AM "Portions staged for commit"}
3851                 {AD "Staged for commit, missing"}
3853                 {_D "Missing"}
3854                 {D_ "Staged for removal"}
3855                 {DO "Staged for removal, still present"}
3857                 {U_ "Requires merge resolution"}
3858                 {UU "Requires merge resolution"}
3859                 {UM "Requires merge resolution"}
3860                 {UD "Requires merge resolution"}
3861         } {
3862         if {$max_status_desc < [string length [lindex $i 1]]} {
3863                 set max_status_desc [string length [lindex $i 1]]
3864         }
3865         set all_descs([lindex $i 0]) [lindex $i 1]
3867 unset i
3869 ######################################################################
3870 ##
3871 ## util
3873 proc bind_button3 {w cmd} {
3874         bind $w <Any-Button-3> $cmd
3875         if {[is_MacOSX]} {
3876                 bind $w <Control-Button-1> $cmd
3877         }
3880 proc scrollbar2many {list mode args} {
3881         foreach w $list {eval $w $mode $args}
3884 proc many2scrollbar {list mode sb top bottom} {
3885         $sb set $top $bottom
3886         foreach w $list {$w $mode moveto $top}
3889 proc incr_font_size {font {amt 1}} {
3890         set sz [font configure $font -size]
3891         incr sz $amt
3892         font configure $font -size $sz
3893         font configure ${font}bold -size $sz
3896 proc hook_failed_popup {hook msg} {
3897         set w .hookfail
3898         toplevel $w
3900         frame $w.m
3901         label $w.m.l1 -text "$hook hook failed:" \
3902                 -anchor w \
3903                 -justify left \
3904                 -font font_uibold
3905         text $w.m.t \
3906                 -background white -borderwidth 1 \
3907                 -relief sunken \
3908                 -width 80 -height 10 \
3909                 -font font_diff \
3910                 -yscrollcommand [list $w.m.sby set]
3911         label $w.m.l2 \
3912                 -text {You must correct the above errors before committing.} \
3913                 -anchor w \
3914                 -justify left \
3915                 -font font_uibold
3916         scrollbar $w.m.sby -command [list $w.m.t yview]
3917         pack $w.m.l1 -side top -fill x
3918         pack $w.m.l2 -side bottom -fill x
3919         pack $w.m.sby -side right -fill y
3920         pack $w.m.t -side left -fill both -expand 1
3921         pack $w.m -side top -fill both -expand 1 -padx 5 -pady 10
3923         $w.m.t insert 1.0 $msg
3924         $w.m.t conf -state disabled
3926         button $w.ok -text OK \
3927                 -width 15 \
3928                 -font font_ui \
3929                 -command "destroy $w"
3930         pack $w.ok -side bottom -anchor e -pady 10 -padx 10
3932         bind $w <Visibility> "grab $w; focus $w"
3933         bind $w <Key-Return> "destroy $w"
3934         wm title $w "[appname] ([reponame]): error"
3935         tkwait window $w
3938 set next_console_id 0
3940 proc new_console {short_title long_title} {
3941         global next_console_id console_data
3942         set w .console[incr next_console_id]
3943         set console_data($w) [list $short_title $long_title]
3944         return [console_init $w]
3947 proc console_init {w} {
3948         global console_cr console_data M1B
3950         set console_cr($w) 1.0
3951         toplevel $w
3952         frame $w.m
3953         label $w.m.l1 -text "[lindex $console_data($w) 1]:" \
3954                 -anchor w \
3955                 -justify left \
3956                 -font font_uibold
3957         text $w.m.t \
3958                 -background white -borderwidth 1 \
3959                 -relief sunken \
3960                 -width 80 -height 10 \
3961                 -font font_diff \
3962                 -state disabled \
3963                 -yscrollcommand [list $w.m.sby set]
3964         label $w.m.s -text {Working... please wait...} \
3965                 -anchor w \
3966                 -justify left \
3967                 -font font_uibold
3968         scrollbar $w.m.sby -command [list $w.m.t yview]
3969         pack $w.m.l1 -side top -fill x
3970         pack $w.m.s -side bottom -fill x
3971         pack $w.m.sby -side right -fill y
3972         pack $w.m.t -side left -fill both -expand 1
3973         pack $w.m -side top -fill both -expand 1 -padx 5 -pady 10
3975         menu $w.ctxm -tearoff 0
3976         $w.ctxm add command -label "Copy" \
3977                 -font font_ui \
3978                 -command "tk_textCopy $w.m.t"
3979         $w.ctxm add command -label "Select All" \
3980                 -font font_ui \
3981                 -command "focus $w.m.t;$w.m.t tag add sel 0.0 end"
3982         $w.ctxm add command -label "Copy All" \
3983                 -font font_ui \
3984                 -command "
3985                         $w.m.t tag add sel 0.0 end
3986                         tk_textCopy $w.m.t
3987                         $w.m.t tag remove sel 0.0 end
3988                 "
3990         button $w.ok -text {Close} \
3991                 -font font_ui \
3992                 -state disabled \
3993                 -command "destroy $w"
3994         pack $w.ok -side bottom -anchor e -pady 10 -padx 10
3996         bind_button3 $w.m.t "tk_popup $w.ctxm %X %Y"
3997         bind $w.m.t <$M1B-Key-a> "$w.m.t tag add sel 0.0 end;break"
3998         bind $w.m.t <$M1B-Key-A> "$w.m.t tag add sel 0.0 end;break"
3999         bind $w <Visibility> "focus $w"
4000         wm title $w "[appname] ([reponame]): [lindex $console_data($w) 0]"
4001         return $w
4004 proc console_exec {w cmd after} {
4005         # -- Cygwin's Tcl tosses the enviroment when we exec our child.
4006         #    But most users need that so we have to relogin. :-(
4007         #
4008         if {[is_Cygwin]} {
4009                 set cmd [list sh --login -c "cd \"[pwd]\" && [join $cmd { }]"]
4010         }
4012         # -- Tcl won't let us redirect both stdout and stderr to
4013         #    the same pipe.  So pass it through cat...
4014         #
4015         set cmd [concat | $cmd |& cat]
4017         set fd_f [open $cmd r]
4018         fconfigure $fd_f -blocking 0 -translation binary
4019         fileevent $fd_f readable [list console_read $w $fd_f $after]
4022 proc console_read {w fd after} {
4023         global console_cr
4025         set buf [read $fd]
4026         if {$buf ne {}} {
4027                 if {![winfo exists $w]} {console_init $w}
4028                 $w.m.t conf -state normal
4029                 set c 0
4030                 set n [string length $buf]
4031                 while {$c < $n} {
4032                         set cr [string first "\r" $buf $c]
4033                         set lf [string first "\n" $buf $c]
4034                         if {$cr < 0} {set cr [expr {$n + 1}]}
4035                         if {$lf < 0} {set lf [expr {$n + 1}]}
4037                         if {$lf < $cr} {
4038                                 $w.m.t insert end [string range $buf $c $lf]
4039                                 set console_cr($w) [$w.m.t index {end -1c}]
4040                                 set c $lf
4041                                 incr c
4042                         } else {
4043                                 $w.m.t delete $console_cr($w) end
4044                                 $w.m.t insert end "\n"
4045                                 $w.m.t insert end [string range $buf $c $cr]
4046                                 set c $cr
4047                                 incr c
4048                         }
4049                 }
4050                 $w.m.t conf -state disabled
4051                 $w.m.t see end
4052         }
4054         fconfigure $fd -blocking 1
4055         if {[eof $fd]} {
4056                 if {[catch {close $fd}]} {
4057                         set ok 0
4058                 } else {
4059                         set ok 1
4060                 }
4061                 uplevel #0 $after $w $ok
4062                 return
4063         }
4064         fconfigure $fd -blocking 0
4067 proc console_chain {cmdlist w {ok 1}} {
4068         if {$ok} {
4069                 if {[llength $cmdlist] == 0} {
4070                         console_done $w $ok
4071                         return
4072                 }
4074                 set cmd [lindex $cmdlist 0]
4075                 set cmdlist [lrange $cmdlist 1 end]
4077                 if {[lindex $cmd 0] eq {console_exec}} {
4078                         console_exec $w \
4079                                 [lindex $cmd 1] \
4080                                 [list console_chain $cmdlist]
4081                 } else {
4082                         uplevel #0 $cmd $cmdlist $w $ok
4083                 }
4084         } else {
4085                 console_done $w $ok
4086         }
4089 proc console_done {args} {
4090         global console_cr console_data
4092         switch -- [llength $args] {
4093         2 {
4094                 set w [lindex $args 0]
4095                 set ok [lindex $args 1]
4096         }
4097         3 {
4098                 set w [lindex $args 1]
4099                 set ok [lindex $args 2]
4100         }
4101         default {
4102                 error "wrong number of args: console_done ?ignored? w ok"
4103         }
4104         }
4106         if {$ok} {
4107                 if {[winfo exists $w]} {
4108                         $w.m.s conf -background green -text {Success}
4109                         $w.ok conf -state normal
4110                         focus $w.ok
4111                 }
4112         } else {
4113                 if {![winfo exists $w]} {
4114                         console_init $w
4115                 }
4116                 $w.m.s conf -background red -text {Error: Command Failed}
4117                 $w.ok conf -state normal
4118                 focus $w.ok
4119         }
4121         array unset console_cr $w
4122         array unset console_data $w
4125 ######################################################################
4126 ##
4127 ## ui commands
4129 set starting_gitk_msg {Starting gitk... please wait...}
4131 proc do_gitk {revs} {
4132         global env ui_status_value starting_gitk_msg
4134         # -- Always start gitk through whatever we were loaded with.  This
4135         #    lets us bypass using shell process on Windows systems.
4136         #
4137         set cmd [info nameofexecutable]
4138         lappend cmd [gitexec gitk]
4139         if {$revs ne {}} {
4140                 append cmd { }
4141                 append cmd $revs
4142         }
4144         if {[catch {eval exec $cmd &} err]} {
4145                 error_popup "Failed to start gitk:\n\n$err"
4146         } else {
4147                 set ui_status_value $starting_gitk_msg
4148                 after 10000 {
4149                         if {$ui_status_value eq $starting_gitk_msg} {
4150                                 set ui_status_value {Ready.}
4151                         }
4152                 }
4153         }
4156 proc do_stats {} {
4157         set fd [open "| git count-objects -v" r]
4158         while {[gets $fd line] > 0} {
4159                 if {[regexp {^([^:]+): (\d+)$} $line _ name value]} {
4160                         set stats($name) $value
4161                 }
4162         }
4163         close $fd
4165         set packed_sz 0
4166         foreach p [glob -directory [gitdir objects pack] \
4167                 -type f \
4168                 -nocomplain -- *] {
4169                 incr packed_sz [file size $p]
4170         }
4171         if {$packed_sz > 0} {
4172                 set stats(size-pack) [expr {$packed_sz / 1024}]
4173         }
4175         set w .stats_view
4176         toplevel $w
4177         wm geometry $w "+[winfo rootx .]+[winfo rooty .]"
4179         label $w.header -text {Database Statistics} \
4180                 -font font_uibold
4181         pack $w.header -side top -fill x
4183         frame $w.buttons -border 1
4184         button $w.buttons.close -text Close \
4185                 -font font_ui \
4186                 -default active \
4187                 -command [list destroy $w]
4188         button $w.buttons.gc -text {Compress Database} \
4189                 -font font_ui \
4190                 -default normal \
4191                 -command "destroy $w;do_gc"
4192         pack $w.buttons.close -side right
4193         pack $w.buttons.gc -side left
4194         pack $w.buttons -side bottom -fill x -pady 10 -padx 10
4196         frame $w.stat -borderwidth 1 -relief solid
4197         foreach s {
4198                 {count           {Number of loose objects}}
4199                 {size            {Disk space used by loose objects} { KiB}}
4200                 {in-pack         {Number of packed objects}}
4201                 {packs           {Number of packs}}
4202                 {size-pack       {Disk space used by packed objects} { KiB}}
4203                 {prune-packable  {Packed objects waiting for pruning}}
4204                 {garbage         {Garbage files}}
4205                 } {
4206                 set name [lindex $s 0]
4207                 set label [lindex $s 1]
4208                 if {[catch {set value $stats($name)}]} continue
4209                 if {[llength $s] > 2} {
4210                         set value "$value[lindex $s 2]"
4211                 }
4213                 label $w.stat.l_$name -text "$label:" -anchor w -font font_ui
4214                 label $w.stat.v_$name -text $value -anchor w -font font_ui
4215                 grid $w.stat.l_$name $w.stat.v_$name -sticky we -padx {0 5}
4216         }
4217         pack $w.stat -pady 10 -padx 10
4219         bind $w <Visibility> "grab $w; focus $w.buttons.close"
4220         bind $w <Key-Escape> [list destroy $w]
4221         bind $w <Key-Return> [list destroy $w]
4222         wm title $w "[appname] ([reponame]): Database Statistics"
4223         tkwait window $w
4226 proc do_gc {} {
4227         set w [new_console {gc} {Compressing the object database}]
4228         console_chain {
4229                 {console_exec {git pack-refs --prune}}
4230                 {console_exec {git reflog expire --all}}
4231                 {console_exec {git repack -a -d -l}}
4232                 {console_exec {git rerere gc}}
4233         } $w
4236 proc do_fsck_objects {} {
4237         set w [new_console {fsck-objects} \
4238                 {Verifying the object database with fsck-objects}]
4239         set cmd [list git fsck-objects]
4240         lappend cmd --full
4241         lappend cmd --cache
4242         lappend cmd --strict
4243         console_exec $w $cmd console_done
4246 set is_quitting 0
4248 proc do_quit {} {
4249         global ui_comm is_quitting repo_config commit_type
4251         if {$is_quitting} return
4252         set is_quitting 1
4254         if {[winfo exists $ui_comm]} {
4255                 # -- Stash our current commit buffer.
4256                 #
4257                 set save [gitdir GITGUI_MSG]
4258                 set msg [string trim [$ui_comm get 0.0 end]]
4259                 regsub -all -line {[ \r\t]+$} $msg {} msg
4260                 if {(![string match amend* $commit_type]
4261                         || [$ui_comm edit modified])
4262                         && $msg ne {}} {
4263                         catch {
4264                                 set fd [open $save w]
4265                                 puts -nonewline $fd $msg
4266                                 close $fd
4267                         }
4268                 } else {
4269                         catch {file delete $save}
4270                 }
4272                 # -- Stash our current window geometry into this repository.
4273                 #
4274                 set cfg_geometry [list]
4275                 lappend cfg_geometry [wm geometry .]
4276                 lappend cfg_geometry [lindex [.vpane sash coord 0] 1]
4277                 lappend cfg_geometry [lindex [.vpane.files sash coord 0] 0]
4278                 if {[catch {set rc_geometry $repo_config(gui.geometry)}]} {
4279                         set rc_geometry {}
4280                 }
4281                 if {$cfg_geometry ne $rc_geometry} {
4282                         catch {git config gui.geometry $cfg_geometry}
4283                 }
4284         }
4286         destroy .
4289 proc do_rescan {} {
4290         rescan {set ui_status_value {Ready.}}
4293 proc unstage_helper {txt paths} {
4294         global file_states current_diff_path
4296         if {![lock_index begin-update]} return
4298         set pathList [list]
4299         set after {}
4300         foreach path $paths {
4301                 switch -glob -- [lindex $file_states($path) 0] {
4302                 A? -
4303                 M? -
4304                 D? {
4305                         lappend pathList $path
4306                         if {$path eq $current_diff_path} {
4307                                 set after {reshow_diff;}
4308                         }
4309                 }
4310                 }
4311         }
4312         if {$pathList eq {}} {
4313                 unlock_index
4314         } else {
4315                 update_indexinfo \
4316                         $txt \
4317                         $pathList \
4318                         [concat $after {set ui_status_value {Ready.}}]
4319         }
4322 proc do_unstage_selection {} {
4323         global current_diff_path selected_paths
4325         if {[array size selected_paths] > 0} {
4326                 unstage_helper \
4327                         {Unstaging selected files from commit} \
4328                         [array names selected_paths]
4329         } elseif {$current_diff_path ne {}} {
4330                 unstage_helper \
4331                         "Unstaging [short_path $current_diff_path] from commit" \
4332                         [list $current_diff_path]
4333         }
4336 proc add_helper {txt paths} {
4337         global file_states current_diff_path
4339         if {![lock_index begin-update]} return
4341         set pathList [list]
4342         set after {}
4343         foreach path $paths {
4344                 switch -glob -- [lindex $file_states($path) 0] {
4345                 _O -
4346                 ?M -
4347                 ?D -
4348                 U? {
4349                         lappend pathList $path
4350                         if {$path eq $current_diff_path} {
4351                                 set after {reshow_diff;}
4352                         }
4353                 }
4354                 }
4355         }
4356         if {$pathList eq {}} {
4357                 unlock_index
4358         } else {
4359                 update_index \
4360                         $txt \
4361                         $pathList \
4362                         [concat $after {set ui_status_value {Ready to commit.}}]
4363         }
4366 proc do_add_selection {} {
4367         global current_diff_path selected_paths
4369         if {[array size selected_paths] > 0} {
4370                 add_helper \
4371                         {Adding selected files} \
4372                         [array names selected_paths]
4373         } elseif {$current_diff_path ne {}} {
4374                 add_helper \
4375                         "Adding [short_path $current_diff_path]" \
4376                         [list $current_diff_path]
4377         }
4380 proc do_add_all {} {
4381         global file_states
4383         set paths [list]
4384         foreach path [array names file_states] {
4385                 switch -glob -- [lindex $file_states($path) 0] {
4386                 U? {continue}
4387                 ?M -
4388                 ?D {lappend paths $path}
4389                 }
4390         }
4391         add_helper {Adding all changed files} $paths
4394 proc revert_helper {txt paths} {
4395         global file_states current_diff_path
4397         if {![lock_index begin-update]} return
4399         set pathList [list]
4400         set after {}
4401         foreach path $paths {
4402                 switch -glob -- [lindex $file_states($path) 0] {
4403                 U? {continue}
4404                 ?M -
4405                 ?D {
4406                         lappend pathList $path
4407                         if {$path eq $current_diff_path} {
4408                                 set after {reshow_diff;}
4409                         }
4410                 }
4411                 }
4412         }
4414         set n [llength $pathList]
4415         if {$n == 0} {
4416                 unlock_index
4417                 return
4418         } elseif {$n == 1} {
4419                 set s "[short_path [lindex $pathList]]"
4420         } else {
4421                 set s "these $n files"
4422         }
4424         set reply [tk_dialog \
4425                 .confirm_revert \
4426                 "[appname] ([reponame])" \
4427                 "Revert changes in $s?
4429 Any unadded changes will be permanently lost by the revert." \
4430                 question \
4431                 1 \
4432                 {Do Nothing} \
4433                 {Revert Changes} \
4434                 ]
4435         if {$reply == 1} {
4436                 checkout_index \
4437                         $txt \
4438                         $pathList \
4439                         [concat $after {set ui_status_value {Ready.}}]
4440         } else {
4441                 unlock_index
4442         }
4445 proc do_revert_selection {} {
4446         global current_diff_path selected_paths
4448         if {[array size selected_paths] > 0} {
4449                 revert_helper \
4450                         {Reverting selected files} \
4451                         [array names selected_paths]
4452         } elseif {$current_diff_path ne {}} {
4453                 revert_helper \
4454                         "Reverting [short_path $current_diff_path]" \
4455                         [list $current_diff_path]
4456         }
4459 proc do_signoff {} {
4460         global ui_comm
4462         set me [committer_ident]
4463         if {$me eq {}} return
4465         set sob "Signed-off-by: $me"
4466         set last [$ui_comm get {end -1c linestart} {end -1c}]
4467         if {$last ne $sob} {
4468                 $ui_comm edit separator
4469                 if {$last ne {}
4470                         && ![regexp {^[A-Z][A-Za-z]*-[A-Za-z-]+: *} $last]} {
4471                         $ui_comm insert end "\n"
4472                 }
4473                 $ui_comm insert end "\n$sob"
4474                 $ui_comm edit separator
4475                 $ui_comm see end
4476         }
4479 proc do_select_commit_type {} {
4480         global commit_type selected_commit_type
4482         if {$selected_commit_type eq {new}
4483                 && [string match amend* $commit_type]} {
4484                 create_new_commit
4485         } elseif {$selected_commit_type eq {amend}
4486                 && ![string match amend* $commit_type]} {
4487                 load_last_commit
4489                 # The amend request was rejected...
4490                 #
4491                 if {![string match amend* $commit_type]} {
4492                         set selected_commit_type new
4493                 }
4494         }
4497 proc do_commit {} {
4498         commit_tree
4501 proc do_about {} {
4502         global appvers copyright
4503         global tcl_patchLevel tk_patchLevel
4505         set w .about_dialog
4506         toplevel $w
4507         wm geometry $w "+[winfo rootx .]+[winfo rooty .]"
4509         label $w.header -text "About [appname]" \
4510                 -font font_uibold
4511         pack $w.header -side top -fill x
4513         frame $w.buttons
4514         button $w.buttons.close -text {Close} \
4515                 -font font_ui \
4516                 -default active \
4517                 -command [list destroy $w]
4518         pack $w.buttons.close -side right
4519         pack $w.buttons -side bottom -fill x -pady 10 -padx 10
4521         label $w.desc \
4522                 -text "git-gui - a graphical user interface for Git.
4523 $copyright" \
4524                 -padx 5 -pady 5 \
4525                 -justify left \
4526                 -anchor w \
4527                 -borderwidth 1 \
4528                 -relief solid \
4529                 -font font_ui
4530         pack $w.desc -side top -fill x -padx 5 -pady 5
4532         set v {}
4533         append v "git-gui version $appvers\n"
4534         append v "[git version]\n"
4535         append v "\n"
4536         if {$tcl_patchLevel eq $tk_patchLevel} {
4537                 append v "Tcl/Tk version $tcl_patchLevel"
4538         } else {
4539                 append v "Tcl version $tcl_patchLevel"
4540                 append v ", Tk version $tk_patchLevel"
4541         }
4543         label $w.vers \
4544                 -text $v \
4545                 -padx 5 -pady 5 \
4546                 -justify left \
4547                 -anchor w \
4548                 -borderwidth 1 \
4549                 -relief solid \
4550                 -font font_ui
4551         pack $w.vers -side top -fill x -padx 5 -pady 5
4553         menu $w.ctxm -tearoff 0
4554         $w.ctxm add command \
4555                 -label {Copy} \
4556                 -font font_ui \
4557                 -command "
4558                 clipboard clear
4559                 clipboard append -format STRING -type STRING -- \[$w.vers cget -text\]
4560         "
4562         bind $w <Visibility> "grab $w; focus $w.buttons.close"
4563         bind $w <Key-Escape> "destroy $w"
4564         bind $w <Key-Return> "destroy $w"
4565         bind_button3 $w.vers "tk_popup $w.ctxm %X %Y; grab $w; focus $w"
4566         wm title $w "About [appname]"
4567         tkwait window $w
4570 proc do_options {} {
4571         global repo_config global_config font_descs
4572         global repo_config_new global_config_new
4574         array unset repo_config_new
4575         array unset global_config_new
4576         foreach name [array names repo_config] {
4577                 set repo_config_new($name) $repo_config($name)
4578         }
4579         load_config 1
4580         foreach name [array names repo_config] {
4581                 switch -- $name {
4582                 gui.diffcontext {continue}
4583                 }
4584                 set repo_config_new($name) $repo_config($name)
4585         }
4586         foreach name [array names global_config] {
4587                 set global_config_new($name) $global_config($name)
4588         }
4590         set w .options_editor
4591         toplevel $w
4592         wm geometry $w "+[winfo rootx .]+[winfo rooty .]"
4594         label $w.header -text "Options" \
4595                 -font font_uibold
4596         pack $w.header -side top -fill x
4598         frame $w.buttons
4599         button $w.buttons.restore -text {Restore Defaults} \
4600                 -font font_ui \
4601                 -default normal \
4602                 -command do_restore_defaults
4603         pack $w.buttons.restore -side left
4604         button $w.buttons.save -text Save \
4605                 -font font_ui \
4606                 -default active \
4607                 -command [list do_save_config $w]
4608         pack $w.buttons.save -side right
4609         button $w.buttons.cancel -text {Cancel} \
4610                 -font font_ui \
4611                 -default normal \
4612                 -command [list destroy $w]
4613         pack $w.buttons.cancel -side right -padx 5
4614         pack $w.buttons -side bottom -fill x -pady 10 -padx 10
4616         labelframe $w.repo -text "[reponame] Repository" \
4617                 -font font_ui
4618         labelframe $w.global -text {Global (All Repositories)} \
4619                 -font font_ui
4620         pack $w.repo -side left -fill both -expand 1 -pady 5 -padx 5
4621         pack $w.global -side right -fill both -expand 1 -pady 5 -padx 5
4623         set optid 0
4624         foreach option {
4625                 {t user.name {User Name}}
4626                 {t user.email {Email Address}}
4628                 {b merge.summary {Summarize Merge Commits}}
4629                 {i-1..5 merge.verbosity {Merge Verbosity}}
4631                 {b gui.trustmtime  {Trust File Modification Timestamps}}
4632                 {i-1..99 gui.diffcontext {Number of Diff Context Lines}}
4633                 {t gui.newbranchtemplate {New Branch Name Template}}
4634                 } {
4635                 set type [lindex $option 0]
4636                 set name [lindex $option 1]
4637                 set text [lindex $option 2]
4638                 incr optid
4639                 foreach f {repo global} {
4640                         switch -glob -- $type {
4641                         b {
4642                                 checkbutton $w.$f.$optid -text $text \
4643                                         -variable ${f}_config_new($name) \
4644                                         -onvalue true \
4645                                         -offvalue false \
4646                                         -font font_ui
4647                                 pack $w.$f.$optid -side top -anchor w
4648                         }
4649                         i-* {
4650                                 regexp -- {-(\d+)\.\.(\d+)$} $type _junk min max
4651                                 frame $w.$f.$optid
4652                                 label $w.$f.$optid.l -text "$text:" -font font_ui
4653                                 pack $w.$f.$optid.l -side left -anchor w -fill x
4654                                 spinbox $w.$f.$optid.v \
4655                                         -textvariable ${f}_config_new($name) \
4656                                         -from $min \
4657                                         -to $max \
4658                                         -increment 1 \
4659                                         -width [expr {1 + [string length $max]}] \
4660                                         -font font_ui
4661                                 bind $w.$f.$optid.v <FocusIn> {%W selection range 0 end}
4662                                 pack $w.$f.$optid.v -side right -anchor e -padx 5
4663                                 pack $w.$f.$optid -side top -anchor w -fill x
4664                         }
4665                         t {
4666                                 frame $w.$f.$optid
4667                                 label $w.$f.$optid.l -text "$text:" -font font_ui
4668                                 entry $w.$f.$optid.v \
4669                                         -borderwidth 1 \
4670                                         -relief sunken \
4671                                         -width 20 \
4672                                         -textvariable ${f}_config_new($name) \
4673                                         -font font_ui
4674                                 pack $w.$f.$optid.l -side left -anchor w
4675                                 pack $w.$f.$optid.v -side left -anchor w \
4676                                         -fill x -expand 1 \
4677                                         -padx 5
4678                                 pack $w.$f.$optid -side top -anchor w -fill x
4679                         }
4680                         }
4681                 }
4682         }
4684         set all_fonts [lsort [font families]]
4685         foreach option $font_descs {
4686                 set name [lindex $option 0]
4687                 set font [lindex $option 1]
4688                 set text [lindex $option 2]
4690                 set global_config_new(gui.$font^^family) \
4691                         [font configure $font -family]
4692                 set global_config_new(gui.$font^^size) \
4693                         [font configure $font -size]
4695                 frame $w.global.$name
4696                 label $w.global.$name.l -text "$text:" -font font_ui
4697                 pack $w.global.$name.l -side left -anchor w -fill x
4698                 set fontmenu [eval tk_optionMenu $w.global.$name.family \
4699                         global_config_new(gui.$font^^family) \
4700                         $all_fonts]
4701                 $w.global.$name.family configure -font font_ui
4702                 $fontmenu configure -font font_ui
4703                 spinbox $w.global.$name.size \
4704                         -textvariable global_config_new(gui.$font^^size) \
4705                         -from 2 -to 80 -increment 1 \
4706                         -width 3 \
4707                         -font font_ui
4708                 bind $w.global.$name.size <FocusIn> {%W selection range 0 end}
4709                 pack $w.global.$name.size -side right -anchor e
4710                 pack $w.global.$name.family -side right -anchor e
4711                 pack $w.global.$name -side top -anchor w -fill x
4712         }
4714         bind $w <Visibility> "grab $w; focus $w.buttons.save"
4715         bind $w <Key-Escape> "destroy $w"
4716         wm title $w "[appname] ([reponame]): Options"
4717         tkwait window $w
4720 proc do_restore_defaults {} {
4721         global font_descs default_config repo_config
4722         global repo_config_new global_config_new
4724         foreach name [array names default_config] {
4725                 set repo_config_new($name) $default_config($name)
4726                 set global_config_new($name) $default_config($name)
4727         }
4729         foreach option $font_descs {
4730                 set name [lindex $option 0]
4731                 set repo_config(gui.$name) $default_config(gui.$name)
4732         }
4733         apply_config
4735         foreach option $font_descs {
4736                 set name [lindex $option 0]
4737                 set font [lindex $option 1]
4738                 set global_config_new(gui.$font^^family) \
4739                         [font configure $font -family]
4740                 set global_config_new(gui.$font^^size) \
4741                         [font configure $font -size]
4742         }
4745 proc do_save_config {w} {
4746         if {[catch {save_config} err]} {
4747                 error_popup "Failed to completely save options:\n\n$err"
4748         }
4749         reshow_diff
4750         destroy $w
4753 proc do_windows_shortcut {} {
4754         global argv0
4756         set fn [tk_getSaveFile \
4757                 -parent . \
4758                 -title "[appname] ([reponame]): Create Desktop Icon" \
4759                 -initialfile "Git [reponame].bat"]
4760         if {$fn != {}} {
4761                 if {[catch {
4762                                 set fd [open $fn w]
4763                                 puts $fd "@ECHO Entering [reponame]"
4764                                 puts $fd "@ECHO Starting git-gui... please wait..."
4765                                 puts $fd "@SET PATH=[file normalize [gitexec]];%PATH%"
4766                                 puts $fd "@SET GIT_DIR=[file normalize [gitdir]]"
4767                                 puts -nonewline $fd "@\"[info nameofexecutable]\""
4768                                 puts $fd " \"[file normalize $argv0]\""
4769                                 close $fd
4770                         } err]} {
4771                         error_popup "Cannot write script:\n\n$err"
4772                 }
4773         }
4776 proc do_cygwin_shortcut {} {
4777         global argv0
4779         if {[catch {
4780                 set desktop [exec cygpath \
4781                         --windows \
4782                         --absolute \
4783                         --long-name \
4784                         --desktop]
4785                 }]} {
4786                         set desktop .
4787         }
4788         set fn [tk_getSaveFile \
4789                 -parent . \
4790                 -title "[appname] ([reponame]): Create Desktop Icon" \
4791                 -initialdir $desktop \
4792                 -initialfile "Git [reponame].bat"]
4793         if {$fn != {}} {
4794                 if {[catch {
4795                                 set fd [open $fn w]
4796                                 set sh [exec cygpath \
4797                                         --windows \
4798                                         --absolute \
4799                                         /bin/sh]
4800                                 set me [exec cygpath \
4801                                         --unix \
4802                                         --absolute \
4803                                         $argv0]
4804                                 set gd [exec cygpath \
4805                                         --unix \
4806                                         --absolute \
4807                                         [gitdir]]
4808                                 set gw [exec cygpath \
4809                                         --windows \
4810                                         --absolute \
4811                                         [file dirname [gitdir]]]
4812                                 regsub -all ' $me "'\\''" me
4813                                 regsub -all ' $gd "'\\''" gd
4814                                 puts $fd "@ECHO Entering $gw"
4815                                 puts $fd "@ECHO Starting git-gui... please wait..."
4816                                 puts -nonewline $fd "@\"$sh\" --login -c \""
4817                                 puts -nonewline $fd "GIT_DIR='$gd'"
4818                                 puts -nonewline $fd " '$me'"
4819                                 puts $fd "&\""
4820                                 close $fd
4821                         } err]} {
4822                         error_popup "Cannot write script:\n\n$err"
4823                 }
4824         }
4827 proc do_macosx_app {} {
4828         global argv0 env
4830         set fn [tk_getSaveFile \
4831                 -parent . \
4832                 -title "[appname] ([reponame]): Create Desktop Icon" \
4833                 -initialdir [file join $env(HOME) Desktop] \
4834                 -initialfile "Git [reponame].app"]
4835         if {$fn != {}} {
4836                 if {[catch {
4837                                 set Contents [file join $fn Contents]
4838                                 set MacOS [file join $Contents MacOS]
4839                                 set exe [file join $MacOS git-gui]
4841                                 file mkdir $MacOS
4843                                 set fd [open [file join $Contents Info.plist] w]
4844                                 puts $fd {<?xml version="1.0" encoding="UTF-8"?>
4845 <!DOCTYPE plist PUBLIC "-//Apple Computer//DTD PLIST 1.0//EN" "http://www.apple.com/DTDs/PropertyList-1.0.dtd">
4846 <plist version="1.0">
4847 <dict>
4848         <key>CFBundleDevelopmentRegion</key>
4849         <string>English</string>
4850         <key>CFBundleExecutable</key>
4851         <string>git-gui</string>
4852         <key>CFBundleIdentifier</key>
4853         <string>org.spearce.git-gui</string>
4854         <key>CFBundleInfoDictionaryVersion</key>
4855         <string>6.0</string>
4856         <key>CFBundlePackageType</key>
4857         <string>APPL</string>
4858         <key>CFBundleSignature</key>
4859         <string>????</string>
4860         <key>CFBundleVersion</key>
4861         <string>1.0</string>
4862         <key>NSPrincipalClass</key>
4863         <string>NSApplication</string>
4864 </dict>
4865 </plist>}
4866                                 close $fd
4868                                 set fd [open $exe w]
4869                                 set gd [file normalize [gitdir]]
4870                                 set ep [file normalize [gitexec]]
4871                                 regsub -all ' $gd "'\\''" gd
4872                                 regsub -all ' $ep "'\\''" ep
4873                                 puts $fd "#!/bin/sh"
4874                                 foreach name [array names env] {
4875                                         if {[string match GIT_* $name]} {
4876                                                 regsub -all ' $env($name) "'\\''" v
4877                                                 puts $fd "export $name='$v'"
4878                                         }
4879                                 }
4880                                 puts $fd "export PATH='$ep':\$PATH"
4881                                 puts $fd "export GIT_DIR='$gd'"
4882                                 puts $fd "exec [file normalize $argv0]"
4883                                 close $fd
4885                                 file attributes $exe -permissions u+x,g+x,o+x
4886                         } err]} {
4887                         error_popup "Cannot write icon:\n\n$err"
4888                 }
4889         }
4892 proc toggle_or_diff {w x y} {
4893         global file_states file_lists current_diff_path ui_index ui_workdir
4894         global last_clicked selected_paths
4896         set pos [split [$w index @$x,$y] .]
4897         set lno [lindex $pos 0]
4898         set col [lindex $pos 1]
4899         set path [lindex $file_lists($w) [expr {$lno - 1}]]
4900         if {$path eq {}} {
4901                 set last_clicked {}
4902                 return
4903         }
4905         set last_clicked [list $w $lno]
4906         array unset selected_paths
4907         $ui_index tag remove in_sel 0.0 end
4908         $ui_workdir tag remove in_sel 0.0 end
4910         if {$col == 0} {
4911                 if {$current_diff_path eq $path} {
4912                         set after {reshow_diff;}
4913                 } else {
4914                         set after {}
4915                 }
4916                 if {$w eq $ui_index} {
4917                         update_indexinfo \
4918                                 "Unstaging [short_path $path] from commit" \
4919                                 [list $path] \
4920                                 [concat $after {set ui_status_value {Ready.}}]
4921                 } elseif {$w eq $ui_workdir} {
4922                         update_index \
4923                                 "Adding [short_path $path]" \
4924                                 [list $path] \
4925                                 [concat $after {set ui_status_value {Ready.}}]
4926                 }
4927         } else {
4928                 show_diff $path $w $lno
4929         }
4932 proc add_one_to_selection {w x y} {
4933         global file_lists last_clicked selected_paths
4935         set lno [lindex [split [$w index @$x,$y] .] 0]
4936         set path [lindex $file_lists($w) [expr {$lno - 1}]]
4937         if {$path eq {}} {
4938                 set last_clicked {}
4939                 return
4940         }
4942         if {$last_clicked ne {}
4943                 && [lindex $last_clicked 0] ne $w} {
4944                 array unset selected_paths
4945                 [lindex $last_clicked 0] tag remove in_sel 0.0 end
4946         }
4948         set last_clicked [list $w $lno]
4949         if {[catch {set in_sel $selected_paths($path)}]} {
4950                 set in_sel 0
4951         }
4952         if {$in_sel} {
4953                 unset selected_paths($path)
4954                 $w tag remove in_sel $lno.0 [expr {$lno + 1}].0
4955         } else {
4956                 set selected_paths($path) 1
4957                 $w tag add in_sel $lno.0 [expr {$lno + 1}].0
4958         }
4961 proc add_range_to_selection {w x y} {
4962         global file_lists last_clicked selected_paths
4964         if {[lindex $last_clicked 0] ne $w} {
4965                 toggle_or_diff $w $x $y
4966                 return
4967         }
4969         set lno [lindex [split [$w index @$x,$y] .] 0]
4970         set lc [lindex $last_clicked 1]
4971         if {$lc < $lno} {
4972                 set begin $lc
4973                 set end $lno
4974         } else {
4975                 set begin $lno
4976                 set end $lc
4977         }
4979         foreach path [lrange $file_lists($w) \
4980                 [expr {$begin - 1}] \
4981                 [expr {$end - 1}]] {
4982                 set selected_paths($path) 1
4983         }
4984         $w tag add in_sel $begin.0 [expr {$end + 1}].0
4987 ######################################################################
4988 ##
4989 ## config defaults
4991 set cursor_ptr arrow
4992 font create font_diff -family Courier -size 10
4993 font create font_ui
4994 catch {
4995         label .dummy
4996         eval font configure font_ui [font actual [.dummy cget -font]]
4997         destroy .dummy
5000 font create font_uibold
5001 font create font_diffbold
5003 if {[is_Windows]} {
5004         set M1B Control
5005         set M1T Ctrl
5006 } elseif {[is_MacOSX]} {
5007         set M1B M1
5008         set M1T Cmd
5009 } else {
5010         set M1B M1
5011         set M1T M1
5014 proc apply_config {} {
5015         global repo_config font_descs
5017         foreach option $font_descs {
5018                 set name [lindex $option 0]
5019                 set font [lindex $option 1]
5020                 if {[catch {
5021                         foreach {cn cv} $repo_config(gui.$name) {
5022                                 font configure $font $cn $cv
5023                         }
5024                         } err]} {
5025                         error_popup "Invalid font specified in gui.$name:\n\n$err"
5026                 }
5027                 foreach {cn cv} [font configure $font] {
5028                         font configure ${font}bold $cn $cv
5029                 }
5030                 font configure ${font}bold -weight bold
5031         }
5034 set default_config(merge.summary) false
5035 set default_config(merge.verbosity) 2
5036 set default_config(user.name) {}
5037 set default_config(user.email) {}
5039 set default_config(gui.trustmtime) false
5040 set default_config(gui.diffcontext) 5
5041 set default_config(gui.newbranchtemplate) {}
5042 set default_config(gui.fontui) [font configure font_ui]
5043 set default_config(gui.fontdiff) [font configure font_diff]
5044 set font_descs {
5045         {fontui   font_ui   {Main Font}}
5046         {fontdiff font_diff {Diff/Console Font}}
5048 load_config 0
5049 apply_config
5051 ######################################################################
5052 ##
5053 ## feature option selection
5055 if {[regexp {^git-(.+)$} [appname] _junk subcommand]} {
5056         unset _junk
5057 } else {
5058         set subcommand gui
5060 if {$subcommand eq {gui.sh}} {
5061         set subcommand gui
5063 if {$subcommand eq {gui} && [llength $argv] > 0} {
5064         set subcommand [lindex $argv 0]
5065         set argv [lrange $argv 1 end]
5068 enable_option multicommit
5069 enable_option branch
5070 enable_option transport
5072 switch -- $subcommand {
5073 browser -
5074 blame {
5075         disable_option multicommit
5076         disable_option branch
5077         disable_option transport
5079 citool {
5080         enable_option singlecommit
5082         disable_option multicommit
5083         disable_option branch
5084         disable_option transport
5088 ######################################################################
5089 ##
5090 ## ui construction
5092 set ui_comm {}
5094 # -- Menu Bar
5096 menu .mbar -tearoff 0
5097 .mbar add cascade -label Repository -menu .mbar.repository -font font_ui
5098 .mbar add cascade -label Edit -menu .mbar.edit -font font_ui
5099 if {[is_enabled branch]} {
5100         .mbar add cascade -label Branch -menu .mbar.branch -font font_ui
5102 if {[is_enabled multicommit] || [is_enabled singlecommit]} {
5103         .mbar add cascade -label Commit -menu .mbar.commit -font font_ui
5105 if {[is_enabled transport]} {
5106         .mbar add cascade -label Merge -menu .mbar.merge -font font_ui
5107         .mbar add cascade -label Fetch -menu .mbar.fetch -font font_ui
5108         .mbar add cascade -label Push -menu .mbar.push -font font_ui
5110 . configure -menu .mbar
5112 # -- Repository Menu
5114 menu .mbar.repository
5116 .mbar.repository add command \
5117         -label {Browse Current Branch} \
5118         -command {new_browser $current_branch} \
5119         -font font_ui
5120 trace add variable current_branch write ".mbar.repository entryconf [.mbar.repository index last] -label \"Browse \$current_branch\" ;#"
5121 .mbar.repository add separator
5123 .mbar.repository add command \
5124         -label {Visualize Current Branch} \
5125         -command {do_gitk $current_branch} \
5126         -font font_ui
5127 trace add variable current_branch write ".mbar.repository entryconf [.mbar.repository index last] -label \"Visualize \$current_branch\" ;#"
5128 .mbar.repository add command \
5129         -label {Visualize All Branches} \
5130         -command {do_gitk --all} \
5131         -font font_ui
5132 .mbar.repository add separator
5134 if {[is_enabled multicommit]} {
5135         .mbar.repository add command -label {Database Statistics} \
5136                 -command do_stats \
5137                 -font font_ui
5139         .mbar.repository add command -label {Compress Database} \
5140                 -command do_gc \
5141                 -font font_ui
5143         .mbar.repository add command -label {Verify Database} \
5144                 -command do_fsck_objects \
5145                 -font font_ui
5147         .mbar.repository add separator
5149         if {[is_Cygwin]} {
5150                 .mbar.repository add command \
5151                         -label {Create Desktop Icon} \
5152                         -command do_cygwin_shortcut \
5153                         -font font_ui
5154         } elseif {[is_Windows]} {
5155                 .mbar.repository add command \
5156                         -label {Create Desktop Icon} \
5157                         -command do_windows_shortcut \
5158                         -font font_ui
5159         } elseif {[is_MacOSX]} {
5160                 .mbar.repository add command \
5161                         -label {Create Desktop Icon} \
5162                         -command do_macosx_app \
5163                         -font font_ui
5164         }
5167 .mbar.repository add command -label Quit \
5168         -command do_quit \
5169         -accelerator $M1T-Q \
5170         -font font_ui
5172 # -- Edit Menu
5174 menu .mbar.edit
5175 .mbar.edit add command -label Undo \
5176         -command {catch {[focus] edit undo}} \
5177         -accelerator $M1T-Z \
5178         -font font_ui
5179 .mbar.edit add command -label Redo \
5180         -command {catch {[focus] edit redo}} \
5181         -accelerator $M1T-Y \
5182         -font font_ui
5183 .mbar.edit add separator
5184 .mbar.edit add command -label Cut \
5185         -command {catch {tk_textCut [focus]}} \
5186         -accelerator $M1T-X \
5187         -font font_ui
5188 .mbar.edit add command -label Copy \
5189         -command {catch {tk_textCopy [focus]}} \
5190         -accelerator $M1T-C \
5191         -font font_ui
5192 .mbar.edit add command -label Paste \
5193         -command {catch {tk_textPaste [focus]; [focus] see insert}} \
5194         -accelerator $M1T-V \
5195         -font font_ui
5196 .mbar.edit add command -label Delete \
5197         -command {catch {[focus] delete sel.first sel.last}} \
5198         -accelerator Del \
5199         -font font_ui
5200 .mbar.edit add separator
5201 .mbar.edit add command -label {Select All} \
5202         -command {catch {[focus] tag add sel 0.0 end}} \
5203         -accelerator $M1T-A \
5204         -font font_ui
5206 # -- Branch Menu
5208 if {[is_enabled branch]} {
5209         menu .mbar.branch
5211         .mbar.branch add command -label {Create...} \
5212                 -command do_create_branch \
5213                 -accelerator $M1T-N \
5214                 -font font_ui
5215         lappend disable_on_lock [list .mbar.branch entryconf \
5216                 [.mbar.branch index last] -state]
5218         .mbar.branch add command -label {Delete...} \
5219                 -command do_delete_branch \
5220                 -font font_ui
5221         lappend disable_on_lock [list .mbar.branch entryconf \
5222                 [.mbar.branch index last] -state]
5224         .mbar.branch add command -label {Reset...} \
5225                 -command do_reset_hard \
5226                 -font font_ui
5227         lappend disable_on_lock [list .mbar.branch entryconf \
5228                 [.mbar.branch index last] -state]
5231 # -- Commit Menu
5233 if {[is_enabled multicommit] || [is_enabled singlecommit]} {
5234         menu .mbar.commit
5236         .mbar.commit add radiobutton \
5237                 -label {New Commit} \
5238                 -command do_select_commit_type \
5239                 -variable selected_commit_type \
5240                 -value new \
5241                 -font font_ui
5242         lappend disable_on_lock \
5243                 [list .mbar.commit entryconf [.mbar.commit index last] -state]
5245         .mbar.commit add radiobutton \
5246                 -label {Amend Last Commit} \
5247                 -command do_select_commit_type \
5248                 -variable selected_commit_type \
5249                 -value amend \
5250                 -font font_ui
5251         lappend disable_on_lock \
5252                 [list .mbar.commit entryconf [.mbar.commit index last] -state]
5254         .mbar.commit add separator
5256         .mbar.commit add command -label Rescan \
5257                 -command do_rescan \
5258                 -accelerator F5 \
5259                 -font font_ui
5260         lappend disable_on_lock \
5261                 [list .mbar.commit entryconf [.mbar.commit index last] -state]
5263         .mbar.commit add command -label {Add To Commit} \
5264                 -command do_add_selection \
5265                 -font font_ui
5266         lappend disable_on_lock \
5267                 [list .mbar.commit entryconf [.mbar.commit index last] -state]
5269         .mbar.commit add command -label {Add Existing To Commit} \
5270                 -command do_add_all \
5271                 -accelerator $M1T-I \
5272                 -font font_ui
5273         lappend disable_on_lock \
5274                 [list .mbar.commit entryconf [.mbar.commit index last] -state]
5276         .mbar.commit add command -label {Unstage From Commit} \
5277                 -command do_unstage_selection \
5278                 -font font_ui
5279         lappend disable_on_lock \
5280                 [list .mbar.commit entryconf [.mbar.commit index last] -state]
5282         .mbar.commit add command -label {Revert Changes} \
5283                 -command do_revert_selection \
5284                 -font font_ui
5285         lappend disable_on_lock \
5286                 [list .mbar.commit entryconf [.mbar.commit index last] -state]
5288         .mbar.commit add separator
5290         .mbar.commit add command -label {Sign Off} \
5291                 -command do_signoff \
5292                 -accelerator $M1T-S \
5293                 -font font_ui
5295         .mbar.commit add command -label Commit \
5296                 -command do_commit \
5297                 -accelerator $M1T-Return \
5298                 -font font_ui
5299         lappend disable_on_lock \
5300                 [list .mbar.commit entryconf [.mbar.commit index last] -state]
5303 # -- Merge Menu
5305 if {[is_enabled branch]} {
5306         menu .mbar.merge
5307         .mbar.merge add command -label {Local Merge...} \
5308                 -command do_local_merge \
5309                 -font font_ui
5310         lappend disable_on_lock \
5311                 [list .mbar.merge entryconf [.mbar.merge index last] -state]
5312         .mbar.merge add command -label {Abort Merge...} \
5313                 -command do_reset_hard \
5314                 -font font_ui
5315         lappend disable_on_lock \
5316                 [list .mbar.merge entryconf [.mbar.merge index last] -state]
5320 # -- Transport Menu
5322 if {[is_enabled transport]} {
5323         menu .mbar.fetch
5325         menu .mbar.push
5326         .mbar.push add command -label {Push...} \
5327                 -command do_push_anywhere \
5328                 -font font_ui
5331 if {[is_MacOSX]} {
5332         # -- Apple Menu (Mac OS X only)
5333         #
5334         .mbar add cascade -label Apple -menu .mbar.apple
5335         menu .mbar.apple
5337         .mbar.apple add command -label "About [appname]" \
5338                 -command do_about \
5339                 -font font_ui
5340         .mbar.apple add command -label "Options..." \
5341                 -command do_options \
5342                 -font font_ui
5343 } else {
5344         # -- Edit Menu
5345         #
5346         .mbar.edit add separator
5347         .mbar.edit add command -label {Options...} \
5348                 -command do_options \
5349                 -font font_ui
5351         # -- Tools Menu
5352         #
5353         if {[file exists /usr/local/miga/lib/gui-miga]
5354                 && [file exists .pvcsrc]} {
5355         proc do_miga {} {
5356                 global ui_status_value
5357                 if {![lock_index update]} return
5358                 set cmd [list sh --login -c "/usr/local/miga/lib/gui-miga \"[pwd]\""]
5359                 set miga_fd [open "|$cmd" r]
5360                 fconfigure $miga_fd -blocking 0
5361                 fileevent $miga_fd readable [list miga_done $miga_fd]
5362                 set ui_status_value {Running miga...}
5363         }
5364         proc miga_done {fd} {
5365                 read $fd 512
5366                 if {[eof $fd]} {
5367                         close $fd
5368                         unlock_index
5369                         rescan [list set ui_status_value {Ready.}]
5370                 }
5371         }
5372         .mbar add cascade -label Tools -menu .mbar.tools
5373         menu .mbar.tools
5374         .mbar.tools add command -label "Migrate" \
5375                 -command do_miga \
5376                 -font font_ui
5377         lappend disable_on_lock \
5378                 [list .mbar.tools entryconf [.mbar.tools index last] -state]
5379         }
5382 # -- Help Menu
5384 .mbar add cascade -label Help -menu .mbar.help -font font_ui
5385 menu .mbar.help
5387 if {![is_MacOSX]} {
5388         .mbar.help add command -label "About [appname]" \
5389                 -command do_about \
5390                 -font font_ui
5393 set browser {}
5394 catch {set browser $repo_config(instaweb.browser)}
5395 set doc_path [file dirname [gitexec]]
5396 set doc_path [file join $doc_path Documentation index.html]
5398 if {[is_Cygwin]} {
5399         set doc_path [exec cygpath --mixed $doc_path]
5402 if {$browser eq {}} {
5403         if {[is_MacOSX]} {
5404                 set browser open
5405         } elseif {[is_Cygwin]} {
5406                 set program_files [file dirname [exec cygpath --windir]]
5407                 set program_files [file join $program_files {Program Files}]
5408                 set firefox [file join $program_files {Mozilla Firefox} firefox.exe]
5409                 set ie [file join $program_files {Internet Explorer} IEXPLORE.EXE]
5410                 if {[file exists $firefox]} {
5411                         set browser $firefox
5412                 } elseif {[file exists $ie]} {
5413                         set browser $ie
5414                 }
5415                 unset program_files firefox ie
5416         }
5419 if {[file isfile $doc_path]} {
5420         set doc_url "file:$doc_path"
5421 } else {
5422         set doc_url {http://www.kernel.org/pub/software/scm/git/docs/}
5425 if {$browser ne {}} {
5426         .mbar.help add command -label {Online Documentation} \
5427                 -command [list exec $browser $doc_url &] \
5428                 -font font_ui
5430 unset browser doc_path doc_url
5432 # -- Standard bindings
5434 bind .   <Destroy> do_quit
5435 bind all <$M1B-Key-q> do_quit
5436 bind all <$M1B-Key-Q> do_quit
5437 bind all <$M1B-Key-w> {destroy [winfo toplevel %W]}
5438 bind all <$M1B-Key-W> {destroy [winfo toplevel %W]}
5440 # -- Not a normal commit type invocation?  Do that instead!
5442 switch -- $subcommand {
5443 browser {
5444         if {[llength $argv] != 1} {
5445                 puts stderr "usage: $argv0 browser commit"
5446                 exit 1
5447         }
5448         set current_branch [lindex $argv 0]
5449         new_browser $current_branch
5450         return
5452 blame {
5453         if {[llength $argv] != 2} {
5454                 puts stderr "usage: $argv0 blame commit path"
5455                 exit 1
5456         }
5457         set current_branch [lindex $argv 0]
5458         show_blame $current_branch [lindex $argv 1]
5459         return
5461 citool -
5462 gui {
5463         if {[llength $argv] != 0} {
5464                 puts -nonewline stderr "usage: $argv0"
5465                 if {$subcommand ne {gui} && [appname] ne "git-$subcommand"} {
5466                         puts -nonewline stderr " $subcommand"
5467                 }
5468                 puts stderr {}
5469                 exit 1
5470         }
5471         # fall through to setup UI for commits
5473 default {
5474         puts stderr "usage: $argv0 \[{blame|browser|citool}\]"
5475         exit 1
5479 # -- Branch Control
5481 frame .branch \
5482         -borderwidth 1 \
5483         -relief sunken
5484 label .branch.l1 \
5485         -text {Current Branch:} \
5486         -anchor w \
5487         -justify left \
5488         -font font_ui
5489 label .branch.cb \
5490         -textvariable current_branch \
5491         -anchor w \
5492         -justify left \
5493         -font font_ui
5494 pack .branch.l1 -side left
5495 pack .branch.cb -side left -fill x
5496 pack .branch -side top -fill x
5498 # -- Main Window Layout
5500 panedwindow .vpane -orient vertical
5501 panedwindow .vpane.files -orient horizontal
5502 .vpane add .vpane.files -sticky nsew -height 100 -width 200
5503 pack .vpane -anchor n -side top -fill both -expand 1
5505 # -- Index File List
5507 frame .vpane.files.index -height 100 -width 200
5508 label .vpane.files.index.title -text {Changes To Be Committed} \
5509         -background green \
5510         -font font_ui
5511 text $ui_index -background white -borderwidth 0 \
5512         -width 20 -height 10 \
5513         -wrap none \
5514         -font font_ui \
5515         -cursor $cursor_ptr \
5516         -xscrollcommand {.vpane.files.index.sx set} \
5517         -yscrollcommand {.vpane.files.index.sy set} \
5518         -state disabled
5519 scrollbar .vpane.files.index.sx -orient h -command [list $ui_index xview]
5520 scrollbar .vpane.files.index.sy -orient v -command [list $ui_index yview]
5521 pack .vpane.files.index.title -side top -fill x
5522 pack .vpane.files.index.sx -side bottom -fill x
5523 pack .vpane.files.index.sy -side right -fill y
5524 pack $ui_index -side left -fill both -expand 1
5525 .vpane.files add .vpane.files.index -sticky nsew
5527 # -- Working Directory File List
5529 frame .vpane.files.workdir -height 100 -width 200
5530 label .vpane.files.workdir.title -text {Changed But Not Updated} \
5531         -background red \
5532         -font font_ui
5533 text $ui_workdir -background white -borderwidth 0 \
5534         -width 20 -height 10 \
5535         -wrap none \
5536         -font font_ui \
5537         -cursor $cursor_ptr \
5538         -xscrollcommand {.vpane.files.workdir.sx set} \
5539         -yscrollcommand {.vpane.files.workdir.sy set} \
5540         -state disabled
5541 scrollbar .vpane.files.workdir.sx -orient h -command [list $ui_workdir xview]
5542 scrollbar .vpane.files.workdir.sy -orient v -command [list $ui_workdir yview]
5543 pack .vpane.files.workdir.title -side top -fill x
5544 pack .vpane.files.workdir.sx -side bottom -fill x
5545 pack .vpane.files.workdir.sy -side right -fill y
5546 pack $ui_workdir -side left -fill both -expand 1
5547 .vpane.files add .vpane.files.workdir -sticky nsew
5549 foreach i [list $ui_index $ui_workdir] {
5550         $i tag conf in_diff -font font_uibold
5551         $i tag conf in_sel \
5552                 -background [$i cget -foreground] \
5553                 -foreground [$i cget -background]
5555 unset i
5557 # -- Diff and Commit Area
5559 frame .vpane.lower -height 300 -width 400
5560 frame .vpane.lower.commarea
5561 frame .vpane.lower.diff -relief sunken -borderwidth 1
5562 pack .vpane.lower.commarea -side top -fill x
5563 pack .vpane.lower.diff -side bottom -fill both -expand 1
5564 .vpane add .vpane.lower -sticky nsew
5566 # -- Commit Area Buttons
5568 frame .vpane.lower.commarea.buttons
5569 label .vpane.lower.commarea.buttons.l -text {} \
5570         -anchor w \
5571         -justify left \
5572         -font font_ui
5573 pack .vpane.lower.commarea.buttons.l -side top -fill x
5574 pack .vpane.lower.commarea.buttons -side left -fill y
5576 button .vpane.lower.commarea.buttons.rescan -text {Rescan} \
5577         -command do_rescan \
5578         -font font_ui
5579 pack .vpane.lower.commarea.buttons.rescan -side top -fill x
5580 lappend disable_on_lock \
5581         {.vpane.lower.commarea.buttons.rescan conf -state}
5583 button .vpane.lower.commarea.buttons.incall -text {Add Existing} \
5584         -command do_add_all \
5585         -font font_ui
5586 pack .vpane.lower.commarea.buttons.incall -side top -fill x
5587 lappend disable_on_lock \
5588         {.vpane.lower.commarea.buttons.incall conf -state}
5590 button .vpane.lower.commarea.buttons.signoff -text {Sign Off} \
5591         -command do_signoff \
5592         -font font_ui
5593 pack .vpane.lower.commarea.buttons.signoff -side top -fill x
5595 button .vpane.lower.commarea.buttons.commit -text {Commit} \
5596         -command do_commit \
5597         -font font_ui
5598 pack .vpane.lower.commarea.buttons.commit -side top -fill x
5599 lappend disable_on_lock \
5600         {.vpane.lower.commarea.buttons.commit conf -state}
5602 # -- Commit Message Buffer
5604 frame .vpane.lower.commarea.buffer
5605 frame .vpane.lower.commarea.buffer.header
5606 set ui_comm .vpane.lower.commarea.buffer.t
5607 set ui_coml .vpane.lower.commarea.buffer.header.l
5608 radiobutton .vpane.lower.commarea.buffer.header.new \
5609         -text {New Commit} \
5610         -command do_select_commit_type \
5611         -variable selected_commit_type \
5612         -value new \
5613         -font font_ui
5614 lappend disable_on_lock \
5615         [list .vpane.lower.commarea.buffer.header.new conf -state]
5616 radiobutton .vpane.lower.commarea.buffer.header.amend \
5617         -text {Amend Last Commit} \
5618         -command do_select_commit_type \
5619         -variable selected_commit_type \
5620         -value amend \
5621         -font font_ui
5622 lappend disable_on_lock \
5623         [list .vpane.lower.commarea.buffer.header.amend conf -state]
5624 label $ui_coml \
5625         -anchor w \
5626         -justify left \
5627         -font font_ui
5628 proc trace_commit_type {varname args} {
5629         global ui_coml commit_type
5630         switch -glob -- $commit_type {
5631         initial       {set txt {Initial Commit Message:}}
5632         amend         {set txt {Amended Commit Message:}}
5633         amend-initial {set txt {Amended Initial Commit Message:}}
5634         amend-merge   {set txt {Amended Merge Commit Message:}}
5635         merge         {set txt {Merge Commit Message:}}
5636         *             {set txt {Commit Message:}}
5637         }
5638         $ui_coml conf -text $txt
5640 trace add variable commit_type write trace_commit_type
5641 pack $ui_coml -side left -fill x
5642 pack .vpane.lower.commarea.buffer.header.amend -side right
5643 pack .vpane.lower.commarea.buffer.header.new -side right
5645 text $ui_comm -background white -borderwidth 1 \
5646         -undo true \
5647         -maxundo 20 \
5648         -autoseparators true \
5649         -relief sunken \
5650         -width 75 -height 9 -wrap none \
5651         -font font_diff \
5652         -yscrollcommand {.vpane.lower.commarea.buffer.sby set}
5653 scrollbar .vpane.lower.commarea.buffer.sby \
5654         -command [list $ui_comm yview]
5655 pack .vpane.lower.commarea.buffer.header -side top -fill x
5656 pack .vpane.lower.commarea.buffer.sby -side right -fill y
5657 pack $ui_comm -side left -fill y
5658 pack .vpane.lower.commarea.buffer -side left -fill y
5660 # -- Commit Message Buffer Context Menu
5662 set ctxm .vpane.lower.commarea.buffer.ctxm
5663 menu $ctxm -tearoff 0
5664 $ctxm add command \
5665         -label {Cut} \
5666         -font font_ui \
5667         -command {tk_textCut $ui_comm}
5668 $ctxm add command \
5669         -label {Copy} \
5670         -font font_ui \
5671         -command {tk_textCopy $ui_comm}
5672 $ctxm add command \
5673         -label {Paste} \
5674         -font font_ui \
5675         -command {tk_textPaste $ui_comm}
5676 $ctxm add command \
5677         -label {Delete} \
5678         -font font_ui \
5679         -command {$ui_comm delete sel.first sel.last}
5680 $ctxm add separator
5681 $ctxm add command \
5682         -label {Select All} \
5683         -font font_ui \
5684         -command {focus $ui_comm;$ui_comm tag add sel 0.0 end}
5685 $ctxm add command \
5686         -label {Copy All} \
5687         -font font_ui \
5688         -command {
5689                 $ui_comm tag add sel 0.0 end
5690                 tk_textCopy $ui_comm
5691                 $ui_comm tag remove sel 0.0 end
5692         }
5693 $ctxm add separator
5694 $ctxm add command \
5695         -label {Sign Off} \
5696         -font font_ui \
5697         -command do_signoff
5698 bind_button3 $ui_comm "tk_popup $ctxm %X %Y"
5700 # -- Diff Header
5702 proc trace_current_diff_path {varname args} {
5703         global current_diff_path diff_actions file_states
5704         if {$current_diff_path eq {}} {
5705                 set s {}
5706                 set f {}
5707                 set p {}
5708                 set o disabled
5709         } else {
5710                 set p $current_diff_path
5711                 set s [mapdesc [lindex $file_states($p) 0] $p]
5712                 set f {File:}
5713                 set p [escape_path $p]
5714                 set o normal
5715         }
5717         .vpane.lower.diff.header.status configure -text $s
5718         .vpane.lower.diff.header.file configure -text $f
5719         .vpane.lower.diff.header.path configure -text $p
5720         foreach w $diff_actions {
5721                 uplevel #0 $w $o
5722         }
5724 trace add variable current_diff_path write trace_current_diff_path
5726 frame .vpane.lower.diff.header -background orange
5727 label .vpane.lower.diff.header.status \
5728         -background orange \
5729         -width $max_status_desc \
5730         -anchor w \
5731         -justify left \
5732         -font font_ui
5733 label .vpane.lower.diff.header.file \
5734         -background orange \
5735         -anchor w \
5736         -justify left \
5737         -font font_ui
5738 label .vpane.lower.diff.header.path \
5739         -background orange \
5740         -anchor w \
5741         -justify left \
5742         -font font_ui
5743 pack .vpane.lower.diff.header.status -side left
5744 pack .vpane.lower.diff.header.file -side left
5745 pack .vpane.lower.diff.header.path -fill x
5746 set ctxm .vpane.lower.diff.header.ctxm
5747 menu $ctxm -tearoff 0
5748 $ctxm add command \
5749         -label {Copy} \
5750         -font font_ui \
5751         -command {
5752                 clipboard clear
5753                 clipboard append \
5754                         -format STRING \
5755                         -type STRING \
5756                         -- $current_diff_path
5757         }
5758 lappend diff_actions [list $ctxm entryconf [$ctxm index last] -state]
5759 bind_button3 .vpane.lower.diff.header.path "tk_popup $ctxm %X %Y"
5761 # -- Diff Body
5763 frame .vpane.lower.diff.body
5764 set ui_diff .vpane.lower.diff.body.t
5765 text $ui_diff -background white -borderwidth 0 \
5766         -width 80 -height 15 -wrap none \
5767         -font font_diff \
5768         -xscrollcommand {.vpane.lower.diff.body.sbx set} \
5769         -yscrollcommand {.vpane.lower.diff.body.sby set} \
5770         -state disabled
5771 scrollbar .vpane.lower.diff.body.sbx -orient horizontal \
5772         -command [list $ui_diff xview]
5773 scrollbar .vpane.lower.diff.body.sby -orient vertical \
5774         -command [list $ui_diff yview]
5775 pack .vpane.lower.diff.body.sbx -side bottom -fill x
5776 pack .vpane.lower.diff.body.sby -side right -fill y
5777 pack $ui_diff -side left -fill both -expand 1
5778 pack .vpane.lower.diff.header -side top -fill x
5779 pack .vpane.lower.diff.body -side bottom -fill both -expand 1
5781 $ui_diff tag conf d_cr -elide true
5782 $ui_diff tag conf d_@ -foreground blue -font font_diffbold
5783 $ui_diff tag conf d_+ -foreground {#00a000}
5784 $ui_diff tag conf d_- -foreground red
5786 $ui_diff tag conf d_++ -foreground {#00a000}
5787 $ui_diff tag conf d_-- -foreground red
5788 $ui_diff tag conf d_+s \
5789         -foreground {#00a000} \
5790         -background {#e2effa}
5791 $ui_diff tag conf d_-s \
5792         -foreground red \
5793         -background {#e2effa}
5794 $ui_diff tag conf d_s+ \
5795         -foreground {#00a000} \
5796         -background ivory1
5797 $ui_diff tag conf d_s- \
5798         -foreground red \
5799         -background ivory1
5801 $ui_diff tag conf d<<<<<<< \
5802         -foreground orange \
5803         -font font_diffbold
5804 $ui_diff tag conf d======= \
5805         -foreground orange \
5806         -font font_diffbold
5807 $ui_diff tag conf d>>>>>>> \
5808         -foreground orange \
5809         -font font_diffbold
5811 $ui_diff tag raise sel
5813 # -- Diff Body Context Menu
5815 set ctxm .vpane.lower.diff.body.ctxm
5816 menu $ctxm -tearoff 0
5817 $ctxm add command \
5818         -label {Refresh} \
5819         -font font_ui \
5820         -command reshow_diff
5821 lappend diff_actions [list $ctxm entryconf [$ctxm index last] -state]
5822 $ctxm add command \
5823         -label {Copy} \
5824         -font font_ui \
5825         -command {tk_textCopy $ui_diff}
5826 lappend diff_actions [list $ctxm entryconf [$ctxm index last] -state]
5827 $ctxm add command \
5828         -label {Select All} \
5829         -font font_ui \
5830         -command {focus $ui_diff;$ui_diff tag add sel 0.0 end}
5831 lappend diff_actions [list $ctxm entryconf [$ctxm index last] -state]
5832 $ctxm add command \
5833         -label {Copy All} \
5834         -font font_ui \
5835         -command {
5836                 $ui_diff tag add sel 0.0 end
5837                 tk_textCopy $ui_diff
5838                 $ui_diff tag remove sel 0.0 end
5839         }
5840 lappend diff_actions [list $ctxm entryconf [$ctxm index last] -state]
5841 $ctxm add separator
5842 $ctxm add command \
5843         -label {Apply/Reverse Hunk} \
5844         -font font_ui \
5845         -command {apply_hunk $cursorX $cursorY}
5846 set ui_diff_applyhunk [$ctxm index last]
5847 lappend diff_actions [list $ctxm entryconf $ui_diff_applyhunk -state]
5848 $ctxm add separator
5849 $ctxm add command \
5850         -label {Decrease Font Size} \
5851         -font font_ui \
5852         -command {incr_font_size font_diff -1}
5853 lappend diff_actions [list $ctxm entryconf [$ctxm index last] -state]
5854 $ctxm add command \
5855         -label {Increase Font Size} \
5856         -font font_ui \
5857         -command {incr_font_size font_diff 1}
5858 lappend diff_actions [list $ctxm entryconf [$ctxm index last] -state]
5859 $ctxm add separator
5860 $ctxm add command \
5861         -label {Show Less Context} \
5862         -font font_ui \
5863         -command {if {$repo_config(gui.diffcontext) >= 2} {
5864                 incr repo_config(gui.diffcontext) -1
5865                 reshow_diff
5866         }}
5867 lappend diff_actions [list $ctxm entryconf [$ctxm index last] -state]
5868 $ctxm add command \
5869         -label {Show More Context} \
5870         -font font_ui \
5871         -command {
5872                 incr repo_config(gui.diffcontext)
5873                 reshow_diff
5874         }
5875 lappend diff_actions [list $ctxm entryconf [$ctxm index last] -state]
5876 $ctxm add separator
5877 $ctxm add command -label {Options...} \
5878         -font font_ui \
5879         -command do_options
5880 bind_button3 $ui_diff "
5881         set cursorX %x
5882         set cursorY %y
5883         if {\$ui_index eq \$current_diff_side} {
5884                 $ctxm entryconf $ui_diff_applyhunk -label {Unstage Hunk From Commit}
5885         } else {
5886                 $ctxm entryconf $ui_diff_applyhunk -label {Stage Hunk For Commit}
5887         }
5888         tk_popup $ctxm %X %Y
5890 unset ui_diff_applyhunk
5892 # -- Status Bar
5894 label .status -textvariable ui_status_value \
5895         -anchor w \
5896         -justify left \
5897         -borderwidth 1 \
5898         -relief sunken \
5899         -font font_ui
5900 pack .status -anchor w -side bottom -fill x
5902 # -- Load geometry
5904 catch {
5905 set gm $repo_config(gui.geometry)
5906 wm geometry . [lindex $gm 0]
5907 .vpane sash place 0 \
5908         [lindex [.vpane sash coord 0] 0] \
5909         [lindex $gm 1]
5910 .vpane.files sash place 0 \
5911         [lindex $gm 2] \
5912         [lindex [.vpane.files sash coord 0] 1]
5913 unset gm
5916 # -- Key Bindings
5918 bind $ui_comm <$M1B-Key-Return> {do_commit;break}
5919 bind $ui_comm <$M1B-Key-i> {do_add_all;break}
5920 bind $ui_comm <$M1B-Key-I> {do_add_all;break}
5921 bind $ui_comm <$M1B-Key-x> {tk_textCut %W;break}
5922 bind $ui_comm <$M1B-Key-X> {tk_textCut %W;break}
5923 bind $ui_comm <$M1B-Key-c> {tk_textCopy %W;break}
5924 bind $ui_comm <$M1B-Key-C> {tk_textCopy %W;break}
5925 bind $ui_comm <$M1B-Key-v> {tk_textPaste %W; %W see insert; break}
5926 bind $ui_comm <$M1B-Key-V> {tk_textPaste %W; %W see insert; break}
5927 bind $ui_comm <$M1B-Key-a> {%W tag add sel 0.0 end;break}
5928 bind $ui_comm <$M1B-Key-A> {%W tag add sel 0.0 end;break}
5930 bind $ui_diff <$M1B-Key-x> {tk_textCopy %W;break}
5931 bind $ui_diff <$M1B-Key-X> {tk_textCopy %W;break}
5932 bind $ui_diff <$M1B-Key-c> {tk_textCopy %W;break}
5933 bind $ui_diff <$M1B-Key-C> {tk_textCopy %W;break}
5934 bind $ui_diff <$M1B-Key-v> {break}
5935 bind $ui_diff <$M1B-Key-V> {break}
5936 bind $ui_diff <$M1B-Key-a> {%W tag add sel 0.0 end;break}
5937 bind $ui_diff <$M1B-Key-A> {%W tag add sel 0.0 end;break}
5938 bind $ui_diff <Key-Up>     {catch {%W yview scroll -1 units};break}
5939 bind $ui_diff <Key-Down>   {catch {%W yview scroll  1 units};break}
5940 bind $ui_diff <Key-Left>   {catch {%W xview scroll -1 units};break}
5941 bind $ui_diff <Key-Right>  {catch {%W xview scroll  1 units};break}
5942 bind $ui_diff <Button-1>   {focus %W}
5944 if {[is_enabled branch]} {
5945         bind . <$M1B-Key-n> do_create_branch
5946         bind . <$M1B-Key-N> do_create_branch
5949 bind all <Key-F5> do_rescan
5950 bind all <$M1B-Key-r> do_rescan
5951 bind all <$M1B-Key-R> do_rescan
5952 bind .   <$M1B-Key-s> do_signoff
5953 bind .   <$M1B-Key-S> do_signoff
5954 bind .   <$M1B-Key-i> do_add_all
5955 bind .   <$M1B-Key-I> do_add_all
5956 bind .   <$M1B-Key-Return> do_commit
5957 foreach i [list $ui_index $ui_workdir] {
5958         bind $i <Button-1>       "toggle_or_diff         $i %x %y; break"
5959         bind $i <$M1B-Button-1>  "add_one_to_selection   $i %x %y; break"
5960         bind $i <Shift-Button-1> "add_range_to_selection $i %x %y; break"
5962 unset i
5964 set file_lists($ui_index) [list]
5965 set file_lists($ui_workdir) [list]
5967 wm title . "[appname] ([file normalize [file dirname [gitdir]]])"
5968 focus -force $ui_comm
5970 # -- Warn the user about environmental problems.  Cygwin's Tcl
5971 #    does *not* pass its env array onto any processes it spawns.
5972 #    This means that git processes get none of our environment.
5974 if {[is_Cygwin]} {
5975         set ignored_env 0
5976         set suggest_user {}
5977         set msg "Possible environment issues exist.
5979 The following environment variables are probably
5980 going to be ignored by any Git subprocess run
5981 by [appname]:
5984         foreach name [array names env] {
5985                 switch -regexp -- $name {
5986                 {^GIT_INDEX_FILE$} -
5987                 {^GIT_OBJECT_DIRECTORY$} -
5988                 {^GIT_ALTERNATE_OBJECT_DIRECTORIES$} -
5989                 {^GIT_DIFF_OPTS$} -
5990                 {^GIT_EXTERNAL_DIFF$} -
5991                 {^GIT_PAGER$} -
5992                 {^GIT_TRACE$} -
5993                 {^GIT_CONFIG$} -
5994                 {^GIT_CONFIG_LOCAL$} -
5995                 {^GIT_(AUTHOR|COMMITTER)_DATE$} {
5996                         append msg " - $name\n"
5997                         incr ignored_env
5998                 }
5999                 {^GIT_(AUTHOR|COMMITTER)_(NAME|EMAIL)$} {
6000                         append msg " - $name\n"
6001                         incr ignored_env
6002                         set suggest_user $name
6003                 }
6004                 }
6005         }
6006         if {$ignored_env > 0} {
6007                 append msg "
6008 This is due to a known issue with the
6009 Tcl binary distributed by Cygwin."
6011                 if {$suggest_user ne {}} {
6012                         append msg "
6014 A good replacement for $suggest_user
6015 is placing values for the user.name and
6016 user.email settings into your personal
6017 ~/.gitconfig file.
6019                 }
6020                 warn_popup $msg
6021         }
6022         unset ignored_env msg suggest_user name
6025 # -- Only initialize complex UI if we are going to stay running.
6027 if {[is_enabled transport]} {
6028         load_all_remotes
6029         load_all_heads
6031         populate_branch_menu
6032         populate_fetch_menu
6033         populate_push_menu
6036 # -- Only suggest a gc run if we are going to stay running.
6038 if {[is_enabled multicommit]} {
6039         set object_limit 2000
6040         if {[is_Windows]} {set object_limit 200}
6041         regexp {^([0-9]+) objects,} [git count-objects] _junk objects_current
6042         if {$objects_current >= $object_limit} {
6043                 if {[ask_popup \
6044                         "This repository currently has $objects_current loose objects.
6046 To maintain optimal performance it is strongly recommended that you compress the database when more than $object_limit loose objects exist.
6048 Compress the database now?"] eq yes} {
6049                         do_gc
6050                 }
6051         }
6052         unset object_limit _junk objects_current
6055 lock_index begin-read
6056 after 1 do_rescan