2 # Tcl ignores the next line -*- tcl -*- \
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 set appname
[lindex
[file split $argv0] end
]
13 ######################################################################
17 proc is_many_config
{name
} {
18 switch
-glob -- $name {
27 proc load_config
{include_global
} {
28 global repo_config global_config default_config
30 array
unset global_config
31 if {$include_global} {
33 set fd_rc
[open
"| git repo-config --global --list" r
]
34 while {[gets
$fd_rc line
] >= 0} {
35 if {[regexp
{^
([^
=]+)=(.
*)$
} $line line name value
]} {
36 if {[is_many_config
$name]} {
37 lappend global_config
($name) $value
39 set global_config
($name) $value
47 array
unset repo_config
49 set fd_rc
[open
"| git repo-config --list" r
]
50 while {[gets
$fd_rc line
] >= 0} {
51 if {[regexp
{^
([^
=]+)=(.
*)$
} $line line name value
]} {
52 if {[is_many_config
$name]} {
53 lappend repo_config
($name) $value
55 set repo_config
($name) $value
62 foreach name
[array names default_config
] {
63 if {[catch
{set v
$global_config($name)}]} {
64 set global_config
($name) $default_config($name)
66 if {[catch
{set v
$repo_config($name)}]} {
67 set repo_config
($name) $default_config($name)
73 global default_config font_descs
74 global repo_config global_config
75 global repo_config_new global_config_new
77 foreach option
$font_descs {
78 set name
[lindex
$option 0]
79 set font
[lindex
$option 1]
80 font configure
$font \
81 -family $global_config_new(gui.
$font^^family
) \
82 -size $global_config_new(gui.
$font^^size
)
83 font configure
${font}bold \
84 -family $global_config_new(gui.
$font^^family
) \
85 -size $global_config_new(gui.
$font^^size
)
86 set global_config_new
(gui.
$name) [font configure
$font]
87 unset global_config_new
(gui.
$font^^family
)
88 unset global_config_new
(gui.
$font^^size
)
91 foreach name
[array names default_config
] {
92 set value
$global_config_new($name)
93 if {$value ne
$global_config($name)} {
94 if {$value eq
$default_config($name)} {
95 catch
{exec git repo-config
--global --unset $name}
97 regsub
-all "\[{}\]" $value {"} value
98 exec git repo-config --global $name $value
100 set global_config($name) $value
101 if {$value eq $repo_config($name)} {
102 catch {exec git repo-config --unset $name}
103 set repo_config($name) $value
108 foreach name [array names default_config] {
109 set value $repo_config_new($name)
110 if {$value ne $repo_config($name)} {
111 if {$value eq $global_config($name)} {
112 catch {exec git repo-config --unset $name}
114 regsub -all "\
[{}\
]" $value {"} value
115 exec git repo-config
$name $value
117 set repo_config
($name) $value
122 proc error_popup
{msg
} {
123 global gitdir appname
128 append title
[lindex \
129 [file split [file normalize
[file dirname $gitdir]]] \
137 -title "$title: error" \
141 proc info_popup
{msg
} {
142 global gitdir appname
147 append title
[lindex \
148 [file split [file normalize
[file dirname $gitdir]]] \
160 ######################################################################
164 if { [catch
{set cdup
[exec git rev-parse
--show-cdup]} err
]
165 ||
[catch
{set gitdir
[exec git rev-parse
--git-dir]} err
]} {
166 catch
{wm withdraw .
}
167 error_popup
"Cannot find the git directory:\n\n$err"
176 if {$appname eq
{git-citool
}} {
180 ######################################################################
188 set disable_on_lock
[list
]
189 set index_lock_type none
195 proc lock_index
{type} {
196 global index_lock_type disable_on_lock
198 if {$index_lock_type eq
{none
}} {
199 set index_lock_type
$type
200 foreach w
$disable_on_lock {
201 uplevel
#0 $w disabled
204 } elseif
{$index_lock_type eq
{begin-update
} && $type eq
{update
}} {
205 set index_lock_type
$type
211 proc unlock_index
{} {
212 global index_lock_type disable_on_lock
214 set index_lock_type none
215 foreach w
$disable_on_lock {
220 ######################################################################
224 proc repository_state
{hdvar ctvar
} {
226 upvar
$hdvar hd
$ctvar ct
228 if {[catch
{set hd
[exec git rev-parse
--verify HEAD
]}]} {
230 } elseif
{[file exists
[file join $gitdir MERGE_HEAD
]]} {
237 proc rescan
{after
} {
238 global HEAD PARENT commit_type
239 global ui_index ui_other ui_status_value ui_comm
240 global rescan_active file_states
243 if {$rescan_active > 0 ||
![lock_index
read]} return
245 repository_state new_HEAD new_type
246 if {$commit_type eq
{amend
}
247 && $new_type eq
{normal
}
248 && $new_HEAD eq
$HEAD} {
252 set commit_type
$new_type
255 array
unset file_states
257 if {![$ui_comm edit modified
]
258 ||
[string trim
[$ui_comm get
0.0 end
]] eq
{}} {
259 if {[load_message GITGUI_MSG
]} {
260 } elseif
{[load_message MERGE_MSG
]} {
261 } elseif
{[load_message SQUASH_MSG
]} {
263 $ui_comm edit modified false
267 if {$repo_config(gui.trustmtime
) eq
{true
}} {
268 rescan_stage2
{} $after
271 set ui_status_value
{Refreshing
file status...
}
272 set cmd
[list git update-index
]
274 lappend cmd
--unmerged
275 lappend cmd
--ignore-missing
276 lappend cmd
--refresh
277 set fd_rf
[open
"| $cmd" r
]
278 fconfigure
$fd_rf -blocking 0 -translation binary
279 fileevent
$fd_rf readable \
280 [list rescan_stage2
$fd_rf $after]
284 proc rescan_stage2
{fd after
} {
285 global gitdir PARENT commit_type
286 global ui_index ui_other ui_status_value ui_comm
288 global buf_rdi buf_rdf buf_rlo
292 if {![eof
$fd]} return
296 set ls_others
[list | git ls-files
--others -z \
297 --exclude-per-directory=.gitignore
]
298 set info_exclude
[file join $gitdir info exclude
]
299 if {[file readable
$info_exclude]} {
300 lappend ls_others
"--exclude-from=$info_exclude"
308 set ui_status_value
{Scanning
for modified files ...
}
309 set fd_di
[open
"| git diff-index --cached -z $PARENT" r
]
310 set fd_df
[open
"| git diff-files -z" r
]
311 set fd_lo
[open
$ls_others r
]
313 fconfigure
$fd_di -blocking 0 -translation binary
314 fconfigure
$fd_df -blocking 0 -translation binary
315 fconfigure
$fd_lo -blocking 0 -translation binary
316 fileevent
$fd_di readable
[list read_diff_index
$fd_di $after]
317 fileevent
$fd_df readable
[list read_diff_files
$fd_df $after]
318 fileevent
$fd_lo readable
[list read_ls_others
$fd_lo $after]
321 proc load_message
{file} {
322 global gitdir ui_comm
324 set f
[file join $gitdir $file]
325 if {[file isfile
$f]} {
326 if {[catch
{set fd
[open
$f r
]}]} {
329 set content
[string trim
[read $fd]]
331 $ui_comm delete
0.0 end
332 $ui_comm insert end
$content
338 proc read_diff_index
{fd after
} {
341 append buf_rdi
[read $fd]
343 set n
[string length
$buf_rdi]
345 set z1
[string first
"\0" $buf_rdi $c]
348 set z2
[string first
"\0" $buf_rdi $z1]
354 [string range
$buf_rdi $z1 $z2] \
355 [string index
$buf_rdi [expr {$z1 - 2}]]_
359 set buf_rdi
[string range
$buf_rdi $c end
]
364 rescan_done
$fd buf_rdi
$after
367 proc read_diff_files
{fd after
} {
370 append buf_rdf
[read $fd]
372 set n
[string length
$buf_rdf]
374 set z1
[string first
"\0" $buf_rdf $c]
377 set z2
[string first
"\0" $buf_rdf $z1]
383 [string range
$buf_rdf $z1 $z2] \
384 _
[string index
$buf_rdf [expr {$z1 - 2}]]
388 set buf_rdf
[string range
$buf_rdf $c end
]
393 rescan_done
$fd buf_rdf
$after
396 proc read_ls_others
{fd after
} {
399 append buf_rlo
[read $fd]
400 set pck
[split $buf_rlo "\0"]
401 set buf_rlo
[lindex
$pck end
]
402 foreach p
[lrange
$pck 0 end-1
] {
405 rescan_done
$fd buf_rlo
$after
408 proc rescan_done
{fd buf after
} {
410 global file_states repo_config
413 if {![eof
$fd]} return
416 if {[incr rescan_active
-1] > 0} return
422 if {$repo_config(gui.partialinclude
) ne
{true
}} {
424 foreach path
[array names file_states
] {
425 switch
-- [lindex
$file_states($path) 0] {
427 MM
{lappend pathList
$path}
430 if {$pathList ne
{}} {
432 "Updating included files" \
434 [concat
{reshow_diff
;} $after]
443 proc prune_selection
{} {
444 global file_states selected_paths
446 foreach path
[array names selected_paths
] {
447 if {[catch
{set still_here
$file_states($path)}]} {
448 unset selected_paths
($path)
453 ######################################################################
458 global ui_diff ui_fname_value ui_fstatus_value ui_index ui_other
460 $ui_diff conf
-state normal
461 $ui_diff delete
0.0 end
462 $ui_diff conf
-state disabled
464 set ui_fname_value
{}
465 set ui_fstatus_value
{}
467 $ui_index tag remove in_diff
0.0 end
468 $ui_other tag remove in_diff
0.0 end
471 proc reshow_diff
{} {
472 global ui_fname_value ui_status_value file_states
474 if {$ui_fname_value eq
{}
475 ||
[catch
{set s
$file_states($ui_fname_value)}]} {
478 show_diff
$ui_fname_value
482 proc handle_empty_diff
{} {
483 global ui_fname_value file_states file_lists
485 set path
$ui_fname_value
486 set s
$file_states($path)
487 if {[lindex
$s 0] ne
{_M
}} return
489 info_popup
"No differences detected.
491 [short_path $path] has no changes.
493 The modification date of this file was updated
494 by another application and you currently have
495 the Trust File Modification Timestamps option
496 enabled, so Git did not automatically detect
497 that there are no content differences in this
500 This file will now be removed from the modified
501 files list, to prevent possible confusion.
503 if {[catch
{exec git update-index
-- $path} err
]} {
504 error_popup
"Failed to refresh index:\n\n$err"
508 set old_w
[mapcol
[lindex
$file_states($path) 0] $path]
509 set lno
[lsearch
-sorted $file_lists($old_w) $path]
511 set file_lists
($old_w) \
512 [lreplace
$file_lists($old_w) $lno $lno]
514 $old_w conf
-state normal
515 $old_w delete
$lno.0 [expr {$lno + 1}].0
516 $old_w conf
-state disabled
520 proc show_diff
{path
{w
{}} {lno
{}}} {
521 global file_states file_lists
522 global PARENT diff_3way diff_active repo_config
523 global ui_diff ui_fname_value ui_fstatus_value ui_status_value
525 if {$diff_active ||
![lock_index
read]} return
528 if {$w eq
{} ||
$lno == {}} {
529 foreach w
[array names file_lists
] {
530 set lno
[lsearch
-sorted $file_lists($w) $path]
537 if {$w ne
{} && $lno >= 1} {
538 $w tag add in_diff
$lno.0 [expr {$lno + 1}].0
541 set s
$file_states($path)
545 set ui_fname_value
$path
546 set ui_fstatus_value
[mapdesc
$m $path]
547 set ui_status_value
"Loading diff of [escape_path $path]..."
549 set cmd
[list | git diff-index
]
550 lappend cmd
--no-color
551 if {$repo_config(gui.diffcontext
) > 0} {
552 lappend cmd
"-U$repo_config(gui.diffcontext)"
562 set fd
[open
$path r
]
563 set content
[read $fd]
568 set ui_status_value
"Unable to display [escape_path $path]"
569 error_popup
"Error loading file:\n\n$err"
572 $ui_diff conf
-state normal
573 $ui_diff insert end
$content
574 $ui_diff conf
-state disabled
577 set ui_status_value
{Ready.
}
586 if {[catch
{set fd
[open
$cmd r
]} err
]} {
589 set ui_status_value
"Unable to display [escape_path $path]"
590 error_popup
"Error loading diff:\n\n$err"
594 fconfigure
$fd -blocking 0 -translation auto
595 fileevent
$fd readable
[list read_diff
$fd]
598 proc read_diff
{fd
} {
599 global ui_diff ui_status_value diff_3way diff_active
602 while {[gets
$fd line
] >= 0} {
603 if {[string match
{diff --git *} $line]} continue
604 if {[string match
{diff --combined *} $line]} continue
605 if {[string match
{--- *} $line]} continue
606 if {[string match
{+++ *} $line]} continue
607 if {[string match index
* $line]} {
608 if {[string first
, $line] >= 0} {
613 $ui_diff conf
-state normal
615 set x
[string index
$line 0]
620 default
{set tags
{}}
623 set x
[string range
$line 0 1]
625 default
{set tags
{}}
627 "++" {set tags dp
; set x
" +"}
628 " +" {set tags
{di bold
}; set x
"++"}
629 "+ " {set tags dni
; set x
"-+"}
630 "--" {set tags dm
; set x
" -"}
631 " -" {set tags
{dm bold
}; set x
"--"}
632 "- " {set tags di
; set x
"+-"}
633 default
{set tags
{}}
635 set line
[string replace
$line 0 1 $x]
637 $ui_diff insert end
$line $tags
638 $ui_diff insert end
"\n"
639 $ui_diff conf
-state disabled
646 set ui_status_value
{Ready.
}
648 if {$repo_config(gui.trustmtime
) eq
{true
}
649 && [$ui_diff index end
] eq
{2.0}} {
655 ######################################################################
659 proc load_last_commit
{} {
660 global HEAD PARENT commit_type ui_comm
662 if {$commit_type eq
{amend
}} return
663 if {$commit_type ne
{normal
}} {
664 error_popup
"Can't amend a $commit_type commit."
672 set fd
[open
"| git cat-file commit $HEAD" r
]
673 while {[gets
$fd line
] > 0} {
674 if {[string match
{parent
*} $line]} {
675 set parent
[string range
$line 7 end
]
679 set msg
[string trim
[read $fd]]
682 error_popup
"Error loading commit data for amend:\n\n$err"
686 if {$parent_count == 0} {
687 set commit_type amend
690 rescan
{set ui_status_value
{Ready.
}}
691 } elseif
{$parent_count == 1} {
692 set commit_type amend
694 $ui_comm delete
0.0 end
695 $ui_comm insert end
$msg
696 $ui_comm edit modified false
698 rescan
{set ui_status_value
{Ready.
}}
700 error_popup
{You can
't amend a merge commit.}
705 proc commit_tree {} {
706 global HEAD commit_type file_states ui_comm repo_config
708 if {![lock_index update]} return
710 # -- Our in memory state should match the repository.
712 repository_state curHEAD cur_type
713 if {$commit_type eq {amend}
714 && $cur_type eq {normal}
715 && $curHEAD eq $HEAD} {
716 } elseif {$commit_type ne $cur_type || $HEAD ne $curHEAD} {
717 error_popup {Last scanned state does not match repository state.
719 Its highly likely that another Git program modified the
720 repository since the last scan. A rescan is required
723 A rescan will be automatically started now.
726 rescan {set ui_status_value {Ready.}}
730 # -- At least one file should differ in the index.
733 foreach path [array names file_states] {
734 switch -glob -- [lindex $file_states($path) 0] {
738 M? {set files_ready 1; break}
740 error_popup "Unmerged files cannot be committed.
742 File [short_path $path] has merge conflicts.
743 You must resolve them and include the file before committing.
749 error_popup "Unknown file state [lindex $s 0] detected.
751 File [short_path $path] cannot be committed by this program.
757 error_popup {No included files to commit.
759 You must include at least 1 file before you can commit.
765 # -- A message is required.
767 set msg [string trim [$ui_comm get 1.0 end]]
769 error_popup {Please supply a commit message.
771 A good commit message has the following format:
773 - First line: Describe in one sentance what you did.
775 - Remaining lines: Describe why this change is good.
781 # -- Update included files if partialincludes are off.
783 if {$repo_config(gui.partialinclude) ne {true}} {
785 foreach path [array names file_states] {
786 switch -glob -- [lindex $file_states($path) 0] {
788 M? {lappend pathList $path}
791 if {$pathList ne {}} {
794 "Updating included files" \
796 [concat {lock_index update;} \
797 [list commit_prehook $curHEAD $msg]]
802 commit_prehook $curHEAD $msg
805 proc commit_prehook {curHEAD msg} {
806 global tcl_platform gitdir ui_status_value pch_error
808 # On Cygwin [file executable] might lie so we need to ask
809 # the shell if the hook is executable. Yes that's annoying.
811 set pchook
[file join $gitdir hooks pre-commit
]
812 if {$tcl_platform(platform
) eq
{windows
}
813 && [file isfile
$pchook]} {
814 set pchook
[list sh
-c [concat \
815 "if test -x \"$pchook\";" \
816 "then exec \"$pchook\" 2>&1;" \
818 } elseif
{[file executable
$pchook]} {
819 set pchook
[list
$pchook |
& cat]
821 commit_writetree
$curHEAD $msg
825 set ui_status_value
{Calling pre-commit hook...
}
827 set fd_ph
[open
"| $pchook" r
]
828 fconfigure
$fd_ph -blocking 0 -translation binary
829 fileevent
$fd_ph readable \
830 [list commit_prehook_wait
$fd_ph $curHEAD $msg]
833 proc commit_prehook_wait
{fd_ph curHEAD msg
} {
834 global pch_error ui_status_value
836 append pch_error
[read $fd_ph]
837 fconfigure
$fd_ph -blocking 1
839 if {[catch
{close
$fd_ph}]} {
840 set ui_status_value
{Commit declined by pre-commit hook.
}
841 hook_failed_popup pre-commit
$pch_error
844 commit_writetree
$curHEAD $msg
849 fconfigure
$fd_ph -blocking 0
852 proc commit_writetree
{curHEAD msg
} {
853 global ui_status_value
855 set ui_status_value
{Committing changes...
}
856 set fd_wt
[open
"| git write-tree" r
]
857 fileevent
$fd_wt readable \
858 [list commit_committree
$fd_wt $curHEAD $msg]
861 proc commit_committree
{fd_wt curHEAD msg
} {
862 global single_commit gitdir HEAD PARENT commit_type tcl_platform
863 global ui_status_value ui_comm
864 global file_states selected_paths
867 if {$tree_id eq
{} ||
[catch
{close
$fd_wt} err
]} {
868 error_popup
"write-tree failed:\n\n$err"
869 set ui_status_value
{Commit failed.
}
874 # -- Create the commit.
876 set cmd
[list git commit-tree
$tree_id]
878 lappend cmd
-p $PARENT
880 if {$commit_type eq
{merge
}} {
882 set fd_mh
[open
[file join $gitdir MERGE_HEAD
] r
]
883 while {[gets
$fd_mh merge_head
] >= 0} {
884 lappend cmd
-p $merge_head
888 error_popup
"Loading MERGE_HEAD failed:\n\n$err"
889 set ui_status_value
{Commit failed.
}
895 # git commit-tree writes to stderr during initial commit.
896 lappend cmd
2>/dev
/null
899 if {[catch
{set cmt_id
[eval exec $cmd]} err
]} {
900 error_popup
"commit-tree failed:\n\n$err"
901 set ui_status_value
{Commit failed.
}
906 # -- Update the HEAD ref.
909 if {$commit_type ne
{normal
}} {
910 append reflogm
" ($commit_type)"
912 set i
[string first
"\n" $msg]
914 append reflogm
{: } [string range
$msg 0 [expr {$i - 1}]]
916 append reflogm
{: } $msg
918 set cmd
[list git update-ref
-m $reflogm HEAD
$cmt_id $curHEAD]
919 if {[catch
{eval exec $cmd} err
]} {
920 error_popup
"update-ref failed:\n\n$err"
921 set ui_status_value
{Commit failed.
}
926 # -- Cleanup after ourselves.
928 catch
{file delete
[file join $gitdir MERGE_HEAD
]}
929 catch
{file delete
[file join $gitdir MERGE_MSG
]}
930 catch
{file delete
[file join $gitdir SQUASH_MSG
]}
931 catch
{file delete
[file join $gitdir GITGUI_MSG
]}
933 # -- Let rerere do its thing.
935 if {[file isdirectory
[file join $gitdir rr-cache
]]} {
936 catch
{exec git rerere
}
939 # -- Run the post-commit hook.
941 set pchook
[file join $gitdir hooks post-commit
]
942 if {$tcl_platform(platform
) eq
{windows
} && [file isfile
$pchook]} {
943 set pchook
[list sh
-c [concat \
944 "if test -x \"$pchook\";" \
945 "then exec \"$pchook\";" \
947 } elseif
{![file executable
$pchook]} {
951 catch
{exec $pchook &}
954 $ui_comm delete
0.0 end
955 $ui_comm edit modified false
958 if {$single_commit} do_quit
960 # -- Update status without invoking any git commands.
962 set commit_type normal
966 foreach path
[array names file_states
] {
967 set s
$file_states($path)
972 D?
{set m _
[string index
$m 1]}
976 unset file_states
($path)
977 catch
{unset selected_paths
($path)}
979 lset file_states
($path) 0 $m
986 set ui_status_value \
987 "Changes committed as [string range $cmt_id 0 7]."
990 ######################################################################
994 proc fetch_from
{remote
} {
995 set w
[new_console
"fetch $remote" \
996 "Fetching new changes from $remote"]
997 set cmd
[list git fetch
]
1002 proc pull_remote
{remote branch
} {
1003 global HEAD commit_type file_states repo_config
1005 if {![lock_index update
]} return
1007 # -- Our in memory state should match the repository.
1009 repository_state curHEAD cur_type
1010 if {$commit_type ne
$cur_type ||
$HEAD ne
$curHEAD} {
1011 error_popup
{Last scanned state does not match repository state.
1013 Its highly likely that another Git program modified the
1014 repository since our last scan. A rescan is required
1015 before a pull can be started.
1018 rescan
{set ui_status_value
{Ready.
}}
1022 # -- No differences should exist before a pull.
1024 if {[array size file_states
] != 0} {
1025 error_popup
{Uncommitted but modified files are present.
1027 You should not perform a pull with unmodified files
in your working
1028 directory as Git would be unable to recover from an incorrect merge.
1030 Commit or throw away all changes before starting a pull operation.
1036 set w
[new_console
"pull $remote $branch" \
1037 "Pulling new changes from branch $branch in $remote"]
1038 set cmd
[list git pull
]
1039 if {$repo_config(gui.pullsummary
) eq
{false
}} {
1040 lappend cmd
--no-summary
1044 console_exec
$w $cmd [list post_pull_remote
$remote $branch]
1047 proc post_pull_remote
{remote branch success
} {
1048 global HEAD PARENT commit_type
1049 global ui_status_value
1053 repository_state HEAD commit_type
1055 set $ui_status_value "Pulling $branch from $remote complete."
1057 set m
"Conflicts detected while pulling $branch from $remote."
1058 rescan
"set ui_status_value {$m}"
1062 proc push_to
{remote
} {
1063 set w
[new_console
"push $remote" \
1064 "Pushing changes to $remote"]
1065 set cmd
[list git push
]
1067 console_exec
$w $cmd
1070 ######################################################################
1074 proc mapcol
{state path
} {
1075 global all_cols ui_other
1077 if {[catch
{set r
$all_cols($state)}]} {
1078 puts
"error: no column for state={$state} $path"
1084 proc mapicon
{state path
} {
1087 if {[catch
{set r
$all_icons($state)}]} {
1088 puts
"error: no icon for state={$state} $path"
1094 proc mapdesc
{state path
} {
1097 if {[catch
{set r
$all_descs($state)}]} {
1098 puts
"error: no desc for state={$state} $path"
1104 proc escape_path
{path
} {
1105 regsub
-all "\n" $path "\\n" path
1109 proc short_path
{path
} {
1110 return [escape_path
[lindex
[file split $path] end
]]
1115 proc merge_state
{path new_state
} {
1116 global file_states next_icon_id
1118 set s0
[string index
$new_state 0]
1119 set s1
[string index
$new_state 1]
1121 if {[catch
{set info
$file_states($path)}]} {
1123 set icon n
[incr next_icon_id
]
1125 set state
[lindex
$info 0]
1126 set icon
[lindex
$info 1]
1130 set s0
[string index
$state 0]
1131 } elseif
{$s0 eq
{*}} {
1136 set s1
[string index
$state 1]
1137 } elseif
{$s1 eq
{*}} {
1141 set file_states
($path) [list
$s0$s1 $icon]
1145 proc display_file
{path state
} {
1146 global file_states file_lists selected_paths rescan_active
1148 set old_m
[merge_state
$path $state]
1149 if {$rescan_active > 0} return
1151 set s
$file_states($path)
1152 set new_m
[lindex
$s 0]
1153 set new_w
[mapcol
$new_m $path]
1154 set old_w
[mapcol
$old_m $path]
1155 set new_icon
[mapicon
$new_m $path]
1157 if {$new_w ne
$old_w} {
1158 set lno
[lsearch
-sorted $file_lists($old_w) $path]
1161 $old_w conf
-state normal
1162 $old_w delete
$lno.0 [expr {$lno + 1}].0
1163 $old_w conf
-state disabled
1166 lappend file_lists
($new_w) $path
1167 set file_lists
($new_w) [lsort
$file_lists($new_w)]
1168 set lno
[lsearch
-sorted $file_lists($new_w) $path]
1170 $new_w conf
-state normal
1171 $new_w image create
$lno.0 \
1172 -align center
-padx 5 -pady 1 \
1173 -name [lindex
$s 1] \
1175 $new_w insert
$lno.1 "[escape_path $path]\n"
1176 if {[catch
{set in_sel
$selected_paths($path)}]} {
1180 $new_w tag add in_sel
$lno.0 [expr {$lno + 1}].0
1182 $new_w conf
-state disabled
1183 } elseif
{$new_icon ne
[mapicon
$old_m $path]} {
1184 $new_w conf
-state normal
1185 $new_w image conf
[lindex
$s 1] -image $new_icon
1186 $new_w conf
-state disabled
1190 proc display_all_files
{} {
1191 global ui_index ui_other
1192 global file_states file_lists
1193 global last_clicked selected_paths
1195 $ui_index conf
-state normal
1196 $ui_other conf
-state normal
1198 $ui_index delete
0.0 end
1199 $ui_other delete
0.0 end
1202 set file_lists
($ui_index) [list
]
1203 set file_lists
($ui_other) [list
]
1205 foreach path
[lsort
[array names file_states
]] {
1206 set s
$file_states($path)
1208 set w
[mapcol
$m $path]
1209 lappend file_lists
($w) $path
1210 set lno
[expr {[lindex
[split [$w index end
] .
] 0] - 1}]
1211 $w image create end \
1212 -align center
-padx 5 -pady 1 \
1213 -name [lindex
$s 1] \
1214 -image [mapicon
$m $path]
1215 $w insert end
"[escape_path $path]\n"
1216 if {[catch
{set in_sel
$selected_paths($path)}]} {
1220 $w tag add in_sel
$lno.0 [expr {$lno + 1}].0
1224 $ui_index conf
-state disabled
1225 $ui_other conf
-state disabled
1228 proc update_index
{msg pathList after
} {
1229 global update_index_cp update_index_rsd ui_status_value
1231 if {![lock_index update
]} return
1233 set update_index_cp
0
1234 set update_index_rsd
0
1235 set pathList
[lsort
$pathList]
1236 set totalCnt
[llength
$pathList]
1237 set batch [expr {int
($totalCnt * .01) + 1}]
1238 if {$batch > 25} {set batch 25}
1240 set ui_status_value
[format \
1241 "$msg... %i/%i files (%.2f%%)" \
1245 set fd
[open
"| git update-index --add --remove -z --stdin" w
]
1251 fileevent
$fd writable
[list \
1252 write_update_index \
1262 proc write_update_index
{fd pathList totalCnt
batch msg after
} {
1263 global update_index_cp update_index_rsd ui_status_value
1264 global file_states ui_fname_value
1266 if {$update_index_cp >= $totalCnt} {
1269 if {$update_index_rsd} reshow_diff
1274 for {set i
$batch} \
1275 {$update_index_cp < $totalCnt && $i > 0} \
1277 set path
[lindex
$pathList $update_index_cp]
1278 incr update_index_cp
1280 switch
-glob -- [lindex
$file_states($path) 0] {
1296 puts
-nonewline $fd $path
1297 puts
-nonewline $fd "\0"
1298 display_file
$path $new
1299 if {$ui_fname_value eq
$path} {
1300 set update_index_rsd
1
1304 set ui_status_value
[format \
1305 "$msg... %i/%i files (%.2f%%)" \
1308 [expr {100.0 * $update_index_cp / $totalCnt}]]
1311 ######################################################################
1313 ## remote management
1315 proc load_all_remotes
{} {
1316 global gitdir all_remotes repo_config
1318 set all_remotes
[list
]
1319 set rm_dir
[file join $gitdir remotes
]
1320 if {[file isdirectory
$rm_dir]} {
1321 set all_remotes
[concat
$all_remotes [glob \
1325 -directory $rm_dir *]]
1328 foreach line
[array names repo_config remote.
*.url
] {
1329 if {[regexp ^remote\.
(.
*)\.url\$
$line line name
]} {
1330 lappend all_remotes
$name
1334 set all_remotes
[lsort
-unique $all_remotes]
1337 proc populate_remote_menu
{m pfx op
} {
1340 foreach remote
$all_remotes {
1341 $m add
command -label "$pfx $remote..." \
1342 -command [list
$op $remote] \
1347 proc populate_pull_menu
{m
} {
1348 global gitdir repo_config all_remotes disable_on_lock
1350 foreach remote
$all_remotes {
1352 if {[array get repo_config remote.
$remote.url
] ne
{}} {
1353 if {[array get repo_config remote.
$remote.fetch
] ne
{}} {
1354 regexp
{^
([^
:]+):} \
1355 [lindex
$repo_config(remote.
$remote.fetch
) 0] \
1360 set fd
[open
[file join $gitdir remotes
$remote] r
]
1361 while {[gets
$fd line
] >= 0} {
1362 if {[regexp
{^Pull
:[ \t]*([^
:]+):} $line line rb
]} {
1371 regsub ^refs
/heads
/ $rb {} rb_short
1372 if {$rb_short ne
{}} {
1374 -label "Branch $rb_short from $remote..." \
1375 -command [list pull_remote
$remote $rb] \
1377 lappend disable_on_lock \
1378 [list
$m entryconf
[$m index last
] -state]
1383 ######################################################################
1388 #define mask_width 14
1389 #define mask_height 15
1390 static unsigned char mask_bits
[] = {
1391 0xfe, 0x1f, 0xfe, 0x1f, 0xfe, 0x1f, 0xfe, 0x1f, 0xfe, 0x1f, 0xfe, 0x1f,
1392 0xfe, 0x1f, 0xfe, 0x1f, 0xfe, 0x1f, 0xfe, 0x1f, 0xfe, 0x1f, 0xfe, 0x1f,
1393 0xfe, 0x1f, 0xfe, 0x1f, 0xfe, 0x1f};
1396 image create bitmap file_plain
-background white
-foreground black
-data {
1397 #define plain_width 14
1398 #define plain_height 15
1399 static unsigned char plain_bits
[] = {
1400 0xfe, 0x01, 0x02, 0x03, 0x02, 0x05, 0x02, 0x09, 0x02, 0x1f, 0x02, 0x10,
1401 0x02, 0x10, 0x02, 0x10, 0x02, 0x10, 0x02, 0x10, 0x02, 0x10, 0x02, 0x10,
1402 0x02, 0x10, 0x02, 0x10, 0xfe, 0x1f};
1403 } -maskdata $filemask
1405 image create bitmap file_mod
-background white
-foreground blue
-data {
1406 #define mod_width 14
1407 #define mod_height 15
1408 static unsigned char mod_bits
[] = {
1409 0xfe, 0x01, 0x02, 0x03, 0x7a, 0x05, 0x02, 0x09, 0x7a, 0x1f, 0x02, 0x10,
1410 0xfa, 0x17, 0x02, 0x10, 0xfa, 0x17, 0x02, 0x10, 0xfa, 0x17, 0x02, 0x10,
1411 0xfa, 0x17, 0x02, 0x10, 0xfe, 0x1f};
1412 } -maskdata $filemask
1414 image create bitmap file_fulltick
-background white
-foreground "#007000" -data {
1415 #define file_fulltick_width 14
1416 #define file_fulltick_height 15
1417 static unsigned char file_fulltick_bits
[] = {
1418 0xfe, 0x01, 0x02, 0x1a, 0x02, 0x0c, 0x02, 0x0c, 0x02, 0x16, 0x02, 0x16,
1419 0x02, 0x13, 0x00, 0x13, 0x86, 0x11, 0x8c, 0x11, 0xd8, 0x10, 0xf2, 0x10,
1420 0x62, 0x10, 0x02, 0x10, 0xfe, 0x1f};
1421 } -maskdata $filemask
1423 image create bitmap file_parttick
-background white
-foreground "#005050" -data {
1424 #define parttick_width 14
1425 #define parttick_height 15
1426 static unsigned char parttick_bits
[] = {
1427 0xfe, 0x01, 0x02, 0x03, 0x7a, 0x05, 0x02, 0x09, 0x7a, 0x1f, 0x02, 0x10,
1428 0x7a, 0x14, 0x02, 0x16, 0x02, 0x13, 0x8a, 0x11, 0xda, 0x10, 0x72, 0x10,
1429 0x22, 0x10, 0x02, 0x10, 0xfe, 0x1f};
1430 } -maskdata $filemask
1432 image create bitmap file_question
-background white
-foreground black
-data {
1433 #define file_question_width 14
1434 #define file_question_height 15
1435 static unsigned char file_question_bits
[] = {
1436 0xfe, 0x01, 0x02, 0x02, 0xe2, 0x04, 0xf2, 0x09, 0x1a, 0x1b, 0x0a, 0x13,
1437 0x82, 0x11, 0xc2, 0x10, 0x62, 0x10, 0x62, 0x10, 0x02, 0x10, 0x62, 0x10,
1438 0x62, 0x10, 0x02, 0x10, 0xfe, 0x1f};
1439 } -maskdata $filemask
1441 image create bitmap file_removed
-background white
-foreground red
-data {
1442 #define file_removed_width 14
1443 #define file_removed_height 15
1444 static unsigned char file_removed_bits
[] = {
1445 0xfe, 0x01, 0x02, 0x03, 0x02, 0x05, 0x02, 0x09, 0x02, 0x1f, 0x02, 0x10,
1446 0x1a, 0x16, 0x32, 0x13, 0xe2, 0x11, 0xc2, 0x10, 0xe2, 0x11, 0x32, 0x13,
1447 0x1a, 0x16, 0x02, 0x10, 0xfe, 0x1f};
1448 } -maskdata $filemask
1450 image create bitmap file_merge
-background white
-foreground blue
-data {
1451 #define file_merge_width 14
1452 #define file_merge_height 15
1453 static unsigned char file_merge_bits
[] = {
1454 0xfe, 0x01, 0x02, 0x03, 0x62, 0x05, 0x62, 0x09, 0x62, 0x1f, 0x62, 0x10,
1455 0xfa, 0x11, 0xf2, 0x10, 0x62, 0x10, 0x02, 0x10, 0xfa, 0x17, 0x02, 0x10,
1456 0xfa, 0x17, 0x02, 0x10, 0xfe, 0x1f};
1457 } -maskdata $filemask
1459 set ui_index .vpane.files.index.list
1460 set ui_other .vpane.files.other.list
1461 set max_status_desc
0
1463 {__ i plain
"Unmodified"}
1464 {_M i mod
"Modified"}
1465 {M_ i fulltick
"Included in commit"}
1466 {MM i parttick
"Partially included"}
1468 {_O o plain
"Untracked"}
1469 {A_ o fulltick
"Added by commit"}
1470 {AM o parttick
"Partially added"}
1471 {AD o question
"Added (but now gone)"}
1473 {_D i question
"Missing"}
1474 {D_ i removed
"Removed by commit"}
1475 {DD i removed
"Removed by commit"}
1476 {DO i removed
"Removed (still exists)"}
1478 {UM i merge
"Merge conflicts"}
1479 {U_ i merge
"Merge conflicts"}
1481 if {$max_status_desc < [string length
[lindex
$i 3]]} {
1482 set max_status_desc
[string length
[lindex
$i 3]]
1484 if {[lindex
$i 1] eq
{i
}} {
1485 set all_cols
([lindex
$i 0]) $ui_index
1487 set all_cols
([lindex
$i 0]) $ui_other
1489 set all_icons
([lindex
$i 0]) file_
[lindex
$i 2]
1490 set all_descs
([lindex
$i 0]) [lindex
$i 3]
1494 ######################################################################
1499 global tcl_platform tk_library
1500 if {$tcl_platform(platform
) eq
{unix
}
1501 && $tcl_platform(os
) eq
{Darwin
}
1502 && [string match
/Library
/Frameworks
/* $tk_library]} {
1508 proc bind_button3
{w cmd
} {
1509 bind $w <Any-Button-3
> $cmd
1511 bind $w <Control-Button-1
> $cmd
1515 proc incr_font_size
{font
{amt
1}} {
1516 set sz
[font configure
$font -size]
1518 font configure
$font -size $sz
1519 font configure
${font}bold
-size $sz
1522 proc hook_failed_popup
{hook msg
} {
1523 global gitdir appname
1529 label
$w.m.l1
-text "$hook hook failed:" \
1534 -background white
-borderwidth 1 \
1536 -width 80 -height 10 \
1538 -yscrollcommand [list
$w.m.sby
set]
1540 -text {You must correct the above errors before committing.
} \
1544 scrollbar
$w.m.sby
-command [list
$w.m.t yview
]
1545 pack
$w.m.l1
-side top
-fill x
1546 pack
$w.m.l2
-side bottom
-fill x
1547 pack
$w.m.sby
-side right
-fill y
1548 pack
$w.m.t
-side left
-fill both
-expand 1
1549 pack
$w.m
-side top
-fill both
-expand 1 -padx 5 -pady 10
1551 $w.m.t insert
1.0 $msg
1552 $w.m.t conf
-state disabled
1554 button
$w.ok
-text OK \
1557 -command "destroy $w"
1558 pack
$w.ok
-side bottom
-anchor e
-pady 10 -padx 10
1560 bind $w <Visibility
> "grab $w; focus $w"
1561 bind $w <Key-Return
> "destroy $w"
1562 wm title
$w "$appname ([lindex [file split \
1563 [file normalize [file dirname $gitdir]]] \
1568 set next_console_id
0
1570 proc new_console
{short_title long_title
} {
1571 global next_console_id console_data
1572 set w .console
[incr next_console_id
]
1573 set console_data
($w) [list
$short_title $long_title]
1574 return [console_init
$w]
1577 proc console_init
{w
} {
1578 global console_cr console_data
1579 global gitdir appname M1B
1581 set console_cr
($w) 1.0
1584 label
$w.m.l1
-text "[lindex $console_data($w) 1]:" \
1589 -background white
-borderwidth 1 \
1591 -width 80 -height 10 \
1594 -yscrollcommand [list
$w.m.sby
set]
1595 label
$w.m.s
-text {Working... please
wait...
} \
1599 scrollbar
$w.m.sby
-command [list
$w.m.t yview
]
1600 pack
$w.m.l1
-side top
-fill x
1601 pack
$w.m.s
-side bottom
-fill x
1602 pack
$w.m.sby
-side right
-fill y
1603 pack
$w.m.t
-side left
-fill both
-expand 1
1604 pack
$w.m
-side top
-fill both
-expand 1 -padx 5 -pady 10
1606 menu
$w.ctxm
-tearoff 0
1607 $w.ctxm add
command -label "Copy" \
1609 -command "tk_textCopy $w.m.t"
1610 $w.ctxm add
command -label "Select All" \
1612 -command "$w.m.t tag add sel 0.0 end"
1613 $w.ctxm add
command -label "Copy All" \
1616 $w.m.t tag add sel 0.0 end
1618 $w.m.t tag remove sel 0.0 end
1621 button
$w.ok
-text {Close
} \
1624 -command "destroy $w"
1625 pack
$w.ok
-side bottom
-anchor e
-pady 10 -padx 10
1627 bind_button3
$w.m.t
"tk_popup $w.ctxm %X %Y"
1628 bind $w.m.t
<$M1B-Key-a> "$w.m.t tag add sel 0.0 end;break"
1629 bind $w.m.t
<$M1B-Key-A> "$w.m.t tag add sel 0.0 end;break"
1630 bind $w <Visibility
> "focus $w"
1631 wm title
$w "$appname ([lindex [file split \
1632 [file normalize [file dirname $gitdir]]] \
1633 end]): [lindex $console_data($w) 0]"
1637 proc console_exec
{w cmd
{after
{}}} {
1640 # -- Windows tosses the enviroment when we exec our child.
1641 # But most users need that so we have to relogin. :-(
1643 if {$tcl_platform(platform
) eq
{windows
}} {
1644 set cmd
[list sh
--login -c "cd \"[pwd]\" && [join $cmd { }]"]
1647 # -- Tcl won't let us redirect both stdout and stderr to
1648 # the same pipe. So pass it through cat...
1650 set cmd
[concat |
$cmd |
& cat]
1652 set fd_f
[open
$cmd r
]
1653 fconfigure
$fd_f -blocking 0 -translation binary
1654 fileevent
$fd_f readable
[list console_read
$w $fd_f $after]
1657 proc console_read
{w fd after
} {
1658 global console_cr console_data
1662 if {![winfo exists
$w]} {console_init
$w}
1663 $w.m.t conf
-state normal
1665 set n
[string length
$buf]
1667 set cr
[string first
"\r" $buf $c]
1668 set lf
[string first
"\n" $buf $c]
1669 if {$cr < 0} {set cr
[expr {$n + 1}]}
1670 if {$lf < 0} {set lf
[expr {$n + 1}]}
1673 $w.m.t insert end
[string range
$buf $c $lf]
1674 set console_cr
($w) [$w.m.t index
{end
-1c}]
1678 $w.m.t delete
$console_cr($w) end
1679 $w.m.t insert end
"\n"
1680 $w.m.t insert end
[string range
$buf $c $cr]
1685 $w.m.t conf
-state disabled
1689 fconfigure
$fd -blocking 1
1691 if {[catch
{close
$fd}]} {
1692 if {![winfo exists
$w]} {console_init
$w}
1693 $w.m.s conf
-background red
-text {Error
: Command Failed
}
1694 $w.ok conf
-state normal
1696 } elseif
{[winfo exists
$w]} {
1697 $w.m.s conf
-background green
-text {Success
}
1698 $w.ok conf
-state normal
1701 array
unset console_cr
$w
1702 array
unset console_data
$w
1704 uplevel
#0 $after $ok
1708 fconfigure
$fd -blocking 0
1711 ######################################################################
1715 set starting_gitk_msg
{Please
wait... Starting gitk...
}
1718 global tcl_platform ui_status_value starting_gitk_msg
1720 set ui_status_value
$starting_gitk_msg
1722 if {$ui_status_value eq
$starting_gitk_msg} {
1723 set ui_status_value
{Ready.
}
1727 if {$tcl_platform(platform
) eq
{windows
}} {
1735 set w
[new_console
"repack" "Repacking the object database"]
1736 set cmd
[list git repack
]
1739 console_exec
$w $cmd
1745 global gitdir ui_comm is_quitting repo_config
1747 if {$is_quitting} return
1750 # -- Stash our current commit buffer.
1752 set save
[file join $gitdir GITGUI_MSG
]
1753 set msg
[string trim
[$ui_comm get
0.0 end
]]
1754 if {[$ui_comm edit modified
] && $msg ne
{}} {
1756 set fd
[open
$save w
]
1757 puts
$fd [string trim
[$ui_comm get
0.0 end
]]
1760 } elseif
{$msg eq
{} && [file exists
$save]} {
1764 # -- Stash our current window geometry into this repository.
1766 set cfg_geometry
[list
]
1767 lappend cfg_geometry
[wm geometry .
]
1768 lappend cfg_geometry
[lindex
[.vpane sash coord
0] 1]
1769 lappend cfg_geometry
[lindex
[.vpane.files sash coord
0] 0]
1770 if {[catch
{set rc_geometry
$repo_config(gui.geometry
)}]} {
1773 if {$cfg_geometry ne
$rc_geometry} {
1774 catch
{exec git repo-config gui.geometry
$cfg_geometry}
1781 rescan
{set ui_status_value
{Ready.
}}
1784 proc do_include_all
{} {
1787 if {![lock_index begin-update
]} return
1790 foreach path
[array names file_states
] {
1791 set s
$file_states($path)
1797 _D
{lappend pathList
$path}
1800 if {$pathList eq
{}} {
1804 "Including all modified files" \
1806 {set ui_status_value
{Ready to commit.
}}
1810 set GIT_COMMITTER_IDENT
{}
1812 proc do_signoff
{} {
1813 global ui_comm GIT_COMMITTER_IDENT
1815 if {$GIT_COMMITTER_IDENT eq
{}} {
1816 if {[catch
{set me
[exec git var GIT_COMMITTER_IDENT
]} err
]} {
1817 error_popup
"Unable to obtain your identity:\n\n$err"
1820 if {![regexp
{^
(.
*) [0-9]+ [-+0-9]+$
} \
1821 $me me GIT_COMMITTER_IDENT
]} {
1822 error_popup
"Invalid GIT_COMMITTER_IDENT:\n\n$me"
1827 set sob
"Signed-off-by: $GIT_COMMITTER_IDENT"
1828 set last
[$ui_comm get
{end
-1c linestart
} {end
-1c}]
1829 if {$last ne
$sob} {
1830 $ui_comm edit separator
1832 && ![regexp
{^
[A-Z
][A-Za-z
]*-[A-Za-z-
]+: *} $last]} {
1833 $ui_comm insert end
"\n"
1835 $ui_comm insert end
"\n$sob"
1836 $ui_comm edit separator
1841 proc do_amend_last
{} {
1849 proc do_options
{} {
1850 global appname gitdir font_descs
1851 global repo_config global_config
1852 global repo_config_new global_config_new
1854 array
unset repo_config_new
1855 array
unset global_config_new
1856 foreach name
[array names repo_config
] {
1857 set repo_config_new
($name) $repo_config($name)
1860 foreach name
[array names repo_config
] {
1862 gui.diffcontext
{continue}
1864 set repo_config_new
($name) $repo_config($name)
1866 foreach name
[array names global_config
] {
1867 set global_config_new
($name) $global_config($name)
1869 set reponame
[lindex
[file split \
1870 [file normalize
[file dirname $gitdir]]] \
1873 set w .options_editor
1875 wm geometry
$w "+[winfo rootx .]+[winfo rooty .]"
1877 label
$w.header
-text "$appname Options" \
1879 pack
$w.header
-side top
-fill x
1882 button
$w.buttons.restore
-text {Restore Defaults
} \
1884 -command do_restore_defaults
1885 pack
$w.buttons.restore
-side left
1886 button
$w.buttons.save
-text Save \
1888 -command [list do_save_config
$w]
1889 pack
$w.buttons.save
-side right
1890 button
$w.buttons.cancel
-text {Cancel
} \
1892 -command [list destroy
$w]
1893 pack
$w.buttons.cancel
-side right
1894 pack
$w.buttons
-side bottom
-fill x
-pady 10 -padx 10
1896 labelframe
$w.repo
-text "$reponame Repository" \
1898 -relief raised
-borderwidth 2
1899 labelframe
$w.global
-text {Global
(All Repositories
)} \
1901 -relief raised
-borderwidth 2
1902 pack
$w.repo
-side left
-fill both
-expand 1 -pady 5 -padx 5
1903 pack
$w.global
-side right
-fill both
-expand 1 -pady 5 -padx 5
1906 {b partialinclude
{Allow Partially Included Files
}}
1907 {b pullsummary
{Show Pull Summary
}}
1908 {b trustmtime
{Trust File Modification Timestamps
}}
1909 {i diffcontext
{Number of Diff Context Lines
}}
1911 set type [lindex
$option 0]
1912 set name
[lindex
$option 1]
1913 set text
[lindex
$option 2]
1914 foreach f
{repo global
} {
1917 checkbutton
$w.
$f.
$name -text $text \
1918 -variable ${f}_config_new
(gui.
$name) \
1922 pack
$w.
$f.
$name -side top
-anchor w
1926 label
$w.
$f.
$name.l
-text "$text:" -font font_ui
1927 pack
$w.
$f.
$name.l
-side left
-anchor w
-fill x
1928 spinbox
$w.
$f.
$name.v \
1929 -textvariable ${f}_config_new
(gui.
$name) \
1930 -from 1 -to 99 -increment 1 \
1933 pack
$w.
$f.
$name.v
-side right
-anchor e
1934 pack
$w.
$f.
$name -side top
-anchor w
-fill x
1940 set all_fonts
[lsort
[font families
]]
1941 foreach option
$font_descs {
1942 set name
[lindex
$option 0]
1943 set font
[lindex
$option 1]
1944 set text
[lindex
$option 2]
1946 set global_config_new
(gui.
$font^^family
) \
1947 [font configure
$font -family]
1948 set global_config_new
(gui.
$font^^size
) \
1949 [font configure
$font -size]
1951 frame
$w.global.
$name
1952 label
$w.global.
$name.l
-text "$text:" -font font_ui
1953 pack
$w.global.
$name.l
-side left
-anchor w
-fill x
1954 eval tk_optionMenu
$w.global.
$name.family \
1955 global_config_new
(gui.
$font^^family
) \
1957 spinbox
$w.global.
$name.size \
1958 -textvariable global_config_new
(gui.
$font^^size
) \
1959 -from 2 -to 80 -increment 1 \
1962 pack
$w.global.
$name.size
-side right
-anchor e
1963 pack
$w.global.
$name.family
-side right
-anchor e
1964 pack
$w.global.
$name -side top
-anchor w
-fill x
1967 bind $w <Visibility
> "grab $w; focus $w"
1968 bind $w <Key-Escape
> "destroy $w"
1969 wm title
$w "$appname ($reponame): Options"
1973 proc do_restore_defaults
{} {
1974 global font_descs default_config repo_config
1975 global repo_config_new global_config_new
1977 foreach name
[array names default_config
] {
1978 set repo_config_new
($name) $default_config($name)
1979 set global_config_new
($name) $default_config($name)
1982 foreach option
$font_descs {
1983 set name
[lindex
$option 0]
1984 set repo_config
(gui.
$name) $default_config(gui.
$name)
1988 foreach option
$font_descs {
1989 set name
[lindex
$option 0]
1990 set font
[lindex
$option 1]
1991 set global_config_new
(gui.
$font^^family
) \
1992 [font configure
$font -family]
1993 set global_config_new
(gui.
$font^^size
) \
1994 [font configure
$font -size]
1998 proc do_save_config
{w
} {
1999 if {[catch
{save_config
} err
]} {
2000 error_popup
"Failed to completely save options:\n\n$err"
2006 proc toggle_or_diff
{w x y
} {
2007 global file_lists ui_index ui_other
2008 global last_clicked selected_paths
2010 set pos
[split [$w index @
$x,$y] .
]
2011 set lno
[lindex
$pos 0]
2012 set col [lindex
$pos 1]
2013 set path
[lindex
$file_lists($w) [expr {$lno - 1}]]
2019 set last_clicked
[list
$w $lno]
2020 array
unset selected_paths
2021 $ui_index tag remove in_sel
0.0 end
2022 $ui_other tag remove in_sel
0.0 end
2026 "Including [short_path $path]" \
2028 {set ui_status_value
{Ready.
}}
2030 show_diff
$path $w $lno
2034 proc add_one_to_selection
{w x y
} {
2036 global last_clicked selected_paths
2038 set pos
[split [$w index @
$x,$y] .
]
2039 set lno
[lindex
$pos 0]
2040 set col [lindex
$pos 1]
2041 set path
[lindex
$file_lists($w) [expr {$lno - 1}]]
2047 set last_clicked
[list
$w $lno]
2048 if {[catch
{set in_sel
$selected_paths($path)}]} {
2052 unset selected_paths
($path)
2053 $w tag remove in_sel
$lno.0 [expr {$lno + 1}].0
2055 set selected_paths
($path) 1
2056 $w tag add in_sel
$lno.0 [expr {$lno + 1}].0
2060 proc add_range_to_selection
{w x y
} {
2062 global last_clicked selected_paths
2064 if {[lindex
$last_clicked 0] ne
$w} {
2065 toggle_or_diff
$w $x $y
2069 set pos
[split [$w index @
$x,$y] .
]
2070 set lno
[lindex
$pos 0]
2071 set lc
[lindex
$last_clicked 1]
2080 foreach path
[lrange
$file_lists($w) \
2081 [expr {$begin - 1}] \
2082 [expr {$end - 1}]] {
2083 set selected_paths
($path) 1
2085 $w tag add in_sel
$begin.0 [expr {$end + 1}].0
2088 ######################################################################
2092 set cursor_ptr arrow
2093 font create font_diff
-family Courier
-size 10
2097 eval font configure font_ui
[font actual
[.dummy cget
-font]]
2101 font create font_uibold
2102 font create font_diffbold
2106 if {$tcl_platform(platform
) eq
{windows
}} {
2109 } elseif
{[is_MacOSX
]} {
2114 proc apply_config
{} {
2115 global repo_config font_descs
2117 foreach option
$font_descs {
2118 set name
[lindex
$option 0]
2119 set font
[lindex
$option 1]
2121 foreach
{cn cv
} $repo_config(gui.
$name) {
2122 font configure
$font $cn $cv
2125 error_popup
"Invalid font specified in gui.$name:\n\n$err"
2127 foreach
{cn cv
} [font configure
$font] {
2128 font configure
${font}bold
$cn $cv
2130 font configure
${font}bold
-weight bold
2134 set default_config
(gui.trustmtime
) false
2135 set default_config
(gui.pullsummary
) true
2136 set default_config
(gui.partialinclude
) false
2137 set default_config
(gui.diffcontext
) 5
2138 set default_config
(gui.fontui
) [font configure font_ui
]
2139 set default_config
(gui.fontdiff
) [font configure font_diff
]
2141 {fontui font_ui
{Main Font
}}
2142 {fontdiff font_diff
{Diff
/Console Font
}}
2147 ######################################################################
2152 menu .mbar
-tearoff 0
2153 .mbar add cascade
-label Project
-menu .mbar.project
2154 .mbar add cascade
-label Edit
-menu .mbar.edit
2155 .mbar add cascade
-label Commit
-menu .mbar.commit
2156 if {!$single_commit} {
2157 .mbar add cascade
-label Fetch
-menu .mbar.fetch
2158 .mbar add cascade
-label Pull
-menu .mbar.pull
2159 .mbar add cascade
-label Push
-menu .mbar.push
2161 . configure
-menu .mbar
2165 .mbar.project add
command -label Visualize \
2168 if {!$single_commit} {
2169 .mbar.project add
command -label {Repack Database
} \
2170 -command do_repack \
2173 .mbar.project add
command -label Quit \
2175 -accelerator $M1T-Q \
2181 .mbar.edit add
command -label Undo \
2182 -command {catch
{[focus
] edit undo
}} \
2183 -accelerator $M1T-Z \
2185 .mbar.edit add
command -label Redo \
2186 -command {catch
{[focus
] edit redo
}} \
2187 -accelerator $M1T-Y \
2189 .mbar.edit add separator
2190 .mbar.edit add
command -label Cut \
2191 -command {catch
{tk_textCut
[focus
]}} \
2192 -accelerator $M1T-X \
2194 .mbar.edit add
command -label Copy \
2195 -command {catch
{tk_textCopy
[focus
]}} \
2196 -accelerator $M1T-C \
2198 .mbar.edit add
command -label Paste \
2199 -command {catch
{tk_textPaste
[focus
]; [focus
] see insert
}} \
2200 -accelerator $M1T-V \
2202 .mbar.edit add
command -label Delete \
2203 -command {catch
{[focus
] delete sel.first sel.last
}} \
2206 .mbar.edit add separator
2207 .mbar.edit add
command -label {Select All
} \
2208 -command {catch
{[focus
] tag add sel
0.0 end
}} \
2209 -accelerator $M1T-A \
2211 .mbar.edit add separator
2212 .mbar.edit add
command -label {Options...
} \
2213 -command do_options \
2218 .mbar.commit add
command -label Rescan \
2219 -command do_rescan \
2222 lappend disable_on_lock \
2223 [list .mbar.commit entryconf
[.mbar.commit index last
] -state]
2224 .mbar.commit add
command -label {Amend Last Commit
} \
2225 -command do_amend_last \
2227 lappend disable_on_lock \
2228 [list .mbar.commit entryconf
[.mbar.commit index last
] -state]
2229 .mbar.commit add
command -label {Include All Files
} \
2230 -command do_include_all \
2231 -accelerator $M1T-I \
2233 lappend disable_on_lock \
2234 [list .mbar.commit entryconf
[.mbar.commit index last
] -state]
2235 .mbar.commit add
command -label {Sign Off
} \
2236 -command do_signoff \
2237 -accelerator $M1T-S \
2239 .mbar.commit add
command -label Commit \
2240 -command do_commit \
2241 -accelerator $M1T-Return \
2243 lappend disable_on_lock \
2244 [list .mbar.commit entryconf
[.mbar.commit index last
] -state]
2246 if {!$single_commit} {
2257 # -- Main Window Layout
2258 panedwindow .vpane
-orient vertical
2259 panedwindow .vpane.files
-orient horizontal
2260 .vpane add .vpane.files
-sticky nsew
-height 100 -width 400
2261 pack .vpane
-anchor n
-side top
-fill both
-expand 1
2263 # -- Index File List
2264 frame .vpane.files.index
-height 100 -width 400
2265 label .vpane.files.index.title
-text {Modified Files
} \
2268 text
$ui_index -background white
-borderwidth 0 \
2269 -width 40 -height 10 \
2271 -cursor $cursor_ptr \
2272 -yscrollcommand {.vpane.files.index.sb
set} \
2274 scrollbar .vpane.files.index.sb
-command [list
$ui_index yview
]
2275 pack .vpane.files.index.title
-side top
-fill x
2276 pack .vpane.files.index.sb
-side right
-fill y
2277 pack
$ui_index -side left
-fill both
-expand 1
2278 .vpane.files add .vpane.files.index
-sticky nsew
2280 # -- Other (Add) File List
2281 frame .vpane.files.other
-height 100 -width 100
2282 label .vpane.files.other.title
-text {Untracked Files
} \
2285 text
$ui_other -background white
-borderwidth 0 \
2286 -width 40 -height 10 \
2288 -cursor $cursor_ptr \
2289 -yscrollcommand {.vpane.files.other.sb
set} \
2291 scrollbar .vpane.files.other.sb
-command [list
$ui_other yview
]
2292 pack .vpane.files.other.title
-side top
-fill x
2293 pack .vpane.files.other.sb
-side right
-fill y
2294 pack
$ui_other -side left
-fill both
-expand 1
2295 .vpane.files add .vpane.files.other
-sticky nsew
2297 foreach i
[list
$ui_index $ui_other] {
2298 $i tag conf in_diff
-font font_uibold
2299 $i tag conf in_sel \
2300 -background [$i cget
-foreground] \
2301 -foreground [$i cget
-background]
2305 # -- Diff and Commit Area
2306 frame .vpane.lower
-height 300 -width 400
2307 frame .vpane.lower.commarea
2308 frame .vpane.lower.
diff -relief sunken
-borderwidth 1
2309 pack .vpane.lower.commarea
-side top
-fill x
2310 pack .vpane.lower.
diff -side bottom
-fill both
-expand 1
2311 .vpane add .vpane.lower
-stick nsew
2313 # -- Commit Area Buttons
2314 frame .vpane.lower.commarea.buttons
2315 label .vpane.lower.commarea.buttons.l
-text {} \
2319 pack .vpane.lower.commarea.buttons.l
-side top
-fill x
2320 pack .vpane.lower.commarea.buttons
-side left
-fill y
2322 button .vpane.lower.commarea.buttons.rescan
-text {Rescan
} \
2323 -command do_rescan \
2325 pack .vpane.lower.commarea.buttons.rescan
-side top
-fill x
2326 lappend disable_on_lock \
2327 {.vpane.lower.commarea.buttons.rescan conf
-state}
2329 button .vpane.lower.commarea.buttons.amend
-text {Amend Last
} \
2330 -command do_amend_last \
2332 pack .vpane.lower.commarea.buttons.amend
-side top
-fill x
2333 lappend disable_on_lock \
2334 {.vpane.lower.commarea.buttons.amend conf
-state}
2336 button .vpane.lower.commarea.buttons.incall
-text {Include All
} \
2337 -command do_include_all \
2339 pack .vpane.lower.commarea.buttons.incall
-side top
-fill x
2340 lappend disable_on_lock \
2341 {.vpane.lower.commarea.buttons.incall conf
-state}
2343 button .vpane.lower.commarea.buttons.signoff
-text {Sign Off
} \
2344 -command do_signoff \
2346 pack .vpane.lower.commarea.buttons.signoff
-side top
-fill x
2348 button .vpane.lower.commarea.buttons.commit
-text {Commit
} \
2349 -command do_commit \
2351 pack .vpane.lower.commarea.buttons.commit
-side top
-fill x
2352 lappend disable_on_lock \
2353 {.vpane.lower.commarea.buttons.commit conf
-state}
2355 # -- Commit Message Buffer
2356 frame .vpane.lower.commarea.buffer
2357 set ui_comm .vpane.lower.commarea.buffer.t
2358 set ui_coml .vpane.lower.commarea.buffer.l
2359 label
$ui_coml -text {Commit Message
:} \
2363 trace add variable commit_type
write {uplevel
#0 {
2364 switch
-glob $commit_type \
2365 initial
{$ui_coml conf
-text {Initial Commit Message
:}} \
2366 amend
{$ui_coml conf
-text {Amended Commit Message
:}} \
2367 merge
{$ui_coml conf
-text {Merge Commit Message
:}} \
2368 * {$ui_coml conf
-text {Commit Message
:}}
2370 text
$ui_comm -background white
-borderwidth 1 \
2373 -autoseparators true \
2375 -width 75 -height 9 -wrap none \
2377 -yscrollcommand {.vpane.lower.commarea.buffer.sby
set}
2378 scrollbar .vpane.lower.commarea.buffer.sby \
2379 -command [list
$ui_comm yview
]
2380 pack
$ui_coml -side top
-fill x
2381 pack .vpane.lower.commarea.buffer.sby
-side right
-fill y
2382 pack
$ui_comm -side left
-fill y
2383 pack .vpane.lower.commarea.buffer
-side left
-fill y
2385 # -- Commit Message Buffer Context Menu
2387 menu
$ui_comm.ctxm
-tearoff 0
2388 $ui_comm.ctxm add
command -label "Cut" \
2390 -command "tk_textCut $ui_comm"
2391 $ui_comm.ctxm add
command -label "Copy" \
2393 -command "tk_textCopy $ui_comm"
2394 $ui_comm.ctxm add
command -label "Paste" \
2396 -command "tk_textPaste $ui_comm"
2397 $ui_comm.ctxm add
command -label "Delete" \
2399 -command "$ui_comm delete sel.first sel.last"
2400 $ui_comm.ctxm add separator
2401 $ui_comm.ctxm add
command -label "Select All" \
2403 -command "$ui_comm tag add sel 0.0 end"
2404 $ui_comm.ctxm add
command -label "Copy All" \
2407 $ui_comm tag add sel 0.0 end
2408 tk_textCopy $ui_comm
2409 $ui_comm tag remove sel 0.0 end
2411 $ui_comm.ctxm add separator
2412 $ui_comm.ctxm add
command -label "Sign Off" \
2415 bind_button3
$ui_comm "tk_popup $ui_comm.ctxm %X %Y"
2418 set ui_fname_value
{}
2419 set ui_fstatus_value
{}
2420 frame .vpane.lower.
diff.header
-background orange
2421 label .vpane.lower.
diff.header.l4 \
2422 -textvariable ui_fstatus_value \
2423 -background orange \
2424 -width $max_status_desc \
2428 label .vpane.lower.
diff.header.l1
-text {File
:} \
2429 -background orange \
2431 set ui_fname .vpane.lower.
diff.header.l2
2433 -textvariable ui_fname_value \
2434 -background orange \
2438 menu
$ui_fname.ctxm
-tearoff 0
2439 $ui_fname.ctxm add
command -label "Copy" \
2448 bind_button3
$ui_fname "tk_popup $ui_fname.ctxm %X %Y"
2449 pack .vpane.lower.
diff.header.l4
-side left
2450 pack .vpane.lower.
diff.header.l1
-side left
2451 pack
$ui_fname -fill x
2454 frame .vpane.lower.
diff.body
2455 set ui_diff .vpane.lower.
diff.body.t
2456 text
$ui_diff -background white
-borderwidth 0 \
2457 -width 80 -height 15 -wrap none \
2459 -xscrollcommand {.vpane.lower.
diff.body.sbx
set} \
2460 -yscrollcommand {.vpane.lower.
diff.body.sby
set} \
2462 scrollbar .vpane.lower.
diff.body.sbx
-orient horizontal \
2463 -command [list
$ui_diff xview
]
2464 scrollbar .vpane.lower.
diff.body.sby
-orient vertical \
2465 -command [list
$ui_diff yview
]
2466 pack .vpane.lower.
diff.body.sbx
-side bottom
-fill x
2467 pack .vpane.lower.
diff.body.sby
-side right
-fill y
2468 pack
$ui_diff -side left
-fill both
-expand 1
2469 pack .vpane.lower.
diff.header
-side top
-fill x
2470 pack .vpane.lower.
diff.body
-side bottom
-fill both
-expand 1
2472 $ui_diff tag conf dm
-foreground red
2473 $ui_diff tag conf dp
-foreground blue
2474 $ui_diff tag conf di
-foreground {#00a000}
2475 $ui_diff tag conf dni
-foreground {#a000a0}
2476 $ui_diff tag conf da
-font font_diffbold
2477 $ui_diff tag conf bold
-font font_diffbold
2479 # -- Diff Body Context Menu
2481 menu
$ui_diff.ctxm
-tearoff 0
2482 $ui_diff.ctxm add
command -label "Copy" \
2484 -command "tk_textCopy $ui_diff"
2485 $ui_diff.ctxm add
command -label "Select All" \
2487 -command "$ui_diff tag add sel 0.0 end"
2488 $ui_diff.ctxm add
command -label "Copy All" \
2491 $ui_diff tag add sel 0.0 end
2492 tk_textCopy $ui_diff
2493 $ui_diff tag remove sel 0.0 end
2495 $ui_diff.ctxm add separator
2496 $ui_diff.ctxm add
command -label "Decrease Font Size" \
2498 -command {incr_font_size font_diff
-1}
2499 $ui_diff.ctxm add
command -label "Increase Font Size" \
2501 -command {incr_font_size font_diff
1}
2502 $ui_diff.ctxm add separator
2503 $ui_diff.ctxm add
command -label "Show Less Context" \
2505 -command {if {$ui_fname_value ne
{}
2506 && $repo_config(gui.diffcontext
) >= 2} {
2507 incr repo_config
(gui.diffcontext
) -1
2510 $ui_diff.ctxm add
command -label "Show More Context" \
2512 -command {if {$ui_fname_value ne
{}} {
2513 incr repo_config
(gui.diffcontext
)
2516 $ui_diff.ctxm add separator
2517 $ui_diff.ctxm add
command -label {Options...
} \
2520 bind_button3
$ui_diff "tk_popup $ui_diff.ctxm %X %Y"
2523 set ui_status_value
{Initializing...
}
2524 label .status
-textvariable ui_status_value \
2530 pack .status
-anchor w
-side bottom
-fill x
2534 set gm
$repo_config(gui.geometry
)
2535 wm geometry .
[lindex
$gm 0]
2536 .vpane sash place
0 \
2537 [lindex
[.vpane sash coord
0] 0] \
2539 .vpane.files sash place
0 \
2541 [lindex
[.vpane.files sash coord
0] 1]
2546 bind $ui_comm <$M1B-Key-Return> {do_commit
;break}
2547 bind $ui_comm <$M1B-Key-i> {do_include_all
;break}
2548 bind $ui_comm <$M1B-Key-I> {do_include_all
;break}
2549 bind $ui_comm <$M1B-Key-x> {tk_textCut
%W
;break}
2550 bind $ui_comm <$M1B-Key-X> {tk_textCut
%W
;break}
2551 bind $ui_comm <$M1B-Key-c> {tk_textCopy
%W
;break}
2552 bind $ui_comm <$M1B-Key-C> {tk_textCopy
%W
;break}
2553 bind $ui_comm <$M1B-Key-v> {tk_textPaste
%W
; %W see insert
; break}
2554 bind $ui_comm <$M1B-Key-V> {tk_textPaste
%W
; %W see insert
; break}
2555 bind $ui_comm <$M1B-Key-a> {%W tag add sel
0.0 end
;break}
2556 bind $ui_comm <$M1B-Key-A> {%W tag add sel
0.0 end
;break}
2558 bind $ui_diff <$M1B-Key-x> {tk_textCopy
%W
;break}
2559 bind $ui_diff <$M1B-Key-X> {tk_textCopy
%W
;break}
2560 bind $ui_diff <$M1B-Key-c> {tk_textCopy
%W
;break}
2561 bind $ui_diff <$M1B-Key-C> {tk_textCopy
%W
;break}
2562 bind $ui_diff <$M1B-Key-v> {break}
2563 bind $ui_diff <$M1B-Key-V> {break}
2564 bind $ui_diff <$M1B-Key-a> {%W tag add sel
0.0 end
;break}
2565 bind $ui_diff <$M1B-Key-A> {%W tag add sel
0.0 end
;break}
2566 bind $ui_diff <Key-Up
> {catch
{%W yview scroll
-1 units
};break}
2567 bind $ui_diff <Key-Down
> {catch
{%W yview scroll
1 units
};break}
2568 bind $ui_diff <Key-Left
> {catch
{%W xview scroll
-1 units
};break}
2569 bind $ui_diff <Key-Right
> {catch
{%W xview scroll
1 units
};break}
2571 bind .
<Destroy
> do_quit
2572 bind all
<Key-F5
> do_rescan
2573 bind all
<$M1B-Key-r> do_rescan
2574 bind all
<$M1B-Key-R> do_rescan
2575 bind .
<$M1B-Key-s> do_signoff
2576 bind .
<$M1B-Key-S> do_signoff
2577 bind .
<$M1B-Key-i> do_include_all
2578 bind .
<$M1B-Key-I> do_include_all
2579 bind .
<$M1B-Key-Return> do_commit
2580 bind all
<$M1B-Key-q> do_quit
2581 bind all
<$M1B-Key-Q> do_quit
2582 bind all
<$M1B-Key-w> {destroy
[winfo toplevel
%W
]}
2583 bind all
<$M1B-Key-W> {destroy
[winfo toplevel
%W
]}
2584 foreach i
[list
$ui_index $ui_other] {
2585 bind $i <Button-1
> "toggle_or_diff $i %x %y; break"
2586 bind $i <$M1B-Button-1> "add_one_to_selection $i %x %y; break"
2587 bind $i <Shift-Button-1
> "add_range_to_selection $i %x %y; break"
2591 set file_lists
($ui_index) [list
]
2592 set file_lists
($ui_other) [list
]
2594 wm title .
"$appname ([file normalize [file dirname $gitdir]])"
2595 focus
-force $ui_comm
2596 if {!$single_commit} {
2598 populate_remote_menu .mbar.fetch From fetch_from
2599 populate_remote_menu .mbar.push To push_to
2600 populate_pull_menu .mbar.pull