Git fork
at reftables-rust 3960 lines 101 kB view raw
1#!/bin/sh 2# Tcl ignores the next line -*- tcl -*- \ 3 if test "z$*" = zversion \ 4 || test "z$*" = z--version; \ 5 then \ 6 echo 'git-gui version @@GITGUI_VERSION@@'; \ 7 exit; \ 8 fi; \ 9 argv0=$0; \ 10 exec wish "$argv0" -- "$@" 11 12set appvers {@@GITGUI_VERSION@@} 13set copyright [string map [list (c) \u00a9] { 14Copyright (c) 2006-2010 Shawn Pearce, et. al. 15 16This program is free software; you can redistribute it and/or modify 17it under the terms of the GNU General Public License as published by 18the Free Software Foundation; either version 2 of the License, or 19(at your option) any later version. 20 21This program is distributed in the hope that it will be useful, 22but WITHOUT ANY WARRANTY; without even the implied warranty of 23MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 24GNU General Public License for more details. 25 26You should have received a copy of the GNU General Public License 27along with this program; if not, see <https://www.gnu.org/licenses/>.}] 28 29###################################################################### 30## 31## Tcl/Tk sanity check 32 33if {[catch {package require Tcl 8.6-} err]} { 34 catch {wm withdraw .} 35 tk_messageBox \ 36 -icon error \ 37 -type ok \ 38 -title "git-gui: fatal error" \ 39 -message $err 40 exit 1 41} 42 43catch {rename send {}} ; # What an evil concept... 44 45###################################################################### 46## 47## Enabling platform-specific code paths 48 49proc is_MacOSX {} { 50 if {[tk windowingsystem] eq {aqua}} { 51 return 1 52 } 53 return 0 54} 55 56proc is_Windows {} { 57 if {$::tcl_platform(platform) eq {windows}} { 58 return 1 59 } 60 return 0 61} 62 63set _iscygwin {} 64proc is_Cygwin {} { 65 global _iscygwin 66 if {$_iscygwin eq {}} { 67 if {[string match "CYGWIN_*" $::tcl_platform(os)]} { 68 set _iscygwin 1 69 } else { 70 set _iscygwin 0 71 } 72 } 73 return $_iscygwin 74} 75 76###################################################################### 77## Enable Tcl8 profile in Tcl9, allowing consumption of data that has 78## bytes not conforming to the assumed encoding profile. 79 80if {[package vcompare $::tcl_version 9.0] >= 0} { 81 rename open _strict_open 82 proc open args { 83 set f [_strict_open {*}$args] 84 chan configure $f -profile tcl8 85 return $f 86 } 87 proc convertfrom args { 88 return [encoding convertfrom -profile tcl8 {*}$args] 89 } 90} else { 91 proc convertfrom args { 92 return [encoding convertfrom {*}$args] 93 } 94} 95 96###################################################################### 97## 98## PATH lookup. Sanitize $PATH, assure exec/open use only that 99 100if {[is_Windows]} { 101 set _path_sep {;} 102} else { 103 set _path_sep {:} 104} 105 106set _path_seen [dict create] 107foreach p [split $env(PATH) $_path_sep] { 108 # Keep only absolute paths, getting rid of ., empty, etc. 109 if {[file pathtype $p] ne {absolute}} { 110 continue 111 } 112 # Keep only the first occurence of any duplicates. 113 set norm_p [file normalize $p] 114 dict set _path_seen $norm_p 1 115} 116set _search_path [dict keys $_path_seen] 117unset _path_seen 118 119set env(PATH) [join $_search_path $_path_sep] 120 121if {[is_Windows]} { 122 proc _which {what args} { 123 global _search_path 124 125 if {[lsearch -exact $args -script] >= 0} { 126 set suffix {} 127 } elseif {[string match *.exe [string tolower $what]]} { 128 # The search string already has the file extension 129 set suffix {} 130 } else { 131 set suffix .exe 132 } 133 134 foreach p $_search_path { 135 set p [file join $p $what$suffix] 136 if {[file exists $p]} { 137 return [file normalize $p] 138 } 139 } 140 return {} 141 } 142 143 proc sanitize_command_line {command_line from_index} { 144 set i $from_index 145 while {$i < [llength $command_line]} { 146 set cmd [lindex $command_line $i] 147 if {[llength [file split $cmd]] < 2} { 148 set fullpath [_which $cmd] 149 if {$fullpath eq ""} { 150 throw {NOT-FOUND} "$cmd not found in PATH" 151 } 152 lset command_line $i $fullpath 153 } 154 155 # handle piped commands, e.g. `exec A | B` 156 for {incr i} {$i < [llength $command_line]} {incr i} { 157 if {[lindex $command_line $i] eq "|"} { 158 incr i 159 break 160 } 161 } 162 } 163 return $command_line 164 } 165 166 # Override `exec` to avoid unsafe PATH lookup 167 168 rename exec real_exec 169 170 proc exec {args} { 171 # skip options 172 for {set i 0} {$i < [llength $args]} {incr i} { 173 set arg [lindex $args $i] 174 if {$arg eq "--"} { 175 incr i 176 break 177 } 178 if {[string range $arg 0 0] ne "-"} { 179 break 180 } 181 } 182 set args [sanitize_command_line $args $i] 183 uplevel 1 real_exec $args 184 } 185 186 # Override `open` to avoid unsafe PATH lookup 187 188 rename open real_open 189 190 proc open {args} { 191 set arg0 [lindex $args 0] 192 if {[string range $arg0 0 0] eq "|"} { 193 set command_line [string trim [string range $arg0 1 end]] 194 lset args 0 "| [sanitize_command_line $command_line 0]" 195 } 196 set fd [real_open {*}$args] 197 fconfigure $fd -eofchar {} 198 return $fd 199 } 200 201} else { 202 # On non-Windows platforms, auto_execok, exec, and open are safe, and will 203 # use the sanitized search path. But, we need _which for these. 204 205 proc _which {what args} { 206 return [lindex [auto_execok $what] 0] 207 } 208} 209 210# Wrap exec/open to sanitize arguments 211 212# unsafe arguments begin with redirections or the pipe or background operators 213proc is_arg_unsafe {arg} { 214 regexp {^([<|>&]|2>)} $arg 215} 216 217proc make_arg_safe {arg} { 218 if {[is_arg_unsafe $arg]} { 219 set arg [file join . $arg] 220 } 221 return $arg 222} 223 224proc make_arglist_safe {arglist} { 225 set res {} 226 foreach arg $arglist { 227 lappend res [make_arg_safe $arg] 228 } 229 return $res 230} 231 232# executes one command 233# no redirections or pipelines are possible 234# cmd is a list that specifies the command and its arguments 235# calls `exec` and returns its value 236proc safe_exec {cmd} { 237 eval exec [make_arglist_safe $cmd] 238} 239 240# executes one command in the background 241# no redirections or pipelines are possible 242# cmd is a list that specifies the command and its arguments 243# calls `exec` and returns its value 244proc safe_exec_bg {cmd} { 245 eval exec [make_arglist_safe $cmd] & 246} 247 248proc safe_open_file {filename flags} { 249 # a file name starting with "|" would attempt to run a process 250 # but such a file name must be treated as a relative path 251 # hide the "|" behind "./" 252 if {[string index $filename 0] eq "|"} { 253 set filename [file join . $filename] 254 } 255 open $filename $flags 256} 257 258# End exec/open wrappers 259 260###################################################################### 261## 262## locate our library 263 264if { [info exists ::env(GIT_GUI_LIB_DIR) ] } { 265 set oguilib $::env(GIT_GUI_LIB_DIR) 266} else { 267 set oguilib {@@GITGUI_LIBDIR@@} 268} 269set oguirel {@@GITGUI_RELATIVE@@} 270if {$oguirel eq {1}} { 271 set oguilib [file dirname [file normalize $argv0]] 272 if {[file tail $oguilib] eq {git-core}} { 273 set oguilib [file dirname $oguilib] 274 } 275 set oguilib [file dirname $oguilib] 276 set oguilib [file join $oguilib share git-gui lib] 277 set oguimsg [file join $oguilib msgs] 278} elseif {[string match @@* $oguirel]} { 279 set oguilib [file join [file dirname [file normalize $argv0]] lib] 280 set oguimsg [file join [file dirname [file normalize $argv0]] po] 281} else { 282 set oguimsg [file join $oguilib msgs] 283} 284unset oguirel 285 286###################################################################### 287## 288## enable verbose loading? 289 290if {![catch {set _verbose $env(GITGUI_VERBOSE)}]} { 291 unset _verbose 292 rename auto_load real__auto_load 293 proc auto_load {name args} { 294 puts stderr "auto_load $name" 295 return [uplevel 1 real__auto_load $name $args] 296 } 297 rename source real__source 298 proc source {args} { 299 puts stderr "source $args" 300 uplevel 1 [linsert $args 0 real__source] 301 } 302 if {[tk windowingsystem] eq "win32"} { console show } 303} 304 305###################################################################### 306## 307## Internationalization (i18n) through msgcat and gettext. See 308## http://www.gnu.org/software/gettext/manual/html_node/Tcl.html 309 310package require msgcat 311 312# Check for Windows 7 MUI language pack (missed by msgcat < 1.4.4) 313if {[tk windowingsystem] eq "win32" 314 && [package vcompare [package provide msgcat] 1.4.4] < 0 315} then { 316 proc _mc_update_locale {} { 317 set key {HKEY_CURRENT_USER\Control Panel\Desktop} 318 if {![catch { 319 package require registry 320 set uilocale [registry get $key "PreferredUILanguages"] 321 msgcat::ConvertLocale [string map {- _} [lindex $uilocale 0]] 322 } uilocale]} { 323 if {[string length $uilocale] > 0} { 324 msgcat::mclocale $uilocale 325 } 326 } 327 } 328 _mc_update_locale 329} 330 331proc _mc_trim {fmt} { 332 set cmk [string first @@ $fmt] 333 if {$cmk > 0} { 334 return [string range $fmt 0 [expr {$cmk - 1}]] 335 } 336 return $fmt 337} 338 339proc mc {en_fmt args} { 340 set fmt [_mc_trim [::msgcat::mc $en_fmt]] 341 if {[catch {set msg [eval [list format $fmt] $args]} err]} { 342 set msg [eval [list format [_mc_trim $en_fmt]] $args] 343 } 344 return $msg 345} 346 347proc strcat {args} { 348 return [join $args {}] 349} 350 351::msgcat::mcload $oguimsg 352unset oguimsg 353 354###################################################################### 355## 356## On Mac, bring the current Wish process window to front 357 358if {[tk windowingsystem] eq "aqua"} { 359 catch { 360 safe_exec [list osascript -e [format { 361 tell application "System Events" 362 set frontmost of processes whose unix id is %d to true 363 end tell 364 } [pid]]] 365 } 366} 367 368###################################################################### 369## 370## read only globals 371 372set _appname {Git Gui} 373set _gitdir {} 374set _gitworktree {} 375set _isbare {} 376set _githtmldir {} 377set _reponame {} 378set _shellpath {@@SHELL_PATH@@} 379 380set _trace [lsearch -exact $argv --trace] 381if {$_trace >= 0} { 382 set argv [lreplace $argv $_trace $_trace] 383 set _trace 1 384 if {[tk windowingsystem] eq "win32"} { console show } 385} else { 386 set _trace 0 387} 388 389# variable for the last merged branch (useful for a default when deleting 390# branches). 391set _last_merged_branch {} 392 393# for testing, allow unconfigured _shellpath 394if {[string match @@* $_shellpath]} { 395 if {[info exists env(SHELL)]} { 396 set _shellpath $env(SHELL) 397 } else { 398 set _shellpath /bin/sh 399 } 400} 401 402if {[is_Windows]} { 403 set _shellpath [safe_exec [list cygpath -m $_shellpath]] 404} 405 406if {![file executable $_shellpath] || \ 407 !([file pathtype $_shellpath] eq {absolute})} { 408 set errmsg "The defined shell ('$_shellpath') is not usable, \ 409 it must be an absolute path to an executable." 410 puts stderr $errmsg 411 412 catch {wm withdraw .} 413 tk_messageBox \ 414 -icon error \ 415 -type ok \ 416 -title "git-gui: configuration error" \ 417 -message $errmsg 418 exit 1 419} 420 421 422proc shellpath {} { 423 global _shellpath 424 return $_shellpath 425} 426 427proc appname {} { 428 global _appname 429 return $_appname 430} 431 432proc gitdir {args} { 433 global _gitdir 434 if {$args eq {}} { 435 return $_gitdir 436 } 437 return [eval [list file join $_gitdir] $args] 438} 439 440proc githtmldir {args} { 441 global _githtmldir 442 if {$_githtmldir eq {}} { 443 if {[catch {set _githtmldir [git --html-path]}]} { 444 # Git not installed or option not yet supported 445 return {} 446 } 447 set _githtmldir [file normalize $_githtmldir] 448 } 449 if {$args eq {}} { 450 return $_githtmldir 451 } 452 return [eval [list file join $_githtmldir] $args] 453} 454 455proc reponame {} { 456 return $::_reponame 457} 458 459proc is_enabled {option} { 460 global enabled_options 461 if {[catch {set on $enabled_options($option)}]} {return 0} 462 return $on 463} 464 465proc enable_option {option} { 466 global enabled_options 467 set enabled_options($option) 1 468} 469 470proc disable_option {option} { 471 global enabled_options 472 set enabled_options($option) 0 473} 474 475###################################################################### 476## 477## config 478 479proc is_many_config {name} { 480 switch -glob -- $name { 481 gui.recentrepo - 482 remote.*.fetch - 483 remote.*.push 484 {return 1} 485 * 486 {return 0} 487 } 488} 489 490proc is_config_true {name} { 491 global repo_config 492 if {[catch {set v $repo_config($name)}]} { 493 return 0 494 } 495 set v [string tolower $v] 496 if {$v eq {} || $v eq {true} || $v eq {1} || $v eq {yes} || $v eq {on}} { 497 return 1 498 } else { 499 return 0 500 } 501} 502 503proc is_config_false {name} { 504 global repo_config 505 if {[catch {set v $repo_config($name)}]} { 506 return 0 507 } 508 set v [string tolower $v] 509 if {$v eq {false} || $v eq {0} || $v eq {no} || $v eq {off}} { 510 return 1 511 } else { 512 return 0 513 } 514} 515 516proc get_config {name} { 517 global repo_config 518 if {[catch {set v $repo_config($name)}]} { 519 return {} 520 } else { 521 return $v 522 } 523} 524 525proc is_bare {} { 526 global _isbare 527 global _gitdir 528 global _gitworktree 529 530 if {$_isbare eq {}} { 531 if {[catch { 532 set _bare [git rev-parse --is-bare-repository] 533 switch -- $_bare { 534 true { set _isbare 1 } 535 false { set _isbare 0} 536 default { throw } 537 } 538 }]} { 539 if {[is_config_true core.bare] 540 || ($_gitworktree eq {} 541 && [lindex [file split $_gitdir] end] ne {.git})} { 542 set _isbare 1 543 } else { 544 set _isbare 0 545 } 546 } 547 } 548 return $_isbare 549} 550 551###################################################################### 552## 553## handy utils 554 555proc _trace_exec {cmd} { 556 if {!$::_trace} return 557 set d {} 558 foreach v $cmd { 559 if {$d ne {}} { 560 append d { } 561 } 562 if {[regexp {[ \t\r\n'"$?*]} $v]} { 563 set v [sq $v] 564 } 565 append d $v 566 } 567 puts stderr $d 568} 569 570#'" fix poor old emacs font-lock mode 571 572# This is for use with textconv filters and uses sh -c "..." to allow it to 573# contain a command with arguments. We presume this 574# to be a shellscript that the configured shell (/bin/sh by default) knows 575# how to run. 576proc open_cmd_pipe {cmd path} { 577 set run [list [shellpath] -c "$cmd \"\$0\"" $path] 578 set run [make_arglist_safe $run] 579 return [open |$run r] 580} 581 582proc git {args} { 583 git_redir $args {} 584} 585 586proc git_redir {cmd redir} { 587 set fd [git_read $cmd $redir] 588 fconfigure $fd -encoding utf-8 589 set result [string trimright [read $fd] "\n"] 590 close $fd 591 if {$::_trace} { 592 puts stderr "< $result" 593 } 594 return $result 595} 596 597proc safe_open_command {cmd {redir {}}} { 598 set cmd [make_arglist_safe $cmd] 599 _trace_exec [concat $cmd $redir] 600 if {[catch { 601 set fd [open [concat [list | ] $cmd $redir] r] 602 } err]} { 603 error $err 604 } 605 return $fd 606} 607 608proc git_read {cmd {redir {}}} { 609 global _git 610 set cmdp [concat [list $_git] $cmd] 611 612 return [safe_open_command $cmdp $redir] 613} 614 615set _nice [list [_which nice]] 616if {[catch {safe_exec [list {*}$_nice git version]}]} { 617 set _nice {} 618} 619 620proc git_read_nice {cmd} { 621 set cmdp [list {*}$::_nice $::_git {*}$cmd] 622 return [safe_open_command $cmdp] 623} 624 625proc git_write {cmd} { 626 global _git 627 set cmd [make_arglist_safe $cmd] 628 set cmdp [concat [list $_git] $cmd] 629 630 _trace_exec $cmdp 631 return [open [concat [list | ] $cmdp] w] 632} 633 634proc githook_read {hook_name args} { 635 git_read [concat [list hook run --ignore-missing $hook_name --] $args] [list 2>@1] 636} 637 638proc kill_file_process {fd} { 639 set process [pid $fd] 640 641 catch { 642 if {[is_Windows]} { 643 safe_exec [list taskkill /pid $process] 644 } else { 645 safe_exec [list kill $process] 646 } 647 } 648} 649 650proc gitattr {path attr default} { 651 if {[catch {set r [git check-attr $attr -- $path]}]} { 652 set r unspecified 653 } else { 654 set r [join [lrange [split $r :] 2 end] :] 655 regsub {^ } $r {} r 656 } 657 if {$r eq {unspecified}} { 658 return $default 659 } 660 return $r 661} 662 663proc sq {value} { 664 regsub -all ' $value "'\\''" value 665 return "'$value'" 666} 667 668proc load_current_branch {} { 669 global current_branch is_detached 670 671 set current_branch [git branch --show-current] 672 set is_detached [expr [string length $current_branch] == 0] 673} 674 675auto_load tk_optionMenu 676rename tk_optionMenu real__tkOptionMenu 677proc tk_optionMenu {w varName args} { 678 set m [eval real__tkOptionMenu $w $varName $args] 679 $m configure -font font_ui 680 $w configure -font font_ui 681 return $m 682} 683 684proc rmsel_tag {text} { 685 $text tag conf sel \ 686 -background [$text cget -background] \ 687 -foreground [$text cget -foreground] \ 688 -borderwidth 0 689 bind $text <Motion> break 690 return $text 691} 692 693wm withdraw . 694set root_exists 0 695bind . <Visibility> { 696 bind . <Visibility> {} 697 set root_exists 1 698} 699 700if {[is_Windows]} { 701 wm iconbitmap . -default $oguilib/git-gui.ico 702 set ::tk::AlwaysShowSelection 1 703 bind . <Control-F2> {console show} 704 705 # Spoof an X11 display for SSH 706 if {![info exists env(DISPLAY)]} { 707 set env(DISPLAY) :9999 708 } 709} else { 710 catch { 711 image create photo gitlogo -width 16 -height 16 712 713 gitlogo put #33CC33 -to 7 0 9 2 714 gitlogo put #33CC33 -to 4 2 12 4 715 gitlogo put #33CC33 -to 7 4 9 6 716 gitlogo put #CC3333 -to 4 6 12 8 717 gitlogo put gray26 -to 4 9 6 10 718 gitlogo put gray26 -to 3 10 6 12 719 gitlogo put gray26 -to 8 9 13 11 720 gitlogo put gray26 -to 8 11 10 12 721 gitlogo put gray26 -to 11 11 13 14 722 gitlogo put gray26 -to 3 12 5 14 723 gitlogo put gray26 -to 5 13 724 gitlogo put gray26 -to 10 13 725 gitlogo put gray26 -to 4 14 12 15 726 gitlogo put gray26 -to 5 15 11 16 727 gitlogo redither 728 729 image create photo gitlogo32 -width 32 -height 32 730 gitlogo32 copy gitlogo -zoom 2 2 731 732 wm iconphoto . -default gitlogo gitlogo32 733 } 734} 735 736###################################################################### 737## 738## config defaults 739 740set cursor_ptr arrow 741font create font_ui 742if {[lsearch -exact [font names] TkDefaultFont] != -1} { 743 eval [linsert [font actual TkDefaultFont] 0 font configure font_ui] 744 eval [linsert [font actual TkFixedFont] 0 font create font_diff] 745} else { 746 font create font_diff -family Courier -size 10 747 catch { 748 label .dummy 749 eval font configure font_ui [font actual [.dummy cget -font]] 750 destroy .dummy 751 } 752} 753 754font create font_uiitalic 755font create font_uibold 756font create font_diffbold 757font create font_diffitalic 758 759foreach class {Button Checkbutton Entry Label 760 Labelframe Listbox Message 761 Radiobutton Spinbox Text} { 762 option add *$class.font font_ui 763} 764if {![is_MacOSX]} { 765 option add *Menu.font font_ui 766 option add *Entry.borderWidth 1 startupFile 767 option add *Entry.relief sunken startupFile 768 option add *RadioButton.anchor w startupFile 769} 770unset class 771 772if {[is_Windows] || [is_MacOSX]} { 773 option add *Menu.tearOff 0 774} 775 776if {[is_MacOSX]} { 777 set M1B M1 778 set M1T Cmd 779} else { 780 set M1B Control 781 set M1T Ctrl 782} 783 784proc bind_button3 {w cmd} { 785 bind $w <Any-Button-3> $cmd 786 if {[is_MacOSX]} { 787 # Mac OS X sends Button-2 on right click through three-button mouse, 788 # or through trackpad right-clicking (two-finger touch + click). 789 bind $w <Any-Button-2> $cmd 790 bind $w <Control-Button-1> $cmd 791 } 792} 793 794proc apply_config {} { 795 global repo_config font_descs 796 797 foreach option $font_descs { 798 set name [lindex $option 0] 799 set font [lindex $option 1] 800 if {[catch { 801 set need_weight 1 802 foreach {cn cv} $repo_config(gui.$name) { 803 if {$cn eq {-weight}} { 804 set need_weight 0 805 } 806 font configure $font $cn $cv 807 } 808 if {$need_weight} { 809 font configure $font -weight normal 810 } 811 } err]} { 812 error_popup [strcat [mc "Invalid font specified in %s:" "gui.$name"] "\n\n$err"] 813 } 814 foreach {cn cv} [font configure $font] { 815 font configure ${font}bold $cn $cv 816 font configure ${font}italic $cn $cv 817 } 818 font configure ${font}bold -weight bold 819 font configure ${font}italic -slant italic 820 } 821 822 bind [winfo class .] <<ThemeChanged>> [list InitTheme] 823 pave_toplevel . 824 color::sync_with_theme 825 826 global comment_string 827 set comment_string [get_config core.commentstring] 828 if {$comment_string eq {}} { 829 set comment_string [get_config core.commentchar] 830 } 831} 832 833set default_config(branch.autosetupmerge) true 834set default_config(merge.tool) {} 835set default_config(mergetool.keepbackup) true 836set default_config(merge.diffstat) true 837set default_config(merge.summary) false 838set default_config(merge.verbosity) 2 839set default_config(user.name) {} 840set default_config(user.email) {} 841set default_config(core.commentchar) "#" 842set default_config(core.commentstring) {} 843 844set default_config(gui.encoding) [encoding system] 845set default_config(gui.matchtrackingbranch) false 846set default_config(gui.textconv) true 847set default_config(gui.pruneduringfetch) false 848set default_config(gui.trustmtime) false 849set default_config(gui.fastcopyblame) false 850set default_config(gui.maxrecentrepo) 10 851set default_config(gui.copyblamethreshold) 40 852set default_config(gui.blamehistoryctx) 7 853set default_config(gui.diffcontext) 5 854set default_config(gui.diffopts) {} 855set default_config(gui.commitmsgwidth) 75 856set default_config(gui.newbranchtemplate) {} 857set default_config(gui.spellingdictionary) {} 858set default_config(gui.fontui) [font configure font_ui] 859set default_config(gui.fontdiff) [font configure font_diff] 860# TODO: this option should be added to the git-config documentation 861set default_config(gui.maxfilesdisplayed) 5000 862set default_config(gui.usettk) 1 863set default_config(gui.warndetachedcommit) 1 864set default_config(gui.tabsize) 8 865set font_descs { 866 {fontui font_ui {mc "Main Font"}} 867 {fontdiff font_diff {mc "Diff/Console Font"}} 868} 869set default_config(gui.stageuntracked) ask 870set default_config(gui.displayuntracked) true 871 872###################################################################### 873## 874## find git 875 876set _git [_which git] 877if {$_git eq {}} { 878 catch {wm withdraw .} 879 tk_messageBox \ 880 -icon error \ 881 -type ok \ 882 -title [mc "git-gui: fatal error"] \ 883 -message [mc "Cannot find git in PATH."] 884 exit 1 885} 886 887###################################################################### 888## 889## version check 890 891set MIN_GIT_VERSION 2.36 892 893if {[catch {set _git_version [git --version]} err]} { 894 catch {wm withdraw .} 895 tk_messageBox \ 896 -icon error \ 897 -type ok \ 898 -title [mc "git-gui: fatal error"] \ 899 -message "Cannot determine Git version: 900 901$err 902 903[appname] requires Git $MIN_GIT_VERSION or later." 904 exit 1 905} 906 907if {![regsub {^git version } $_git_version {} _git_version]} { 908 catch {wm withdraw .} 909 tk_messageBox \ 910 -icon error \ 911 -type ok \ 912 -title [mc "git-gui: fatal error"] \ 913 -message [strcat [mc "Cannot parse Git version string:"] "\n\n$_git_version"] 914 exit 1 915} 916 917proc get_trimmed_version {s} { 918 set r {} 919 foreach x [split $s -._] { 920 if {[string is integer -strict $x]} { 921 lappend r $x 922 } else { 923 break 924 } 925 } 926 return [join $r .] 927} 928set _real_git_version $_git_version 929set _git_version [get_trimmed_version $_git_version] 930 931if {[catch {set vcheck [package vcompare $_git_version $MIN_GIT_VERSION]}] || 932 [expr $vcheck < 0] } { 933 934 set msg1 [mc "Insufficient git version, require: "] 935 set msg2 [mc "git returned:"] 936 set message "$msg1 $MIN_GIT_VERSION\n$msg2 $_real_git_version" 937 catch {wm withdraw .} 938 tk_messageBox \ 939 -icon error \ 940 -type ok \ 941 -title [mc "git-gui: fatal error"] \ 942 -message $message 943 exit 1 944} 945unset _real_git_version 946 947###################################################################### 948## 949## configure our library 950 951set idx [file join $oguilib tclIndex] 952if {[catch {set fd [safe_open_file $idx r]} err]} { 953 catch {wm withdraw .} 954 tk_messageBox \ 955 -icon error \ 956 -type ok \ 957 -title [mc "git-gui: fatal error"] \ 958 -message $err 959 exit 1 960} 961if {[gets $fd] eq {# Autogenerated by git-gui Makefile}} { 962 set idx [list] 963 while {[gets $fd n] >= 0} { 964 if {$n ne {} && ![string match #* $n]} { 965 lappend idx $n 966 } 967 } 968} else { 969 set idx {} 970} 971close $fd 972 973if {$idx ne {}} { 974 set loaded [list] 975 foreach p $idx { 976 if {[lsearch -exact $loaded $p] >= 0} continue 977 source [file join $oguilib $p] 978 lappend loaded $p 979 } 980 unset loaded p 981} else { 982 set auto_path [concat [list $oguilib] $auto_path] 983} 984unset -nocomplain idx fd 985 986###################################################################### 987## 988## config file parsing 989 990proc _parse_config {arr_name args} { 991 upvar $arr_name arr 992 array unset arr 993 set buf {} 994 catch { 995 set fd_rc [git_read \ 996 [concat config \ 997 $args \ 998 --null --list]] 999 fconfigure $fd_rc -encoding utf-8 1000 set buf [read $fd_rc] 1001 close $fd_rc 1002 } 1003 foreach line [split $buf "\0"] { 1004 if {[regexp {^([^\n]+)\n(.*)$} $line line name value]} { 1005 if {[is_many_config $name]} { 1006 lappend arr($name) $value 1007 } else { 1008 set arr($name) $value 1009 } 1010 } elseif {[regexp {^([^\n]+)$} $line line name]} { 1011 # no value given, but interpreting them as 1012 # boolean will be handled as true 1013 set arr($name) {} 1014 } 1015 } 1016} 1017 1018proc load_config {include_global} { 1019 global repo_config global_config system_config default_config 1020 1021 if {$include_global} { 1022 _parse_config system_config --system 1023 _parse_config global_config --global 1024 } 1025 _parse_config repo_config 1026 1027 foreach name [array names default_config] { 1028 if {[catch {set v $system_config($name)}]} { 1029 set system_config($name) $default_config($name) 1030 } 1031 } 1032 foreach name [array names system_config] { 1033 if {[catch {set v $global_config($name)}]} { 1034 set global_config($name) $system_config($name) 1035 } 1036 if {[catch {set v $repo_config($name)}]} { 1037 set repo_config($name) $system_config($name) 1038 } 1039 } 1040} 1041 1042###################################################################### 1043## 1044## feature option selection 1045 1046if {[regexp {^git-(.+)$} [file tail $argv0] _junk subcommand]} { 1047 unset _junk 1048} else { 1049 set subcommand gui 1050} 1051if {$subcommand eq {gui.sh}} { 1052 set subcommand gui 1053} 1054if {$subcommand eq {gui} && [llength $argv] > 0} { 1055 set subcommand [lindex $argv 0] 1056 set argv [lrange $argv 1 end] 1057} 1058 1059enable_option multicommit 1060enable_option branch 1061enable_option transport 1062disable_option bare 1063 1064switch -- $subcommand { 1065browser - 1066blame { 1067 enable_option bare 1068 1069 disable_option multicommit 1070 disable_option branch 1071 disable_option transport 1072} 1073citool { 1074 enable_option singlecommit 1075 enable_option retcode 1076 1077 disable_option multicommit 1078 disable_option branch 1079 disable_option transport 1080 1081 while {[llength $argv] > 0} { 1082 set a [lindex $argv 0] 1083 switch -- $a { 1084 --amend { 1085 enable_option initialamend 1086 } 1087 --nocommit { 1088 enable_option nocommit 1089 enable_option nocommitmsg 1090 } 1091 --commitmsg { 1092 disable_option nocommitmsg 1093 } 1094 default { 1095 break 1096 } 1097 } 1098 1099 set argv [lrange $argv 1 end] 1100 } 1101} 1102} 1103 1104###################################################################### 1105## 1106## execution environment 1107 1108# Suggest our implementation of askpass, if none is set 1109set argv0dir [file dirname [file normalize $::argv0]] 1110if {![info exists env(SSH_ASKPASS)]} { 1111 set env(SSH_ASKPASS) [file join $argv0dir git-gui--askpass] 1112} 1113if {![info exists env(GIT_ASKPASS)]} { 1114 set env(GIT_ASKPASS) [file join $argv0dir git-gui--askpass] 1115} 1116if {![info exists env(GIT_ASK_YESNO)]} { 1117 set env(GIT_ASK_YESNO) [file join $argv0dir git-gui--askyesno] 1118} 1119unset argv0dir 1120 1121###################################################################### 1122## 1123## repository setup 1124 1125set picked 0 1126if {[catch { 1127 set _gitdir $env(GIT_DIR) 1128 set _prefix {} 1129 }] 1130 && [catch { 1131 # beware that from the .git dir this sets _gitdir to . 1132 # and _prefix to the empty string 1133 set _gitdir [git rev-parse --git-dir] 1134 set _prefix [git rev-parse --show-prefix] 1135 } err]} { 1136 load_config 1 1137 apply_config 1138 choose_repository::pick 1139 if {![file isdirectory $_gitdir]} { 1140 exit 1 1141 } 1142 set picked 1 1143} 1144 1145# Use object format as hash algorithm (either "sha1" or "sha256") 1146set hashalgorithm [git rev-parse --show-object-format] 1147if {$hashalgorithm eq "sha1"} { 1148 set hashlength 40 1149} elseif {$hashalgorithm eq "sha256"} { 1150 set hashlength 64 1151} else { 1152 puts stderr "Unknown hash algorithm: $hashalgorithm" 1153 exit 1 1154} 1155 1156# we expand the _gitdir when it's just a single dot (i.e. when we're being 1157# run from the .git dir itself) lest the routines to find the worktree 1158# get confused 1159if {$_gitdir eq "."} { 1160 set _gitdir [pwd] 1161} 1162 1163if {![file isdirectory $_gitdir]} { 1164 catch {wm withdraw .} 1165 error_popup [strcat [mc "Git directory not found:"] "\n\n$_gitdir"] 1166 exit 1 1167} 1168# _gitdir exists, so try loading the config 1169load_config 0 1170apply_config 1171 1172set _gitworktree [git rev-parse --show-toplevel] 1173 1174if {$_prefix ne {}} { 1175 if {$_gitworktree eq {}} { 1176 regsub -all {[^/]+/} $_prefix ../ cdup 1177 } else { 1178 set cdup $_gitworktree 1179 } 1180 if {[catch {cd $cdup} err]} { 1181 catch {wm withdraw .} 1182 error_popup [strcat [mc "Cannot move to top of working directory:"] "\n\n$err"] 1183 exit 1 1184 } 1185 set _gitworktree [pwd] 1186 unset cdup 1187} elseif {![is_enabled bare]} { 1188 if {[is_bare]} { 1189 catch {wm withdraw .} 1190 error_popup [strcat [mc "Cannot use bare repository:"] "\n\n$_gitdir"] 1191 exit 1 1192 } 1193 if {$_gitworktree eq {}} { 1194 set _gitworktree [file dirname $_gitdir] 1195 } 1196 if {[catch {cd $_gitworktree} err]} { 1197 catch {wm withdraw .} 1198 error_popup [strcat [mc "No working directory"] " $_gitworktree:\n\n$err"] 1199 exit 1 1200 } 1201 set _gitworktree [pwd] 1202} 1203set _reponame [file split [file normalize $_gitdir]] 1204if {[lindex $_reponame end] eq {.git}} { 1205 set _reponame [lindex $_reponame end-1] 1206} else { 1207 set _reponame [lindex $_reponame end] 1208} 1209 1210set env(GIT_DIR) $_gitdir 1211set env(GIT_WORK_TREE) $_gitworktree 1212 1213###################################################################### 1214## 1215## global init 1216 1217set current_diff_path {} 1218set current_diff_side {} 1219set diff_actions [list] 1220 1221set HEAD {} 1222set PARENT {} 1223set MERGE_HEAD [list] 1224set commit_type {} 1225set commit_type_is_amend 0 1226set empty_tree {} 1227set current_branch {} 1228set is_detached 0 1229set current_diff_path {} 1230set is_3way_diff 0 1231set is_submodule_diff 0 1232set is_conflict_diff 0 1233set last_revert {} 1234set last_revert_enc {} 1235 1236set nullid [string repeat 0 $hashlength] 1237set nullid2 "[string repeat 0 [expr $hashlength - 1]]1" 1238 1239###################################################################### 1240## 1241## task management 1242 1243set rescan_active 0 1244set diff_active 0 1245set last_clicked {} 1246 1247set disable_on_lock [list] 1248set index_lock_type none 1249 1250proc lock_index {type} { 1251 global index_lock_type disable_on_lock 1252 1253 if {$index_lock_type eq {none}} { 1254 set index_lock_type $type 1255 foreach w $disable_on_lock { 1256 uplevel #0 $w disabled 1257 } 1258 return 1 1259 } elseif {$index_lock_type eq "begin-$type"} { 1260 set index_lock_type $type 1261 return 1 1262 } 1263 return 0 1264} 1265 1266proc unlock_index {} { 1267 global index_lock_type disable_on_lock 1268 1269 set index_lock_type none 1270 foreach w $disable_on_lock { 1271 uplevel #0 $w normal 1272 } 1273} 1274 1275###################################################################### 1276## 1277## status 1278 1279proc repository_state {ctvar hdvar mhvar} { 1280 global current_branch 1281 upvar $ctvar ct $hdvar hd $mhvar mh 1282 1283 set mh [list] 1284 1285 load_current_branch 1286 if {[catch {set hd [git rev-parse --verify HEAD]}]} { 1287 set hd {} 1288 set ct initial 1289 return 1290 } 1291 1292 set merge_head [gitdir MERGE_HEAD] 1293 if {[file exists $merge_head]} { 1294 set ct merge 1295 set fd_mh [safe_open_file $merge_head r] 1296 while {[gets $fd_mh line] >= 0} { 1297 lappend mh $line 1298 } 1299 close $fd_mh 1300 return 1301 } 1302 1303 set ct normal 1304} 1305 1306proc PARENT {} { 1307 global PARENT empty_tree 1308 1309 set p [lindex $PARENT 0] 1310 if {$p ne {}} { 1311 return $p 1312 } 1313 if {$empty_tree eq {}} { 1314 set empty_tree [git_redir [list mktree] [list << {}]] 1315 } 1316 return $empty_tree 1317} 1318 1319proc force_amend {} { 1320 global commit_type_is_amend 1321 global HEAD PARENT MERGE_HEAD commit_type 1322 1323 repository_state newType newHEAD newMERGE_HEAD 1324 set HEAD $newHEAD 1325 set PARENT $newHEAD 1326 set MERGE_HEAD $newMERGE_HEAD 1327 set commit_type $newType 1328 1329 set commit_type_is_amend 1 1330 do_select_commit_type 1331} 1332 1333proc rescan {after {honor_trustmtime 1}} { 1334 global HEAD PARENT MERGE_HEAD commit_type 1335 global ui_index ui_workdir ui_comm 1336 global rescan_active file_states 1337 global repo_config 1338 1339 if {$rescan_active > 0 || ![lock_index read]} return 1340 1341 repository_state newType newHEAD newMERGE_HEAD 1342 if {[string match amend* $commit_type] 1343 && $newType eq {normal} 1344 && $newHEAD eq $HEAD} { 1345 } else { 1346 set HEAD $newHEAD 1347 set PARENT $newHEAD 1348 set MERGE_HEAD $newMERGE_HEAD 1349 set commit_type $newType 1350 } 1351 1352 array unset file_states 1353 1354 if {!$::GITGUI_BCK_exists && 1355 (![$ui_comm edit modified] 1356 || [string trim [$ui_comm get 0.0 end]] eq {})} { 1357 if {[string match amend* $commit_type]} { 1358 } elseif {[load_message GITGUI_MSG utf-8]} { 1359 } elseif {[run_prepare_commit_msg_hook]} { 1360 } elseif {[load_message MERGE_MSG]} { 1361 } elseif {[load_message SQUASH_MSG]} { 1362 } elseif {[load_message [get_config commit.template]]} { 1363 } 1364 $ui_comm edit reset 1365 $ui_comm edit modified false 1366 } 1367 1368 if {$honor_trustmtime && $repo_config(gui.trustmtime) eq {true}} { 1369 rescan_stage2 {} $after 1370 } else { 1371 set rescan_active 1 1372 ui_status [mc "Refreshing file status..."] 1373 set fd_rf [git_read [list update-index \ 1374 -q \ 1375 --unmerged \ 1376 --ignore-missing \ 1377 --refresh \ 1378 ]] 1379 fconfigure $fd_rf -blocking 0 -translation binary 1380 fileevent $fd_rf readable \ 1381 [list rescan_stage2 $fd_rf $after] 1382 } 1383} 1384 1385proc have_info_exclude {} { 1386 return [file readable [gitdir info exclude]] 1387} 1388 1389proc rescan_stage2 {fd after} { 1390 global rescan_active buf_rdi buf_rdf buf_rlo 1391 1392 if {$fd ne {}} { 1393 read $fd 1394 if {![eof $fd]} return 1395 close $fd 1396 } 1397 1398 set ls_others [list --exclude-standard] 1399 1400 set buf_rdi {} 1401 set buf_rdf {} 1402 set buf_rlo {} 1403 1404 set rescan_active 2 1405 ui_status [mc "Scanning for modified files ..."] 1406 set fd_di [git_read [list diff-index --cached --ignore-submodules=dirty -z [PARENT]]] 1407 set fd_df [git_read [list diff-files -z]] 1408 1409 fconfigure $fd_di -blocking 0 -translation binary 1410 fconfigure $fd_df -blocking 0 -translation binary 1411 1412 fileevent $fd_di readable [list read_diff_index $fd_di $after] 1413 fileevent $fd_df readable [list read_diff_files $fd_df $after] 1414 1415 if {[is_config_true gui.displayuntracked]} { 1416 set fd_lo [git_read [concat ls-files --others -z $ls_others]] 1417 fconfigure $fd_lo -blocking 0 -translation binary 1418 fileevent $fd_lo readable [list read_ls_others $fd_lo $after] 1419 incr rescan_active 1420 } 1421} 1422 1423proc load_message {file {encoding {}}} { 1424 global ui_comm 1425 1426 set f [gitdir $file] 1427 if {[file isfile $f]} { 1428 if {[catch {set fd [safe_open_file $f r]}]} { 1429 return 0 1430 } 1431 if {$encoding ne {}} { 1432 fconfigure $fd -encoding $encoding 1433 } 1434 set content [string trim [read $fd]] 1435 close $fd 1436 regsub -all -line {[ \r\t]+$} $content {} content 1437 $ui_comm delete 0.0 end 1438 $ui_comm insert end $content 1439 return 1 1440 } 1441 return 0 1442} 1443 1444proc run_prepare_commit_msg_hook {} { 1445 global pch_error 1446 1447 # prepare-commit-msg requires PREPARE_COMMIT_MSG exist. From git-gui 1448 # it will be .git/MERGE_MSG (merge), .git/SQUASH_MSG (squash), or an 1449 # empty file but existent file. 1450 1451 set fd_pcm [safe_open_file [gitdir PREPARE_COMMIT_MSG] a] 1452 1453 if {[file isfile [gitdir MERGE_MSG]]} { 1454 set pcm_source "merge" 1455 set fd_mm [safe_open_file [gitdir MERGE_MSG] r] 1456 fconfigure $fd_mm -encoding utf-8 1457 puts -nonewline $fd_pcm [read $fd_mm] 1458 close $fd_mm 1459 } elseif {[file isfile [gitdir SQUASH_MSG]]} { 1460 set pcm_source "squash" 1461 set fd_sm [safe_open_file [gitdir SQUASH_MSG] r] 1462 fconfigure $fd_sm -encoding utf-8 1463 puts -nonewline $fd_pcm [read $fd_sm] 1464 close $fd_sm 1465 } elseif {[file isfile [get_config commit.template]]} { 1466 set pcm_source "template" 1467 set fd_sm [safe_open_file [get_config commit.template] r] 1468 fconfigure $fd_sm -encoding utf-8 1469 puts -nonewline $fd_pcm [read $fd_sm] 1470 close $fd_sm 1471 } else { 1472 set pcm_source "" 1473 } 1474 1475 close $fd_pcm 1476 1477 set fd_ph [githook_read prepare-commit-msg \ 1478 [gitdir PREPARE_COMMIT_MSG] $pcm_source] 1479 if {$fd_ph eq {}} { 1480 catch {file delete [gitdir PREPARE_COMMIT_MSG]} 1481 return 0; 1482 } 1483 1484 ui_status [mc "Calling prepare-commit-msg hook..."] 1485 set pch_error {} 1486 1487 fconfigure $fd_ph -blocking 0 -translation binary 1488 fileevent $fd_ph readable \ 1489 [list prepare_commit_msg_hook_wait $fd_ph] 1490 1491 return 1; 1492} 1493 1494proc prepare_commit_msg_hook_wait {fd_ph} { 1495 global pch_error 1496 1497 append pch_error [read $fd_ph] 1498 fconfigure $fd_ph -blocking 1 1499 if {[eof $fd_ph]} { 1500 if {[catch {close $fd_ph}]} { 1501 ui_status [mc "Commit declined by prepare-commit-msg hook."] 1502 hook_failed_popup prepare-commit-msg $pch_error 1503 catch {file delete [gitdir PREPARE_COMMIT_MSG]} 1504 exit 1 1505 } else { 1506 load_message PREPARE_COMMIT_MSG 1507 } 1508 set pch_error {} 1509 catch {file delete [gitdir PREPARE_COMMIT_MSG]} 1510 return 1511 } 1512 fconfigure $fd_ph -blocking 0 1513 catch {file delete [gitdir PREPARE_COMMIT_MSG]} 1514} 1515 1516proc read_diff_index {fd after} { 1517 global buf_rdi 1518 1519 append buf_rdi [read $fd] 1520 set c 0 1521 set n [string length $buf_rdi] 1522 while {$c < $n} { 1523 set z1 [string first "\0" $buf_rdi $c] 1524 if {$z1 == -1} break 1525 incr z1 1526 set z2 [string first "\0" $buf_rdi $z1] 1527 if {$z2 == -1} break 1528 1529 incr c 1530 set i [split [string range $buf_rdi $c [expr {$z1 - 2}]] { }] 1531 set p [string range $buf_rdi $z1 [expr {$z2 - 1}]] 1532 merge_state \ 1533 [convertfrom utf-8 $p] \ 1534 [lindex $i 4]? \ 1535 [list [lindex $i 0] [lindex $i 2]] \ 1536 [list] 1537 set c $z2 1538 incr c 1539 } 1540 if {$c < $n} { 1541 set buf_rdi [string range $buf_rdi $c end] 1542 } else { 1543 set buf_rdi {} 1544 } 1545 1546 rescan_done $fd buf_rdi $after 1547} 1548 1549proc read_diff_files {fd after} { 1550 global buf_rdf 1551 1552 append buf_rdf [read $fd] 1553 set c 0 1554 set n [string length $buf_rdf] 1555 while {$c < $n} { 1556 set z1 [string first "\0" $buf_rdf $c] 1557 if {$z1 == -1} break 1558 incr z1 1559 set z2 [string first "\0" $buf_rdf $z1] 1560 if {$z2 == -1} break 1561 1562 incr c 1563 set i [split [string range $buf_rdf $c [expr {$z1 - 2}]] { }] 1564 set p [string range $buf_rdf $z1 [expr {$z2 - 1}]] 1565 merge_state \ 1566 [convertfrom utf-8 $p] \ 1567 ?[lindex $i 4] \ 1568 [list] \ 1569 [list [lindex $i 0] [lindex $i 2]] 1570 set c $z2 1571 incr c 1572 } 1573 if {$c < $n} { 1574 set buf_rdf [string range $buf_rdf $c end] 1575 } else { 1576 set buf_rdf {} 1577 } 1578 1579 rescan_done $fd buf_rdf $after 1580} 1581 1582proc read_ls_others {fd after} { 1583 global buf_rlo 1584 1585 append buf_rlo [read $fd] 1586 set pck [split $buf_rlo "\0"] 1587 set buf_rlo [lindex $pck end] 1588 foreach p [lrange $pck 0 end-1] { 1589 set p [convertfrom utf-8 $p] 1590 if {[string index $p end] eq {/}} { 1591 set p [string range $p 0 end-1] 1592 } 1593 merge_state $p ?O 1594 } 1595 rescan_done $fd buf_rlo $after 1596} 1597 1598proc rescan_done {fd buf after} { 1599 global rescan_active current_diff_path 1600 global file_states repo_config 1601 upvar $buf to_clear 1602 1603 if {![eof $fd]} return 1604 set to_clear {} 1605 close $fd 1606 if {[incr rescan_active -1] > 0} return 1607 1608 prune_selection 1609 unlock_index 1610 display_all_files 1611 if {$current_diff_path ne {}} { reshow_diff $after } 1612 if {$current_diff_path eq {}} { select_first_diff $after } 1613} 1614 1615proc prune_selection {} { 1616 global file_states selected_paths 1617 1618 foreach path [array names selected_paths] { 1619 if {[catch {set still_here $file_states($path)}]} { 1620 unset selected_paths($path) 1621 } 1622 } 1623} 1624 1625###################################################################### 1626## 1627## ui helpers 1628 1629proc mapicon {w state path} { 1630 global all_icons 1631 1632 if {[catch {set r $all_icons($state$w)}]} { 1633 puts "error: no icon for $w state={$state} $path" 1634 return file_plain 1635 } 1636 return $r 1637} 1638 1639proc mapdesc {state path} { 1640 global all_descs 1641 1642 if {[catch {set r $all_descs($state)}]} { 1643 puts "error: no desc for state={$state} $path" 1644 return $state 1645 } 1646 return $r 1647} 1648 1649proc ui_status {msg} { 1650 global main_status 1651 if {[info exists main_status]} { 1652 $main_status show $msg 1653 } 1654} 1655 1656proc ui_ready {} { 1657 global main_status 1658 if {[info exists main_status]} { 1659 $main_status show [mc "Ready."] 1660 } 1661} 1662 1663proc escape_path {path} { 1664 regsub -all {\\} $path "\\\\" path 1665 regsub -all "\n" $path "\\n" path 1666 return $path 1667} 1668 1669proc short_path {path} { 1670 return [escape_path [lindex [file split $path] end]] 1671} 1672 1673set next_icon_id 0 1674 1675proc merge_state {path new_state {head_info {}} {index_info {}}} { 1676 global file_states next_icon_id nullid 1677 1678 set s0 [string index $new_state 0] 1679 set s1 [string index $new_state 1] 1680 1681 if {[catch {set info $file_states($path)}]} { 1682 set state __ 1683 set icon n[incr next_icon_id] 1684 } else { 1685 set state [lindex $info 0] 1686 set icon [lindex $info 1] 1687 if {$head_info eq {}} {set head_info [lindex $info 2]} 1688 if {$index_info eq {}} {set index_info [lindex $info 3]} 1689 } 1690 1691 if {$s0 eq {?}} {set s0 [string index $state 0]} \ 1692 elseif {$s0 eq {_}} {set s0 _} 1693 1694 if {$s1 eq {?}} {set s1 [string index $state 1]} \ 1695 elseif {$s1 eq {_}} {set s1 _} 1696 1697 if {$s0 eq {A} && $s1 eq {_} && $head_info eq {}} { 1698 set head_info [list 0 $nullid] 1699 } elseif {$s0 ne {_} && [string index $state 0] eq {_} 1700 && $head_info eq {}} { 1701 set head_info $index_info 1702 } elseif {$s0 eq {_} && [string index $state 0] ne {_}} { 1703 set index_info $head_info 1704 set head_info {} 1705 } 1706 1707 set file_states($path) [list $s0$s1 $icon \ 1708 $head_info $index_info \ 1709 ] 1710 return $state 1711} 1712 1713proc display_file_helper {w path icon_name old_m new_m} { 1714 global file_lists 1715 1716 if {$new_m eq {_}} { 1717 set lno [lsearch -sorted -exact $file_lists($w) $path] 1718 if {$lno >= 0} { 1719 set file_lists($w) [lreplace $file_lists($w) $lno $lno] 1720 incr lno 1721 $w conf -state normal 1722 $w delete $lno.0 [expr {$lno + 1}].0 1723 $w conf -state disabled 1724 } 1725 } elseif {$old_m eq {_} && $new_m ne {_}} { 1726 lappend file_lists($w) $path 1727 set file_lists($w) [lsort -unique $file_lists($w)] 1728 set lno [lsearch -sorted -exact $file_lists($w) $path] 1729 incr lno 1730 $w conf -state normal 1731 $w image create $lno.0 \ 1732 -align center -padx 5 -pady 1 \ 1733 -name $icon_name \ 1734 -image [mapicon $w $new_m $path] 1735 $w insert $lno.1 "[escape_path $path]\n" 1736 $w conf -state disabled 1737 } elseif {$old_m ne $new_m} { 1738 $w conf -state normal 1739 $w image conf $icon_name -image [mapicon $w $new_m $path] 1740 $w conf -state disabled 1741 } 1742} 1743 1744proc display_file {path state} { 1745 global file_states selected_paths 1746 global ui_index ui_workdir 1747 1748 set old_m [merge_state $path $state] 1749 set s $file_states($path) 1750 set new_m [lindex $s 0] 1751 set icon_name [lindex $s 1] 1752 1753 set o [string index $old_m 0] 1754 set n [string index $new_m 0] 1755 if {$o eq {U}} { 1756 set o _ 1757 } 1758 if {$n eq {U}} { 1759 set n _ 1760 } 1761 display_file_helper $ui_index $path $icon_name $o $n 1762 1763 if {[string index $old_m 0] eq {U}} { 1764 set o U 1765 } else { 1766 set o [string index $old_m 1] 1767 } 1768 if {[string index $new_m 0] eq {U}} { 1769 set n U 1770 } else { 1771 set n [string index $new_m 1] 1772 } 1773 display_file_helper $ui_workdir $path $icon_name $o $n 1774 1775 if {$new_m eq {__}} { 1776 unset file_states($path) 1777 catch {unset selected_paths($path)} 1778 } 1779} 1780 1781proc display_all_files_helper {w path icon_name m} { 1782 global file_lists 1783 1784 lappend file_lists($w) $path 1785 set lno [expr {[lindex [split [$w index end] .] 0] - 1}] 1786 $w image create end \ 1787 -align center -padx 5 -pady 1 \ 1788 -name $icon_name \ 1789 -image [mapicon $w $m $path] 1790 $w insert end "[escape_path $path]\n" 1791} 1792 1793set files_warning 0 1794proc display_all_files {} { 1795 global ui_index ui_workdir 1796 global file_states file_lists 1797 global last_clicked 1798 global files_warning 1799 1800 $ui_index conf -state normal 1801 $ui_workdir conf -state normal 1802 1803 $ui_index delete 0.0 end 1804 $ui_workdir delete 0.0 end 1805 set last_clicked {} 1806 1807 set file_lists($ui_index) [list] 1808 set file_lists($ui_workdir) [list] 1809 1810 set to_display [lsort [array names file_states]] 1811 set display_limit [get_config gui.maxfilesdisplayed] 1812 set displayed 0 1813 foreach path $to_display { 1814 set s $file_states($path) 1815 set m [lindex $s 0] 1816 set icon_name [lindex $s 1] 1817 1818 if {$displayed > $display_limit && [string index $m 1] eq {O} } { 1819 if {!$files_warning} { 1820 # do not repeatedly warn: 1821 set files_warning 1 1822 info_popup [mc "Display limit (gui.maxfilesdisplayed = %s) reached, not showing all %s files." \ 1823 $display_limit [llength $to_display]] 1824 } 1825 continue 1826 } 1827 1828 set s [string index $m 0] 1829 if {$s ne {U} && $s ne {_}} { 1830 display_all_files_helper $ui_index $path \ 1831 $icon_name $s 1832 } 1833 1834 if {[string index $m 0] eq {U}} { 1835 set s U 1836 } else { 1837 set s [string index $m 1] 1838 } 1839 if {$s ne {_}} { 1840 display_all_files_helper $ui_workdir $path \ 1841 $icon_name $s 1842 incr displayed 1843 } 1844 } 1845 1846 $ui_index conf -state disabled 1847 $ui_workdir conf -state disabled 1848} 1849 1850###################################################################### 1851## 1852## icons 1853 1854set filemask { 1855#define mask_width 14 1856#define mask_height 15 1857static unsigned char mask_bits[] = { 1858 0xfe, 0x1f, 0xfe, 0x1f, 0xfe, 0x1f, 0xfe, 0x1f, 0xfe, 0x1f, 0xfe, 0x1f, 1859 0xfe, 0x1f, 0xfe, 0x1f, 0xfe, 0x1f, 0xfe, 0x1f, 0xfe, 0x1f, 0xfe, 0x1f, 1860 0xfe, 0x1f, 0xfe, 0x1f, 0xfe, 0x1f}; 1861} 1862 1863image create bitmap file_plain -background white -foreground black -data { 1864#define plain_width 14 1865#define plain_height 15 1866static unsigned char plain_bits[] = { 1867 0xfe, 0x01, 0x02, 0x03, 0x02, 0x05, 0x02, 0x09, 0x02, 0x1f, 0x02, 0x10, 1868 0x02, 0x10, 0x02, 0x10, 0x02, 0x10, 0x02, 0x10, 0x02, 0x10, 0x02, 0x10, 1869 0x02, 0x10, 0x02, 0x10, 0xfe, 0x1f}; 1870} -maskdata $filemask 1871 1872image create bitmap file_mod -background white -foreground blue -data { 1873#define mod_width 14 1874#define mod_height 15 1875static unsigned char mod_bits[] = { 1876 0xfe, 0x01, 0x02, 0x03, 0x7a, 0x05, 0x02, 0x09, 0x7a, 0x1f, 0x02, 0x10, 1877 0xfa, 0x17, 0x02, 0x10, 0xfa, 0x17, 0x02, 0x10, 0xfa, 0x17, 0x02, 0x10, 1878 0xfa, 0x17, 0x02, 0x10, 0xfe, 0x1f}; 1879} -maskdata $filemask 1880 1881image create bitmap file_fulltick -background white -foreground "#007000" -data { 1882#define file_fulltick_width 14 1883#define file_fulltick_height 15 1884static unsigned char file_fulltick_bits[] = { 1885 0xfe, 0x01, 0x02, 0x1a, 0x02, 0x0c, 0x02, 0x0c, 0x02, 0x16, 0x02, 0x16, 1886 0x02, 0x13, 0x00, 0x13, 0x86, 0x11, 0x8c, 0x11, 0xd8, 0x10, 0xf2, 0x10, 1887 0x62, 0x10, 0x02, 0x10, 0xfe, 0x1f}; 1888} -maskdata $filemask 1889 1890image create bitmap file_question -background white -foreground black -data { 1891#define file_question_width 14 1892#define file_question_height 15 1893static unsigned char file_question_bits[] = { 1894 0xfe, 0x01, 0x02, 0x02, 0xe2, 0x04, 0xf2, 0x09, 0x1a, 0x1b, 0x0a, 0x13, 1895 0x82, 0x11, 0xc2, 0x10, 0x62, 0x10, 0x62, 0x10, 0x02, 0x10, 0x62, 0x10, 1896 0x62, 0x10, 0x02, 0x10, 0xfe, 0x1f}; 1897} -maskdata $filemask 1898 1899image create bitmap file_removed -background white -foreground red -data { 1900#define file_removed_width 14 1901#define file_removed_height 15 1902static unsigned char file_removed_bits[] = { 1903 0xfe, 0x01, 0x02, 0x03, 0x02, 0x05, 0x02, 0x09, 0x02, 0x1f, 0x02, 0x10, 1904 0x1a, 0x16, 0x32, 0x13, 0xe2, 0x11, 0xc2, 0x10, 0xe2, 0x11, 0x32, 0x13, 1905 0x1a, 0x16, 0x02, 0x10, 0xfe, 0x1f}; 1906} -maskdata $filemask 1907 1908image create bitmap file_merge -background white -foreground blue -data { 1909#define file_merge_width 14 1910#define file_merge_height 15 1911static unsigned char file_merge_bits[] = { 1912 0xfe, 0x01, 0x02, 0x03, 0x62, 0x05, 0x62, 0x09, 0x62, 0x1f, 0x62, 0x10, 1913 0xfa, 0x11, 0xf2, 0x10, 0x62, 0x10, 0x02, 0x10, 0xfa, 0x17, 0x02, 0x10, 1914 0xfa, 0x17, 0x02, 0x10, 0xfe, 0x1f}; 1915} -maskdata $filemask 1916 1917image create bitmap file_statechange -background white -foreground green -data { 1918#define file_statechange_width 14 1919#define file_statechange_height 15 1920static unsigned char file_statechange_bits[] = { 1921 0xfe, 0x01, 0x02, 0x03, 0x02, 0x05, 0x02, 0x09, 0x02, 0x1f, 0x62, 0x10, 1922 0x62, 0x10, 0xba, 0x11, 0xba, 0x11, 0x62, 0x10, 0x62, 0x10, 0x02, 0x10, 1923 0x02, 0x10, 0x02, 0x10, 0xfe, 0x1f}; 1924} -maskdata $filemask 1925 1926set ui_index .vpane.files.index.list 1927set ui_workdir .vpane.files.workdir.list 1928 1929set all_icons(_$ui_index) file_plain 1930set all_icons(A$ui_index) file_plain 1931set all_icons(M$ui_index) file_fulltick 1932set all_icons(D$ui_index) file_removed 1933set all_icons(U$ui_index) file_merge 1934set all_icons(T$ui_index) file_statechange 1935 1936set all_icons(_$ui_workdir) file_plain 1937set all_icons(M$ui_workdir) file_mod 1938set all_icons(D$ui_workdir) file_question 1939set all_icons(U$ui_workdir) file_merge 1940set all_icons(O$ui_workdir) file_plain 1941set all_icons(T$ui_workdir) file_statechange 1942 1943set max_status_desc 0 1944foreach i { 1945 {__ {mc "Unmodified"}} 1946 1947 {_M {mc "Modified, not staged"}} 1948 {M_ {mc "Staged for commit"}} 1949 {MM {mc "Portions staged for commit"}} 1950 {MD {mc "Staged for commit, missing"}} 1951 1952 {_T {mc "File type changed, not staged"}} 1953 {MT {mc "File type changed, old type staged for commit"}} 1954 {AT {mc "File type changed, old type staged for commit"}} 1955 {T_ {mc "File type changed, staged"}} 1956 {TM {mc "File type change staged, modification not staged"}} 1957 {TD {mc "File type change staged, file missing"}} 1958 1959 {_O {mc "Untracked, not staged"}} 1960 {A_ {mc "Staged for commit"}} 1961 {AM {mc "Portions staged for commit"}} 1962 {AD {mc "Staged for commit, missing"}} 1963 1964 {_D {mc "Missing"}} 1965 {D_ {mc "Staged for removal"}} 1966 {DO {mc "Staged for removal, still present"}} 1967 1968 {_U {mc "Requires merge resolution"}} 1969 {U_ {mc "Requires merge resolution"}} 1970 {UU {mc "Requires merge resolution"}} 1971 {UM {mc "Requires merge resolution"}} 1972 {UD {mc "Requires merge resolution"}} 1973 {UT {mc "Requires merge resolution"}} 1974 } { 1975 set text [eval [lindex $i 1]] 1976 if {$max_status_desc < [string length $text]} { 1977 set max_status_desc [string length $text] 1978 } 1979 set all_descs([lindex $i 0]) $text 1980} 1981unset i 1982 1983###################################################################### 1984## 1985## util 1986 1987proc scrollbar2many {list mode args} { 1988 foreach w $list {eval $w $mode $args} 1989} 1990 1991proc many2scrollbar {list mode sb top bottom} { 1992 $sb set $top $bottom 1993 foreach w $list {$w $mode moveto $top} 1994} 1995 1996proc incr_font_size {font {amt 1}} { 1997 set sz [font configure $font -size] 1998 incr sz $amt 1999 font configure $font -size $sz 2000 font configure ${font}bold -size $sz 2001 font configure ${font}italic -size $sz 2002} 2003 2004###################################################################### 2005## 2006## ui commands 2007 2008proc do_gitk {revs {is_submodule false}} { 2009 global current_diff_path file_states current_diff_side ui_index 2010 global _gitdir _gitworktree 2011 2012 # -- Always start gitk through whatever we were loaded with. This 2013 # lets us bypass using shell process on Windows systems. 2014 # 2015 set exe [_which gitk -script] 2016 set cmd [list [info nameofexecutable] $exe] 2017 if {$exe eq {}} { 2018 error_popup [mc "Couldn't find gitk in PATH"] 2019 } else { 2020 global env 2021 2022 set pwd [pwd] 2023 2024 if {!$is_submodule} { 2025 if {![is_bare]} { 2026 cd $_gitworktree 2027 } 2028 } else { 2029 cd $current_diff_path 2030 if {$revs eq {--}} { 2031 set s $file_states($current_diff_path) 2032 set old_sha1 {} 2033 set new_sha1 {} 2034 switch -glob -- [lindex $s 0] { 2035 M_ { set old_sha1 [lindex [lindex $s 2] 1] } 2036 _M { set old_sha1 [lindex [lindex $s 3] 1] } 2037 MM { 2038 if {$current_diff_side eq $ui_index} { 2039 set old_sha1 [lindex [lindex $s 2] 1] 2040 set new_sha1 [lindex [lindex $s 3] 1] 2041 } else { 2042 set old_sha1 [lindex [lindex $s 3] 1] 2043 } 2044 } 2045 } 2046 set revs $old_sha1...$new_sha1 2047 } 2048 # GIT_DIR and GIT_WORK_TREE for the submodule are not the ones 2049 # we've been using for the main repository, so unset them. 2050 # TODO we could make life easier (start up faster?) for gitk 2051 # by setting these to the appropriate values to allow gitk 2052 # to skip the heuristics to find their proper value 2053 unset env(GIT_DIR) 2054 unset env(GIT_WORK_TREE) 2055 } 2056 safe_exec_bg [concat $cmd $revs "--" "--"] 2057 2058 set env(GIT_DIR) $_gitdir 2059 set env(GIT_WORK_TREE) $_gitworktree 2060 cd $pwd 2061 2062 if {[info exists main_status]} { 2063 set status_operation [$::main_status \ 2064 start \ 2065 [mc "Starting %s... please wait..." "gitk"]] 2066 2067 after 3500 [list $status_operation stop] 2068 } 2069 } 2070} 2071 2072proc do_git_gui {} { 2073 global current_diff_path 2074 2075 # -- Always start git gui through whatever we were loaded with. This 2076 # lets us bypass using shell process on Windows systems. 2077 # 2078 set exe [list [_which git]] 2079 if {$exe eq {}} { 2080 error_popup [mc "Couldn't find git gui in PATH"] 2081 } else { 2082 global env 2083 global _gitdir _gitworktree 2084 2085 # see note in do_gitk about unsetting these vars when 2086 # running tools in a submodule 2087 unset env(GIT_DIR) 2088 unset env(GIT_WORK_TREE) 2089 2090 set pwd [pwd] 2091 cd $current_diff_path 2092 2093 safe_exec_bg [concat $exe gui] 2094 2095 set env(GIT_DIR) $_gitdir 2096 set env(GIT_WORK_TREE) $_gitworktree 2097 cd $pwd 2098 2099 set status_operation [$::main_status \ 2100 start \ 2101 [mc "Starting %s... please wait..." "git-gui"]] 2102 2103 after 3500 [list $status_operation stop] 2104 } 2105} 2106 2107# Get the system-specific explorer app/command. 2108proc get_explorer {} { 2109 if {[is_Cygwin]} { 2110 set explorer "/bin/cygstart.exe --explore" 2111 } elseif {[is_Windows]} { 2112 set explorer "explorer.exe" 2113 } elseif {[is_MacOSX]} { 2114 set explorer "open" 2115 } else { 2116 # freedesktop.org-conforming system is our best shot 2117 set explorer "xdg-open" 2118 } 2119 return $explorer 2120} 2121 2122proc do_explore {} { 2123 global _gitworktree 2124 set cmd [get_explorer] 2125 lappend cmd [file nativename $_gitworktree] 2126 safe_exec_bg $cmd 2127} 2128 2129# Open file relative to the working tree by the default associated app. 2130proc do_file_open {file} { 2131 global _gitworktree 2132 set cmd [get_explorer] 2133 set full_file_path [file join $_gitworktree $file] 2134 lappend cmd [file nativename $full_file_path] 2135 safe_exec_bg $cmd 2136} 2137 2138set is_quitting 0 2139set ret_code 1 2140 2141proc terminate_me {win} { 2142 global ret_code 2143 if {$win ne {.}} return 2144 exit $ret_code 2145} 2146 2147proc do_quit {{rc {1}}} { 2148 global ui_comm is_quitting repo_config commit_type 2149 global GITGUI_BCK_exists GITGUI_BCK_i 2150 global ui_comm_spell 2151 global ret_code 2152 2153 if {$is_quitting} return 2154 set is_quitting 1 2155 2156 if {[winfo exists $ui_comm]} { 2157 # -- Stash our current commit buffer. 2158 # 2159 set save [gitdir GITGUI_MSG] 2160 if {$GITGUI_BCK_exists && ![$ui_comm edit modified]} { 2161 catch { file rename -force [gitdir GITGUI_BCK] $save } 2162 set GITGUI_BCK_exists 0 2163 } elseif {[$ui_comm edit modified]} { 2164 set msg [string trim [$ui_comm get 0.0 end]] 2165 regsub -all -line {[ \r\t]+$} $msg {} msg 2166 if {![string match amend* $commit_type] 2167 && $msg ne {}} { 2168 catch { 2169 set fd [safe_open_file $save w] 2170 fconfigure $fd -encoding utf-8 2171 puts -nonewline $fd $msg 2172 close $fd 2173 } 2174 } else { 2175 catch {file delete $save} 2176 } 2177 } 2178 2179 # -- Cancel our spellchecker if its running. 2180 # 2181 if {[info exists ui_comm_spell]} { 2182 $ui_comm_spell stop 2183 } 2184 2185 # -- Remove our editor backup, its not needed. 2186 # 2187 after cancel $GITGUI_BCK_i 2188 if {$GITGUI_BCK_exists} { 2189 catch {file delete [gitdir GITGUI_BCK]} 2190 } 2191 2192 # -- Stash our current window geometry into this repository. 2193 # 2194 set cfg_wmstate [wm state .] 2195 if {[catch {set rc_wmstate $repo_config(gui.wmstate)}]} { 2196 set rc_wmstate {} 2197 } 2198 if {$cfg_wmstate ne $rc_wmstate} { 2199 catch {git config gui.wmstate $cfg_wmstate} 2200 } 2201 if {$cfg_wmstate eq {zoomed}} { 2202 # on Windows wm geometry will lie about window 2203 # position (but not size) when window is zoomed 2204 # restore the window before querying wm geometry 2205 wm state . normal 2206 } 2207 set cfg_geometry [list] 2208 lappend cfg_geometry [wm geometry .] 2209 lappend cfg_geometry [.vpane sashpos 0] 2210 lappend cfg_geometry [.vpane.files sashpos 0] 2211 if {[catch {set rc_geometry $repo_config(gui.geometry)}]} { 2212 set rc_geometry {} 2213 } 2214 if {$cfg_geometry ne $rc_geometry} { 2215 catch {git config gui.geometry $cfg_geometry} 2216 } 2217 } 2218 2219 set ret_code $rc 2220 2221 # Briefly enable send again, working around Tk bug 2222 # https://sourceforge.net/p/tktoolkit/bugs/2343/ 2223 tk appname [appname] 2224 2225 destroy . 2226} 2227 2228proc do_rescan {} { 2229 rescan ui_ready 2230} 2231 2232proc ui_do_rescan {} { 2233 rescan {force_first_diff ui_ready} 2234} 2235 2236proc do_commit {} { 2237 commit_tree 2238} 2239 2240proc next_diff {{after {}}} { 2241 global next_diff_p next_diff_w next_diff_i 2242 show_diff $next_diff_p $next_diff_w {} {} $after 2243} 2244 2245proc find_anchor_pos {lst name} { 2246 set lid [lsearch -sorted -exact $lst $name] 2247 2248 if {$lid == -1} { 2249 set lid 0 2250 foreach lname $lst { 2251 if {$lname >= $name} break 2252 incr lid 2253 } 2254 } 2255 2256 return $lid 2257} 2258 2259proc find_file_from {flist idx delta path mmask} { 2260 global file_states 2261 2262 set len [llength $flist] 2263 while {$idx >= 0 && $idx < $len} { 2264 set name [lindex $flist $idx] 2265 2266 if {$name ne $path && [info exists file_states($name)]} { 2267 set state [lindex $file_states($name) 0] 2268 2269 if {$mmask eq {} || [regexp $mmask $state]} { 2270 return $idx 2271 } 2272 } 2273 2274 incr idx $delta 2275 } 2276 2277 return {} 2278} 2279 2280proc find_next_diff {w path {lno {}} {mmask {}}} { 2281 global next_diff_p next_diff_w next_diff_i 2282 global file_lists ui_index ui_workdir 2283 2284 set flist $file_lists($w) 2285 if {$lno eq {}} { 2286 set lno [find_anchor_pos $flist $path] 2287 } else { 2288 incr lno -1 2289 } 2290 2291 if {$mmask ne {} && ![regexp {(^\^)|(\$$)} $mmask]} { 2292 if {$w eq $ui_index} { 2293 set mmask "^$mmask" 2294 } else { 2295 set mmask "$mmask\$" 2296 } 2297 } 2298 2299 set idx [find_file_from $flist $lno 1 $path $mmask] 2300 if {$idx eq {}} { 2301 incr lno -1 2302 set idx [find_file_from $flist $lno -1 $path $mmask] 2303 } 2304 2305 if {$idx ne {}} { 2306 set next_diff_w $w 2307 set next_diff_p [lindex $flist $idx] 2308 set next_diff_i [expr {$idx+1}] 2309 return 1 2310 } else { 2311 return 0 2312 } 2313} 2314 2315proc next_diff_after_action {w path {lno {}} {mmask {}}} { 2316 global current_diff_path 2317 2318 if {$path ne $current_diff_path} { 2319 return {} 2320 } elseif {[find_next_diff $w $path $lno $mmask]} { 2321 return {next_diff;} 2322 } else { 2323 return {reshow_diff;} 2324 } 2325} 2326 2327proc select_first_diff {after} { 2328 global ui_workdir 2329 2330 if {[find_next_diff $ui_workdir {} 1 {^_?U}] || 2331 [find_next_diff $ui_workdir {} 1 {[^O]$}]} { 2332 next_diff $after 2333 } else { 2334 uplevel #0 $after 2335 } 2336} 2337 2338proc force_first_diff {after} { 2339 global ui_workdir current_diff_path file_states 2340 2341 if {[info exists file_states($current_diff_path)]} { 2342 set state [lindex $file_states($current_diff_path) 0] 2343 } else { 2344 set state {OO} 2345 } 2346 2347 set reselect 0 2348 if {[string first {U} $state] >= 0} { 2349 # Already a conflict, do nothing 2350 } elseif {[find_next_diff $ui_workdir $current_diff_path {} {^_?U}]} { 2351 set reselect 1 2352 } elseif {[string index $state 1] ne {O}} { 2353 # Already a diff & no conflicts, do nothing 2354 } elseif {[find_next_diff $ui_workdir $current_diff_path {} {[^O]$}]} { 2355 set reselect 1 2356 } 2357 2358 if {$reselect} { 2359 next_diff $after 2360 } else { 2361 uplevel #0 $after 2362 } 2363} 2364 2365proc toggle_or_diff {mode w args} { 2366 global file_states file_lists current_diff_path ui_index ui_workdir 2367 global last_clicked selected_paths file_lists_last_clicked 2368 2369 if {$mode eq "click"} { 2370 foreach {x y} $args break 2371 set pos [split [$w index @$x,$y] .] 2372 foreach {lno col} $pos break 2373 } else { 2374 if {$mode eq "toggle"} { 2375 if {$w eq $ui_workdir} { 2376 do_add_selection 2377 set last_clicked {} 2378 return 2379 } 2380 if {$w eq $ui_index} { 2381 do_unstage_selection 2382 set last_clicked {} 2383 return 2384 } 2385 } 2386 2387 if {$last_clicked ne {}} { 2388 set lno [lindex $last_clicked 1] 2389 } else { 2390 if {![info exists file_lists] 2391 || ![info exists file_lists($w)] 2392 || [llength $file_lists($w)] == 0} { 2393 set last_clicked {} 2394 return 2395 } 2396 set lno [expr {int([lindex [$w tag ranges in_diff] 0])}] 2397 } 2398 if {$mode eq "toggle"} { 2399 set col 0; set y 2 2400 } else { 2401 incr lno [expr {$mode eq "up" ? -1 : 1}] 2402 set col 1 2403 } 2404 } 2405 2406 if {![info exists file_lists] 2407 || ![info exists file_lists($w)] 2408 || [llength $file_lists($w)] < $lno - 1} { 2409 set path {} 2410 } else { 2411 set path [lindex $file_lists($w) [expr {$lno - 1}]] 2412 } 2413 if {$path eq {}} { 2414 set last_clicked {} 2415 return 2416 } 2417 2418 set last_clicked [list $w $lno] 2419 focus $w 2420 array unset selected_paths 2421 $ui_index tag remove in_sel 0.0 end 2422 $ui_workdir tag remove in_sel 0.0 end 2423 2424 set file_lists_last_clicked($w) $path 2425 2426 # Determine the state of the file 2427 if {[info exists file_states($path)]} { 2428 set state [lindex $file_states($path) 0] 2429 } else { 2430 set state {__} 2431 } 2432 2433 # Restage the file, or simply show the diff 2434 if {$col == 0 && $y > 1} { 2435 # Conflicts need special handling 2436 if {[string first {U} $state] >= 0} { 2437 # $w must always be $ui_workdir, but... 2438 if {$w ne $ui_workdir} { set lno {} } 2439 merge_stage_workdir $path $lno 2440 return 2441 } 2442 2443 if {[string index $state 1] eq {O}} { 2444 set mmask {} 2445 } else { 2446 set mmask {[^O]} 2447 } 2448 2449 set after [next_diff_after_action $w $path $lno $mmask] 2450 2451 if {$w eq $ui_index} { 2452 update_indexinfo \ 2453 "Unstaging [short_path $path] from commit" \ 2454 [list $path] \ 2455 [concat $after {ui_ready;}] 2456 } elseif {$w eq $ui_workdir} { 2457 update_index \ 2458 "Adding [short_path $path]" \ 2459 [list $path] \ 2460 [concat $after {ui_ready;}] 2461 } 2462 } else { 2463 set selected_paths($path) 1 2464 show_diff $path $w $lno 2465 } 2466} 2467 2468proc add_one_to_selection {w x y} { 2469 global file_lists last_clicked selected_paths 2470 2471 set lno [lindex [split [$w index @$x,$y] .] 0] 2472 set path [lindex $file_lists($w) [expr {$lno - 1}]] 2473 if {$path eq {}} { 2474 set last_clicked {} 2475 return 2476 } 2477 2478 if {$last_clicked ne {} 2479 && [lindex $last_clicked 0] ne $w} { 2480 array unset selected_paths 2481 [lindex $last_clicked 0] tag remove in_sel 0.0 end 2482 } 2483 2484 set last_clicked [list $w $lno] 2485 if {[catch {set in_sel $selected_paths($path)}]} { 2486 set in_sel 0 2487 } 2488 if {$in_sel} { 2489 unset selected_paths($path) 2490 $w tag remove in_sel $lno.0 [expr {$lno + 1}].0 2491 } else { 2492 set selected_paths($path) 1 2493 $w tag add in_sel $lno.0 [expr {$lno + 1}].0 2494 } 2495} 2496 2497proc add_range_to_selection {w x y} { 2498 global file_lists last_clicked selected_paths 2499 2500 if {[lindex $last_clicked 0] ne $w} { 2501 toggle_or_diff click $w $x $y 2502 return 2503 } 2504 2505 set lno [lindex [split [$w index @$x,$y] .] 0] 2506 set lc [lindex $last_clicked 1] 2507 if {$lc < $lno} { 2508 set begin $lc 2509 set end $lno 2510 } else { 2511 set begin $lno 2512 set end $lc 2513 } 2514 2515 foreach path [lrange $file_lists($w) \ 2516 [expr {$begin - 1}] \ 2517 [expr {$end - 1}]] { 2518 set selected_paths($path) 1 2519 } 2520 $w tag add in_sel $begin.0 [expr {$end + 1}].0 2521} 2522 2523proc show_more_context {} { 2524 global repo_config 2525 if {$repo_config(gui.diffcontext) < 99} { 2526 incr repo_config(gui.diffcontext) 2527 reshow_diff 2528 } 2529} 2530 2531proc show_less_context {} { 2532 global repo_config 2533 if {$repo_config(gui.diffcontext) > 1} { 2534 incr repo_config(gui.diffcontext) -1 2535 reshow_diff 2536 } 2537} 2538 2539proc focus_widget {widget} { 2540 global file_lists last_clicked selected_paths 2541 global file_lists_last_clicked 2542 2543 if {[llength $file_lists($widget)] > 0} { 2544 set path $file_lists_last_clicked($widget) 2545 set index [lsearch -sorted -exact $file_lists($widget) $path] 2546 if {$index < 0} { 2547 set index 0 2548 set path [lindex $file_lists($widget) $index] 2549 } 2550 2551 focus $widget 2552 set last_clicked [list $widget [expr $index + 1]] 2553 array unset selected_paths 2554 set selected_paths($path) 1 2555 show_diff $path $widget 2556 } 2557} 2558 2559proc toggle_commit_type {} { 2560 global commit_type_is_amend 2561 set commit_type_is_amend [expr !$commit_type_is_amend] 2562 do_select_commit_type 2563} 2564 2565###################################################################### 2566## 2567## ui construction 2568 2569set ui_comm {} 2570 2571# -- Menu Bar 2572# 2573menu .mbar -tearoff 0 2574if {[is_MacOSX]} { 2575 # -- Apple Menu (Mac OS X only) 2576 # 2577 .mbar add cascade -label Apple -menu .mbar.apple 2578 menu .mbar.apple 2579} 2580.mbar add cascade -label [mc Repository] -menu .mbar.repository 2581.mbar add cascade -label [mc Edit] -menu .mbar.edit 2582if {[is_enabled branch]} { 2583 .mbar add cascade -label [mc Branch] -menu .mbar.branch 2584} 2585if {[is_enabled multicommit] || [is_enabled singlecommit]} { 2586 .mbar add cascade -label [mc Commit@@noun] -menu .mbar.commit 2587} 2588if {[is_enabled transport]} { 2589 .mbar add cascade -label [mc Merge] -menu .mbar.merge 2590 .mbar add cascade -label [mc Remote] -menu .mbar.remote 2591} 2592if {[is_enabled multicommit] || [is_enabled singlecommit]} { 2593 .mbar add cascade -label [mc Tools] -menu .mbar.tools 2594} 2595 2596# -- Repository Menu 2597# 2598menu .mbar.repository 2599 2600if {![is_bare]} { 2601 .mbar.repository add command \ 2602 -label [mc "Explore Working Copy"] \ 2603 -command {do_explore} 2604} 2605 2606if {[is_Windows]} { 2607 # Use /git-bash.exe if available 2608 set _git_bash [safe_exec [list cygpath -m /git-bash.exe]] 2609 if {[file executable $_git_bash]} { 2610 set _bash_cmdline [list "Git Bash" $_git_bash] 2611 } else { 2612 set _bash_cmdline [list "Git Bash" bash --login -l] 2613 } 2614 .mbar.repository add command \ 2615 -label [mc "Git Bash"] \ 2616 -command {safe_exec_bg [concat [list [_which cmd] /c start] $_bash_cmdline]} 2617 unset _git_bash 2618} 2619 2620if {[is_Windows] || ![is_bare]} { 2621 .mbar.repository add separator 2622} 2623 2624.mbar.repository add command \ 2625 -label [mc "Browse Current Branch's Files"] \ 2626 -command {browser::new $current_branch} 2627set ui_browse_current [.mbar.repository index last] 2628.mbar.repository add command \ 2629 -label [mc "Browse Branch Files..."] \ 2630 -command browser_open::dialog 2631.mbar.repository add separator 2632 2633.mbar.repository add command \ 2634 -label [mc "Visualize Current Branch's History"] \ 2635 -command {do_gitk $current_branch} 2636set ui_visualize_current [.mbar.repository index last] 2637.mbar.repository add command \ 2638 -label [mc "Visualize All Branch History"] \ 2639 -command {do_gitk --all} 2640.mbar.repository add separator 2641 2642proc current_branch_write {args} { 2643 global current_branch 2644 .mbar.repository entryconf $::ui_browse_current \ 2645 -label [mc "Browse %s's Files" $current_branch] 2646 .mbar.repository entryconf $::ui_visualize_current \ 2647 -label [mc "Visualize %s's History" $current_branch] 2648} 2649trace add variable current_branch write current_branch_write 2650 2651if {[is_enabled multicommit]} { 2652 .mbar.repository add command -label [mc "Database Statistics"] \ 2653 -command do_stats 2654 2655 .mbar.repository add command -label [mc "Compress Database"] \ 2656 -command do_gc 2657 2658 .mbar.repository add command -label [mc "Verify Database"] \ 2659 -command do_fsck_objects 2660 2661 .mbar.repository add separator 2662 2663 if {[is_Cygwin]} { 2664 .mbar.repository add command \ 2665 -label [mc "Create Desktop Icon"] \ 2666 -command do_cygwin_shortcut 2667 } elseif {[is_Windows]} { 2668 .mbar.repository add command \ 2669 -label [mc "Create Desktop Icon"] \ 2670 -command do_windows_shortcut 2671 } elseif {[is_MacOSX]} { 2672 .mbar.repository add command \ 2673 -label [mc "Create Desktop Icon"] \ 2674 -command do_macosx_app 2675 } 2676} 2677 2678if {[is_MacOSX]} { 2679 proc ::tk::mac::Quit {args} { do_quit } 2680} else { 2681 .mbar.repository add command -label [mc Quit] \ 2682 -command do_quit \ 2683 -accelerator $M1T-Q 2684} 2685 2686# -- Edit Menu 2687# 2688menu .mbar.edit 2689.mbar.edit add command -label [mc Undo] \ 2690 -command {catch {[focus] edit undo}} \ 2691 -accelerator $M1T-Z 2692.mbar.edit add command -label [mc Redo] \ 2693 -command {catch {[focus] edit redo}} \ 2694 -accelerator $M1T-Y 2695.mbar.edit add separator 2696.mbar.edit add command -label [mc Cut] \ 2697 -command {catch {tk_textCut [focus]}} \ 2698 -accelerator $M1T-X 2699.mbar.edit add command -label [mc Copy] \ 2700 -command {catch {tk_textCopy [focus]}} \ 2701 -accelerator $M1T-C 2702.mbar.edit add command -label [mc Paste] \ 2703 -command {catch {tk_textPaste [focus]; [focus] see insert}} \ 2704 -accelerator $M1T-V 2705.mbar.edit add command -label [mc Delete] \ 2706 -command {catch {[focus] delete sel.first sel.last}} \ 2707 -accelerator Del 2708.mbar.edit add separator 2709.mbar.edit add command -label [mc "Select All"] \ 2710 -command {catch {[focus] tag add sel 0.0 end}} \ 2711 -accelerator $M1T-A 2712 2713# -- Branch Menu 2714# 2715if {[is_enabled branch]} { 2716 menu .mbar.branch 2717 2718 .mbar.branch add command -label [mc "Create..."] \ 2719 -command branch_create::dialog \ 2720 -accelerator $M1T-N 2721 lappend disable_on_lock [list .mbar.branch entryconf \ 2722 [.mbar.branch index last] -state] 2723 2724 .mbar.branch add command -label [mc "Checkout..."] \ 2725 -command branch_checkout::dialog \ 2726 -accelerator $M1T-O 2727 lappend disable_on_lock [list .mbar.branch entryconf \ 2728 [.mbar.branch index last] -state] 2729 2730 .mbar.branch add command -label [mc "Rename..."] \ 2731 -command branch_rename::dialog 2732 lappend disable_on_lock [list .mbar.branch entryconf \ 2733 [.mbar.branch index last] -state] 2734 2735 .mbar.branch add command -label [mc "Delete..."] \ 2736 -command branch_delete::dialog 2737 lappend disable_on_lock [list .mbar.branch entryconf \ 2738 [.mbar.branch index last] -state] 2739 2740 .mbar.branch add command -label [mc "Reset..."] \ 2741 -command merge::reset_hard 2742 lappend disable_on_lock [list .mbar.branch entryconf \ 2743 [.mbar.branch index last] -state] 2744} 2745 2746# -- Commit Menu 2747# 2748proc commit_btn_caption {} { 2749 if {[is_enabled nocommit]} { 2750 return [mc "Done"] 2751 } else { 2752 return [mc Commit@@verb] 2753 } 2754} 2755 2756if {[is_enabled multicommit] || [is_enabled singlecommit]} { 2757 menu .mbar.commit 2758 2759 if {![is_enabled nocommit]} { 2760 .mbar.commit add checkbutton \ 2761 -label [mc "Amend Last Commit"] \ 2762 -accelerator $M1T-E \ 2763 -variable commit_type_is_amend \ 2764 -command do_select_commit_type 2765 lappend disable_on_lock \ 2766 [list .mbar.commit entryconf [.mbar.commit index last] -state] 2767 2768 .mbar.commit add separator 2769 } 2770 2771 .mbar.commit add command -label [mc Rescan] \ 2772 -command ui_do_rescan \ 2773 -accelerator F5 2774 lappend disable_on_lock \ 2775 [list .mbar.commit entryconf [.mbar.commit index last] -state] 2776 2777 .mbar.commit add command -label [mc "Stage To Commit"] \ 2778 -command do_add_selection \ 2779 -accelerator $M1T-T 2780 lappend disable_on_lock \ 2781 [list .mbar.commit entryconf [.mbar.commit index last] -state] 2782 2783 .mbar.commit add command -label [mc "Stage Changed Files To Commit"] \ 2784 -command do_add_all \ 2785 -accelerator $M1T-I 2786 lappend disable_on_lock \ 2787 [list .mbar.commit entryconf [.mbar.commit index last] -state] 2788 2789 .mbar.commit add command -label [mc "Unstage From Commit"] \ 2790 -command do_unstage_selection \ 2791 -accelerator $M1T-U 2792 lappend disable_on_lock \ 2793 [list .mbar.commit entryconf [.mbar.commit index last] -state] 2794 2795 .mbar.commit add command -label [mc "Revert Changes"] \ 2796 -command do_revert_selection \ 2797 -accelerator $M1T-J 2798 lappend disable_on_lock \ 2799 [list .mbar.commit entryconf [.mbar.commit index last] -state] 2800 2801 .mbar.commit add separator 2802 2803 .mbar.commit add command -label [mc "Show Less Context"] \ 2804 -command show_less_context \ 2805 -accelerator $M1T-\- 2806 2807 .mbar.commit add command -label [mc "Show More Context"] \ 2808 -command show_more_context \ 2809 -accelerator $M1T-= 2810 2811 .mbar.commit add separator 2812 2813 if {![is_enabled nocommitmsg]} { 2814 .mbar.commit add command -label [mc "Sign Off"] \ 2815 -command do_signoff \ 2816 -accelerator $M1T-S 2817 } 2818 2819 .mbar.commit add command -label [commit_btn_caption] \ 2820 -command do_commit \ 2821 -accelerator $M1T-Return 2822 lappend disable_on_lock \ 2823 [list .mbar.commit entryconf [.mbar.commit index last] -state] 2824} 2825 2826# -- Merge Menu 2827# 2828if {[is_enabled branch]} { 2829 menu .mbar.merge 2830 .mbar.merge add command -label [mc "Local Merge..."] \ 2831 -command merge::dialog \ 2832 -accelerator $M1T-M 2833 lappend disable_on_lock \ 2834 [list .mbar.merge entryconf [.mbar.merge index last] -state] 2835 .mbar.merge add command -label [mc "Abort Merge..."] \ 2836 -command merge::reset_hard 2837 lappend disable_on_lock \ 2838 [list .mbar.merge entryconf [.mbar.merge index last] -state] 2839} 2840 2841# -- Transport Menu 2842# 2843if {[is_enabled transport]} { 2844 menu .mbar.remote 2845 2846 .mbar.remote add command \ 2847 -label [mc "Add..."] \ 2848 -command remote_add::dialog \ 2849 -accelerator $M1T-A 2850 .mbar.remote add command \ 2851 -label [mc "Push..."] \ 2852 -command do_push_anywhere \ 2853 -accelerator $M1T-P 2854 .mbar.remote add command \ 2855 -label [mc "Delete Branch..."] \ 2856 -command remote_branch_delete::dialog 2857} 2858 2859if {[is_MacOSX]} { 2860 proc ::tk::mac::ShowPreferences {} {do_options} 2861} else { 2862 # -- Edit Menu 2863 # 2864 .mbar.edit add separator 2865 .mbar.edit add command -label [mc "Options..."] \ 2866 -command do_options 2867} 2868 2869# -- Tools Menu 2870# 2871if {[is_enabled multicommit] || [is_enabled singlecommit]} { 2872 set tools_menubar .mbar.tools 2873 menu $tools_menubar 2874 $tools_menubar add separator 2875 $tools_menubar add command -label [mc "Add..."] -command tools_add::dialog 2876 $tools_menubar add command -label [mc "Remove..."] -command tools_remove::dialog 2877 set tools_tailcnt 3 2878 if {[array names repo_config guitool.*.cmd] ne {}} { 2879 tools_populate_all 2880 } 2881} 2882 2883# -- Help Menu 2884# 2885.mbar add cascade -label [mc Help] -menu .mbar.help 2886menu .mbar.help 2887 2888if {[is_MacOSX]} { 2889 .mbar.apple add command -label [mc "About %s" [appname]] \ 2890 -command do_about 2891 .mbar.apple add separator 2892} else { 2893 .mbar.help add command -label [mc "About %s" [appname]] \ 2894 -command do_about 2895} 2896. configure -menu .mbar 2897 2898set doc_path [githtmldir] 2899if {$doc_path ne {}} { 2900 set doc_path [file join $doc_path index.html] 2901} 2902 2903if {[file isfile $doc_path]} { 2904 set doc_url "file:$doc_path" 2905} else { 2906 set doc_url {https://www.kernel.org/pub/software/scm/git/docs/} 2907} 2908 2909proc start_browser {url} { 2910 git "web--browse" $url 2911} 2912 2913.mbar.help add command -label [mc "Online Documentation"] \ 2914 -command [list start_browser $doc_url] 2915 2916.mbar.help add command -label [mc "Show SSH Key"] \ 2917 -command do_ssh_key 2918 2919unset doc_path doc_url 2920 2921# -- Standard bindings 2922# 2923wm protocol . WM_DELETE_WINDOW do_quit 2924bind all <$M1B-Key-q> do_quit 2925bind all <$M1B-Key-Q> do_quit 2926 2927set m1b_w_script { 2928 set toplvl_win [winfo toplevel %W] 2929 2930 # If we are destroying the main window, we should call do_quit to take 2931 # care of cleanup before exiting the program. 2932 if {$toplvl_win eq "."} { 2933 do_quit 2934 } else { 2935 destroy $toplvl_win 2936 } 2937} 2938 2939bind all <$M1B-Key-w> $m1b_w_script 2940bind all <$M1B-Key-W> $m1b_w_script 2941 2942unset m1b_w_script 2943 2944set subcommand_args {} 2945proc usage {} { 2946 set s "[mc usage:] $::argv0 $::subcommand $::subcommand_args" 2947 if {[tk windowingsystem] eq "win32"} { 2948 wm withdraw . 2949 tk_messageBox -icon info -message $s \ 2950 -title [mc "Usage"] 2951 } else { 2952 puts stderr $s 2953 } 2954 exit 1 2955} 2956 2957proc normalize_relpath {path} { 2958 set elements {} 2959 foreach item [file split $path] { 2960 if {$item eq {.}} continue 2961 if {$item eq {..} && [llength $elements] > 0 2962 && [lindex $elements end] ne {..}} { 2963 set elements [lrange $elements 0 end-1] 2964 continue 2965 } 2966 lappend elements $item 2967 } 2968 return [eval file join $elements] 2969} 2970 2971# -- Not a normal commit type invocation? Do that instead! 2972# 2973switch -- $subcommand { 2974browser - 2975blame { 2976 if {$subcommand eq "blame"} { 2977 set subcommand_args {[--line=<num>] rev? path} 2978 } else { 2979 set subcommand_args {rev? path} 2980 } 2981 if {$argv eq {}} usage 2982 set head {} 2983 set path {} 2984 set jump_spec {} 2985 set is_path 0 2986 foreach a $argv { 2987 set p [file join $_prefix $a] 2988 2989 if {$is_path || [file exists $p]} { 2990 if {$path ne {}} usage 2991 set path [normalize_relpath $p] 2992 break 2993 } elseif {$a eq {--}} { 2994 if {$path ne {}} { 2995 if {$head ne {}} usage 2996 set head $path 2997 set path {} 2998 } 2999 set is_path 1 3000 } elseif {[regexp {^--line=(\d+)$} $a a lnum]} { 3001 if {$jump_spec ne {} || $head ne {}} usage 3002 set jump_spec [list $lnum] 3003 } elseif {$head eq {}} { 3004 if {$head ne {}} usage 3005 set head $a 3006 set is_path 1 3007 } else { 3008 usage 3009 } 3010 } 3011 unset is_path 3012 3013 if {$head ne {} && $path eq {}} { 3014 if {[string index $head 0] eq {/}} { 3015 set path [normalize_relpath $head] 3016 set head {} 3017 } else { 3018 set path [normalize_relpath $_prefix$head] 3019 set head {} 3020 } 3021 } 3022 3023 if {$head eq {}} { 3024 load_current_branch 3025 } else { 3026 if {[regexp [string map "@@ [expr $hashlength - 1]" {^[0-9a-f]{1,@@}$}] $head]} { 3027 if {[catch { 3028 set head [git rev-parse --verify $head] 3029 } err]} { 3030 if {[tk windowingsystem] eq "win32"} { 3031 tk_messageBox -icon error -title [mc Error] -message $err 3032 } else { 3033 puts stderr $err 3034 } 3035 exit 1 3036 } 3037 } 3038 set current_branch $head 3039 } 3040 3041 wm deiconify . 3042 switch -- $subcommand { 3043 browser { 3044 if {$jump_spec ne {}} usage 3045 if {$head eq {}} { 3046 if {$path ne {} && [file isdirectory $path]} { 3047 set head $current_branch 3048 } else { 3049 set head $path 3050 set path {} 3051 } 3052 } 3053 browser::new $head $path 3054 } 3055 blame { 3056 if {$head eq {} && ![file exists $path]} { 3057 catch {wm withdraw .} 3058 tk_messageBox \ 3059 -icon error \ 3060 -type ok \ 3061 -title [mc "git-gui: fatal error"] \ 3062 -message [mc "fatal: cannot stat path %s: No such file or directory" $path] 3063 exit 1 3064 } 3065 blame::new $head $path $jump_spec 3066 } 3067 } 3068 return 3069} 3070citool - 3071gui { 3072 if {[llength $argv] != 0} { 3073 usage 3074 } 3075 # fall through to setup UI for commits 3076} 3077default { 3078 set err "[mc usage:] $argv0 \[{blame|browser|citool}\]" 3079 if {[tk windowingsystem] eq "win32"} { 3080 wm withdraw . 3081 tk_messageBox -icon error -message $err \ 3082 -title [mc "Usage"] 3083 } else { 3084 puts stderr $err 3085 } 3086 exit 1 3087} 3088} 3089 3090# -- Branch Control 3091# 3092ttk::frame .branch 3093ttk::label .branch.l1 \ 3094 -text [mc "Current Branch:"] \ 3095 -anchor w \ 3096 -justify left 3097ttk::label .branch.cb \ 3098 -textvariable current_branch \ 3099 -anchor w \ 3100 -justify left 3101pack .branch.l1 -side left 3102pack .branch.cb -side left -fill x 3103pack .branch -side top -fill x 3104 3105# -- Main Window Layout 3106# 3107ttk::panedwindow .vpane -orient horizontal 3108ttk::panedwindow .vpane.files -orient vertical 3109.vpane add .vpane.files 3110pack .vpane -anchor n -side top -fill both -expand 1 3111 3112# -- Working Directory File List 3113 3114textframe .vpane.files.workdir -height 100 -width 200 3115tlabel .vpane.files.workdir.title -text [mc "Unstaged Changes"] \ 3116 -background lightsalmon -foreground black 3117ttext $ui_workdir \ 3118 -borderwidth 0 \ 3119 -width 20 -height 10 \ 3120 -wrap none \ 3121 -takefocus 1 -highlightthickness 1\ 3122 -cursor $cursor_ptr \ 3123 -xscrollcommand {.vpane.files.workdir.sx set} \ 3124 -yscrollcommand {.vpane.files.workdir.sy set} \ 3125 -state disabled 3126ttk::scrollbar .vpane.files.workdir.sx -orient h -command [list $ui_workdir xview] 3127ttk::scrollbar .vpane.files.workdir.sy -orient v -command [list $ui_workdir yview] 3128pack .vpane.files.workdir.title -side top -fill x 3129pack .vpane.files.workdir.sx -side bottom -fill x 3130pack .vpane.files.workdir.sy -side right -fill y 3131pack $ui_workdir -side left -fill both -expand 1 3132 3133# -- Index File List 3134# 3135textframe .vpane.files.index -height 100 -width 200 3136tlabel .vpane.files.index.title \ 3137 -text [mc "Staged Changes (Will Commit)"] \ 3138 -background lightgreen -foreground black 3139ttext $ui_index \ 3140 -borderwidth 0 \ 3141 -width 20 -height 10 \ 3142 -wrap none \ 3143 -takefocus 1 -highlightthickness 1\ 3144 -cursor $cursor_ptr \ 3145 -xscrollcommand {.vpane.files.index.sx set} \ 3146 -yscrollcommand {.vpane.files.index.sy set} \ 3147 -state disabled 3148ttk::scrollbar .vpane.files.index.sx -orient h -command [list $ui_index xview] 3149ttk::scrollbar .vpane.files.index.sy -orient v -command [list $ui_index yview] 3150pack .vpane.files.index.title -side top -fill x 3151pack .vpane.files.index.sx -side bottom -fill x 3152pack .vpane.files.index.sy -side right -fill y 3153pack $ui_index -side left -fill both -expand 1 3154 3155# -- Insert the workdir and index into the panes 3156# 3157.vpane.files add .vpane.files.workdir 3158.vpane.files add .vpane.files.index 3159 3160proc set_selection_colors {w has_focus} { 3161 foreach tag [list in_diff in_sel] { 3162 $w tag conf $tag \ 3163 -background [expr {$has_focus ? $color::select_bg : $color::inactive_select_bg}] \ 3164 -foreground [expr {$has_focus ? $color::select_fg : $color::inactive_select_fg}] 3165 } 3166} 3167 3168foreach i [list $ui_index $ui_workdir] { 3169 rmsel_tag $i 3170 3171 set_selection_colors $i 0 3172 bind $i <FocusIn> { set_selection_colors %W 1 } 3173 bind $i <FocusOut> { set_selection_colors %W 0 } 3174} 3175unset i 3176 3177# -- Diff and Commit Area 3178# 3179ttk::panedwindow .vpane.lower -orient vertical 3180ttk::frame .vpane.lower.commarea 3181ttk::frame .vpane.lower.diff -relief sunken -borderwidth 1 -height 500 3182.vpane.lower add .vpane.lower.diff 3183.vpane.lower add .vpane.lower.commarea 3184.vpane add .vpane.lower 3185.vpane.lower pane .vpane.lower.diff -weight 1 3186.vpane.lower pane .vpane.lower.commarea -weight 0 3187 3188# -- Commit Area Buttons 3189# 3190ttk::frame .vpane.lower.commarea.buttons 3191ttk::label .vpane.lower.commarea.buttons.l -text {} \ 3192 -anchor w \ 3193 -justify left 3194pack .vpane.lower.commarea.buttons.l -side top -fill x 3195pack .vpane.lower.commarea.buttons -side left -fill y 3196 3197ttk::button .vpane.lower.commarea.buttons.rescan -text [mc Rescan] \ 3198 -command ui_do_rescan 3199pack .vpane.lower.commarea.buttons.rescan -side top -fill x 3200lappend disable_on_lock \ 3201 {.vpane.lower.commarea.buttons.rescan conf -state} 3202 3203ttk::button .vpane.lower.commarea.buttons.incall -text [mc "Stage Changed"] \ 3204 -command do_add_all 3205pack .vpane.lower.commarea.buttons.incall -side top -fill x 3206lappend disable_on_lock \ 3207 {.vpane.lower.commarea.buttons.incall conf -state} 3208 3209if {![is_enabled nocommitmsg]} { 3210 ttk::button .vpane.lower.commarea.buttons.signoff -text [mc "Sign Off"] \ 3211 -command do_signoff 3212 pack .vpane.lower.commarea.buttons.signoff -side top -fill x 3213} 3214 3215ttk::button .vpane.lower.commarea.buttons.commit -text [commit_btn_caption] \ 3216 -command do_commit 3217pack .vpane.lower.commarea.buttons.commit -side top -fill x 3218lappend disable_on_lock \ 3219 {.vpane.lower.commarea.buttons.commit conf -state} 3220 3221if {![is_enabled nocommit]} { 3222 ttk::button .vpane.lower.commarea.buttons.push -text [mc Push] \ 3223 -command do_push_anywhere 3224 pack .vpane.lower.commarea.buttons.push -side top -fill x 3225} 3226 3227# -- Commit Message Buffer 3228# 3229ttk::frame .vpane.lower.commarea.buffer 3230ttk::frame .vpane.lower.commarea.buffer.header 3231set ui_comm .vpane.lower.commarea.buffer.frame.t 3232set ui_coml .vpane.lower.commarea.buffer.header.l 3233 3234if {![is_enabled nocommit]} { 3235 ttk::checkbutton .vpane.lower.commarea.buffer.header.amend \ 3236 -text [mc "Amend Last Commit"] \ 3237 -variable commit_type_is_amend \ 3238 -command do_select_commit_type 3239 lappend disable_on_lock \ 3240 [list .vpane.lower.commarea.buffer.header.amend conf -state] 3241} 3242 3243ttk::label $ui_coml \ 3244 -anchor w \ 3245 -justify left 3246proc trace_commit_type {varname args} { 3247 global ui_coml commit_type 3248 switch -glob -- $commit_type { 3249 initial {set txt [mc "Initial Commit Message:"]} 3250 amend {set txt [mc "Amended Commit Message:"]} 3251 amend-initial {set txt [mc "Amended Initial Commit Message:"]} 3252 amend-merge {set txt [mc "Amended Merge Commit Message:"]} 3253 merge {set txt [mc "Merge Commit Message:"]} 3254 * {set txt [mc "Commit Message:"]} 3255 } 3256 $ui_coml conf -text $txt 3257} 3258trace add variable commit_type write trace_commit_type 3259pack $ui_coml -side left -fill x 3260 3261if {![is_enabled nocommit]} { 3262 pack .vpane.lower.commarea.buffer.header.amend -side right 3263} 3264 3265textframe .vpane.lower.commarea.buffer.frame 3266ttext $ui_comm \ 3267 -borderwidth 1 \ 3268 -undo true \ 3269 -maxundo 20 \ 3270 -autoseparators true \ 3271 -takefocus 1 \ 3272 -highlightthickness 1 \ 3273 -relief sunken \ 3274 -width $repo_config(gui.commitmsgwidth) -height 9 -wrap none \ 3275 -font font_diff \ 3276 -xscrollcommand {.vpane.lower.commarea.buffer.frame.sbx set} \ 3277 -yscrollcommand {.vpane.lower.commarea.buffer.frame.sby set} 3278ttk::scrollbar .vpane.lower.commarea.buffer.frame.sbx \ 3279 -orient horizontal \ 3280 -command [list $ui_comm xview] 3281ttk::scrollbar .vpane.lower.commarea.buffer.frame.sby \ 3282 -orient vertical \ 3283 -command [list $ui_comm yview] 3284 3285pack .vpane.lower.commarea.buffer.frame.sbx -side bottom -fill x 3286pack .vpane.lower.commarea.buffer.frame.sby -side right -fill y 3287pack $ui_comm -side left -fill y 3288pack .vpane.lower.commarea.buffer.header -side top -fill x 3289pack .vpane.lower.commarea.buffer.frame -side left -fill y 3290pack .vpane.lower.commarea.buffer -side left -fill y 3291 3292# -- Commit Message Buffer Context Menu 3293# 3294set ctxm .vpane.lower.commarea.buffer.ctxm 3295menu $ctxm -tearoff 0 3296$ctxm add command \ 3297 -label [mc Cut] \ 3298 -command {tk_textCut $ui_comm} 3299$ctxm add command \ 3300 -label [mc Copy] \ 3301 -command {tk_textCopy $ui_comm} 3302$ctxm add command \ 3303 -label [mc Paste] \ 3304 -command {tk_textPaste $ui_comm} 3305$ctxm add command \ 3306 -label [mc Delete] \ 3307 -command {catch {$ui_comm delete sel.first sel.last}} 3308$ctxm add separator 3309$ctxm add command \ 3310 -label [mc "Select All"] \ 3311 -command {focus $ui_comm;$ui_comm tag add sel 0.0 end} 3312$ctxm add command \ 3313 -label [mc "Copy All"] \ 3314 -command { 3315 $ui_comm tag add sel 0.0 end 3316 tk_textCopy $ui_comm 3317 $ui_comm tag remove sel 0.0 end 3318 } 3319$ctxm add separator 3320$ctxm add command \ 3321 -label [mc "Sign Off"] \ 3322 -command do_signoff 3323set ui_comm_ctxm $ctxm 3324 3325# -- Diff Header 3326# 3327proc trace_current_diff_path {varname args} { 3328 global current_diff_path diff_actions file_states 3329 if {$current_diff_path eq {}} { 3330 set s {} 3331 set f {} 3332 set p {} 3333 set o disabled 3334 } else { 3335 set p $current_diff_path 3336 set s [mapdesc [lindex $file_states($p) 0] $p] 3337 set f [mc "File:"] 3338 set p [escape_path $p] 3339 set o normal 3340 } 3341 3342 .vpane.lower.diff.header.status configure -text $s 3343 .vpane.lower.diff.header.file configure -text $f 3344 .vpane.lower.diff.header.path configure -text $p 3345 foreach w $diff_actions { 3346 uplevel #0 $w $o 3347 } 3348} 3349trace add variable current_diff_path write trace_current_diff_path 3350 3351gold_frame .vpane.lower.diff.header 3352tlabel .vpane.lower.diff.header.status \ 3353 -background gold \ 3354 -foreground black \ 3355 -width $max_status_desc \ 3356 -anchor w \ 3357 -justify left 3358tlabel .vpane.lower.diff.header.file \ 3359 -background gold \ 3360 -foreground black \ 3361 -anchor w \ 3362 -justify left 3363tlabel .vpane.lower.diff.header.path \ 3364 -background gold \ 3365 -foreground blue \ 3366 -anchor w \ 3367 -justify left \ 3368 -font [eval font create [font configure font_ui] -underline 1] \ 3369 -cursor hand2 3370pack .vpane.lower.diff.header.status -side left 3371pack .vpane.lower.diff.header.file -side left 3372pack .vpane.lower.diff.header.path -fill x 3373set ctxm .vpane.lower.diff.header.ctxm 3374menu $ctxm -tearoff 0 3375$ctxm add command \ 3376 -label [mc Copy] \ 3377 -command { 3378 clipboard clear 3379 clipboard append \ 3380 -format STRING \ 3381 -type STRING \ 3382 -- $current_diff_path 3383 } 3384$ctxm add command \ 3385 -label [mc Open] \ 3386 -command {do_file_open $current_diff_path} 3387lappend diff_actions [list $ctxm entryconf [$ctxm index last] -state] 3388bind_button3 .vpane.lower.diff.header.path "tk_popup $ctxm %X %Y" 3389bind .vpane.lower.diff.header.path <Button-1> {do_file_open $current_diff_path} 3390 3391# -- Diff Body 3392# 3393textframe .vpane.lower.diff.body 3394set ui_diff .vpane.lower.diff.body.t 3395ttext $ui_diff \ 3396 -borderwidth 0 \ 3397 -width 80 -height 5 -wrap none \ 3398 -font font_diff \ 3399 -takefocus 1 -highlightthickness 1 \ 3400 -xscrollcommand {.vpane.lower.diff.body.sbx set} \ 3401 -yscrollcommand {.vpane.lower.diff.body.sby set} \ 3402 -state disabled 3403catch {$ui_diff configure -tabstyle wordprocessor} 3404ttk::scrollbar .vpane.lower.diff.body.sbx -orient horizontal \ 3405 -command [list $ui_diff xview] 3406ttk::scrollbar .vpane.lower.diff.body.sby -orient vertical \ 3407 -command [list $ui_diff yview] 3408pack .vpane.lower.diff.body.sbx -side bottom -fill x 3409pack .vpane.lower.diff.body.sby -side right -fill y 3410pack $ui_diff -side left -fill both -expand 1 3411pack .vpane.lower.diff.header -side top -fill x 3412pack .vpane.lower.diff.body -side bottom -fill both -expand 1 3413 3414foreach {n c} {0 black 1 red4 2 green4 3 yellow4 4 blue4 5 magenta4 6 cyan4 7 grey60} { 3415 $ui_diff tag configure clr4$n -background $c 3416 $ui_diff tag configure clri4$n -foreground $c 3417 $ui_diff tag configure clr3$n -foreground $c 3418 $ui_diff tag configure clri3$n -background $c 3419} 3420$ui_diff tag configure clr1 -font font_diffbold 3421$ui_diff tag configure clr4 -underline 1 3422 3423$ui_diff tag conf d_info -foreground blue -font font_diffbold 3424$ui_diff tag conf d_rescan -foreground blue -underline 1 -font font_diffbold 3425$ui_diff tag bind d_rescan <Button-1> { clear_diff; rescan ui_ready 0 } 3426 3427$ui_diff tag conf d_cr -elide true 3428$ui_diff tag conf d_@ -font font_diffbold 3429$ui_diff tag conf d_+ -foreground {#00a000} 3430$ui_diff tag conf d_- -foreground red 3431 3432$ui_diff tag conf d_++ -foreground {#00a000} 3433$ui_diff tag conf d_-- -foreground red 3434$ui_diff tag conf d_+s \ 3435 -foreground {#00a000} \ 3436 -background {#e2effa} 3437$ui_diff tag conf d_-s \ 3438 -foreground red \ 3439 -background {#e2effa} 3440$ui_diff tag conf d_s+ \ 3441 -foreground {#00a000} \ 3442 -background ivory1 3443$ui_diff tag conf d_s- \ 3444 -foreground red \ 3445 -background ivory1 3446 3447$ui_diff tag conf d< \ 3448 -foreground orange \ 3449 -font font_diffbold 3450$ui_diff tag conf d| \ 3451 -foreground orange \ 3452 -font font_diffbold 3453$ui_diff tag conf d= \ 3454 -foreground orange \ 3455 -font font_diffbold 3456$ui_diff tag conf d> \ 3457 -foreground orange \ 3458 -font font_diffbold 3459 3460$ui_diff tag raise sel 3461 3462# -- Diff Body Context Menu 3463# 3464 3465proc create_common_diff_popup {ctxm} { 3466 $ctxm add command \ 3467 -label [mc Refresh] \ 3468 -command reshow_diff 3469 lappend diff_actions [list $ctxm entryconf [$ctxm index last] -state] 3470 $ctxm add command \ 3471 -label [mc Copy] \ 3472 -command {tk_textCopy $ui_diff} 3473 lappend diff_actions [list $ctxm entryconf [$ctxm index last] -state] 3474 $ctxm add command \ 3475 -label [mc "Select All"] \ 3476 -command {focus $ui_diff;$ui_diff tag add sel 0.0 end} 3477 lappend diff_actions [list $ctxm entryconf [$ctxm index last] -state] 3478 $ctxm add command \ 3479 -label [mc "Copy All"] \ 3480 -command { 3481 $ui_diff tag add sel 0.0 end 3482 tk_textCopy $ui_diff 3483 $ui_diff tag remove sel 0.0 end 3484 } 3485 lappend diff_actions [list $ctxm entryconf [$ctxm index last] -state] 3486 $ctxm add separator 3487 $ctxm add command \ 3488 -label [mc "Decrease Font Size"] \ 3489 -command {incr_font_size font_diff -1} 3490 lappend diff_actions [list $ctxm entryconf [$ctxm index last] -state] 3491 $ctxm add command \ 3492 -label [mc "Increase Font Size"] \ 3493 -command {incr_font_size font_diff 1} 3494 lappend diff_actions [list $ctxm entryconf [$ctxm index last] -state] 3495 $ctxm add separator 3496 set emenu $ctxm.enc 3497 menu $emenu 3498 build_encoding_menu $emenu [list force_diff_encoding] 3499 $ctxm add cascade \ 3500 -label [mc "Encoding"] \ 3501 -menu $emenu 3502 lappend diff_actions [list $ctxm entryconf [$ctxm index last] -state] 3503 $ctxm add separator 3504 $ctxm add command -label [mc "Options..."] \ 3505 -command do_options 3506} 3507 3508set ctxm .vpane.lower.diff.body.ctxm 3509menu $ctxm -tearoff 0 3510$ctxm add command \ 3511 -label [mc "Apply/Reverse Hunk"] \ 3512 -command {apply_or_revert_hunk $cursorX $cursorY 0} 3513set ui_diff_applyhunk [$ctxm index last] 3514lappend diff_actions [list $ctxm entryconf $ui_diff_applyhunk -state] 3515$ctxm add command \ 3516 -label [mc "Apply/Reverse Line"] \ 3517 -command {apply_or_revert_range_or_line $cursorX $cursorY 0; do_rescan} 3518set ui_diff_applyline [$ctxm index last] 3519lappend diff_actions [list $ctxm entryconf $ui_diff_applyline -state] 3520$ctxm add separator 3521$ctxm add command \ 3522 -label [mc "Revert Hunk"] \ 3523 -command {apply_or_revert_hunk $cursorX $cursorY 1} 3524set ui_diff_reverthunk [$ctxm index last] 3525lappend diff_actions [list $ctxm entryconf $ui_diff_reverthunk -state] 3526$ctxm add command \ 3527 -label [mc "Revert Line"] \ 3528 -command {apply_or_revert_range_or_line $cursorX $cursorY 1; do_rescan} 3529set ui_diff_revertline [$ctxm index last] 3530lappend diff_actions [list $ctxm entryconf $ui_diff_revertline -state] 3531$ctxm add command \ 3532 -label [mc "Undo Last Revert"] \ 3533 -command {undo_last_revert; do_rescan} 3534set ui_diff_undorevert [$ctxm index last] 3535lappend diff_actions [list $ctxm entryconf $ui_diff_undorevert -state] 3536$ctxm add separator 3537$ctxm add command \ 3538 -label [mc "Show Less Context"] \ 3539 -command show_less_context 3540lappend diff_actions [list $ctxm entryconf [$ctxm index last] -state] 3541$ctxm add command \ 3542 -label [mc "Show More Context"] \ 3543 -command show_more_context 3544lappend diff_actions [list $ctxm entryconf [$ctxm index last] -state] 3545$ctxm add separator 3546create_common_diff_popup $ctxm 3547 3548set ctxmmg .vpane.lower.diff.body.ctxmmg 3549menu $ctxmmg -tearoff 0 3550$ctxmmg add command \ 3551 -label [mc "Run Merge Tool"] \ 3552 -command {merge_resolve_tool} 3553lappend diff_actions [list $ctxmmg entryconf [$ctxmmg index last] -state] 3554$ctxmmg add separator 3555$ctxmmg add command \ 3556 -label [mc "Use Remote Version"] \ 3557 -command {merge_resolve_one 3} 3558lappend diff_actions [list $ctxmmg entryconf [$ctxmmg index last] -state] 3559$ctxmmg add command \ 3560 -label [mc "Use Local Version"] \ 3561 -command {merge_resolve_one 2} 3562lappend diff_actions [list $ctxmmg entryconf [$ctxmmg index last] -state] 3563$ctxmmg add command \ 3564 -label [mc "Revert To Base"] \ 3565 -command {merge_resolve_one 1} 3566lappend diff_actions [list $ctxmmg entryconf [$ctxmmg index last] -state] 3567$ctxmmg add separator 3568$ctxmmg add command \ 3569 -label [mc "Show Less Context"] \ 3570 -command show_less_context 3571lappend diff_actions [list $ctxmmg entryconf [$ctxmmg index last] -state] 3572$ctxmmg add command \ 3573 -label [mc "Show More Context"] \ 3574 -command show_more_context 3575lappend diff_actions [list $ctxmmg entryconf [$ctxmmg index last] -state] 3576$ctxmmg add separator 3577create_common_diff_popup $ctxmmg 3578 3579set ctxmsm .vpane.lower.diff.body.ctxmsm 3580menu $ctxmsm -tearoff 0 3581$ctxmsm add command \ 3582 -label [mc "Visualize These Changes In The Submodule"] \ 3583 -command {do_gitk -- true} 3584lappend diff_actions [list $ctxmsm entryconf [$ctxmsm index last] -state] 3585$ctxmsm add command \ 3586 -label [mc "Visualize Current Branch History In The Submodule"] \ 3587 -command {do_gitk {} true} 3588lappend diff_actions [list $ctxmsm entryconf [$ctxmsm index last] -state] 3589$ctxmsm add command \ 3590 -label [mc "Visualize All Branch History In The Submodule"] \ 3591 -command {do_gitk --all true} 3592lappend diff_actions [list $ctxmsm entryconf [$ctxmsm index last] -state] 3593$ctxmsm add separator 3594$ctxmsm add command \ 3595 -label [mc "Start git gui In The Submodule"] \ 3596 -command {do_git_gui} 3597lappend diff_actions [list $ctxmsm entryconf [$ctxmsm index last] -state] 3598$ctxmsm add separator 3599create_common_diff_popup $ctxmsm 3600 3601proc has_textconv {path} { 3602 if {[is_config_false gui.textconv]} { 3603 return 0 3604 } 3605 set filter [gitattr $path diff set] 3606 set textconv [get_config [join [list diff $filter textconv] .]] 3607 if {$filter ne {set} && $textconv ne {}} { 3608 return 1 3609 } else { 3610 return 0 3611 } 3612} 3613 3614proc popup_diff_menu {ctxm ctxmmg ctxmsm x y X Y} { 3615 global current_diff_path file_states last_revert 3616 set ::cursorX $x 3617 set ::cursorY $y 3618 if {[info exists file_states($current_diff_path)]} { 3619 set state [lindex $file_states($current_diff_path) 0] 3620 } else { 3621 set state {__} 3622 } 3623 if {[string first {U} $state] >= 0} { 3624 tk_popup $ctxmmg $X $Y 3625 } elseif {$::is_submodule_diff} { 3626 tk_popup $ctxmsm $X $Y 3627 } else { 3628 set has_range [expr {[$::ui_diff tag nextrange sel 0.0] != {}}] 3629 set u [mc "Undo Last Revert"] 3630 if {$::ui_index eq $::current_diff_side} { 3631 set l [mc "Unstage Hunk From Commit"] 3632 set h [mc "Revert Hunk"] 3633 3634 if {$has_range} { 3635 set t [mc "Unstage Lines From Commit"] 3636 set r [mc "Revert Lines"] 3637 } else { 3638 set t [mc "Unstage Line From Commit"] 3639 set r [mc "Revert Line"] 3640 } 3641 } else { 3642 set l [mc "Stage Hunk For Commit"] 3643 set h [mc "Revert Hunk"] 3644 3645 if {$has_range} { 3646 set t [mc "Stage Lines For Commit"] 3647 set r [mc "Revert Lines"] 3648 } else { 3649 set t [mc "Stage Line For Commit"] 3650 set r [mc "Revert Line"] 3651 } 3652 } 3653 if {$::is_3way_diff 3654 || $current_diff_path eq {} 3655 || {__} eq $state 3656 || {_O} eq $state 3657 || [string match {?T} $state] 3658 || [string match {T?} $state] 3659 || [has_textconv $current_diff_path]} { 3660 set s disabled 3661 set revert_state disabled 3662 } else { 3663 set s normal 3664 3665 # Only allow reverting changes in the working tree. If 3666 # the user wants to revert changes in the index, they 3667 # need to unstage those first. 3668 if {$::ui_workdir eq $::current_diff_side} { 3669 set revert_state normal 3670 } else { 3671 set revert_state disabled 3672 } 3673 } 3674 3675 if {$last_revert eq {}} { 3676 set undo_state disabled 3677 } else { 3678 set undo_state normal 3679 } 3680 3681 $ctxm entryconf $::ui_diff_applyhunk -state $s -label $l 3682 $ctxm entryconf $::ui_diff_applyline -state $s -label $t 3683 $ctxm entryconf $::ui_diff_revertline -state $revert_state \ 3684 -label $r 3685 $ctxm entryconf $::ui_diff_reverthunk -state $revert_state \ 3686 -label $h 3687 $ctxm entryconf $::ui_diff_undorevert -state $undo_state \ 3688 -label $u 3689 3690 tk_popup $ctxm $X $Y 3691 } 3692} 3693bind_button3 $ui_diff [list popup_diff_menu $ctxm $ctxmmg $ctxmsm %x %y %X %Y] 3694 3695# -- Status Bar 3696# 3697set main_status [::status_bar::new .status] 3698pack .status -anchor w -side bottom -fill x 3699$main_status show [mc "Initializing..."] 3700 3701# -- Load geometry 3702# 3703proc on_ttk_pane_mapped {w pane pos} { 3704 bind $w <Map> {} 3705 after 0 [list after idle [list $w sashpos $pane $pos]] 3706} 3707proc on_application_mapped {} { 3708 global repo_config 3709 bind . <Map> {} 3710 set gm $repo_config(gui.geometry) 3711 bind .vpane <Map> \ 3712 [list on_ttk_pane_mapped %W 0 [lindex $gm 1]] 3713 bind .vpane.files <Map> \ 3714 [list on_ttk_pane_mapped %W 0 [lindex $gm 2]] 3715 wm geometry . [lindex $gm 0] 3716} 3717if {[info exists repo_config(gui.geometry)]} { 3718 bind . <Map> [list on_application_mapped] 3719 wm geometry . [lindex $repo_config(gui.geometry) 0] 3720} 3721 3722# -- Load window state 3723# 3724if {[info exists repo_config(gui.wmstate)]} { 3725 catch {wm state . $repo_config(gui.wmstate)} 3726} 3727 3728# -- Key Bindings 3729# 3730bind $ui_comm <$M1B-Key-Return> {do_commit;break} 3731bind $ui_comm <$M1B-Key-t> {do_add_selection;break} 3732bind $ui_comm <$M1B-Key-T> {do_add_selection;break} 3733bind $ui_comm <$M1B-Key-u> {do_unstage_selection;break} 3734bind $ui_comm <$M1B-Key-U> {do_unstage_selection;break} 3735bind $ui_comm <$M1B-Key-j> {do_revert_selection;break} 3736bind $ui_comm <$M1B-Key-J> {do_revert_selection;break} 3737bind $ui_comm <$M1B-Key-i> {do_add_all;break} 3738bind $ui_comm <$M1B-Key-I> {do_add_all;break} 3739bind $ui_comm <$M1B-Key-x> {tk_textCut %W;break} 3740bind $ui_comm <$M1B-Key-X> {tk_textCut %W;break} 3741bind $ui_comm <$M1B-Key-c> {tk_textCopy %W;break} 3742bind $ui_comm <$M1B-Key-C> {tk_textCopy %W;break} 3743bind $ui_comm <$M1B-Key-v> {tk_textPaste %W; %W see insert; break} 3744bind $ui_comm <$M1B-Key-V> {tk_textPaste %W; %W see insert; break} 3745bind $ui_comm <$M1B-Key-a> {%W tag add sel 0.0 end;break} 3746bind $ui_comm <$M1B-Key-A> {%W tag add sel 0.0 end;break} 3747bind $ui_comm <$M1B-Key-minus> {show_less_context;break} 3748bind $ui_comm <$M1B-Key-KP_Subtract> {show_less_context;break} 3749bind $ui_comm <$M1B-Key-equal> {show_more_context;break} 3750bind $ui_comm <$M1B-Key-plus> {show_more_context;break} 3751bind $ui_comm <$M1B-Key-KP_Add> {show_more_context;break} 3752bind $ui_comm <$M1B-Key-BackSpace> {event generate %W <Meta-Delete>;break} 3753bind $ui_comm <$M1B-Key-Delete> {event generate %W <Meta-d>;break} 3754 3755bind $ui_diff <$M1B-Key-x> {tk_textCopy %W;break} 3756bind $ui_diff <$M1B-Key-X> {tk_textCopy %W;break} 3757bind $ui_diff <$M1B-Key-c> {tk_textCopy %W;break} 3758bind $ui_diff <$M1B-Key-C> {tk_textCopy %W;break} 3759bind $ui_diff <$M1B-Key-v> {break} 3760bind $ui_diff <$M1B-Key-V> {break} 3761bind $ui_diff <$M1B-Key-a> {%W tag add sel 0.0 end;break} 3762bind $ui_diff <$M1B-Key-A> {%W tag add sel 0.0 end;break} 3763bind $ui_diff <$M1B-Key-j> {do_revert_selection;break} 3764bind $ui_diff <$M1B-Key-J> {do_revert_selection;break} 3765bind $ui_diff <Key-Up> {catch {%W yview scroll -1 units};break} 3766bind $ui_diff <Key-Down> {catch {%W yview scroll 1 units};break} 3767bind $ui_diff <Key-Left> {catch {%W xview scroll -1 units};break} 3768bind $ui_diff <Key-Right> {catch {%W xview scroll 1 units};break} 3769bind $ui_diff <Key-k> {catch {%W yview scroll -1 units};break} 3770bind $ui_diff <Key-j> {catch {%W yview scroll 1 units};break} 3771bind $ui_diff <Key-h> {catch {%W xview scroll -1 units};break} 3772bind $ui_diff <Key-l> {catch {%W xview scroll 1 units};break} 3773bind $ui_diff <Control-Key-b> {catch {%W yview scroll -1 pages};break} 3774bind $ui_diff <Control-Key-f> {catch {%W yview scroll 1 pages};break} 3775bind $ui_diff <Button-1> {focus %W} 3776 3777if {[is_enabled branch]} { 3778 bind . <$M1B-Key-n> branch_create::dialog 3779 bind . <$M1B-Key-N> branch_create::dialog 3780 bind . <$M1B-Key-o> branch_checkout::dialog 3781 bind . <$M1B-Key-O> branch_checkout::dialog 3782 bind . <$M1B-Key-m> merge::dialog 3783 bind . <$M1B-Key-M> merge::dialog 3784} 3785if {[is_enabled transport]} { 3786 bind . <$M1B-Key-p> do_push_anywhere 3787 bind . <$M1B-Key-P> do_push_anywhere 3788} 3789 3790bind . <Key-F5> ui_do_rescan 3791bind . <$M1B-Key-r> ui_do_rescan 3792bind . <$M1B-Key-R> ui_do_rescan 3793bind . <$M1B-Key-s> do_signoff 3794bind . <$M1B-Key-S> do_signoff 3795bind . <$M1B-Key-t> { toggle_or_diff toggle %W } 3796bind . <$M1B-Key-T> { toggle_or_diff toggle %W } 3797bind . <$M1B-Key-u> { toggle_or_diff toggle %W } 3798bind . <$M1B-Key-U> { toggle_or_diff toggle %W } 3799bind . <$M1B-Key-j> do_revert_selection 3800bind . <$M1B-Key-J> do_revert_selection 3801bind . <$M1B-Key-i> do_add_all 3802bind . <$M1B-Key-I> do_add_all 3803bind . <$M1B-Key-e> toggle_commit_type 3804bind . <$M1B-Key-E> toggle_commit_type 3805bind . <$M1B-Key-minus> {show_less_context;break} 3806bind . <$M1B-Key-KP_Subtract> {show_less_context;break} 3807bind . <$M1B-Key-equal> {show_more_context;break} 3808bind . <$M1B-Key-plus> {show_more_context;break} 3809bind . <$M1B-Key-KP_Add> {show_more_context;break} 3810bind . <$M1B-Key-Return> do_commit 3811bind . <$M1B-Key-KP_Enter> do_commit 3812foreach i [list $ui_index $ui_workdir] { 3813 bind $i <Button-1> { toggle_or_diff click %W %x %y; break } 3814 bind $i <$M1B-Button-1> { add_one_to_selection %W %x %y; break } 3815 bind $i <Shift-Button-1> { add_range_to_selection %W %x %y; break } 3816 bind $i <Key-Up> { toggle_or_diff up %W; break } 3817 bind $i <Key-Down> { toggle_or_diff down %W; break } 3818} 3819unset i 3820 3821bind . <Alt-Key-1> {focus_widget $::ui_workdir} 3822bind . <Alt-Key-2> {focus_widget $::ui_index} 3823bind . <Alt-Key-3> {focus $::ui_diff} 3824bind . <Alt-Key-4> {focus $::ui_comm} 3825 3826set file_lists_last_clicked($ui_index) {} 3827set file_lists_last_clicked($ui_workdir) {} 3828 3829set file_lists($ui_index) [list] 3830set file_lists($ui_workdir) [list] 3831 3832wm title . "[appname] ([reponame]) [file normalize $_gitworktree]" 3833focus -force $ui_comm 3834 3835# -- Only initialize complex UI if we are going to stay running. 3836# 3837if {[is_enabled transport]} { 3838 load_all_remotes 3839 3840 set n [.mbar.remote index end] 3841 populate_remotes_menu 3842 set n [expr {[.mbar.remote index end] - $n}] 3843 if {$n > 0} { 3844 if {[.mbar.remote type 0] eq "tearoff"} { incr n } 3845 .mbar.remote insert $n separator 3846 } 3847 unset n 3848} 3849 3850if {[winfo exists $ui_comm]} { 3851 set GITGUI_BCK_exists [load_message GITGUI_BCK utf-8] 3852 3853 # -- If both our backup and message files exist use the 3854 # newer of the two files to initialize the buffer. 3855 # 3856 if {$GITGUI_BCK_exists} { 3857 set m [gitdir GITGUI_MSG] 3858 if {[file isfile $m]} { 3859 if {[file mtime [gitdir GITGUI_BCK]] > [file mtime $m]} { 3860 catch {file delete [gitdir GITGUI_MSG]} 3861 } else { 3862 $ui_comm delete 0.0 end 3863 $ui_comm edit reset 3864 $ui_comm edit modified false 3865 catch {file delete [gitdir GITGUI_BCK]} 3866 set GITGUI_BCK_exists 0 3867 } 3868 } 3869 unset m 3870 } 3871 3872 proc backup_commit_buffer {} { 3873 global ui_comm GITGUI_BCK_exists 3874 3875 set m [$ui_comm edit modified] 3876 if {$m || $GITGUI_BCK_exists} { 3877 set msg [string trim [$ui_comm get 0.0 end]] 3878 regsub -all -line {[ \r\t]+$} $msg {} msg 3879 3880 if {$msg eq {}} { 3881 if {$GITGUI_BCK_exists} { 3882 catch {file delete [gitdir GITGUI_BCK]} 3883 set GITGUI_BCK_exists 0 3884 } 3885 } elseif {$m} { 3886 catch { 3887 set fd [safe_open_file [gitdir GITGUI_BCK] w] 3888 fconfigure $fd -encoding utf-8 3889 puts -nonewline $fd $msg 3890 close $fd 3891 set GITGUI_BCK_exists 1 3892 } 3893 } 3894 3895 $ui_comm edit modified false 3896 } 3897 3898 set ::GITGUI_BCK_i [after 2000 backup_commit_buffer] 3899 } 3900 3901 backup_commit_buffer 3902 3903 # -- If the user has aspell available we can drive it 3904 # in pipe mode to spellcheck the commit message. 3905 # 3906 set spell_cmd [list |] 3907 set spell_dict [get_config gui.spellingdictionary] 3908 lappend spell_cmd aspell 3909 if {$spell_dict ne {}} { 3910 lappend spell_cmd --master=$spell_dict 3911 } 3912 lappend spell_cmd --mode=none 3913 lappend spell_cmd --encoding=utf-8 3914 lappend spell_cmd pipe 3915 if {$spell_dict eq {none} 3916 || [catch {set spell_fd [open $spell_cmd r+]} spell_err]} { 3917 bind_button3 $ui_comm [list tk_popup $ui_comm_ctxm %X %Y] 3918 } else { 3919 set ui_comm_spell [spellcheck::init \ 3920 $spell_fd \ 3921 $ui_comm \ 3922 $ui_comm_ctxm \ 3923 ] 3924 } 3925 unset -nocomplain spell_cmd spell_fd spell_err spell_dict 3926} 3927 3928lock_index begin-read 3929if {![winfo ismapped .]} { 3930 wm deiconify . 3931} 3932after 1 { 3933 if {[is_enabled initialamend]} { 3934 force_amend 3935 } else { 3936 do_rescan 3937 } 3938 3939 if {[is_enabled nocommitmsg]} { 3940 $ui_comm configure -state disabled -background gray 3941 } 3942} 3943if {[is_enabled multicommit] && ![is_config_false gui.gcwarning]} { 3944 after 1000 hint_gc 3945} 3946if {[is_enabled retcode]} { 3947 bind . <Destroy> {+terminate_me %W} 3948} 3949if {$picked && [is_config_true gui.autoexplore]} { 3950 do_explore 3951} 3952 3953# Clear "Initializing..." status 3954after 500 {$main_status show ""} 3955 3956# Local variables: 3957# mode: tcl 3958# indent-tabs-mode: t 3959# tab-width: 4 3960# End: