Code

git-gui: Don't complain if no .git/remotes exist.
[git.git] / git-gui
1 #!/bin/sh
2 # Tcl ignores the next line -*- tcl -*- \
3 exec wish "$0" -- "$@"
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.
10 ######################################################################
11 ##
12 ## task management
14 set single_commit 0
15 set status_active 0
16 set diff_active 0
17 set checkin_active 0
18 set commit_active 0
19 set update_index_fd {}
21 set disable_on_lock [list]
22 set index_lock_type none
24 set HEAD {}
25 set PARENT {}
26 set commit_type {}
28 proc lock_index {type} {
29         global index_lock_type disable_on_lock
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 }
44 proc unlock_index {} {
45         global index_lock_type disable_on_lock
47         set index_lock_type none
48         foreach w $disable_on_lock {
49                 uplevel #0 $w normal
50         }
51 }
53 ######################################################################
54 ##
55 ## status
57 proc repository_state {hdvar ctvar} {
58         global gitdir
59         upvar $hdvar hd $ctvar ct
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 }
70 proc 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
75         if {$status_active || ![lock_index read]} return
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         }
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         }
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         }
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]
110 proc 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
115         read $fd
116         if {![eof $fd]} return
117         close $fd
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         }
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]
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]
140 proc load_message {file} {
141         global gitdir ui_comm
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
157 proc read_diff_index {fd final} {
158         global buf_rdi
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
171 proc read_diff_files {fd final} {
172         global buf_rdf
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
185 proc read_ls_others {fd final} {
186         global buf_rlo
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
197 proc status_eof {fd buf final} {
198         global status_active $buf
199         global ui_fname_value ui_status_value file_states
201         if {[eof $fd]} {
202                 set $buf {}
203                 close $fd
204                 if {[incr status_active -1] == 0} {
205                         unlock_index
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         }
218 ######################################################################
219 ##
220 ## diff
222 proc clear_diff {} {
223         global ui_diff ui_fname_value ui_fstatus_value
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 {}
232 proc 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
236         if {$diff_active || ![lock_index read]} return
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..."
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         }
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         }
284         fconfigure $fd -blocking 0 -translation auto
285         fileevent $fd readable [list read_diff $fd]
288 proc read_diff {fd} {
289         global ui_diff ui_status_value diff_3way diff_active
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                 }
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         }
331         if {[eof $fd]} {
332                 close $fd
333                 set diff_active 0
334                 unlock_index
335                 set ui_status_value {Ready.}
336         }
339 ######################################################################
340 ##
341 ## commit
343 proc load_last_commit {} {
344         global HEAD PARENT commit_type ui_comm
346         if {$commit_type == {amend}} return
347         if {$commit_type != {normal}} {
348                 error_popup "Can't amend a $commit_type commit."
349                 return
350         }
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         }
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         }
388 proc commit_tree {} {
389         global tcl_platform HEAD gitdir commit_type file_states
390         global commit_active ui_status_value
391         global ui_comm
393         if {$commit_active || ![lock_index update]} return
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.
404 Its highly likely that another Git program modified the
405 repository since our last scan.  A rescan is required
406 before committing.
408                 unlock_index
409                 update_status
410                 return
411         }
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.
426 File $path has merge conflicts.
427 You must resolve them and check the file in before committing.
429                         unlock_index
430                         return
431                 }
432                 default {
433                         error_popup "Unknown file state [lindex $s 0] detected.
435 File $path cannot be committed by this program.
437                 }
438                 }
439         }
440         if {!$files_ready} {
441                 error_popup {No checked-in files to commit.
443 You must check-in at least 1 file before you can commit.
445                 unlock_index
446                 return
447         }
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.
455 A good commit message has the following format:
457 - First line: Describe in one sentance what you did.
458 - Second line: Blank
459 - Remaining lines: Describe why this change is good.
461                 unlock_index
462                 return
463         }
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         }
482         # -- Write the tree in the background.
483         #
484         set commit_active 1
485         set ui_status_value {Committing changes...}
487         set fd_wt [open "| git write-tree" r]
488         fileevent $fd_wt readable [list commit_stage2 $fd_wt $curHEAD $msg]
491 proc commit_stage2 {fd_wt curHEAD msg} {
492         global single_commit gitdir PARENT commit_type
493         global commit_active ui_status_value ui_comm
495         gets $fd_wt tree_id
496         close $fd_wt
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         }
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         }
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         }
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]}
568         # -- Let rerere do its thing.
569         #
570         if {[file isdirectory [file join $gitdir rr-cache]]} {
571                 catch {exec git rerere}
572         }
574         $ui_comm delete 0.0 end
575         $ui_comm edit modified false
577         if {$single_commit} do_quit
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."
587 ######################################################################
588 ##
589 ## fetch pull push
591 proc 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
600 proc 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
609 ######################################################################
610 ##
611 ## ui helpers
613 proc mapcol {state path} {
614         global all_cols
616         if {[catch {set r $all_cols($state)}]} {
617                 puts "error: no column for state={$state} $path"
618                 return o
619         }
620         return $r
623 proc mapicon {state path} {
624         global all_icons
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
633 proc mapdesc {state path} {
634         global all_descs
636         if {[catch {set r $all_descs($state)}]} {
637                 puts "error: no desc for state={$state} $path"
638                 return $state
639         }
640         return $r
643 proc 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]
664 proc merge_state {path state} {
665         global file_states
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         }
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         }
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         }
688         set file_states($path) [lreplace $s 0 0 $state]
689         return $o
692 proc display_file {path state} {
693         global ui_index ui_other file_states
695         set old_m [merge_state $path $state]
696         set s $file_states($path)
697         set m [lindex $s 0]
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         }
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         }
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         }
739 proc with_update_index {body} {
740         global update_index_fd
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         }
757 proc update_index {path} {
758         global update_index_fd
760         if {$update_index_fd == {}} {
761                 error {not in with_update_index}
762         } else {
763                 puts -nonewline $update_index_fd "$path\0"
764         }
767 proc toggle_mode {path} {
768         global file_states ui_fname_value
770         set s $file_states($path)
771         set m [lindex $s 0]
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         }
783         with_update_index {update_index $path}
784         display_file $path $new
785         if {$ui_fname_value == $path} {
786                 show_diff $path
787         }
790 ######################################################################
791 ##
792 ## config (fetch push pull)
794 proc load_all_remotes {} {
795         global gitdir all_remotes
797         set all_remotes [list]
798         set rm_dir [file join $gitdir remotes]
799         if {[file isdirectory $rm_dir]} {
800                 set all_remotes [concat $all_remotes [glob \
801                         -types f \
802                         -tails \
803                         -nocomplain \
804                         -directory $rm_dir *]]
805         }
807         set fd_rc [open "| git repo-config --list" r]
808         while {[gets $fd_rc line] >= 0} {
809                 if {[regexp ^remote\.(.*)\.url= $line line name]} {
810                         lappend all_remotes $name
811                 }
812         }
813         close $fd_rc
815         set all_remotes [lsort -unique $all_remotes]
818 proc populate_remote_menu {m pfx op} {
819         global gitdir all_remotes mainfont
821         foreach remote $all_remotes {
822                 $m add command -label "$pfx $remote..." \
823                         -command [list $op $remote] \
824                         -font $mainfont
825         }
828 ######################################################################
829 ##
830 ## icons
832 set filemask {
833 #define mask_width 14
834 #define mask_height 15
835 static unsigned char mask_bits[] = {
836    0xfe, 0x1f, 0xfe, 0x1f, 0xfe, 0x1f, 0xfe, 0x1f, 0xfe, 0x1f, 0xfe, 0x1f,
837    0xfe, 0x1f, 0xfe, 0x1f, 0xfe, 0x1f, 0xfe, 0x1f, 0xfe, 0x1f, 0xfe, 0x1f,
838    0xfe, 0x1f, 0xfe, 0x1f, 0xfe, 0x1f};
841 image create bitmap file_plain -background white -foreground black -data {
842 #define plain_width 14
843 #define plain_height 15
844 static unsigned char plain_bits[] = {
845    0xfe, 0x01, 0x02, 0x03, 0x02, 0x05, 0x02, 0x09, 0x02, 0x1f, 0x02, 0x10,
846    0x02, 0x10, 0x02, 0x10, 0x02, 0x10, 0x02, 0x10, 0x02, 0x10, 0x02, 0x10,
847    0x02, 0x10, 0x02, 0x10, 0xfe, 0x1f};
848 } -maskdata $filemask
850 image create bitmap file_mod -background white -foreground blue -data {
851 #define mod_width 14
852 #define mod_height 15
853 static unsigned char mod_bits[] = {
854    0xfe, 0x01, 0x02, 0x03, 0x7a, 0x05, 0x02, 0x09, 0x7a, 0x1f, 0x02, 0x10,
855    0xfa, 0x17, 0x02, 0x10, 0xfa, 0x17, 0x02, 0x10, 0xfa, 0x17, 0x02, 0x10,
856    0xfa, 0x17, 0x02, 0x10, 0xfe, 0x1f};
857 } -maskdata $filemask
859 image create bitmap file_fulltick -background white -foreground "#007000" -data {
860 #define file_fulltick_width 14
861 #define file_fulltick_height 15
862 static unsigned char file_fulltick_bits[] = {
863    0xfe, 0x01, 0x02, 0x1a, 0x02, 0x0c, 0x02, 0x0c, 0x02, 0x16, 0x02, 0x16,
864    0x02, 0x13, 0x00, 0x13, 0x86, 0x11, 0x8c, 0x11, 0xd8, 0x10, 0xf2, 0x10,
865    0x62, 0x10, 0x02, 0x10, 0xfe, 0x1f};
866 } -maskdata $filemask
868 image create bitmap file_parttick -background white -foreground "#005050" -data {
869 #define parttick_width 14
870 #define parttick_height 15
871 static unsigned char parttick_bits[] = {
872    0xfe, 0x01, 0x02, 0x03, 0x7a, 0x05, 0x02, 0x09, 0x7a, 0x1f, 0x02, 0x10,
873    0x7a, 0x14, 0x02, 0x16, 0x02, 0x13, 0x8a, 0x11, 0xda, 0x10, 0x72, 0x10,
874    0x22, 0x10, 0x02, 0x10, 0xfe, 0x1f};
875 } -maskdata $filemask
877 image create bitmap file_question -background white -foreground black -data {
878 #define file_question_width 14
879 #define file_question_height 15
880 static unsigned char file_question_bits[] = {
881    0xfe, 0x01, 0x02, 0x02, 0xe2, 0x04, 0xf2, 0x09, 0x1a, 0x1b, 0x0a, 0x13,
882    0x82, 0x11, 0xc2, 0x10, 0x62, 0x10, 0x62, 0x10, 0x02, 0x10, 0x62, 0x10,
883    0x62, 0x10, 0x02, 0x10, 0xfe, 0x1f};
884 } -maskdata $filemask
886 image create bitmap file_removed -background white -foreground red -data {
887 #define file_removed_width 14
888 #define file_removed_height 15
889 static unsigned char file_removed_bits[] = {
890    0xfe, 0x01, 0x02, 0x03, 0x02, 0x05, 0x02, 0x09, 0x02, 0x1f, 0x02, 0x10,
891    0x1a, 0x16, 0x32, 0x13, 0xe2, 0x11, 0xc2, 0x10, 0xe2, 0x11, 0x32, 0x13,
892    0x1a, 0x16, 0x02, 0x10, 0xfe, 0x1f};
893 } -maskdata $filemask
895 image create bitmap file_merge -background white -foreground blue -data {
896 #define file_merge_width 14
897 #define file_merge_height 15
898 static unsigned char file_merge_bits[] = {
899    0xfe, 0x01, 0x02, 0x03, 0x62, 0x05, 0x62, 0x09, 0x62, 0x1f, 0x62, 0x10,
900    0xfa, 0x11, 0xf2, 0x10, 0x62, 0x10, 0x02, 0x10, 0xfa, 0x17, 0x02, 0x10,
901    0xfa, 0x17, 0x02, 0x10, 0xfe, 0x1f};
902 } -maskdata $filemask
904 set max_status_desc 0
905 foreach i {
906                 {__ i plain    "Unmodified"}
907                 {_M i mod      "Modified"}
908                 {M_ i fulltick "Checked in"}
909                 {MM i parttick "Partially checked in"}
911                 {_O o plain    "Untracked"}
912                 {A_ o fulltick "Added"}
913                 {AM o parttick "Partially added"}
914                 {AD o question  "Added (but now gone)"}
916                 {_D i question "Missing"}
917                 {D_ i removed  "Removed"}
918                 {DD i removed  "Removed"}
919                 {DO i removed  "Removed (still exists)"}
921                 {UM i merge    "Merge conflicts"}
922                 {U_ i merge    "Merge conflicts"}
923         } {
924         if {$max_status_desc < [string length [lindex $i 3]]} {
925                 set max_status_desc [string length [lindex $i 3]]
926         }
927         set all_cols([lindex $i 0]) [lindex $i 1]
928         set all_icons([lindex $i 0]) file_[lindex $i 2]
929         set all_descs([lindex $i 0]) [lindex $i 3]
931 unset filemask i
933 ######################################################################
934 ##
935 ## util
937 proc error_popup {msg} {
938         set w .error
939         toplevel $w
940         wm transient $w .
941         show_msg $w $w $msg
944 proc show_msg {w top msg} {
945         global gitdir appname mainfont
947         message $w.m -text $msg -justify left -aspect 400
948         pack $w.m -side top -fill x -padx 5 -pady 10
949         button $w.ok -text OK \
950                 -width 15 \
951                 -font $mainfont \
952                 -command "destroy $top"
953         pack $w.ok -side bottom
954         bind $top <Visibility> "grab $top; focus $top"
955         bind $top <Key-Return> "destroy $top"
956         wm title $top "error: $appname ([file normalize [file dirname $gitdir]])"
957         tkwait window $top
960 proc hook_failed_popup {hook msg} {
961         global gitdir mainfont difffont appname
963         set w .hookfail
964         toplevel $w
965         wm transient $w .
967         frame $w.m
968         label $w.m.l1 -text "$hook hook failed:" \
969                 -anchor w \
970                 -justify left \
971                 -font [concat $mainfont bold]
972         text $w.m.t \
973                 -background white -borderwidth 1 \
974                 -relief sunken \
975                 -width 80 -height 10 \
976                 -font $difffont \
977                 -yscrollcommand [list $w.m.sby set]
978         label $w.m.l2 \
979                 -text {You must correct the above errors before committing.} \
980                 -anchor w \
981                 -justify left \
982                 -font [concat $mainfont bold]
983         scrollbar $w.m.sby -command [list $w.m.t yview]
984         pack $w.m.l1 -side top -fill x
985         pack $w.m.l2 -side bottom -fill x
986         pack $w.m.sby -side right -fill y
987         pack $w.m.t -side left -fill both -expand 1
988         pack $w.m -side top -fill both -expand 1 -padx 5 -pady 10
990         $w.m.t insert 1.0 $msg
991         $w.m.t conf -state disabled
993         button $w.ok -text OK \
994                 -width 15 \
995                 -font $mainfont \
996                 -command "destroy $w"
997         pack $w.ok -side bottom
999         bind $w <Visibility> "grab $w; focus $w"
1000         bind $w <Key-Return> "destroy $w"
1001         wm title $w "error: $appname ([file normalize [file dirname $gitdir]])"
1002         tkwait window $w
1005 set next_console_id 0
1007 proc new_console {short_title long_title} {
1008         global next_console_id console_cr
1009         global gitdir appname mainfont difffont
1011         set w .console[incr next_console_id]
1012         set console_cr($w) 1.0
1013         toplevel $w
1014         frame $w.m
1015         label $w.m.l1 -text "$long_title:" \
1016                 -anchor w \
1017                 -justify left \
1018                 -font [concat $mainfont bold]
1019         text $w.m.t \
1020                 -background white -borderwidth 1 \
1021                 -relief sunken \
1022                 -width 80 -height 10 \
1023                 -font $difffont \
1024                 -state disabled \
1025                 -yscrollcommand [list $w.m.sby set]
1026         label $w.m.s -anchor w \
1027                 -justify left \
1028                 -font [concat $mainfont bold]
1029         scrollbar $w.m.sby -command [list $w.m.t yview]
1030         pack $w.m.l1 -side top -fill x
1031         pack $w.m.s -side bottom -fill x
1032         pack $w.m.sby -side right -fill y
1033         pack $w.m.t -side left -fill both -expand 1
1034         pack $w.m -side top -fill both -expand 1 -padx 5 -pady 10
1036         button $w.ok -text {Running...} \
1037                 -width 15 \
1038                 -font $mainfont \
1039                 -state disabled \
1040                 -command "destroy $w"
1041         pack $w.ok -side bottom
1043         bind $w <Visibility> "focus $w"
1044         bind $w <Destroy> break
1045         wm title $w "$appname ([file dirname [file normalize [file dirname $gitdir]]]): $short_title"
1046         return $w
1049 proc console_exec {w cmd} {
1050         global tcl_platform
1052         # -- Windows tosses the enviroment when we exec our child.
1053         #    But most users need that so we have to relogin. :-(
1054         #
1055         if {$tcl_platform(platform) == {windows}} {
1056                 set cmd [list sh --login -c "cd \"[pwd]\" && [join $cmd { }]"]
1057         }
1059         # -- Tcl won't let us redirect both stdout and stderr to
1060         #    the same pipe.  So pass it through cat...
1061         #
1062         set cmd [concat | $cmd |& cat]
1064         set fd_f [open $cmd r]
1065         fconfigure $fd_f -blocking 0 -translation binary
1066         fileevent $fd_f readable [list console_read $w $fd_f]
1069 proc console_read {w fd} {
1070         global console_cr
1072         $w.m.t conf -state normal
1073         set buf [read $fd]
1074         set c 0
1075         set n [string length $buf]
1076         while {$c < $n} {
1077                 set cr [string first "\r" $buf $c]
1078                 set lf [string first "\n" $buf $c]
1079                 if {$cr < 0} {set cr [expr $n + 1]}
1080                 if {$lf < 0} {set lf [expr $n + 1]}
1082                 if {$lf < $cr} {
1083                         $w.m.t insert end [string range $buf $c $lf]
1084                         set console_cr($w) [$w.m.t index {end -1c}]
1085                         set c $lf
1086                         incr c
1087                 } else {
1088                         $w.m.t delete $console_cr($w) end
1089                         $w.m.t insert end "\n"
1090                         $w.m.t insert end [string range $buf $c $cr]
1091                         set c $cr
1092                         incr c
1093                 }
1094         }
1095         $w.m.t conf -state disabled
1096         $w.m.t see end
1098         fconfigure $fd -blocking 1
1099         if {[eof $fd]} {
1100                 if {[catch {close $fd}]} {
1101                         $w.m.s conf -background red -text {Error: Command Failed}
1102                 } else {
1103                         $w.m.s conf -background green -text {Success}
1104                 }
1105                 $w.ok conf -text Close
1106                 $w.ok conf -state normal
1107                 array unset console_cr $w
1108                 return
1109         }
1110         fconfigure $fd -blocking 0
1113 ######################################################################
1114 ##
1115 ## ui commands
1117 set starting_gitk_msg {Please wait... Starting gitk...}
1119 proc do_gitk {} {
1120         global tcl_platform ui_status_value starting_gitk_msg
1122         set ui_status_value $starting_gitk_msg
1123         after 10000 {
1124                 if {$ui_status_value == $starting_gitk_msg} {
1125                         set ui_status_value {Ready.}
1126                 }
1127         }
1129         if {$tcl_platform(platform) == {windows}} {
1130                 exec sh -c gitk &
1131         } else {
1132                 exec gitk &
1133         }
1136 proc do_quit {} {
1137         global gitdir ui_comm
1139         set save [file join $gitdir GITGUI_MSG]
1140         set msg [string trim [$ui_comm get 0.0 end]]
1141         if {[$ui_comm edit modified] && $msg != {}} {
1142                 catch {
1143                         set fd [open $save w]
1144                         puts $fd [string trim [$ui_comm get 0.0 end]]
1145                         close $fd
1146                 }
1147         } elseif {$msg == {} && [file exists $save]} {
1148                 file delete $save
1149         }
1151         destroy .
1154 proc do_rescan {} {
1155         update_status
1158 proc do_checkin_all {} {
1159         global checkin_active ui_status_value
1161         if {$checkin_active || ![lock_index begin-update]} return
1163         set checkin_active 1
1164         set ui_status_value {Checking in all files...}
1165         after 1 {
1166                 with_update_index {
1167                         foreach path [array names file_states] {
1168                                 set s $file_states($path)
1169                                 set m [lindex $s 0]
1170                                 switch -- $m {
1171                                 AM -
1172                                 MM -
1173                                 _M -
1174                                 _D {toggle_mode $path}
1175                                 }
1176                         }
1177                 }
1178                 set checkin_active 0
1179                 set ui_status_value {Ready.}
1180         }
1183 proc do_signoff {} {
1184         global ui_comm
1186         catch {
1187                 set me [exec git var GIT_COMMITTER_IDENT]
1188                 if {[regexp {(.*) [0-9]+ [-+0-9]+$} $me me name]} {
1189                         set str "Signed-off-by: $name"
1190                         if {[$ui_comm get {end -1c linestart} {end -1c}] != $str} {
1191                                 $ui_comm insert end "\n"
1192                                 $ui_comm insert end $str
1193                                 $ui_comm see end
1194                         }
1195                 }
1196         }
1199 proc do_amend_last {} {
1200         load_last_commit
1203 proc do_commit {} {
1204         commit_tree
1207 # shift == 1: left click
1208 #          3: right click  
1209 proc click {w x y shift wx wy} {
1210         global ui_index ui_other
1212         set pos [split [$w index @$x,$y] .]
1213         set lno [lindex $pos 0]
1214         set col [lindex $pos 1]
1215         set path [$w get $lno.1 $lno.end]
1216         if {$path == {}} return
1218         if {$col > 0 && $shift == 1} {
1219                 $ui_index tag remove in_diff 0.0 end
1220                 $ui_other tag remove in_diff 0.0 end
1221                 $w tag add in_diff $lno.0 [expr $lno + 1].0
1222                 show_diff $path
1223         }
1226 proc unclick {w x y} {
1227         set pos [split [$w index @$x,$y] .]
1228         set lno [lindex $pos 0]
1229         set col [lindex $pos 1]
1230         set path [$w get $lno.1 $lno.end]
1231         if {$path == {}} return
1233         if {$col == 0} {
1234                 toggle_mode $path
1235         }
1238 ######################################################################
1239 ##
1240 ## ui init
1242 set mainfont {Helvetica 10}
1243 set difffont {Courier 10}
1244 set maincursor [. cget -cursor]
1246 switch -glob -- "$tcl_platform(platform),$tcl_platform(os)" {
1247 windows,*   {set M1B Control; set M1T Ctrl}
1248 unix,Darwin {set M1B M1; set M1T Cmd}
1249 default     {set M1B M1; set M1T M1}
1252 # -- Menu Bar
1253 menu .mbar -tearoff 0
1254 .mbar add cascade -label Project -menu .mbar.project
1255 .mbar add cascade -label Commit -menu .mbar.commit
1256 .mbar add cascade -label Fetch -menu .mbar.fetch
1257 .mbar add cascade -label Pull -menu .mbar.pull
1258 .mbar add cascade -label Push -menu .mbar.push
1259 . configure -menu .mbar
1261 # -- Project Menu
1262 menu .mbar.project
1263 .mbar.project add command -label Visualize \
1264         -command do_gitk \
1265         -font $mainfont
1266 .mbar.project add command -label Quit \
1267         -command do_quit \
1268         -accelerator $M1T-Q \
1269         -font $mainfont
1271 # -- Commit Menu
1272 menu .mbar.commit
1273 .mbar.commit add command -label Rescan \
1274         -command do_rescan \
1275         -accelerator F5 \
1276         -font $mainfont
1277 lappend disable_on_lock \
1278         [list .mbar.commit entryconf [.mbar.commit index last] -state]
1279 .mbar.commit add command -label {Amend Last Commit} \
1280         -command do_amend_last \
1281         -font $mainfont
1282 lappend disable_on_lock \
1283         [list .mbar.commit entryconf [.mbar.commit index last] -state]
1284 .mbar.commit add command -label {Check-in All Files} \
1285         -command do_checkin_all \
1286         -accelerator $M1T-U \
1287         -font $mainfont
1288 lappend disable_on_lock \
1289         [list .mbar.commit entryconf [.mbar.commit index last] -state]
1290 .mbar.commit add command -label {Sign Off} \
1291         -command do_signoff \
1292         -accelerator $M1T-S \
1293         -font $mainfont
1294 .mbar.commit add command -label Commit \
1295         -command do_commit \
1296         -accelerator $M1T-Return \
1297         -font $mainfont
1298 lappend disable_on_lock \
1299         [list .mbar.commit entryconf [.mbar.commit index last] -state]
1301 # -- Fetch Menu
1302 menu .mbar.fetch
1304 # -- Pull Menu
1305 menu .mbar.pull
1307 # -- Push Menu
1308 menu .mbar.push
1310 # -- Main Window Layout
1311 panedwindow .vpane -orient vertical
1312 panedwindow .vpane.files -orient horizontal
1313 .vpane add .vpane.files -sticky nsew -height 100 -width 400
1314 pack .vpane -anchor n -side top -fill both -expand 1
1316 # -- Index File List
1317 set ui_index .vpane.files.index.list
1318 frame .vpane.files.index -height 100 -width 400
1319 label .vpane.files.index.title -text {Modified Files} \
1320         -background green \
1321         -font $mainfont
1322 text $ui_index -background white -borderwidth 0 \
1323         -width 40 -height 10 \
1324         -font $mainfont \
1325         -yscrollcommand {.vpane.files.index.sb set} \
1326         -cursor $maincursor \
1327         -state disabled
1328 scrollbar .vpane.files.index.sb -command [list $ui_index yview]
1329 pack .vpane.files.index.title -side top -fill x
1330 pack .vpane.files.index.sb -side right -fill y
1331 pack $ui_index -side left -fill both -expand 1
1332 .vpane.files add .vpane.files.index -sticky nsew
1334 # -- Other (Add) File List
1335 set ui_other .vpane.files.other.list
1336 frame .vpane.files.other -height 100 -width 100
1337 label .vpane.files.other.title -text {Untracked Files} \
1338         -background red \
1339         -font $mainfont
1340 text $ui_other -background white -borderwidth 0 \
1341         -width 40 -height 10 \
1342         -font $mainfont \
1343         -yscrollcommand {.vpane.files.other.sb set} \
1344         -cursor $maincursor \
1345         -state disabled
1346 scrollbar .vpane.files.other.sb -command [list $ui_other yview]
1347 pack .vpane.files.other.title -side top -fill x
1348 pack .vpane.files.other.sb -side right -fill y
1349 pack $ui_other -side left -fill both -expand 1
1350 .vpane.files add .vpane.files.other -sticky nsew
1352 $ui_index tag conf in_diff -font [concat $mainfont bold]
1353 $ui_other tag conf in_diff -font [concat $mainfont bold]
1355 # -- Diff Header
1356 set ui_fname_value {}
1357 set ui_fstatus_value {}
1358 frame .vpane.diff -height 200 -width 400
1359 frame .vpane.diff.header
1360 label .vpane.diff.header.l1 -text {File:} -font $mainfont
1361 label .vpane.diff.header.l2 -textvariable ui_fname_value \
1362         -anchor w \
1363         -justify left \
1364         -font $mainfont
1365 label .vpane.diff.header.l3 -text {Status:} -font $mainfont
1366 label .vpane.diff.header.l4 -textvariable ui_fstatus_value \
1367         -width $max_status_desc \
1368         -anchor w \
1369         -justify left \
1370         -font $mainfont
1371 pack .vpane.diff.header.l1 -side left
1372 pack .vpane.diff.header.l2 -side left -fill x
1373 pack .vpane.diff.header.l4 -side right
1374 pack .vpane.diff.header.l3 -side right
1376 # -- Diff Body
1377 frame .vpane.diff.body
1378 set ui_diff .vpane.diff.body.t
1379 text $ui_diff -background white -borderwidth 0 \
1380         -width 80 -height 15 -wrap none \
1381         -font $difffont \
1382         -xscrollcommand {.vpane.diff.body.sbx set} \
1383         -yscrollcommand {.vpane.diff.body.sby set} \
1384         -cursor $maincursor \
1385         -state disabled
1386 scrollbar .vpane.diff.body.sbx -orient horizontal \
1387         -command [list $ui_diff xview]
1388 scrollbar .vpane.diff.body.sby -orient vertical \
1389         -command [list $ui_diff yview]
1390 pack .vpane.diff.body.sbx -side bottom -fill x
1391 pack .vpane.diff.body.sby -side right -fill y
1392 pack $ui_diff -side left -fill both -expand 1
1393 pack .vpane.diff.header -side top -fill x
1394 pack .vpane.diff.body -side bottom -fill both -expand 1
1395 .vpane add .vpane.diff -stick nsew
1397 $ui_diff tag conf dm -foreground red
1398 $ui_diff tag conf dp -foreground blue
1399 $ui_diff tag conf da -font [concat $difffont bold]
1400 $ui_diff tag conf di -foreground "#00a000"
1401 $ui_diff tag conf dni -foreground "#a000a0"
1402 $ui_diff tag conf bold -font [concat $difffont bold]
1404 # -- Commit Area
1405 frame .vpane.commarea -height 170
1406 .vpane add .vpane.commarea -stick nsew
1408 # -- Commit Area Buttons
1409 frame .vpane.commarea.buttons
1410 label .vpane.commarea.buttons.l -text {} \
1411         -anchor w \
1412         -justify left \
1413         -font $mainfont
1414 pack .vpane.commarea.buttons.l -side top -fill x
1415 pack .vpane.commarea.buttons -side left -fill y
1417 button .vpane.commarea.buttons.rescan -text {Rescan} \
1418         -command do_rescan \
1419         -font $mainfont
1420 pack .vpane.commarea.buttons.rescan -side top -fill x
1421 lappend disable_on_lock {.vpane.commarea.buttons.rescan conf -state}
1423 button .vpane.commarea.buttons.amend -text {Amend Last} \
1424         -command do_amend_last \
1425         -font $mainfont
1426 pack .vpane.commarea.buttons.amend -side top -fill x
1427 lappend disable_on_lock {.vpane.commarea.buttons.amend conf -state}
1429 button .vpane.commarea.buttons.ciall -text {Check-in All} \
1430         -command do_checkin_all \
1431         -font $mainfont
1432 pack .vpane.commarea.buttons.ciall -side top -fill x
1433 lappend disable_on_lock {.vpane.commarea.buttons.ciall conf -state}
1435 button .vpane.commarea.buttons.signoff -text {Sign Off} \
1436         -command do_signoff \
1437         -font $mainfont
1438 pack .vpane.commarea.buttons.signoff -side top -fill x
1440 button .vpane.commarea.buttons.commit -text {Commit} \
1441         -command do_commit \
1442         -font $mainfont
1443 pack .vpane.commarea.buttons.commit -side top -fill x
1444 lappend disable_on_lock {.vpane.commarea.buttons.commit conf -state}
1446 # -- Commit Message Buffer
1447 frame .vpane.commarea.buffer
1448 set ui_comm .vpane.commarea.buffer.t
1449 set ui_coml .vpane.commarea.buffer.l
1450 label $ui_coml -text {Commit Message:} \
1451         -anchor w \
1452         -justify left \
1453         -font $mainfont
1454 trace add variable commit_type write {uplevel #0 {
1455         switch -glob $commit_type \
1456         initial {$ui_coml conf -text {Initial Commit Message:}} \
1457         amend   {$ui_coml conf -text {Amended Commit Message:}} \
1458         merge   {$ui_coml conf -text {Merge Commit Message:}} \
1459         *       {$ui_coml conf -text {Commit Message:}}
1460 }}
1461 text $ui_comm -background white -borderwidth 1 \
1462         -relief sunken \
1463         -width 75 -height 10 -wrap none \
1464         -font $difffont \
1465         -yscrollcommand {.vpane.commarea.buffer.sby set} \
1466         -cursor $maincursor
1467 scrollbar .vpane.commarea.buffer.sby -command [list $ui_comm yview]
1468 pack $ui_coml -side top -fill x
1469 pack .vpane.commarea.buffer.sby -side right -fill y
1470 pack $ui_comm -side left -fill y
1471 pack .vpane.commarea.buffer -side left -fill y
1473 # -- Status Bar
1474 set ui_status_value {Initializing...}
1475 label .status -textvariable ui_status_value \
1476         -anchor w \
1477         -justify left \
1478         -borderwidth 1 \
1479         -relief sunken \
1480         -font $mainfont
1481 pack .status -anchor w -side bottom -fill x
1483 # -- Key Bindings
1484 bind $ui_comm <$M1B-Key-Return> {do_commit;break}
1485 bind .   <Destroy> do_quit
1486 bind all <Key-F5> do_rescan
1487 bind all <$M1B-Key-r> do_rescan
1488 bind all <$M1B-Key-R> do_rescan
1489 bind .   <$M1B-Key-s> do_signoff
1490 bind .   <$M1B-Key-S> do_signoff
1491 bind .   <$M1B-Key-u> do_checkin_all
1492 bind .   <$M1B-Key-U> do_checkin_all
1493 bind .   <$M1B-Key-Return> do_commit
1494 bind all <$M1B-Key-q> do_quit
1495 bind all <$M1B-Key-Q> do_quit
1496 bind all <$M1B-Key-w> {destroy [winfo toplevel %W]}
1497 bind all <$M1B-Key-W> {destroy [winfo toplevel %W]}
1498 foreach i [list $ui_index $ui_other] {
1499         bind $i <Button-1> {click %W %x %y 1 %X %Y; break}
1500         bind $i <Button-3> {click %W %x %y 3 %X %Y; break}
1501         bind $i <ButtonRelease-1> {unclick %W %x %y; break}
1503 unset i M1B M1T
1505 ######################################################################
1506 ##
1507 ## main
1509 set appname [lindex [file split $argv0] end]
1510 set gitdir {}
1512 if {[catch {set cdup [exec git rev-parse --show-cdup]} err]} {
1513         show_msg {} . "Cannot find the git directory: $err"
1514         exit 1
1516 if {$cdup != ""} {
1517         cd $cdup
1519 unset cdup
1521 if {[catch {set gitdir [exec git rev-parse --git-dir]} err]} {
1522         show_msg {} . "Cannot find the git directory: $err"
1523         exit 1
1526 if {$appname == {git-citool}} {
1527         set single_commit 1
1530 wm title . "$appname ([file normalize [file dirname $gitdir]])"
1531 focus -force $ui_comm
1532 load_all_remotes
1533 populate_remote_menu .mbar.fetch From fetch_from
1534 populate_remote_menu .mbar.push To push_to
1535 update_status