Merge branch 'js/commit-gpgsign' into pu
[gitweb.git] / lib / checkout_op.tcl
index 844d8e551c14ba743fbb31746462440b15bb22d0..9e7412c446f715588b9cfe21654a3447e8680e12 100644 (file)
@@ -9,16 +9,20 @@ field w_cons   {}; # embedded console window object
 field new_expr   ; # expression the user saw/thinks this is
 field new_hash   ; # commit SHA-1 we are switching to
 field new_ref    ; # ref we are updating/creating
+field old_hash   ; # commit SHA-1 that was checked out when we started
 
 field parent_w      .; # window that started us
 field merge_type none; # type of merge to apply to existing branch
+field merge_base   {}; # merge base if we have another ref involved
 field fetch_spec   {}; # refetch tracking branch if used?
 field checkout      1; # actually checkout the branch?
 field create        0; # create the branch if it doesn't exist?
+field remote_source {}; # same as fetch_spec, to setup tracking
 
 field reset_ok      0; # did the user agree to reset?
 field fetch_ok      0; # did the fetch succeed?
 
+field readtree_d   {}; # buffered output from read-tree
 field update_old   {}; # was the update-ref call deferred?
 field reflog_msg   {}; # log message for the update-ref call
 
@@ -42,6 +46,10 @@ method enable_fetch {spec} {
        set fetch_spec $spec
 }
 
+method remote_source {spec} {
+       set remote_source $spec
+}
+
 method enable_checkout {co} {
        set checkout $co
 }
@@ -64,14 +72,19 @@ method run {} {
                set r_head [lindex $fetch_spec 2]
                regsub ^refs/heads/ $r_head {} r_name
 
+               set cmd [list git fetch $remote]
+               if {$l_trck ne {}} {
+                       lappend cmd +$r_head:$l_trck
+               } else {
+                       lappend cmd $r_head
+               }
+
                _toplevel $this {Refreshing Tracking Branch}
                set w_cons [::console::embed \
                        $w.console \
-                       "Fetching $r_name from $remote"]
+                       [mc "Fetching %s from %s" $r_name $remote]]
                pack $w.console -fill both -expand 1
-               $w_cons exec \
-                       [list git fetch $remote +$r_head:$l_trck] \
-                       [cb _finish_fetch]
+               $w_cons exec $cmd [cb _finish_fetch]
 
                bind $w <$M1B-Key-w> break
                bind $w <$M1B-Key-W> break
