1#!/bin/sh
2# Tcl ignores the next line -*- tcl -*- \
3exec wish "$0" -- "$@"
4
5# Copyright (C) 2006 Shawn Pearce, Paul Mackerras. All rights reserved.
6# This program is free software; it may be used, copied, modified
7# and distributed under the terms of the GNU General Public Licence,
8# either version 2, or (at your option) any later version.
9
10######################################################################
11##
12## task management
13
14set single_commit 0
15set status_active 0
16set diff_active 0
17set checkin_active 0
18set commit_active 0
19set update_index_fd {}
20
21set disable_on_lock [list]
22set index_lock_type none
23
24set HEAD {}
25set PARENT {}
26set commit_type {}
27
28proc lock_index {type} {
29 global index_lock_type disable_on_lock
30
31 if {$index_lock_type == {none}} {
32 set index_lock_type $type
33 foreach w $disable_on_lock {
34 uplevel #0 $w disabled
35 }
36 return 1
37 } elseif {$index_lock_type == {begin-update} && $type == {update}} {
38 set index_lock_type $type
39 return 1
40 }
41 return 0
42}
43
44proc unlock_index {} {
45 global index_lock_type disable_on_lock
46
47 set index_lock_type none
48 foreach w $disable_on_lock {
49 uplevel #0 $w normal
50 }
51}
52
53######################################################################
54##
55## status
56
57proc repository_state {hdvar ctvar} {
58 global gitdir
59 upvar $hdvar hd $ctvar ct
60
61 if {[catch {set hd [exec git rev-parse --verify HEAD]}]} {
62 set ct initial
63 } elseif {[file exists [file join $gitdir MERGE_HEAD]]} {
64 set ct merge
65 } else {
66 set ct normal
67 }
68}
69
70proc update_status {{final Ready.}} {
71 global HEAD PARENT commit_type
72 global ui_index ui_other ui_status_value ui_comm
73 global status_active file_states
74
75 if {$status_active || ![lock_index read]} return
76
77 repository_state new_HEAD new_type
78 if {$commit_type == {amend}
79 && $new_type == {normal}
80 && $new_HEAD == $HEAD} {
81 } else {
82 set HEAD $new_HEAD
83 set PARENT $new_HEAD
84 set commit_type $new_type
85 }
86
87 array unset file_states
88 foreach w [list $ui_index $ui_other] {
89 $w conf -state normal
90 $w delete 0.0 end
91 $w conf -state disabled
92 }
93
94 if {![$ui_comm edit modified]
95 || [string trim [$ui_comm get 0.0 end]] == {}} {
96 if {[load_message GITGUI_MSG]} {
97 } elseif {[load_message MERGE_MSG]} {
98 } elseif {[load_message SQUASH_MSG]} {
99 }
100 $ui_comm edit modified false
101 }
102
103 set status_active 1
104 set ui_status_value {Refreshing file status...}
105 set fd_rf [open "| git update-index -q --unmerged --refresh" r]
106 fconfigure $fd_rf -blocking 0 -translation binary
107 fileevent $fd_rf readable [list read_refresh $fd_rf $final]
108}
109
110proc read_refresh {fd final} {
111 global gitdir PARENT commit_type
112 global ui_index ui_other ui_status_value ui_comm
113 global status_active file_states
114
115 read $fd
116 if {![eof $fd]} return
117 close $fd
118
119 set ls_others [list | git ls-files --others -z \
120 --exclude-per-directory=.gitignore]
121 set info_exclude [file join $gitdir info exclude]
122 if {[file readable $info_exclude]} {
123 lappend ls_others "--exclude-from=$info_exclude"
124 }
125
126 set status_active 3
127 set ui_status_value {Scanning for modified files ...}
128 set fd_di [open "| git diff-index --cached -z $PARENT" r]
129 set fd_df [open "| git diff-files -z" r]
130 set fd_lo [open $ls_others r]
131
132 fconfigure $fd_di -blocking 0 -translation binary
133 fconfigure $fd_df -blocking 0 -translation binary
134 fconfigure $fd_lo -blocking 0 -translation binary
135 fileevent $fd_di readable [list read_diff_index $fd_di $final]
136 fileevent $fd_df readable [list read_diff_files $fd_df $final]
137 fileevent $fd_lo readable [list read_ls_others $fd_lo $final]
138}
139
140proc load_message {file} {
141 global gitdir ui_comm
142
143 set f [file join $gitdir $file]
144 if {[file isfile $f]} {
145 if {[catch {set fd [open $f r]}]} {
146 return 0
147 }
148 set content [string trim [read $fd]]
149 close $fd
150 $ui_comm delete 0.0 end
151 $ui_comm insert end $content
152 return 1
153 }
154 return 0
155}
156
157proc read_diff_index {fd final} {
158 global buf_rdi
159
160 append buf_rdi [read $fd]
161 set pck [split $buf_rdi "\0"]
162 set buf_rdi [lindex $pck end]
163 foreach {m p} [lrange $pck 0 end-1] {
164 if {$m != {} && $p != {}} {
165 display_file $p [string index $m end]_
166 }
167 }
168 status_eof $fd buf_rdi $final
169}
170
171proc read_diff_files {fd final} {
172 global buf_rdf
173
174 append buf_rdf [read $fd]
175 set pck [split $buf_rdf "\0"]
176 set buf_rdf [lindex $pck end]
177 foreach {m p} [lrange $pck 0 end-1] {
178 if {$m != {} && $p != {}} {
179 display_file $p _[string index $m end]
180 }
181 }
182 status_eof $fd buf_rdf $final
183}
184
185proc read_ls_others {fd final} {
186 global buf_rlo
187
188 append buf_rlo [read $fd]
189 set pck [split $buf_rlo "\0"]
190 set buf_rlo [lindex $pck end]
191 foreach p [lrange $pck 0 end-1] {
192 display_file $p _O
193 }
194 status_eof $fd buf_rlo $final
195}
196
197proc status_eof {fd buf final} {
198 global status_active $buf
199 global ui_fname_value ui_status_value file_states
200
201 if {[eof $fd]} {
202 set $buf {}
203 close $fd
204 if {[incr status_active -1] == 0} {
205 unlock_index
206
207 set ui_status_value $final
208 if {$ui_fname_value != {} && [array names file_states \
209 -exact $ui_fname_value] != {}} {
210 show_diff $ui_fname_value
211 } else {
212 clear_diff
213 }
214 }
215 }
216}
217
218######################################################################
219##
220## diff
221
222proc clear_diff {} {
223 global ui_diff ui_fname_value ui_fstatus_value
224
225 $ui_diff conf -state normal
226 $ui_diff delete 0.0 end
227 $ui_diff conf -state disabled
228 set ui_fname_value {}
229 set ui_fstatus_value {}
230}
231
232proc show_diff {path} {
233 global file_states PARENT diff_3way diff_active
234 global ui_diff ui_fname_value ui_fstatus_value ui_status_value
235
236 if {$diff_active || ![lock_index read]} return
237
238 clear_diff
239 set s $file_states($path)
240 set m [lindex $s 0]
241 set diff_3way 0
242 set diff_active 1
243 set ui_fname_value $path
244 set ui_fstatus_value [mapdesc $m $path]
245 set ui_status_value "Loading diff of $path..."
246
247 set cmd [list | git diff-index -p $PARENT -- $path]
248 switch $m {
249 AM {
250 }
251 MM {
252 set cmd [list | git diff-index -p -c $PARENT $path]
253 }
254 _O {
255 if {[catch {
256 set fd [open $path r]
257 set content [read $fd]
258 close $fd
259 } err ]} {
260 set diff_active 0
261 unlock_index
262 set ui_status_value "Unable to display $path"
263 error_popup "Error loading file:\n$err"
264 return
265 }
266 $ui_diff conf -state normal
267 $ui_diff insert end $content
268 $ui_diff conf -state disabled
269 set diff_active 0
270 unlock_index
271 set ui_status_value {Ready.}
272 return
273 }
274 }
275
276 if {[catch {set fd [open $cmd r]} err]} {
277 set diff_active 0
278 unlock_index
279 set ui_status_value "Unable to display $path"
280 error_popup "Error loading diff:\n$err"
281 return
282 }
283
284 fconfigure $fd -blocking 0 -translation auto
285 fileevent $fd readable [list read_diff $fd]
286}
287
288proc read_diff {fd} {
289 global ui_diff ui_status_value diff_3way diff_active
290
291 while {[gets $fd line] >= 0} {
292 if {[string match {diff --git *} $line]} continue
293 if {[string match {diff --combined *} $line]} continue
294 if {[string match {--- *} $line]} continue
295 if {[string match {+++ *} $line]} continue
296 if {[string match index* $line]} {
297 if {[string first , $line] >= 0} {
298 set diff_3way 1
299 }
300 }
301
302 $ui_diff conf -state normal
303 if {!$diff_3way} {
304 set x [string index $line 0]
305 switch -- $x {
306 "@" {set tags da}
307 "+" {set tags dp}
308 "-" {set tags dm}
309 default {set tags {}}
310 }
311 } else {
312 set x [string range $line 0 1]
313 switch -- $x {
314 default {set tags {}}
315 "@@" {set tags da}
316 "++" {set tags dp; set x " +"}
317 " +" {set tags {di bold}; set x "++"}
318 "+ " {set tags dni; set x "-+"}
319 "--" {set tags dm; set x " -"}
320 " -" {set tags {dm bold}; set x "--"}
321 "- " {set tags di; set x "+-"}
322 default {set tags {}}
323 }
324 set line [string replace $line 0 1 $x]
325 }
326 $ui_diff insert end $line $tags
327 $ui_diff insert end "\n"
328 $ui_diff conf -state disabled
329 }
330
331 if {[eof $fd]} {
332 close $fd
333 set diff_active 0
334 unlock_index
335 set ui_status_value {Ready.}
336 }
337}
338
339######################################################################
340##
341## commit
342
343proc load_last_commit {} {
344 global HEAD PARENT commit_type ui_comm
345
346 if {$commit_type == {amend}} return
347 if {$commit_type != {normal}} {
348 error_popup "Can't amend a $commit_type commit."
349 return
350 }
351
352 set msg {}
353 set parent {}
354 set parent_count 0
355 if {[catch {
356 set fd [open "| git cat-file commit $HEAD" r]
357 while {[gets $fd line] > 0} {
358 if {[string match {parent *} $line]} {
359 set parent [string range $line 7 end]
360 incr parent_count
361 }
362 }
363 set msg [string trim [read $fd]]
364 close $fd
365 } err]} {
366 error_popup "Error loading commit data for amend:\n$err"
367 return
368 }
369
370 if {$parent_count == 0} {
371 set commit_type amend
372 set HEAD {}
373 set PARENT {}
374 update_status
375 } elseif {$parent_count == 1} {
376 set commit_type amend
377 set PARENT $parent
378 $ui_comm delete 0.0 end
379 $ui_comm insert end $msg
380 $ui_comm edit modified false
381 update_status
382 } else {
383 error_popup {You can't amend a merge commit.}
384 return
385 }
386}
387
388proc commit_tree {} {
389 global tcl_platform HEAD gitdir commit_type file_states
390 global commit_active ui_status_value
391 global ui_comm
392
393 if {$commit_active || ![lock_index update]} return
394
395 # -- Our in memory state should match the repository.
396 #
397 repository_state curHEAD cur_type
398 if {$commit_type == {amend}
399 && $cur_type == {normal}
400 && $curHEAD == $HEAD} {
401 } elseif {$commit_type != $cur_type || $HEAD != $curHEAD} {
402 error_popup {Last scanned state does not match repository state.
403
404Its highly likely that another Git program modified the
405repository since our last scan. A rescan is required
406before committing.
407}
408 unlock_index
409 update_status
410 return
411 }
412
413 # -- At least one file should differ in the index.
414 #
415 set files_ready 0
416 foreach path [array names file_states] {
417 set s $file_states($path)
418 switch -glob -- [lindex $s 0] {
419 _* {continue}
420 A* -
421 D* -
422 M* {set files_ready 1; break}
423 U* {
424 error_popup "Unmerged files cannot be committed.
425
426File $path has merge conflicts.
427You must resolve them and check the file in before committing.
428"
429 unlock_index
430 return
431 }
432 default {
433 error_popup "Unknown file state [lindex $s 0] detected.
434
435File $path cannot be committed by this program.
436"
437 }
438 }
439 }
440 if {!$files_ready} {
441 error_popup {No checked-in files to commit.
442
443You must check-in at least 1 file before you can commit.
444}
445 unlock_index
446 return
447 }
448
449 # -- A message is required.
450 #
451 set msg [string trim [$ui_comm get 1.0 end]]
452 if {$msg == {}} {
453 error_popup {Please supply a commit message.
454
455A good commit message has the following format:
456
457- First line: Describe in one sentance what you did.
458- Second line: Blank
459- Remaining lines: Describe why this change is good.
460}
461 unlock_index
462 return
463 }
464
465 # -- Ask the pre-commit hook for the go-ahead.
466 #
467 set pchook [file join $gitdir hooks pre-commit]
468 if {$tcl_platform(platform) == {windows} && [file isfile $pchook]} {
469 set pchook [list sh -c \
470 "if test -x \"$pchook\"; then exec \"$pchook\"; fi"]
471 } elseif {[file executable $pchook]} {
472 set pchook [list $pchook]
473 } else {
474 set pchook {}
475 }
476 if {$pchook != {} && [catch {eval exec $pchook} err]} {
477 hook_failed_popup pre-commit $err
478 unlock_index
479 return
480 }
481
482 # -- Write the tree in the background.
483 #
484 set commit_active 1
485 set ui_status_value {Committing changes...}
486
487 set fd_wt [open "| git write-tree" r]
488 fileevent $fd_wt readable [list commit_stage2 $fd_wt $curHEAD $msg]
489}
490
491proc commit_stage2 {fd_wt curHEAD msg} {
492 global single_commit gitdir PARENT commit_type
493 global commit_active ui_status_value ui_comm
494
495 gets $fd_wt tree_id
496 close $fd_wt
497
498 if {$tree_id == {}} {
499 error_popup "write-tree failed"
500 set commit_active 0
501 set ui_status_value {Commit failed.}
502 unlock_index
503 return
504 }
505
506 # -- Create the commit.
507 #
508 set cmd [list git commit-tree $tree_id]
509 if {$PARENT != {}} {
510 lappend cmd -p $PARENT
511 }
512 if {$commit_type == {merge}} {
513 if {[catch {
514 set fd_mh [open [file join $gitdir MERGE_HEAD] r]
515 while {[gets $fd_mh merge_head] >= 0} {
516 lappend cmd -p $merge_head
517 }
518 close $fd_mh
519 } err]} {
520 error_popup "Loading MERGE_HEADs failed:\n$err"
521 set commit_active 0
522 set ui_status_value {Commit failed.}
523 unlock_index
524 return
525 }
526 }
527 if {$PARENT == {}} {
528 # git commit-tree writes to stderr during initial commit.
529 lappend cmd 2>/dev/null
530 }
531 lappend cmd << $msg
532 if {[catch {set cmt_id [eval exec $cmd]} err]} {
533 error_popup "commit-tree failed:\n$err"
534 set commit_active 0
535 set ui_status_value {Commit failed.}
536 unlock_index
537 return
538 }
539
540 # -- Update the HEAD ref.
541 #
542 set reflogm commit
543 if {$commit_type != {normal}} {
544 append reflogm " ($commit_type)"
545 }
546 set i [string first "\n" $msg]
547 if {$i >= 0} {
548 append reflogm {: } [string range $msg 0 [expr $i - 1]]
549 } else {
550 append reflogm {: } $msg
551 }
552 set cmd [list git update-ref -m $reflogm HEAD $cmt_id $curHEAD]
553 if {[catch {eval exec $cmd} err]} {
554 error_popup "update-ref failed:\n$err"
555 set commit_active 0
556 set ui_status_value {Commit failed.}
557 unlock_index
558 return
559 }
560
561 # -- Cleanup after ourselves.
562 #
563 catch {file delete [file join $gitdir MERGE_HEAD]}
564 catch {file delete [file join $gitdir MERGE_MSG]}
565 catch {file delete [file join $gitdir SQUASH_MSG]}
566 catch {file delete [file join $gitdir GITGUI_MSG]}
567
568 # -- Let rerere do its thing.
569 #
570 if {[file isdirectory [file join $gitdir rr-cache]]} {
571 catch {exec git rerere}
572 }
573
574 $ui_comm delete 0.0 end
575 $ui_comm edit modified false
576
577 if {$single_commit} do_quit
578
579 set commit_type {}
580 set commit_active 0
581 set HEAD $cmt_id
582 set PARENT $cmt_id
583 unlock_index
584 update_status "Changes committed as $cmt_id."
585}
586
587######################################################################
588##
589## fetch pull push
590
591proc fetch_from {remote} {
592 set w [new_console "fetch $remote" \
593 "Fetching new changes from $remote"]
594 set cmd [list git fetch]
595 lappend cmd -v
596 lappend cmd $remote
597 console_exec $w $cmd
598}
599
600proc push_to {remote} {
601 set w [new_console "push $remote" \
602 "Pushing changes to $remote"]
603 set cmd [list git push]
604 lappend -v
605 lappend cmd $remote
606 console_exec $w $cmd
607}
608
609######################################################################
610##
611## ui helpers
612
613proc mapcol {state path} {
614 global all_cols
615
616 if {[catch {set r $all_cols($state)}]} {
617 puts "error: no column for state={$state} $path"
618 return o
619 }
620 return $r
621}
622
623proc mapicon {state path} {
624 global all_icons
625
626 if {[catch {set r $all_icons($state)}]} {
627 puts "error: no icon for state={$state} $path"
628 return file_plain
629 }
630 return $r
631}
632
633proc mapdesc {state path} {
634 global all_descs
635
636 if {[catch {set r $all_descs($state)}]} {
637 puts "error: no desc for state={$state} $path"
638 return $state
639 }
640 return $r
641}
642
643proc bsearch {w path} {
644 set hi [expr [lindex [split [$w index end] .] 0] - 2]
645 if {$hi == 0} {
646 return -1
647 }
648 set lo 0
649 while {$lo < $hi} {
650 set mi [expr [expr $lo + $hi] / 2]
651 set ti [expr $mi + 1]
652 set cmp [string compare [$w get $ti.1 $ti.end] $path]
653 if {$cmp < 0} {
654 set lo $ti
655 } elseif {$cmp == 0} {
656 return $mi
657 } else {
658 set hi $mi
659 }
660 }
661 return -[expr $lo + 1]
662}
663
664proc merge_state {path state} {
665 global file_states
666
667 if {[array names file_states -exact $path] == {}} {
668 set o __
669 set s [list $o none none]
670 } else {
671 set s $file_states($path)
672 set o [lindex $s 0]
673 }
674
675 set m [lindex $s 0]
676 if {[string index $state 0] == "_"} {
677 set state [string index $m 0][string index $state 1]
678 } elseif {[string index $state 0] == "*"} {
679 set state _[string index $state 1]
680 }
681
682 if {[string index $state 1] == "_"} {
683 set state [string index $state 0][string index $m 1]
684 } elseif {[string index $state 1] == "*"} {
685 set state [string index $state 0]_
686 }
687
688 set file_states($path) [lreplace $s 0 0 $state]
689 return $o
690}
691
692proc display_file {path state} {
693 global ui_index ui_other file_states
694
695 set old_m [merge_state $path $state]
696 set s $file_states($path)
697 set m [lindex $s 0]
698
699 if {[mapcol $m $path] == "o"} {
700 set ii 1
701 set ai 2
702 set iw $ui_index
703 set aw $ui_other
704 } else {
705 set ii 2
706 set ai 1
707 set iw $ui_other
708 set aw $ui_index
709 }
710
711 set d [lindex $s $ii]
712 if {$d != "none"} {
713 set lno [bsearch $iw $path]
714 if {$lno >= 0} {
715 incr lno
716 $iw conf -state normal
717 $iw delete $lno.0 [expr $lno + 1].0
718 $iw conf -state disabled
719 set s [lreplace $s $ii $ii none]
720 }
721 }
722
723 set d [lindex $s $ai]
724 if {$d == "none"} {
725 set lno [expr abs([bsearch $aw $path] + 1) + 1]
726 $aw conf -state normal
727 set ico [$aw image create $lno.0 \
728 -align center -padx 5 -pady 1 \
729 -image [mapicon $m $path]]
730 $aw insert $lno.1 "$path\n"
731 $aw conf -state disabled
732 set file_states($path) [lreplace $s $ai $ai [list $ico]]
733 } elseif {[mapicon $m $path] != [mapicon $old_m $path]} {
734 set ico [lindex $d 0]
735 $aw image conf $ico -image [mapicon $m $path]
736 }
737}
738
739proc with_update_index {body} {
740 global update_index_fd
741
742 if {$update_index_fd == {}} {
743 if {![lock_index update]} return
744 set update_index_fd [open \
745 "| git update-index --add --remove -z --stdin" \
746 w]
747 fconfigure $update_index_fd -translation binary
748 uplevel 1 $body
749 close $update_index_fd
750 set update_index_fd {}
751 unlock_index
752 } else {
753 uplevel 1 $body
754 }
755}
756
757proc update_index {path} {
758 global update_index_fd
759
760 if {$update_index_fd == {}} {
761 error {not in with_update_index}
762 } else {
763 puts -nonewline $update_index_fd "$path\0"
764 }
765}
766
767proc toggle_mode {path} {
768 global file_states ui_fname_value
769
770 set s $file_states($path)
771 set m [lindex $s 0]
772
773 switch -- $m {
774 AM -
775 _O {set new A*}
776 _M -
777 MM {set new M*}
778 AD -
779 _D {set new D*}
780 default {return}
781 }
782
783 with_update_index {update_index $path}
784 display_file $path $new
785 if {$ui_fname_value == $path} {
786 show_diff $path
787 }
788}
789
790######################################################################
791##
792## config (fetch push pull)
793
794proc load_repo_config {} {
795 global repo_config
796
797 array unset repo_config
798 catch {
799 set fd_rc [open "| git repo-config --list" r]
800 while {[gets $fd_rc line] >= 0} {
801 if {[regexp {^([^=]+)=(.*)$} $line line name value]} {
802 lappend repo_config($name) $value
803 }
804 }
805 close $fd_rc
806 }
807}
808
809proc load_all_remotes {} {
810 global gitdir all_remotes repo_config
811
812 set all_remotes [list]
813 set rm_dir [file join $gitdir remotes]
814 if {[file isdirectory $rm_dir]} {
815 set all_remotes [concat $all_remotes [glob \
816 -types f \
817 -tails \
818 -nocomplain \
819 -directory $rm_dir *]]
820 }
821
822 foreach line [array names repo_config remote.*.url] {
823 if {[regexp ^remote\.(.*)\.url\$ $line line name]} {
824 lappend all_remotes $name
825 }
826 }
827
828 set all_remotes [lsort -unique $all_remotes]
829}
830
831proc populate_remote_menu {m pfx op} {
832 global gitdir all_remotes mainfont
833
834 foreach remote $all_remotes {
835 $m add command -label "$pfx $remote..." \
836 -command [list $op $remote] \
837 -font $mainfont
838 }
839}
840
841######################################################################
842##
843## icons
844
845set filemask {
846#define mask_width 14
847#define mask_height 15
848static unsigned char mask_bits[] = {
849 0xfe, 0x1f, 0xfe, 0x1f, 0xfe, 0x1f, 0xfe, 0x1f, 0xfe, 0x1f, 0xfe, 0x1f,
850 0xfe, 0x1f, 0xfe, 0x1f, 0xfe, 0x1f, 0xfe, 0x1f, 0xfe, 0x1f, 0xfe, 0x1f,
851 0xfe, 0x1f, 0xfe, 0x1f, 0xfe, 0x1f};
852}
853
854image create bitmap file_plain -background white -foreground black -data {
855#define plain_width 14
856#define plain_height 15
857static unsigned char plain_bits[] = {
858 0xfe, 0x01, 0x02, 0x03, 0x02, 0x05, 0x02, 0x09, 0x02, 0x1f, 0x02, 0x10,
859 0x02, 0x10, 0x02, 0x10, 0x02, 0x10, 0x02, 0x10, 0x02, 0x10, 0x02, 0x10,
860 0x02, 0x10, 0x02, 0x10, 0xfe, 0x1f};
861} -maskdata $filemask
862
863image create bitmap file_mod -background white -foreground blue -data {
864#define mod_width 14
865#define mod_height 15
866static unsigned char mod_bits[] = {
867 0xfe, 0x01, 0x02, 0x03, 0x7a, 0x05, 0x02, 0x09, 0x7a, 0x1f, 0x02, 0x10,
868 0xfa, 0x17, 0x02, 0x10, 0xfa, 0x17, 0x02, 0x10, 0xfa, 0x17, 0x02, 0x10,
869 0xfa, 0x17, 0x02, 0x10, 0xfe, 0x1f};
870} -maskdata $filemask
871
872image create bitmap file_fulltick -background white -foreground "#007000" -data {
873#define file_fulltick_width 14
874#define file_fulltick_height 15
875static unsigned char file_fulltick_bits[] = {
876 0xfe, 0x01, 0x02, 0x1a, 0x02, 0x0c, 0x02, 0x0c, 0x02, 0x16, 0x02, 0x16,
877 0x02, 0x13, 0x00, 0x13, 0x86, 0x11, 0x8c, 0x11, 0xd8, 0x10, 0xf2, 0x10,
878 0x62, 0x10, 0x02, 0x10, 0xfe, 0x1f};
879} -maskdata $filemask
880
881image create bitmap file_parttick -background white -foreground "#005050" -data {
882#define parttick_width 14
883#define parttick_height 15
884static unsigned char parttick_bits[] = {
885 0xfe, 0x01, 0x02, 0x03, 0x7a, 0x05, 0x02, 0x09, 0x7a, 0x1f, 0x02, 0x10,
886 0x7a, 0x14, 0x02, 0x16, 0x02, 0x13, 0x8a, 0x11, 0xda, 0x10, 0x72, 0x10,
887 0x22, 0x10, 0x02, 0x10, 0xfe, 0x1f};
888} -maskdata $filemask
889
890image create bitmap file_question -background white -foreground black -data {
891#define file_question_width 14
892#define file_question_height 15
893static unsigned char file_question_bits[] = {
894 0xfe, 0x01, 0x02, 0x02, 0xe2, 0x04, 0xf2, 0x09, 0x1a, 0x1b, 0x0a, 0x13,
895 0x82, 0x11, 0xc2, 0x10, 0x62, 0x10, 0x62, 0x10, 0x02, 0x10, 0x62, 0x10,
896 0x62, 0x10, 0x02, 0x10, 0xfe, 0x1f};
897} -maskdata $filemask
898
899image create bitmap file_removed -background white -foreground red -data {
900#define file_removed_width 14
901#define file_removed_height 15
902static unsigned char file_removed_bits[] = {
903 0xfe, 0x01, 0x02, 0x03, 0x02, 0x05, 0x02, 0x09, 0x02, 0x1f, 0x02, 0x10,
904 0x1a, 0x16, 0x32, 0x13, 0xe2, 0x11, 0xc2, 0x10, 0xe2, 0x11, 0x32, 0x13,
905 0x1a, 0x16, 0x02, 0x10, 0xfe, 0x1f};
906} -maskdata $filemask
907
908image create bitmap file_merge -background white -foreground blue -data {
909#define file_merge_width 14
910#define file_merge_height 15
911static unsigned char file_merge_bits[] = {
912 0xfe, 0x01, 0x02, 0x03, 0x62, 0x05, 0x62, 0x09, 0x62, 0x1f, 0x62, 0x10,
913 0xfa, 0x11, 0xf2, 0x10, 0x62, 0x10, 0x02, 0x10, 0xfa, 0x17, 0x02, 0x10,
914 0xfa, 0x17, 0x02, 0x10, 0xfe, 0x1f};
915} -maskdata $filemask
916
917set max_status_desc 0
918foreach i {
919 {__ i plain "Unmodified"}
920 {_M i mod "Modified"}
921 {M_ i fulltick "Checked in"}
922 {MM i parttick "Partially checked in"}
923
924 {_O o plain "Untracked"}
925 {A_ o fulltick "Added"}
926 {AM o parttick "Partially added"}
927 {AD o question "Added (but now gone)"}
928
929 {_D i question "Missing"}
930 {D_ i removed "Removed"}
931 {DD i removed "Removed"}
932 {DO i removed "Removed (still exists)"}
933
934 {UM i merge "Merge conflicts"}
935 {U_ i merge "Merge conflicts"}
936 } {
937 if {$max_status_desc < [string length [lindex $i 3]]} {
938 set max_status_desc [string length [lindex $i 3]]
939 }
940 set all_cols([lindex $i 0]) [lindex $i 1]
941 set all_icons([lindex $i 0]) file_[lindex $i 2]
942 set all_descs([lindex $i 0]) [lindex $i 3]
943}
944unset filemask i
945
946######################################################################
947##
948## util
949
950proc error_popup {msg} {
951 set w .error
952 toplevel $w
953 wm transient $w .
954 show_msg $w $w $msg
955}
956
957proc show_msg {w top msg} {
958 global gitdir appname mainfont
959
960 message $w.m -text $msg -justify left -aspect 400
961 pack $w.m -side top -fill x -padx 5 -pady 10
962 button $w.ok -text OK \
963 -width 15 \
964 -font $mainfont \
965 -command "destroy $top"
966 pack $w.ok -side bottom
967 bind $top <Visibility> "grab $top; focus $top"
968 bind $top <Key-Return> "destroy $top"
969 wm title $top "error: $appname ([file normalize [file dirname $gitdir]])"
970 tkwait window $top
971}
972
973proc hook_failed_popup {hook msg} {
974 global gitdir mainfont difffont appname
975
976 set w .hookfail
977 toplevel $w
978 wm transient $w .
979
980 frame $w.m
981 label $w.m.l1 -text "$hook hook failed:" \
982 -anchor w \
983 -justify left \
984 -font [concat $mainfont bold]
985 text $w.m.t \
986 -background white -borderwidth 1 \
987 -relief sunken \
988 -width 80 -height 10 \
989 -font $difffont \
990 -yscrollcommand [list $w.m.sby set]
991 label $w.m.l2 \
992 -text {You must correct the above errors before committing.} \
993 -anchor w \
994 -justify left \
995 -font [concat $mainfont bold]
996 scrollbar $w.m.sby -command [list $w.m.t yview]
997 pack $w.m.l1 -side top -fill x
998 pack $w.m.l2 -side bottom -fill x
999 pack $w.m.sby -side right -fill y
1000 pack $w.m.t -side left -fill both -expand 1
1001 pack $w.m -side top -fill both -expand 1 -padx 5 -pady 10
1002
1003 $w.m.t insert 1.0 $msg
1004 $w.m.t conf -state disabled
1005
1006 button $w.ok -text OK \
1007 -width 15 \
1008 -font $mainfont \
1009 -command "destroy $w"
1010 pack $w.ok -side bottom
1011
1012 bind $w <Visibility> "grab $w; focus $w"
1013 bind $w <Key-Return> "destroy $w"
1014 wm title $w "error: $appname ([file normalize [file dirname $gitdir]])"
1015 tkwait window $w
1016}
1017
1018set next_console_id 0
1019
1020proc new_console {short_title long_title} {
1021 global next_console_id console_data
1022 set w .console[incr next_console_id]
1023 set console_data($w) [list $short_title $long_title]
1024 return [console_init $w]
1025}
1026
1027proc console_init {w} {
1028 global console_cr console_data
1029 global gitdir appname mainfont difffont
1030
1031 set console_cr($w) 1.0
1032 toplevel $w
1033 frame $w.m
1034 label $w.m.l1 -text "[lindex $console_data($w) 1]:" \
1035 -anchor w \
1036 -justify left \
1037 -font [concat $mainfont bold]
1038 text $w.m.t \
1039 -background white -borderwidth 1 \
1040 -relief sunken \
1041 -width 80 -height 10 \
1042 -font $difffont \
1043 -state disabled \
1044 -yscrollcommand [list $w.m.sby set]
1045 label $w.m.s -anchor w \
1046 -justify left \
1047 -font [concat $mainfont bold]
1048 scrollbar $w.m.sby -command [list $w.m.t yview]
1049 pack $w.m.l1 -side top -fill x
1050 pack $w.m.s -side bottom -fill x
1051 pack $w.m.sby -side right -fill y
1052 pack $w.m.t -side left -fill both -expand 1
1053 pack $w.m -side top -fill both -expand 1 -padx 5 -pady 10
1054
1055 button $w.ok -text {Running...} \
1056 -width 15 \
1057 -font $mainfont \
1058 -state disabled \
1059 -command "destroy $w"
1060 pack $w.ok -side bottom
1061
1062 bind $w <Visibility> "focus $w"
1063 wm title $w "$appname ([file dirname [file normalize [file dirname $gitdir]]]): [lindex $console_data($w) 0]"
1064 return $w
1065}
1066
1067proc console_exec {w cmd} {
1068 global tcl_platform
1069
1070 # -- Windows tosses the enviroment when we exec our child.
1071 # But most users need that so we have to relogin. :-(
1072 #
1073 if {$tcl_platform(platform) == {windows}} {
1074 set cmd [list sh --login -c "cd \"[pwd]\" && [join $cmd { }]"]
1075 }
1076
1077 # -- Tcl won't let us redirect both stdout and stderr to
1078 # the same pipe. So pass it through cat...
1079 #
1080 set cmd [concat | $cmd |& cat]
1081
1082 set fd_f [open $cmd r]
1083 fconfigure $fd_f -blocking 0 -translation binary
1084 fileevent $fd_f readable [list console_read $w $fd_f]
1085}
1086
1087proc console_read {w fd} {
1088 global console_cr console_data
1089
1090 set buf [read $fd]
1091 if {$buf != {}} {
1092 if {![winfo exists $w]} {console_init $w}
1093 $w.m.t conf -state normal
1094 set c 0
1095 set n [string length $buf]
1096 while {$c < $n} {
1097 set cr [string first "\r" $buf $c]
1098 set lf [string first "\n" $buf $c]
1099 if {$cr < 0} {set cr [expr $n + 1]}
1100 if {$lf < 0} {set lf [expr $n + 1]}
1101
1102 if {$lf < $cr} {
1103 $w.m.t insert end [string range $buf $c $lf]
1104 set console_cr($w) [$w.m.t index {end -1c}]
1105 set c $lf
1106 incr c
1107 } else {
1108 $w.m.t delete $console_cr($w) end
1109 $w.m.t insert end "\n"
1110 $w.m.t insert end [string range $buf $c $cr]
1111 set c $cr
1112 incr c
1113 }
1114 }
1115 $w.m.t conf -state disabled
1116 $w.m.t see end
1117 }
1118
1119 fconfigure $fd -blocking 1
1120 if {[eof $fd]} {
1121 if {[catch {close $fd}]} {
1122 if {![winfo exists $w]} {console_init $w}
1123 $w.m.s conf -background red -text {Error: Command Failed}
1124 $w.ok conf -text Close
1125 $w.ok conf -state normal
1126 } elseif {[winfo exists $w]} {
1127 $w.m.s conf -background green -text {Success}
1128 $w.ok conf -text Close
1129 $w.ok conf -state normal
1130 }
1131 array unset console_cr $w
1132 array unset console_data $w
1133 return
1134 }
1135 fconfigure $fd -blocking 0
1136}
1137
1138######################################################################
1139##
1140## ui commands
1141
1142set starting_gitk_msg {Please wait... Starting gitk...}
1143
1144proc do_gitk {} {
1145 global tcl_platform ui_status_value starting_gitk_msg
1146
1147 set ui_status_value $starting_gitk_msg
1148 after 10000 {
1149 if {$ui_status_value == $starting_gitk_msg} {
1150 set ui_status_value {Ready.}
1151 }
1152 }
1153
1154 if {$tcl_platform(platform) == {windows}} {
1155 exec sh -c gitk &
1156 } else {
1157 exec gitk &
1158 }
1159}
1160
1161proc do_quit {} {
1162 global gitdir ui_comm
1163
1164 set save [file join $gitdir GITGUI_MSG]
1165 set msg [string trim [$ui_comm get 0.0 end]]
1166 if {[$ui_comm edit modified] && $msg != {}} {
1167 catch {
1168 set fd [open $save w]
1169 puts $fd [string trim [$ui_comm get 0.0 end]]
1170 close $fd
1171 }
1172 } elseif {$msg == {} && [file exists $save]} {
1173 file delete $save
1174 }
1175
1176 destroy .
1177}
1178
1179proc do_rescan {} {
1180 update_status
1181}
1182
1183proc do_checkin_all {} {
1184 global checkin_active ui_status_value
1185
1186 if {$checkin_active || ![lock_index begin-update]} return
1187
1188 set checkin_active 1
1189 set ui_status_value {Checking in all files...}
1190 after 1 {
1191 with_update_index {
1192 foreach path [array names file_states] {
1193 set s $file_states($path)
1194 set m [lindex $s 0]
1195 switch -- $m {
1196 AM -
1197 MM -
1198 _M -
1199 _D {toggle_mode $path}
1200 }
1201 }
1202 }
1203 set checkin_active 0
1204 set ui_status_value {Ready.}
1205 }
1206}
1207
1208proc do_signoff {} {
1209 global ui_comm
1210
1211 catch {
1212 set me [exec git var GIT_COMMITTER_IDENT]
1213 if {[regexp {(.*) [0-9]+ [-+0-9]+$} $me me name]} {
1214 set str "Signed-off-by: $name"
1215 if {[$ui_comm get {end -1c linestart} {end -1c}] != $str} {
1216 $ui_comm insert end "\n"
1217 $ui_comm insert end $str
1218 $ui_comm see end
1219 }
1220 }
1221 }
1222}
1223
1224proc do_amend_last {} {
1225 load_last_commit
1226}
1227
1228proc do_commit {} {
1229 commit_tree
1230}
1231
1232# shift == 1: left click
1233# 3: right click
1234proc click {w x y shift wx wy} {
1235 global ui_index ui_other
1236
1237 set pos [split [$w index @$x,$y] .]
1238 set lno [lindex $pos 0]
1239 set col [lindex $pos 1]
1240 set path [$w get $lno.1 $lno.end]
1241 if {$path == {}} return
1242
1243 if {$col > 0 && $shift == 1} {
1244 $ui_index tag remove in_diff 0.0 end
1245 $ui_other tag remove in_diff 0.0 end
1246 $w tag add in_diff $lno.0 [expr $lno + 1].0
1247 show_diff $path
1248 }
1249}
1250
1251proc unclick {w x y} {
1252 set pos [split [$w index @$x,$y] .]
1253 set lno [lindex $pos 0]
1254 set col [lindex $pos 1]
1255 set path [$w get $lno.1 $lno.end]
1256 if {$path == {}} return
1257
1258 if {$col == 0} {
1259 toggle_mode $path
1260 }
1261}
1262
1263######################################################################
1264##
1265## ui init
1266
1267set mainfont {Helvetica 10}
1268set difffont {Courier 10}
1269set maincursor [. cget -cursor]
1270
1271switch -glob -- "$tcl_platform(platform),$tcl_platform(os)" {
1272windows,* {set M1B Control; set M1T Ctrl}
1273unix,Darwin {set M1B M1; set M1T Cmd}
1274default {set M1B M1; set M1T M1}
1275}
1276
1277# -- Menu Bar
1278menu .mbar -tearoff 0
1279.mbar add cascade -label Project -menu .mbar.project
1280.mbar add cascade -label Commit -menu .mbar.commit
1281.mbar add cascade -label Fetch -menu .mbar.fetch
1282.mbar add cascade -label Pull -menu .mbar.pull
1283.mbar add cascade -label Push -menu .mbar.push
1284. configure -menu .mbar
1285
1286# -- Project Menu
1287menu .mbar.project
1288.mbar.project add command -label Visualize \
1289 -command do_gitk \
1290 -font $mainfont
1291.mbar.project add command -label Quit \
1292 -command do_quit \
1293 -accelerator $M1T-Q \
1294 -font $mainfont
1295
1296# -- Commit Menu
1297menu .mbar.commit
1298.mbar.commit add command -label Rescan \
1299 -command do_rescan \
1300 -accelerator F5 \
1301 -font $mainfont
1302lappend disable_on_lock \
1303 [list .mbar.commit entryconf [.mbar.commit index last] -state]
1304.mbar.commit add command -label {Amend Last Commit} \
1305 -command do_amend_last \
1306 -font $mainfont
1307lappend disable_on_lock \
1308 [list .mbar.commit entryconf [.mbar.commit index last] -state]
1309.mbar.commit add command -label {Check-in All Files} \
1310 -command do_checkin_all \
1311 -accelerator $M1T-U \
1312 -font $mainfont
1313lappend disable_on_lock \
1314 [list .mbar.commit entryconf [.mbar.commit index last] -state]
1315.mbar.commit add command -label {Sign Off} \
1316 -command do_signoff \
1317 -accelerator $M1T-S \
1318 -font $mainfont
1319.mbar.commit add command -label Commit \
1320 -command do_commit \
1321 -accelerator $M1T-Return \
1322 -font $mainfont
1323lappend disable_on_lock \
1324 [list .mbar.commit entryconf [.mbar.commit index last] -state]
1325
1326# -- Fetch Menu
1327menu .mbar.fetch
1328
1329# -- Pull Menu
1330menu .mbar.pull
1331
1332# -- Push Menu
1333menu .mbar.push
1334
1335# -- Main Window Layout
1336panedwindow .vpane -orient vertical
1337panedwindow .vpane.files -orient horizontal
1338.vpane add .vpane.files -sticky nsew -height 100 -width 400
1339pack .vpane -anchor n -side top -fill both -expand 1
1340
1341# -- Index File List
1342set ui_index .vpane.files.index.list
1343frame .vpane.files.index -height 100 -width 400
1344label .vpane.files.index.title -text {Modified Files} \
1345 -background green \
1346 -font $mainfont
1347text $ui_index -background white -borderwidth 0 \
1348 -width 40 -height 10 \
1349 -font $mainfont \
1350 -yscrollcommand {.vpane.files.index.sb set} \
1351 -cursor $maincursor \
1352 -state disabled
1353scrollbar .vpane.files.index.sb -command [list $ui_index yview]
1354pack .vpane.files.index.title -side top -fill x
1355pack .vpane.files.index.sb -side right -fill y
1356pack $ui_index -side left -fill both -expand 1
1357.vpane.files add .vpane.files.index -sticky nsew
1358
1359# -- Other (Add) File List
1360set ui_other .vpane.files.other.list
1361frame .vpane.files.other -height 100 -width 100
1362label .vpane.files.other.title -text {Untracked Files} \
1363 -background red \
1364 -font $mainfont
1365text $ui_other -background white -borderwidth 0 \
1366 -width 40 -height 10 \
1367 -font $mainfont \
1368 -yscrollcommand {.vpane.files.other.sb set} \
1369 -cursor $maincursor \
1370 -state disabled
1371scrollbar .vpane.files.other.sb -command [list $ui_other yview]
1372pack .vpane.files.other.title -side top -fill x
1373pack .vpane.files.other.sb -side right -fill y
1374pack $ui_other -side left -fill both -expand 1
1375.vpane.files add .vpane.files.other -sticky nsew
1376
1377$ui_index tag conf in_diff -font [concat $mainfont bold]
1378$ui_other tag conf in_diff -font [concat $mainfont bold]
1379
1380# -- Diff Header
1381set ui_fname_value {}
1382set ui_fstatus_value {}
1383frame .vpane.diff -height 200 -width 400
1384frame .vpane.diff.header
1385label .vpane.diff.header.l1 -text {File:} -font $mainfont
1386label .vpane.diff.header.l2 -textvariable ui_fname_value \
1387 -anchor w \
1388 -justify left \
1389 -font $mainfont
1390label .vpane.diff.header.l3 -text {Status:} -font $mainfont
1391label .vpane.diff.header.l4 -textvariable ui_fstatus_value \
1392 -width $max_status_desc \
1393 -anchor w \
1394 -justify left \
1395 -font $mainfont
1396pack .vpane.diff.header.l1 -side left
1397pack .vpane.diff.header.l2 -side left -fill x
1398pack .vpane.diff.header.l4 -side right
1399pack .vpane.diff.header.l3 -side right
1400
1401# -- Diff Body
1402frame .vpane.diff.body
1403set ui_diff .vpane.diff.body.t
1404text $ui_diff -background white -borderwidth 0 \
1405 -width 80 -height 15 -wrap none \
1406 -font $difffont \
1407 -xscrollcommand {.vpane.diff.body.sbx set} \
1408 -yscrollcommand {.vpane.diff.body.sby set} \
1409 -cursor $maincursor \
1410 -state disabled
1411scrollbar .vpane.diff.body.sbx -orient horizontal \
1412 -command [list $ui_diff xview]
1413scrollbar .vpane.diff.body.sby -orient vertical \
1414 -command [list $ui_diff yview]
1415pack .vpane.diff.body.sbx -side bottom -fill x
1416pack .vpane.diff.body.sby -side right -fill y
1417pack $ui_diff -side left -fill both -expand 1
1418pack .vpane.diff.header -side top -fill x
1419pack .vpane.diff.body -side bottom -fill both -expand 1
1420.vpane add .vpane.diff -stick nsew
1421
1422$ui_diff tag conf dm -foreground red
1423$ui_diff tag conf dp -foreground blue
1424$ui_diff tag conf da -font [concat $difffont bold]
1425$ui_diff tag conf di -foreground "#00a000"
1426$ui_diff tag conf dni -foreground "#a000a0"
1427$ui_diff tag conf bold -font [concat $difffont bold]
1428
1429# -- Commit Area
1430frame .vpane.commarea -height 170
1431.vpane add .vpane.commarea -stick nsew
1432
1433# -- Commit Area Buttons
1434frame .vpane.commarea.buttons
1435label .vpane.commarea.buttons.l -text {} \
1436 -anchor w \
1437 -justify left \
1438 -font $mainfont
1439pack .vpane.commarea.buttons.l -side top -fill x
1440pack .vpane.commarea.buttons -side left -fill y
1441
1442button .vpane.commarea.buttons.rescan -text {Rescan} \
1443 -command do_rescan \
1444 -font $mainfont
1445pack .vpane.commarea.buttons.rescan -side top -fill x
1446lappend disable_on_lock {.vpane.commarea.buttons.rescan conf -state}
1447
1448button .vpane.commarea.buttons.amend -text {Amend Last} \
1449 -command do_amend_last \
1450 -font $mainfont
1451pack .vpane.commarea.buttons.amend -side top -fill x
1452lappend disable_on_lock {.vpane.commarea.buttons.amend conf -state}
1453
1454button .vpane.commarea.buttons.ciall -text {Check-in All} \
1455 -command do_checkin_all \
1456 -font $mainfont
1457pack .vpane.commarea.buttons.ciall -side top -fill x
1458lappend disable_on_lock {.vpane.commarea.buttons.ciall conf -state}
1459
1460button .vpane.commarea.buttons.signoff -text {Sign Off} \
1461 -command do_signoff \
1462 -font $mainfont
1463pack .vpane.commarea.buttons.signoff -side top -fill x
1464
1465button .vpane.commarea.buttons.commit -text {Commit} \
1466 -command do_commit \
1467 -font $mainfont
1468pack .vpane.commarea.buttons.commit -side top -fill x
1469lappend disable_on_lock {.vpane.commarea.buttons.commit conf -state}
1470
1471# -- Commit Message Buffer
1472frame .vpane.commarea.buffer
1473set ui_comm .vpane.commarea.buffer.t
1474set ui_coml .vpane.commarea.buffer.l
1475label $ui_coml -text {Commit Message:} \
1476 -anchor w \
1477 -justify left \
1478 -font $mainfont
1479trace add variable commit_type write {uplevel #0 {
1480 switch -glob $commit_type \
1481 initial {$ui_coml conf -text {Initial Commit Message:}} \
1482 amend {$ui_coml conf -text {Amended Commit Message:}} \
1483 merge {$ui_coml conf -text {Merge Commit Message:}} \
1484 * {$ui_coml conf -text {Commit Message:}}
1485}}
1486text $ui_comm -background white -borderwidth 1 \
1487 -relief sunken \
1488 -width 75 -height 10 -wrap none \
1489 -font $difffont \
1490 -yscrollcommand {.vpane.commarea.buffer.sby set} \
1491 -cursor $maincursor
1492scrollbar .vpane.commarea.buffer.sby -command [list $ui_comm yview]
1493pack $ui_coml -side top -fill x
1494pack .vpane.commarea.buffer.sby -side right -fill y
1495pack $ui_comm -side left -fill y
1496pack .vpane.commarea.buffer -side left -fill y
1497
1498# -- Status Bar
1499set ui_status_value {Initializing...}
1500label .status -textvariable ui_status_value \
1501 -anchor w \
1502 -justify left \
1503 -borderwidth 1 \
1504 -relief sunken \
1505 -font $mainfont
1506pack .status -anchor w -side bottom -fill x
1507
1508# -- Key Bindings
1509bind $ui_comm <$M1B-Key-Return> {do_commit;break}
1510bind . <Destroy> do_quit
1511bind all <Key-F5> do_rescan
1512bind all <$M1B-Key-r> do_rescan
1513bind all <$M1B-Key-R> do_rescan
1514bind . <$M1B-Key-s> do_signoff
1515bind . <$M1B-Key-S> do_signoff
1516bind . <$M1B-Key-u> do_checkin_all
1517bind . <$M1B-Key-U> do_checkin_all
1518bind . <$M1B-Key-Return> do_commit
1519bind all <$M1B-Key-q> do_quit
1520bind all <$M1B-Key-Q> do_quit
1521bind all <$M1B-Key-w> {destroy [winfo toplevel %W]}
1522bind all <$M1B-Key-W> {destroy [winfo toplevel %W]}
1523foreach i [list $ui_index $ui_other] {
1524 bind $i <Button-1> {click %W %x %y 1 %X %Y; break}
1525 bind $i <Button-3> {click %W %x %y 3 %X %Y; break}
1526 bind $i <ButtonRelease-1> {unclick %W %x %y; break}
1527}
1528unset i M1B M1T
1529
1530######################################################################
1531##
1532## main
1533
1534set appname [lindex [file split $argv0] end]
1535set gitdir {}
1536
1537if {[catch {set cdup [exec git rev-parse --show-cdup]} err]} {
1538 show_msg {} . "Cannot find the git directory: $err"
1539 exit 1
1540}
1541if {$cdup != ""} {
1542 cd $cdup
1543}
1544unset cdup
1545
1546if {[catch {set gitdir [exec git rev-parse --git-dir]} err]} {
1547 show_msg {} . "Cannot find the git directory: $err"
1548 exit 1
1549}
1550
1551if {$appname == {git-citool}} {
1552 set single_commit 1
1553}
1554
1555wm title . "$appname ([file normalize [file dirname $gitdir]])"
1556focus -force $ui_comm
1557load_repo_config
1558load_all_remotes
1559populate_remote_menu .mbar.fetch From fetch_from
1560populate_remote_menu .mbar.push To push_to
1561update_status