]> asedeno.scripts.mit.edu Git - git.git/blobdiff - git-gui.sh
git-gui: handle "deleted symlink" diff marker
[git.git] / git-gui.sh
index 7b6a96e4f23d379905d2fc37adeaa07249c1b674..6d676097a6a3aeecfda3a752b7344dc88094956c 100755 (executable)
@@ -42,6 +42,8 @@ if {[catch {package require Tcl 8.4} err]
        exit 1
 }
 
+rename send {} ; # What an evil concept...
+
 ######################################################################
 ##
 ## enable verbose loading?
@@ -60,54 +62,6 @@ if {![catch {set _verbose $env(GITGUI_VERBOSE)}]} {
        }
 }
 
-######################################################################
-##
-## configure our library
-
-set oguilib {@@GITGUI_LIBDIR@@}
-set oguirel {@@GITGUI_RELATIVE@@}
-if {$oguirel eq {1}} {
-       set oguilib [file dirname [file dirname [file normalize $argv0]]]
-       set oguilib [file join $oguilib share git-gui lib]
-} elseif {[string match @@* $oguirel]} {
-       set oguilib [file join [file dirname [file normalize $argv0]] lib]
-}
-
-set idx [file join $oguilib tclIndex]
-if {[catch {set fd [open $idx r]} err]} {
-       catch {wm withdraw .}
-       tk_messageBox \
-               -icon error \
-               -type ok \
-               -title "git-gui: fatal error" \
-               -message $err
-       exit 1
-}
-if {[gets $fd] eq {# Autogenerated by git-gui Makefile}} {
-       set idx [list]
-       while {[gets $fd n] >= 0} {
-               if {$n ne {} && ![string match #* $n]} {
-                       lappend idx $n
-               }
-       }
-} else {
-       set idx {}
-}
-close $fd
-
-if {$idx ne {}} {
-       set loaded [list]
-       foreach p $idx {
-               if {[lsearch -exact $loaded $p] >= 0} continue
-               source [file join $oguilib $p]
-               lappend loaded $p
-       }
-       unset loaded p
-} else {
-       set auto_path [concat [list $oguilib] $auto_path]
-}
-unset -nocomplain oguirel idx fd
-
 ######################################################################
 ##
 ## read only globals
@@ -154,12 +108,10 @@ proc gitexec {args} {
 }
 
 proc reponame {} {
-       global _reponame
-       return $_reponame
+       return $::_reponame
 }
 
 proc is_MacOSX {} {
-       global tcl_platform tk_library
        if {[tk windowingsystem] eq {aqua}} {
                return 1
        }
@@ -167,17 +119,16 @@ proc is_MacOSX {} {
 }
 
 proc is_Windows {} {
-       global tcl_platform
-       if {$tcl_platform(platform) eq {windows}} {
+       if {$::tcl_platform(platform) eq {windows}} {
                return 1
        }
        return 0
 }
 
 proc is_Cygwin {} {
-       global tcl_platform _iscygwin
+       global _iscygwin
        if {$_iscygwin eq {}} {
-               if {$tcl_platform(platform) eq {windows}} {
+               if {$::tcl_platform(platform) eq {windows}} {
                        if {[catch {set p [exec cygpath --windir]} err]} {
                                set _iscygwin 0
                        } else {
@@ -312,7 +263,7 @@ proc _git_cmd {name} {
                        set s [gets $f]
                        close $f
 
-                       switch -glob -- $s {
+                       switch -glob -- [lindex $s 0] {
                        #!*sh     { set i sh     }
                        #!*perl   { set i perl   }
                        #!*python { set i python }
@@ -326,7 +277,7 @@ proc _git_cmd {name} {
                        if {$interp eq {}} {
                                error "git-$name requires $i (not in PATH)"
                        }
-                       set v [list $interp $p]
+                       set v [concat [list $interp] [lrange $s 1 end] [list $p]]
                } else {
                        # Assume it is builtin to git somehow and we
                        # aren't actually able to see a file for it.
@@ -535,7 +486,11 @@ if {$_git eq {}} {
 
 if {[catch {set _git_version [git --version]} err]} {
        catch {wm withdraw .}
-       error_popup "Cannot determine Git version:
+       tk_messageBox \
+               -icon error \
+               -type ok \
+               -title "git-gui: fatal error" \
+               -message "Cannot determine Git version:
 
 $err
 
@@ -544,7 +499,11 @@ $err
 }
 if {![regsub {^git version } $_git_version {} _git_version]} {
        catch {wm withdraw .}
-       error_popup "Cannot parse Git version string:\n\n$_git_version"
+       tk_messageBox \
+               -icon error \
+               -type ok \
+               -title "git-gui: fatal error" \
+               -message "Cannot parse Git version string:\n\n$_git_version"
        exit 1
 }
 
@@ -622,7 +581,11 @@ proc git-version {args} {
 
 if {[git-version < 1.5]} {
        catch {wm withdraw .}
-       error_popup "[appname] requires Git 1.5.0 or later.
+       tk_messageBox \
+               -icon error \
+               -type ok \
+               -title "git-gui: fatal error" \
+               -message "[appname] requires Git 1.5.0 or later.
 
 You are using [git-version]:
 
@@ -630,6 +593,54 @@ You are using [git-version]:
        exit 1
 }
 
+######################################################################
+##
+## configure our library
+
+set oguilib {@@GITGUI_LIBDIR@@}
+set oguirel {@@GITGUI_RELATIVE@@}
+if {$oguirel eq {1}} {
+       set oguilib [file dirname [file dirname [file normalize $argv0]]]
+       set oguilib [file join $oguilib share git-gui lib]
+} elseif {[string match @@* $oguirel]} {
+       set oguilib [file join [file dirname [file normalize $argv0]] lib]
+}
+
+set idx [file join $oguilib tclIndex]
+if {[catch {set fd [open $idx r]} err]} {
+       catch {wm withdraw .}
+       tk_messageBox \
+               -icon error \
+               -type ok \
+               -title "git-gui: fatal error" \
+               -message $err
+       exit 1
+}
+if {[gets $fd] eq {# Autogenerated by git-gui Makefile}} {
+       set idx [list]
+       while {[gets $fd n] >= 0} {
+               if {$n ne {} && ![string match #* $n]} {
+                       lappend idx $n
+               }
+       }
+} else {
+       set idx {}
+}
+close $fd
+
+if {$idx ne {}} {
+       set loaded [list]
+       foreach p $idx {
+               if {[lsearch -exact $loaded $p] >= 0} continue
+               source [file join $oguilib $p]
+               lappend loaded $p
+       }
+       unset loaded p
+} else {
+       set auto_path [concat [list $oguilib] $auto_path]
+}
+unset -nocomplain oguirel idx fd
+
 ######################################################################
 ##
 ## feature option selection
@@ -694,7 +705,15 @@ if {![file isdirectory $_gitdir]} {
        error_popup "Git directory not found:\n\n$_gitdir"
        exit 1
 }
-if {![is_enabled bare]} {
+if {$_prefix ne {}} {
+       regsub -all {[^/]+/} $_prefix ../ cdup
+       if {[catch {cd $cdup} err]} {
+               catch {wm withdraw .}
+               error_popup "Cannot move to top of working directory:\n\n$err"
+               exit 1
+       }
+       unset cdup
+} elseif {![is_enabled bare]} {
        if {[lindex [file split $_gitdir] end] ne {.git}} {
                catch {wm withdraw .}
                error_popup "Cannot use funny .git directory:\n\n$_gitdir"
@@ -729,6 +748,7 @@ set empty_tree {}
 set current_branch {}
 set is_detached 0
 set current_diff_path {}
+set is_3way_diff 0
 set selected_commit_type new
 
 ######################################################################
@@ -832,8 +852,9 @@ proc rescan {after {honor_trustmtime 1}} {
 
        array unset file_states
 
-       if {![$ui_comm edit modified]
-               || [string trim [$ui_comm get 0.0 end]] eq {}} {
+       if {!$::GITGUI_BCK_exists &&
+               (![$ui_comm edit modified]
+               || [string trim [$ui_comm get 0.0 end]] eq {})} {
                if {[string match amend* $commit_type]} {
                } elseif {[load_message GITGUI_MSG]} {
                } elseif {[load_message MERGE_MSG]} {
@@ -874,6 +895,10 @@ proc rescan_stage2 {fd after} {
        if {[file readable $info_exclude]} {
                lappend ls_others "--exclude-from=$info_exclude"
        }
+       set user_exclude [get_config core.excludesfile]
+       if {$user_exclude ne {} && [file readable $user_exclude]} {
+               lappend ls_others "--exclude-from=$user_exclude"
+       }
 
        set buf_rdi {}
        set buf_rdf {}
@@ -1294,32 +1319,6 @@ static unsigned char file_merge_bits[] = {
    0xfa, 0x17, 0x02, 0x10, 0xfe, 0x1f};
 } -maskdata $filemask
 
-set file_dir_data {
-#define file_width 18
-#define file_height 18
-static unsigned char file_bits[] = {
-  0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0xf8, 0x03, 0x00,
-  0x0c, 0x03, 0x00, 0x04, 0xfe, 0x00, 0x06, 0x80, 0x00, 0xff, 0x9f, 0x00,
-  0x03, 0x98, 0x00, 0x02, 0x90, 0x00, 0x06, 0xb0, 0x00, 0x04, 0xa0, 0x00,
-  0x0c, 0xe0, 0x00, 0x08, 0xc0, 0x00, 0xf8, 0xff, 0x00, 0x00, 0x00, 0x00,
-  0x00, 0x00, 0x00, 0x00, 0x00, 0x00};
-}
-image create bitmap file_dir -background white -foreground blue \
-       -data $file_dir_data -maskdata $file_dir_data
-unset file_dir_data
-
-set file_uplevel_data {
-#define up_width 15
-#define up_height 15
-static unsigned char up_bits[] = {
-  0x80, 0x00, 0xc0, 0x01, 0xe0, 0x03, 0xf0, 0x07, 0xf8, 0x0f, 0xfc, 0x1f,
-  0xfe, 0x3f, 0xc0, 0x01, 0xc0, 0x01, 0xc0, 0x01, 0xc0, 0x01, 0xc0, 0x01,
-  0xc0, 0x01, 0xc0, 0x01, 0x00, 0x00};
-}
-image create bitmap file_uplevel -background white -foreground red \
-       -data $file_uplevel_data -maskdata $file_uplevel_data
-unset file_uplevel_data
-
 set ui_index .vpane.files.index.list
 set ui_workdir .vpane.files.workdir.list
 
@@ -1372,6 +1371,9 @@ unset i
 proc bind_button3 {w cmd} {
        bind $w <Any-Button-3> $cmd
        if {[is_MacOSX]} {
+               # Mac OS X sends Button-2 on right click through three-button mouse,
+               # or through trackpad right-clicking (two-finger touch + click).
+               bind $w <Any-Button-2> $cmd
                bind $w <Control-Button-1> $cmd
        }
 }
@@ -1420,6 +1422,7 @@ set is_quitting 0
 
 proc do_quit {} {
        global ui_comm is_quitting repo_config commit_type
+       global GITGUI_BCK_exists GITGUI_BCK_i
 
        if {$is_quitting} return
        set is_quitting 1
@@ -1428,18 +1431,30 @@ proc do_quit {} {
                # -- Stash our current commit buffer.
                #
                set save [gitdir GITGUI_MSG]
-               set msg [string trim [$ui_comm get 0.0 end]]
-               regsub -all -line {[ \r\t]+$} $msg {} msg
-               if {(![string match amend* $commit_type]
-                       || [$ui_comm edit modified])
-                       && $msg ne {}} {
-                       catch {
-                               set fd [open $save w]
-                               puts -nonewline $fd $msg
-                               close $fd
-                       }
+               if {$GITGUI_BCK_exists && ![$ui_comm edit modified]} {
+                       file rename -force [gitdir GITGUI_BCK] $save
+                       set GITGUI_BCK_exists 0
                } else {
-                       catch {file delete $save}
+                       set msg [string trim [$ui_comm get 0.0 end]]
+                       regsub -all -line {[ \r\t]+$} $msg {} msg
+                       if {(![string match amend* $commit_type]
+                               || [$ui_comm edit modified])
+                               && $msg ne {}} {
+                               catch {
+                                       set fd [open $save w]
+                                       puts -nonewline $fd $msg
+                                       close $fd
+                               }
+                       } else {
+                               catch {file delete $save}
+                       }
+               }
+
+               # -- Remove our editor backup, its not needed.
+               #
+               after cancel $GITGUI_BCK_i
+               if {$GITGUI_BCK_exists} {
+                       catch {file delete [gitdir GITGUI_BCK]}
                }
 
                # -- Stash our current window geometry into this repository.
@@ -1672,7 +1687,7 @@ menu .mbar.repository
 .mbar.repository add command \
        -label {Browse Current Branch's Files} \
        -command {browser::new $current_branch}
-trace add variable current_branch write ".mbar.repository entryconf [.mbar.repository index last] -label \"Browse \$current_branch's Files\" ;#"
+set ui_browse_current [.mbar.repository index last]
 .mbar.repository add command \
        -label {Browse Branch Files...} \
        -command browser_open::dialog
@@ -1681,12 +1696,21 @@ trace add variable current_branch write ".mbar.repository entryconf [.mbar.repos
 .mbar.repository add command \
        -label {Visualize Current Branch's History} \
        -command {do_gitk $current_branch}
-trace add variable current_branch write ".mbar.repository entryconf [.mbar.repository index last] -label \"Visualize \$current_branch's History\" ;#"
+set ui_visualize_current [.mbar.repository index last]
 .mbar.repository add command \
        -label {Visualize All Branch History} \
        -command {do_gitk --all}
 .mbar.repository add separator
 
+proc current_branch_write {args} {
+       global current_branch
+       .mbar.repository entryconf $::ui_browse_current \
+               -label "Browse $current_branch's Files"
+       .mbar.repository entryconf $::ui_visualize_current \
+               -label "Visualize $current_branch's History"
+}
+trace add variable current_branch write current_branch_write
+
 if {[is_enabled multicommit]} {
        .mbar.repository add command -label {Database Statistics} \
                -command do_stats
@@ -1807,12 +1831,12 @@ if {[is_enabled multicommit] || [is_enabled singlecommit]} {
        lappend disable_on_lock \
                [list .mbar.commit entryconf [.mbar.commit index last] -state]
 
-       .mbar.commit add command -label {Add To Commit} \
+       .mbar.commit add command -label {Stage To Commit} \
                -command do_add_selection
        lappend disable_on_lock \
                [list .mbar.commit entryconf [.mbar.commit index last] -state]
 
-       .mbar.commit add command -label {Add Existing To Commit} \
+       .mbar.commit add command -label {Stage Changed Files To Commit} \
                -command do_add_all \
                -accelerator $M1T-I
        lappend disable_on_lock \
@@ -1846,14 +1870,14 @@ if {[is_enabled multicommit] || [is_enabled singlecommit]} {
 if {[is_enabled branch]} {
        menu .mbar.merge
        .mbar.merge add command -label {Local Merge...} \
-               -command merge::dialog
+               -command merge::dialog \
+               -accelerator $M1T-M
        lappend disable_on_lock \
                [list .mbar.merge entryconf [.mbar.merge index last] -state]
        .mbar.merge add command -label {Abort Merge...} \
                -command merge::reset_hard
        lappend disable_on_lock \
                [list .mbar.merge entryconf [.mbar.merge index last] -state]
-
 }
 
 # -- Transport Menu
@@ -1885,35 +1909,6 @@ if {[is_MacOSX]} {
        .mbar.edit add separator
        .mbar.edit add command -label {Options...} \
                -command do_options
-
-       # -- Tools Menu
-       #
-       if {[is_Cygwin]
-               && [is_enabled multicommit]
-               && [file exists /usr/local/miga/lib/gui-miga]} {
-       proc do_miga {} {
-               if {![lock_index update]} return
-               set cmd [list sh --login -c "/usr/local/miga/lib/gui-miga \"[pwd]\""]
-               set miga_fd [open "|$cmd" r]
-               fconfigure $miga_fd -blocking 0
-               fileevent $miga_fd readable [list miga_done $miga_fd]
-               ui_status {Running miga...}
-       }
-       proc miga_done {fd} {
-               read $fd 512
-               if {[eof $fd]} {
-                       close $fd
-                       unlock_index
-                       rescan ui_ready
-               }
-       }
-       .mbar add cascade -label Tools -menu .mbar.tools
-       menu .mbar.tools
-       .mbar.tools add command -label "Migrate" \
-               -command do_miga
-       lappend disable_on_lock \
-               [list .mbar.tools entryconf [.mbar.tools index last] -state]
-       }
 }
 
 # -- Help Menu
@@ -1964,6 +1959,12 @@ if {$browser ne {}} {
 }
 unset browser doc_path doc_url
 
+set root_exists 0
+bind . <Visibility> {
+       bind . <Visibility> {}
+       set root_exists 1
+}
+
 # -- Standard bindings
 #
 wm protocol . WM_DELETE_WINDOW do_quit
@@ -2163,7 +2164,7 @@ pack .vpane.lower.commarea.buttons.rescan -side top -fill x
 lappend disable_on_lock \
        {.vpane.lower.commarea.buttons.rescan conf -state}
 
-button .vpane.lower.commarea.buttons.incall -text {Add Existing} \
+button .vpane.lower.commarea.buttons.incall -text {Stage Changed} \
        -command do_add_all
 pack .vpane.lower.commarea.buttons.incall -side top -fill x
 lappend disable_on_lock \
@@ -2437,17 +2438,27 @@ lappend diff_actions [list $ctxm entryconf [$ctxm index last] -state]
 $ctxm add separator
 $ctxm add command -label {Options...} \
        -command do_options
-bind_button3 $ui_diff "
-       set cursorX %x
-       set cursorY %y
-       if {\$ui_index eq \$current_diff_side} {
-               $ctxm entryconf $ui_diff_applyhunk -label {Unstage Hunk From Commit}
+proc popup_diff_menu {ctxm x y X Y} {
+       global current_diff_path file_states
+       set ::cursorX $x
+       set ::cursorY $y
+       if {$::ui_index eq $::current_diff_side} {
+               set l "Unstage Hunk From Commit"
        } else {
-               $ctxm entryconf $ui_diff_applyhunk -label {Stage Hunk For Commit}
+               set l "Stage Hunk For Commit"
        }
-       tk_popup $ctxm %X %Y
-"
-unset ui_diff_applyhunk
+       if {$::is_3way_diff
+               || $current_diff_path eq {}
+               || ![info exists file_states($current_diff_path)]
+               || {_O} eq [lindex $file_states($current_diff_path) 0]} {
+               set s disabled
+       } else {
+               set s normal
+       }
+       $ctxm entryconf $::ui_diff_applyhunk -state $s -label $l
+       tk_popup $ctxm $X $Y
+}
+bind_button3 $ui_diff [list popup_diff_menu $ctxm %x %y %X %Y]
 
 # -- Status Bar
 #
@@ -2508,6 +2519,8 @@ if {[is_enabled branch]} {
        bind . <$M1B-Key-N> branch_create::dialog
        bind . <$M1B-Key-o> branch_checkout::dialog
        bind . <$M1B-Key-O> branch_checkout::dialog
+       bind . <$M1B-Key-m> merge::dialog
+       bind . <$M1B-Key-M> merge::dialog
 }
 if {[is_enabled transport]} {
        bind . <$M1B-Key-p> do_push_anywhere
@@ -2599,6 +2612,59 @@ if {[is_enabled transport]} {
        populate_push_menu
 }
 
+if {[winfo exists $ui_comm]} {
+       set GITGUI_BCK_exists [load_message GITGUI_BCK]
+
+       # -- If both our backup and message files exist use the
+       #    newer of the two files to initialize the buffer.
+       #
+       if {$GITGUI_BCK_exists} {
+               set m [gitdir GITGUI_MSG]
+               if {[file isfile $m]} {
+                       if {[file mtime [gitdir GITGUI_BCK]] > [file mtime $m]} {
+                               catch {file delete [gitdir GITGUI_MSG]}
+                       } else {
+                               $ui_comm delete 0.0 end
+                               $ui_comm edit reset
+                               $ui_comm edit modified false
+                               catch {file delete [gitdir GITGUI_BCK]}
+                               set GITGUI_BCK_exists 0
+                       }
+               }
+               unset m
+       }
+
+       proc backup_commit_buffer {} {
+               global ui_comm GITGUI_BCK_exists
+
+               set m [$ui_comm edit modified]
+               if {$m || $GITGUI_BCK_exists} {
+                       set msg [string trim [$ui_comm get 0.0 end]]
+                       regsub -all -line {[ \r\t]+$} $msg {} msg
+
+                       if {$msg eq {}} {
+                               if {$GITGUI_BCK_exists} {
+                                       catch {file delete [gitdir GITGUI_BCK]}
+                                       set GITGUI_BCK_exists 0
+                               }
+                       } elseif {$m} {
+                               catch {
+                                       set fd [open [gitdir GITGUI_BCK] w]
+                                       puts -nonewline $fd $msg
+                                       close $fd
+                                       set GITGUI_BCK_exists 1
+                               }
+                       }
+
+                       $ui_comm edit modified false
+               }
+
+               set ::GITGUI_BCK_i [after 2000 backup_commit_buffer]
+       }
+
+       backup_commit_buffer
+}
+
 lock_index begin-read
 if {![winfo ismapped .]} {
        wm deiconify .