@@ -112,9 +125,12 @@ method _noop {} {}
 method _finish_fetch {ok} {
        if {$ok} {
                set l_trck [lindex $fetch_spec 0]
+               if {$l_trck eq {}} {
+                       set l_trck FETCH_HEAD
+               }
                if {[catch {set new_hash [git rev-parse --verify "$l_trck^0"]} err]} {
                        set ok 0
-                       $w_cons insert "fatal: Cannot resolve $l_trck"
+                       $w_cons insert [mc "fatal: Cannot resolve %s" $l_trck]
                        $w_cons insert $err
                }
        }
@@ -127,7 +143,7 @@ method _finish_fetch {ok} {
                destroy $w
                set w {}
        } else {
-               button $w.close -text Close -command [list destroy $w]
+               button $w.close -text [mc Close] -command [list destroy $w]
                pack $w.close -side bottom -anchor e -padx 10 -pady 10
        }
 
@@ -135,7 +151,7 @@ method _finish_fetch {ok} {
 }
 
 method _update_ref {} {
-       global null_sha1 current_branch
+       global null_sha1 current_branch repo_config
 
        set ref $new_ref
        set new $new_hash
@@ -156,17 +172,34 @@ method _update_ref {} {
                # Assume it does not exist, and that is what the error was.
                #
                if {!$create} {
-                       _error $this "Branch '$newbranch' does not exist."
+                       _error $this [mc "Branch '%s' does not exist." $newbranch]
                        return 0
                }
 
                set reflog_msg "branch: Created from $new_expr"
                set cur $null_sha1
+
+               if {($repo_config(branch.autosetupmerge) eq {true}
+                       || $repo_config(branch.autosetupmerge) eq {always})
+                       && $remote_source ne {}
+                       && "refs/heads/$newbranch" eq $ref} {
+
+                       set c_remote [lindex $remote_source 1]
+                       set c_merge [lindex $remote_source 2]
+                       if {[catch {
+                                       git config branch.$newbranch.remote $c_remote
+                                       git config branch.$newbranch.merge  $c_merge
+                               } err]} {
+                               _error $this [strcat \
+                               [mc "Failed to configure simplified git-pull for '%s'." $newbranch] \
+                               "\n\n$err"]
+                       }
+               }
        } elseif {$create && $merge_type eq {none}} {
                # We were told to create it, but not do a merge.
                # Bad.  Name shouldn't have existed.
                #
-               _error $this "Branch '$newbranch' already exists."
+               _error $this [mc "Branch '%s' already exists." $newbranch]
                return 0
        } elseif {!$create && $merge_type eq {none}} {
                # We aren't creating, it exists and we don't merge.
@@ -179,29 +212,25 @@ method _update_ref {} {
                # No merge would be required, don't compute anything.
                #
        } else {
-               set mrb {}
-               catch {set mrb [git merge-base $new $cur]}
-               switch -- $merge_type {
-               ff {
-                       if {$mrb eq $new} {
-                               # The current branch is actually newer.
-                               #
-                               set new $cur
-                       } elseif {$mrb eq $cur} {
-                               # The current branch is older.
-                               #
-                               set reflog_msg "merge $new_expr: Fast-forward"
-                       } else {
-                               _error $this "Branch '$newbranch' already exists.\n\nIt cannot fast-forward to $new_expr.\nA merge is required."
-                               return 0
+               catch {set merge_base [git merge-base $new $cur]}
+               if {$merge_base eq $cur} {
+                       # The current branch is older.
+                       #
+                       set reflog_msg "merge $new_expr: Fast-forward"
+               } else {
+                       switch -- $merge_type {
+                       ff {
+                               if {$merge_base eq $new} {
+                                       # The current branch is actually newer.
+                                       #
+                                       set new $cur
+                                       set new_hash $cur
+                               } else {
+                                       _error $this [mc "Branch '%s' already exists.\n\nIt cannot fast-forward to %s.\nA merge is required." $newbranch $new_expr]
+                                       return 0
+                               }
                        }
-               }
-               reset {
-                       if {$mrb eq $cur} {
-                               # The current branch is older.
-                               #
-                               set reflog_msg "merge $new_expr: Fast-forward"
-                       } else {
+                       reset {
                                # The current branch will lose things.
                                #
                                if {[_confirm_reset $this $cur]} {
@@ -210,11 +239,11 @@ method _update_ref {} {
                                        return 0
                                }
                        }
-               }
-               default {
-                       _error $this "Only 'ff' and 'reset' merge is currently supported."
-                       return 0
-               }
+                       default {
+                               _error $this [mc "Merge strategy '%s' not supported." $merge_type]
+                               return 0
+                       }
+                       }
                }
        }
 
@@ -230,7 +259,7 @@ method _update_ref {} {
                if {[catch {
                                git update-ref -m $reflog_msg $ref $new $cur
                        } err]} {
-                       _error $this "Failed to update '$newbranch'.\n\n$err"
+                       _error $this [strcat [mc "Failed to update '%s'." $newbranch] "\n\n$err"]
                        return 0
                }
        }
@@ -242,7 +271,7 @@ method _checkout {} {
        if {[lock_index checkout_op]} {
                after idle [cb _start_checkout]
        } else {
-               _error $this "Index is already locked."
+               _error $this [mc "Staging area (index) is already locked."]
                delete_this
        }
 }
@@ -252,33 +281,35 @@ method _start_checkout {} {
 
        # -- Our in memory state should match the repository.
        #
-       repository_state curType curHEAD curMERGE_HEAD
+       repository_state curType old_hash curMERGE_HEAD
        if {[string match amend* $commit_type]
                && $curType eq {normal}
-               && $curHEAD eq $HEAD} {
-       } elseif {$commit_type ne $curType || $HEAD ne $curHEAD} {
-               info_popup {Last scanned state does not match repository state.
+               && $old_hash eq $HEAD} {
+       } elseif {$commit_type ne $curType || $HEAD ne $old_hash} {
+               info_popup [mc "Last scanned state does not match repository state.
 
 Another Git program has modified this repository since the last scan.  A rescan must be performed before the current branch can be changed.
 
 The rescan will be automatically started now.
-}
+"]
                unlock_index
                rescan ui_ready
                delete_this
                return
        }
 
-       if {[is_config_true gui.trustmtime]} {
+       if {$old_hash eq $new_hash} {
+               _after_readtree $this
+       } elseif {[is_config_true gui.trustmtime]} {
                _readtree $this
        } else {
-               ui_status {Refreshing file status...}
-               set cmd [list git update-index]
-               lappend cmd -q
-               lappend cmd --unmerged
-               lappend cmd --ignore-missing
-               lappend cmd --refresh
-               set fd [open "| $cmd" r]
+               ui_status [mc "Refreshing file status..."]
+               set fd [git_read update-index \
+                       -q \
+                       --unmerged \
+                       --ignore-missing \
+                       --refresh \
+                       ]
                fconfigure $fd -blocking 0 -translation binary
                fileevent $fd readable [cb _refresh_wait $fd]
        }
@@ -309,51 +340,60 @@ method _name {} {
 method _readtree {} {
        global HEAD
 
-       ui_status "Updating working directory to '[_name $this]'..."
-       set cmd [list git read-tree]
-       lappend cmd -m
-       lappend cmd -u
-       lappend cmd --exclude-per-directory=.gitignore
-       lappend cmd $HEAD
-       lappend cmd $new_hash
-       set fd [open "| $cmd" r]
+       set readtree_d {}
+       $::main_status start \
+               [mc "Updating working directory to '%s'..." [_name $this]] \
+               [mc "files checked out"]
+
+       set fd [git_read --stderr read-tree \
+               -m \
+               -u \
+               -v \
+               --exclude-per-directory=.gitignore \
+               $HEAD \
+               $new_hash \
+               ]
        fconfigure $fd -blocking 0 -translation binary
        fileevent $fd readable [cb _readtree_wait $fd]
 }
 
 method _readtree_wait {fd} {
-       global selected_commit_type commit_type HEAD MERGE_HEAD PARENT
-       global current_branch is_detached
-       global ui_comm
+       global current_branch
+
+       set buf [read $fd]
+       $::main_status update_meter $buf
+       append readtree_d $buf
 
-       # -- We never get interesting output on stdout; only stderr.
-       #
-       read $fd
        fconfigure $fd -blocking 1
        if {![eof $fd]} {
                fconfigure $fd -blocking 0
                return
        }
 
-       set name [_name $this]
-
-       # -- The working directory wasn't in sync with the index and
-       #    we'd have to overwrite something to make the switch. A
-       #    merge is required.
-       #
-       if {[catch {close $fd} err]} {
+       if {[catch {close $fd}]} {
+               set err $readtree_d
                regsub {^fatal: } $err {} err
-               warn_popup "File level merge required.
+               $::main_status stop [mc "Aborted checkout of '%s' (file level merging is required)." [_name $this]]
+               warn_popup [strcat [mc "File level merge required."] "
 
 $err
 
-Staying on branch '$current_branch'."
-               ui_status "Aborted checkout of '$name' (file level merging is required)."
+" [mc "Staying on branch '%s'." $current_branch]]
                unlock_index
                delete_this
                return
        }
 
+       $::main_status stop
+       _after_readtree $this
+}
+
+method _after_readtree {} {
+       global selected_commit_type commit_type HEAD MERGE_HEAD PARENT
+       global current_branch is_detached
+       global ui_comm
+
+       set name [_name $this]
        set log "checkout: moving"
        if {!$is_detached} {
                append log " from $current_branch"
@@ -368,22 +408,24 @@ Staying on branch '$current_branch'."
        set rn [string length $rh]
        if {[string equal -length $rn $rh $new_ref]} {
                set new_branch [string range $new_ref $rn end]
-               append log " to $new_branch"
-
-               if {[catch {
-                               git symbolic-ref -m $log HEAD $new_ref
-                       } err]} {
-                       _fatal $this $err
+               if {$is_detached || $current_branch ne $new_branch} {
+                       append log " to $new_branch"
+                       if {[catch {
+                                       git symbolic-ref -m $log HEAD $new_ref
+                               } err]} {
+                               _fatal $this $err
+                       }
+                       set current_branch $new_branch
+                       set is_detached 0
                }
-               set current_branch $new_branch
-               set is_detached 0
        } else {
-               append log " to $new_expr"
-
-               if {[catch {
-                               _detach_HEAD $log $new_hash
-                       } err]} {
-                       _fatal $this $err
+               if {!$is_detached || $new_hash ne $HEAD} {
+                       append log " to $new_expr"
+                       if {[catch {
+                                       _detach_HEAD $log $new_hash
+                               } err]} {
+                               _fatal $this $err
+                       }
                }
                set current_branch HEAD
                set is_detached 1
@@ -407,28 +449,62 @@ Staying on branch '$current_branch'."
        }
 
        if {$is_detached} {
-               info_popup "You are no longer on a local branch.
+               info_popup [mc "You are no longer on a local branch.
+
+If you wanted to be on a branch, create one now starting from 'This Detached Checkout'."]
+       }
+
+       # -- Run the post-checkout hook.
+       #
+       set fd_ph [githook_read post-checkout $old_hash $new_hash 1]
+       if {$fd_ph ne {}} {
+               global pch_error
+               set pch_error {}
+               fconfigure $fd_ph -blocking 0 -translation binary -eofchar {}
+               fileevent $fd_ph readable [cb _postcheckout_wait $fd_ph]
+       } else {
+               _update_repo_state $this
+       }
+}
 
-If you wanted to be on a branch, create one now starting from 'This Detached Checkout'."
+method _postcheckout_wait {fd_ph} {
+       global pch_error
+
+       append pch_error [read $fd_ph]
+       fconfigure $fd_ph -blocking 1
+       if {[eof $fd_ph]} {
+               if {[catch {close $fd_ph}]} {
+                       hook_failed_popup post-checkout $pch_error 0
+               }
+               unset pch_error
+               _update_repo_state $this
+               return
        }
+       fconfigure $fd_ph -blocking 0
+}
 
+method _update_repo_state {} {
        # -- Update our repository state.  If we were previously in
        #    amend mode we need to toss the current buffer and do a
        #    full rescan to update our file lists.  If we weren't in
        #    amend mode our file lists are accurate and we can avoid
        #    the rescan.
        #
+       global selected_commit_type commit_type HEAD MERGE_HEAD PARENT
+       global ui_comm
+
        unlock_index
+       set name [_name $this]
        set selected_commit_type new
        if {[string match amend* $commit_type]} {
                $ui_comm delete 0.0 end
                $ui_comm edit reset
                $ui_comm edit modified false
-               rescan [list ui_status "Checked out '$name'."]
+               rescan [list ui_status [mc "Checked out '%s'." $name]]
        } else {
                repository_state commit_type HEAD MERGE_HEAD
                set PARENT $HEAD
-               ui_status "Checked out '$name'."
+               ui_status [mc "Checked out '%s'." $name]
        }
        delete_this
 }
@@ -456,7 +532,7 @@ method _confirm_reset {cur} {
        pack [label $w.msg1 \
                -anchor w \
                -justify left \
-               -text "Resetting '$name' to $new_expr will lose the following commits:" \
+               -text [mc "Resetting '%s' to '%s' will lose the following commits:" $name $new_expr]\
                ] -anchor w
 
        set list $w.list.l
@@ -478,21 +554,21 @@ method _confirm_reset {cur} {
        pack [label $w.msg2 \
                -anchor w \
                -justify left \
-               -text {Recovering lost commits may not be easy.} \
+               -text [mc "Recovering lost commits may not be easy."] \
                ]
        pack [label $w.msg3 \
                -anchor w \
                -justify left \
-               -text "Reset '$name'?" \
+               -text [mc "Reset '%s'?" $name] \
                ]
 
        frame $w.buttons
        button $w.buttons.visualize \
-               -text Visualize \
+               -text [mc Visualize] \
                -command $gitk
        pack $w.buttons.visualize -side left
        button $w.buttons.reset \
-               -text Reset \
+               -text [mc Reset] \
                -command "
                        set @reset_ok 1
                        destroy $w
@@ -500,12 +576,12 @@ method _confirm_reset {cur} {
        pack $w.buttons.reset -side right
        button $w.buttons.cancel \
                -default active \
-               -text Cancel \
+               -text [mc Cancel] \
                -command [list destroy $w]
        pack $w.buttons.cancel -side right -padx 5
        pack $w.buttons -side bottom -fill x -pady 10 -padx 10
 
-       set fd [open "| git rev-list --pretty=oneline $cur ^$new_hash" r]
+       set fd [git_read rev-list --pretty=oneline $cur ^$new_hash]
        while {[gets $fd line] > 0} {
                set abbr [string range $line 0 7]
                set subj [string range $line 41 end]
@@ -556,13 +632,13 @@ method _toplevel {title} {
 }
 
 method _fatal {err} {
-       error_popup "Failed to set current branch.
+       error_popup [strcat [mc "Failed to set current branch.
 
 This working directory is only partially switched.  We successfully updated your files, but failed to update an internal Git file.
 
-This should not have occurred.  [appname] will now close and give up.
+This should not have occurred.  %s will now close and give up." [appname]] "
 
-$err"
+$err"]
        exit 1
 }