git-gui: Added support for pulling from default branch of a remote.
[gitweb.git] / git-gui
diff --git a/git-gui b/git-gui
index bb89da43320ed53b819fa9f973c6cdaaf8783128..4aa035a6c4536d266f21ec41d131ecbd9651fe6f 100755 (executable)
--- a/git-gui
+++ b/git-gui
@@ -92,7 +92,7 @@ proc update_status {{final Ready.}} {
        }
 
        if {![$ui_comm edit modified]
-           || [string trim [$ui_comm get 0.0 end]] == {}} {
+               || [string trim [$ui_comm get 0.0 end]] == {}} {
                if {[load_message GITGUI_MSG]} {
                } elseif {[load_message MERGE_MSG]} {
                } elseif {[load_message SQUASH_MSG]} {
@@ -266,6 +266,9 @@ proc show_diff {path} {
                $ui_diff conf -state normal
                $ui_diff insert end $content
                $ui_diff conf -state disabled
+               set diff_active 0
+               unlock_index
+               set ui_status_value {Ready.}
                return
        }
        }
@@ -482,8 +485,7 @@ A good commit message has the following format:
        set ui_status_value {Committing changes...}
 
        set fd_wt [open "| git write-tree" r]
-       fileevent $fd_wt readable \
-               [list commit_stage2 $fd_wt $curHEAD $msg]
+       fileevent $fd_wt readable [list commit_stage2 $fd_wt $curHEAD $msg]
 }
 
 proc commit_stage2 {fd_wt curHEAD msg} {
@@ -510,8 +512,8 @@ proc commit_stage2 {fd_wt curHEAD msg} {
        if {$commit_type == {merge}} {
                if {[catch {
                                set fd_mh [open [file join $gitdir MERGE_HEAD] r]
-                               while {[gets $fd_mh merge_head] > 0} {
-                                       lappend -p $merge_head
+                               while {[gets $fd_mh merge_head] >= 0} {
+                                       lappend cmd -p $merge_head
                                }
                                close $fd_mh
                        } err]} {
@@ -576,10 +578,49 @@ proc commit_stage2 {fd_wt curHEAD msg} {
 
        set commit_type {}
        set commit_active 0
+       set HEAD $cmt_id
+       set PARENT $cmt_id
        unlock_index
        update_status "Changes committed as $cmt_id."
 }
 
+######################################################################
+##
+## fetch pull push
+
+proc fetch_from {remote} {
+       set w [new_console "fetch $remote" \
+               "Fetching new changes from $remote"]
+       set cmd [list git fetch]
+       lappend cmd $remote
+       console_exec $w $cmd
+}
+
+proc pull_remote {remote branch} {
+       set w [new_console "pull $remote $branch" \
+               "Pulling new changes from branch $branch in $remote"]
+       set cmd [list git pull]
+       lappend cmd $remote
+       lappend cmd $branch
+       console_exec $w $cmd [list post_pull_remote $remote $branch]
+}
+
+proc post_pull_remote {remote branch success} {
+       if {$success} {
+               update_status "Successfully pulled $branch from $remote."
+       } else {
+               update_status "Conflicts detected while pulling $branch from $remote."
+       }
+}
+
+proc push_to {remote} {
+       set w [new_console "push $remote" \
+               "Pushing changes to $remote"]
+       set cmd [list git push]
+       lappend cmd $remote
+       console_exec $w $cmd
+}
+
 ######################################################################
 ##
 ## ui helpers
@@ -739,7 +780,7 @@ proc update_index {path} {
 }
 
 proc toggle_mode {path} {
-       global file_states
+       global file_states ui_fname_value
 
        set s $file_states($path)
        set m [lindex $s 0]
@@ -749,12 +790,101 @@ proc toggle_mode {path} {
        _O {set new A*}
        _M -
        MM {set new M*}
+       AD -
        _D {set new D*}
        default {return}
        }
 
        with_update_index {update_index $path}
        display_file $path $new
+       if {$ui_fname_value == $path} {
+               show_diff $path
+       }
+}
+
+######################################################################
+##
+## config (fetch push pull)
+
+proc load_repo_config {} {
+       global repo_config
+
+       array unset repo_config
+       catch {
+               set fd_rc [open "| git repo-config --list" r]
+               while {[gets $fd_rc line] >= 0} {
+                       if {[regexp {^([^=]+)=(.*)$} $line line name value]} {
+                               lappend repo_config($name) $value
+                       }
+               }
+               close $fd_rc
+       }
+}
+
+proc load_all_remotes {} {
+       global gitdir all_remotes repo_config
+
+       set all_remotes [list]
+       set rm_dir [file join $gitdir remotes]
+       if {[file isdirectory $rm_dir]} {
+               set all_remotes [concat $all_remotes [glob \
+                       -types f \
+                       -tails \
+                       -nocomplain \
+                       -directory $rm_dir *]]
+       }
+
+       foreach line [array names repo_config remote.*.url] {
+               if {[regexp ^remote\.(.*)\.url\$ $line line name]} {
+                       lappend all_remotes $name
+               }
+       }
+
+       set all_remotes [lsort -unique $all_remotes]
+}
+
+proc populate_remote_menu {m pfx op} {
+       global all_remotes mainfont
+
+       foreach remote $all_remotes {
+               $m add command -label "$pfx $remote..." \
+                       -command [list $op $remote] \
+                       -font $mainfont
+       }
+}
+
+proc populate_pull_menu {m} {
+       global gitdir repo_config all_remotes mainfont
+
+       foreach remote $all_remotes {
+               set rb {}
+               if {[array get repo_config remote.$remote.url] != {}} {
+                       if {[array get repo_config remote.$remote.fetch] != {}} {
+                               regexp {^([^:]+):} \
+                                       [lindex $repo_config(remote.$remote.fetch) 0] \
+                                       line rb
+                       }
+               } else {
+                       catch {
+                               set fd [open [file join $gitdir remotes $remote] r]
+                               while {[gets $fd line] >= 0} {
+                                       if {[regexp {^Pull:[ \t]*([^:]+):} $line line rb]} {
+                                               break
+                                       }
+                               }
+                               close $fd
+                       }
+               }
+
+               set rb_short $rb
+               regsub ^refs/heads/ $rb {} rb_short
+               if {$rb_short != {}} {
+                       $m add command \
+                               -label "Branch $rb_short from $remote..." \
+                               -command [list pull_remote $remote $rb] \
+                               -font $mainfont
+               }
+       }
 }
 
 ######################################################################
@@ -843,6 +973,7 @@ foreach i {
                {_O o plain    "Untracked"}
                {A_ o fulltick "Added"}
                {AM o parttick "Partially added"}
+               {AD o question  "Added (but now gone)"}
 
                {_D i question "Missing"}
                {D_ i removed  "Removed"}
@@ -873,17 +1004,20 @@ proc error_popup {msg} {
 }
 
 proc show_msg {w top msg} {
-       global gitdir appname
+       global gitdir appname mainfont
 
        message $w.m -text $msg -justify left -aspect 400
        pack $w.m -side top -fill x -padx 5 -pady 10
        button $w.ok -text OK \
                -width 15 \
+               -font $mainfont \
                -command "destroy $top"
        pack $w.ok -side bottom
        bind $top <Visibility> "grab $top; focus $top"
        bind $top <Key-Return> "destroy $top"
-       wm title $top "error: $appname ([file normalize [file dirname $gitdir]])"
+       wm title $w "$appname ([lindex [file split \
+               [file normalize [file dirname $gitdir]]] \
+               end]): error"
        tkwait window $top
 }
 
@@ -922,20 +1056,151 @@ proc hook_failed_popup {hook msg} {
 
        button $w.ok -text OK \
                -width 15 \
+               -font $mainfont \
                -command "destroy $w"
        pack $w.ok -side bottom
 
        bind $w <Visibility> "grab $w; focus $w"
        bind $w <Key-Return> "destroy $w"
-       wm title $w "error: $appname ([file normalize [file dirname $gitdir]])"
+       wm title $w "$appname ([lindex [file split \
+               [file normalize [file dirname $gitdir]]] \
+               end]): error"
        tkwait window $w
 }
 
+set next_console_id 0
+
+proc new_console {short_title long_title} {
+       global next_console_id console_data
+       set w .console[incr next_console_id]
+       set console_data($w) [list $short_title $long_title]
+       return [console_init $w]
+}
+
+proc console_init {w} {
+       global console_cr console_data
+       global gitdir appname mainfont difffont
+
+       set console_cr($w) 1.0
+       toplevel $w
+       frame $w.m
+       label $w.m.l1 -text "[lindex $console_data($w) 1]:" \
+               -anchor w \
+               -justify left \
+               -font [concat $mainfont bold]
+       text $w.m.t \
+               -background white -borderwidth 1 \
+               -relief sunken \
+               -width 80 -height 10 \
+               -font $difffont \
+               -state disabled \
+               -yscrollcommand [list $w.m.sby set]
+       label $w.m.s -anchor w \
+               -justify left \
+               -font [concat $mainfont bold]
+       scrollbar $w.m.sby -command [list $w.m.t yview]
+       pack $w.m.l1 -side top -fill x
+       pack $w.m.s -side bottom -fill x
+       pack $w.m.sby -side right -fill y
+       pack $w.m.t -side left -fill both -expand 1
+       pack $w.m -side top -fill both -expand 1 -padx 5 -pady 10
+
+       button $w.ok -text {Running...} \
+               -width 15 \
+               -font $mainfont \
+               -state disabled \
+               -command "destroy $w"
+       pack $w.ok -side bottom
+
+       bind $w <Visibility> "focus $w"
+       wm title $w "$appname ([lindex [file split \
+               [file normalize [file dirname $gitdir]]] \
+               end]): [lindex $console_data($w) 0]"
+       return $w
+}
+
+proc console_exec {w cmd {after {}}} {
+       global tcl_platform
+
+       # -- Windows tosses the enviroment when we exec our child.
+       #    But most users need that so we have to relogin. :-(
+       #
+       if {$tcl_platform(platform) == {windows}} {
+               set cmd [list sh --login -c "cd \"[pwd]\" && [join $cmd { }]"]
+       }
+
+       # -- Tcl won't let us redirect both stdout and stderr to
+       #    the same pipe.  So pass it through cat...
+       #
+       set cmd [concat | $cmd |& cat]
+
+       set fd_f [open $cmd r]
+       fconfigure $fd_f -blocking 0 -translation binary
+       fileevent $fd_f readable [list console_read $w $fd_f $after]
+}
+
+proc console_read {w fd after} {
+       global console_cr console_data
+
+       set buf [read $fd]
+       if {$buf != {}} {
+               if {![winfo exists $w]} {console_init $w}
+               $w.m.t conf -state normal
+               set c 0
+               set n [string length $buf]
+               while {$c < $n} {
+                       set cr [string first "\r" $buf $c]
+                       set lf [string first "\n" $buf $c]
+                       if {$cr < 0} {set cr [expr $n + 1]}
+                       if {$lf < 0} {set lf [expr $n + 1]}
+
+                       if {$lf < $cr} {
+                               $w.m.t insert end [string range $buf $c $lf]
+                               set console_cr($w) [$w.m.t index {end -1c}]
+                               set c $lf
+                               incr c
+                       } else {
+                               $w.m.t delete $console_cr($w) end
+                               $w.m.t insert end "\n"
+                               $w.m.t insert end [string range $buf $c $cr]
+                               set c $cr
+                               incr c
+                       }
+               }
+               $w.m.t conf -state disabled
+               $w.m.t see end
+       }
+
+       fconfigure $fd -blocking 1
+       if {[eof $fd]} {
+               if {[catch {close $fd}]} {
+                       if {![winfo exists $w]} {console_init $w}
+                       $w.m.s conf -background red -text {Error: Command Failed}
+                       $w.ok conf -text Close
+                       $w.ok conf -state normal
+                       set ok 0
+               } elseif {[winfo exists $w]} {
+                       $w.m.s conf -background green -text {Success}
+                       $w.ok conf -text Close
+                       $w.ok conf -state normal
+                       set ok 1
+               }
+               array unset console_cr $w
+               array unset console_data $w
+               if {$after != {}} {
+                       uplevel #0 $after $ok
+               }
+               return
+       }
+       fconfigure $fd -blocking 0
+}
+
 ######################################################################
 ##
 ## ui commands
 
 set starting_gitk_msg {Please wait... Starting gitk...}
+
 proc do_gitk {} {
        global tcl_platform ui_status_value starting_gitk_msg
 
@@ -946,7 +1211,7 @@ proc do_gitk {} {
                }
        }
 
-    if {$tcl_platform(platform) == "windows"} {
+       if {$tcl_platform(platform) == {windows}} {
                exec sh -c gitk &
        } else {
                exec gitk &
@@ -1063,9 +1328,10 @@ set mainfont {Helvetica 10}
 set difffont {Courier 10}
 set maincursor [. cget -cursor]
 
-switch -- $tcl_platform(platform) {
-windows {set M1B Control; set M1T Ctrl}
-default {set M1B M1; set M1T M1}
+switch -glob -- "$tcl_platform(platform),$tcl_platform(os)" {
+windows,*   {set M1B Control; set M1T Ctrl}
+unix,Darwin {set M1B M1; set M1T Cmd}
+default     {set M1B M1; set M1T M1}
 }
 
 # -- Menu Bar
@@ -1074,6 +1340,7 @@ menu .mbar -tearoff 0
 .mbar add cascade -label Commit -menu .mbar.commit
 .mbar add cascade -label Fetch -menu .mbar.fetch
 .mbar add cascade -label Pull -menu .mbar.pull
+.mbar add cascade -label Push -menu .mbar.push
 . configure -menu .mbar
 
 # -- Project Menu
@@ -1122,6 +1389,9 @@ menu .mbar.fetch
 # -- Pull Menu
 menu .mbar.pull
 
+# -- Push Menu
+menu .mbar.push
+
 # -- Main Window Layout
 panedwindow .vpane -orient vertical
 panedwindow .vpane.files -orient horizontal
@@ -1261,10 +1531,18 @@ lappend disable_on_lock {.vpane.commarea.buttons.commit conf -state}
 # -- Commit Message Buffer
 frame .vpane.commarea.buffer
 set ui_comm .vpane.commarea.buffer.t
-label .vpane.commarea.buffer.l -text {Commit Message:} \
+set ui_coml .vpane.commarea.buffer.l
+label $ui_coml -text {Commit Message:} \
        -anchor w \
        -justify left \
        -font $mainfont
+trace add variable commit_type write {uplevel #0 {
+       switch -glob $commit_type \
+       initial {$ui_coml conf -text {Initial Commit Message:}} \
+       amend   {$ui_coml conf -text {Amended Commit Message:}} \
+       merge   {$ui_coml conf -text {Merge Commit Message:}} \
+       *       {$ui_coml conf -text {Commit Message:}}
+}}
 text $ui_comm -background white -borderwidth 1 \
        -relief sunken \
        -width 75 -height 10 -wrap none \
@@ -1272,7 +1550,7 @@ text $ui_comm -background white -borderwidth 1 \
        -yscrollcommand {.vpane.commarea.buffer.sby set} \
        -cursor $maincursor
 scrollbar .vpane.commarea.buffer.sby -command [list $ui_comm yview]
-pack .vpane.commarea.buffer.l -side top -fill x
+pack $ui_coml -side top -fill x
 pack .vpane.commarea.buffer.sby -side right -fill y
 pack $ui_comm -side left -fill y
 pack .vpane.commarea.buffer -side left -fill y
@@ -1289,17 +1567,19 @@ pack .status -anchor w -side bottom -fill x
 
 # -- Key Bindings
 bind $ui_comm <$M1B-Key-Return> {do_commit;break}
-bind . <Destroy> do_quit
-bind . <Key-F5> do_rescan
-bind . <$M1B-Key-r> do_rescan
-bind . <$M1B-Key-R> do_rescan
-bind . <$M1B-Key-s> do_signoff
-bind . <$M1B-Key-S> do_signoff
-bind . <$M1B-Key-u> do_checkin_all
-bind . <$M1B-Key-U> do_checkin_all
-bind . <$M1B-Key-Return> do_commit
-bind . <$M1B-Key-q> do_quit
-bind . <$M1B-Key-Q> do_quit
+bind .   <Destroy> do_quit
+bind all <Key-F5> do_rescan
+bind all <$M1B-Key-r> do_rescan
+bind all <$M1B-Key-R> do_rescan
+bind .   <$M1B-Key-s> do_signoff
+bind .   <$M1B-Key-S> do_signoff
+bind .   <$M1B-Key-u> do_checkin_all
+bind .   <$M1B-Key-U> do_checkin_all
+bind .   <$M1B-Key-Return> do_commit
+bind all <$M1B-Key-q> do_quit
+bind all <$M1B-Key-Q> do_quit
+bind all <$M1B-Key-w> {destroy [winfo toplevel %W]}
+bind all <$M1B-Key-W> {destroy [winfo toplevel %W]}
 foreach i [list $ui_index $ui_other] {
        bind $i <Button-1> {click %W %x %y 1 %X %Y; break}
        bind $i <Button-3> {click %W %x %y 3 %X %Y; break}
@@ -1311,16 +1591,32 @@ unset i M1B M1T
 ##
 ## main
 
+set appname [lindex [file split $argv0] end]
+set gitdir {}
+
+if {[catch {set cdup [exec git rev-parse --show-cdup]} err]} {
+       show_msg {} . "Cannot find the git directory: $err"
+       exit 1
+}
+if {$cdup != ""} {
+       cd $cdup
+}
+unset cdup
+
 if {[catch {set gitdir [exec git rev-parse --git-dir]} err]} {
        show_msg {} . "Cannot find the git directory: $err"
        exit 1
 }
 
-set appname [lindex [file split $argv0] end]
 if {$appname == {git-citool}} {
        set single_commit 1
 }
 
 wm title . "$appname ([file normalize [file dirname $gitdir]])"
 focus -force $ui_comm
+load_repo_config
+load_all_remotes
+populate_remote_menu .mbar.fetch From fetch_from
+populate_remote_menu .mbar.push To push_to
+populate_pull_menu .mbar.pull
 update_status