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