Code

git-gui: Fast-forward existing branch in branch create dialog
[git.git] / lib / branch_create.tcl
index 7f82424edace4b473977d5fe9d37ee268cb3148f..0272d6f0248cab0bdcf03e072d374ca3215819d0 100644 (file)
@@ -10,7 +10,9 @@ field w_name         ; # new branch name widget
 field name         {}; # name of the branch the user has chosen
 field name_type  user; # type of branch name to use
 
+field opt_merge    ff; # type of merge to apply to existing branch
 field opt_checkout  1; # automatically checkout the new branch?
+field reset_ok      0; # did the user agree to reset?
 
 constructor dialog {} {
        global repo_config
@@ -63,12 +65,33 @@ constructor dialog {} {
        set w_rev [::choose_rev::new $w.rev {Starting Revision}]
        pack $w.rev -anchor nw -fill x -pady 5 -padx 5
 
-       labelframe $w.postActions -text {Post Creation Actions}
-       checkbutton $w.postActions.checkout \
-               -text {Checkout after creation} \
+       labelframe $w.options -text {Options}
+
+       frame $w.options.merge
+       label $w.options.merge.l -text {Update Existing Branch:}
+       pack $w.options.merge.l -side left
+       radiobutton $w.options.merge.no \
+               -text No \
+               -value no \
+               -variable @opt_merge
+       pack $w.options.merge.no -side left
+       radiobutton $w.options.merge.ff \
+               -text {Fast Forward Only} \
+               -value ff \
+               -variable @opt_merge
+       pack $w.options.merge.ff -side left
+       radiobutton $w.options.merge.reset \
+               -text {Reset} \
+               -value reset \
+               -variable @opt_merge
+       pack $w.options.merge.reset -side left
+       pack $w.options.merge -anchor nw
+
+       checkbutton $w.options.checkout \
+               -text {Checkout After Creation} \
                -variable @opt_checkout
-       pack $w.postActions.checkout -anchor nw
-       pack $w.postActions -anchor nw -fill x -pady 5 -padx 5
+       pack $w.options.checkout -anchor nw
+       pack $w.options -anchor nw -fill x -pady 5 -padx 5
 
        set name $repo_config(gui.newbranchtemplate)
 
@@ -84,7 +107,7 @@ constructor dialog {} {
 
 method _create {} {
        global null_sha1 repo_config
-       global all_heads
+       global all_heads current_branch
 
        switch -- $name_type {
        user {
@@ -124,61 +147,214 @@ method _create {} {
                focus $w_name
                return
        }
-       if {![catch {git show-ref --verify -- "refs/heads/$newbranch"}]} {
+
+       if {$newbranch eq $current_branch} {
                tk_messageBox \
                        -icon error \
                        -type ok \
                        -title [wm title $w] \
                        -parent $w \
-                       -message "Branch '$newbranch' already exists."
+                       -message "'$newbranch' already exists and is the current branch."
                focus $w_name
                return
        }
+
        if {[catch {git check-ref-format "heads/$newbranch"}]} {
                tk_messageBox \
                        -icon error \
                        -type ok \
                        -title [wm title $w] \
                        -parent $w \
-                       -message "We do not like '$newbranch' as a branch name."
+                       -message "'$newbranch' is not an acceptable branch name."
                focus $w_name
                return
        }
 
-       if {[catch {set cmt [$w_rev get_commit]}]} {
+       if {[catch {set new [$w_rev get_commit]}]} {
                tk_messageBox \
                        -icon error \
                        -type ok \
                        -title [wm title $w] \
                        -parent $w \
-                       -message "Invalid starting revision: [$w_rev get]"
+                       -message "Invalid revision: [$w_rev get]"
                return
        }
-       if {[catch {
-                       git update-ref \
-                               -m "branch: Created from [$w_rev get]" \
-                               "refs/heads/$newbranch" \
-                               $cmt \
-                               $null_sha1
-               } err]} {
+
+       set ref refs/heads/$newbranch
+       if {[catch {set cur [git rev-parse --verify "$ref^0"]}]} {
+               # Assume it does not exist, and that is what the error was.
+               #
+               set reflog_msg "branch: Created from [$w_rev get]"
+               set cur $null_sha1
+       } elseif {$opt_merge eq {no}} {
                tk_messageBox \
                        -icon error \
                        -type ok \
                        -title [wm title $w] \
                        -parent $w \
-                       -message "Failed to create '$newbranch'.\n\n$err"
+                       -message "Branch '$newbranch' already exists."
+               focus $w_name
                return
+       } else {
+               set mrb {}
+               catch {set mrb [git merge-base $new $cur]}
+               switch -- $opt_merge {
+               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 [$w_rev get]: Fast-forward"
+                       } else {
+                               tk_messageBox \
+                                       -icon error \
+                                       -type ok \
+                                       -title [wm title $w] \
+                                       -parent $w \
+                                       -message "Branch '$newbranch' already exists.\n\nIt cannot fast-forward to [$w_rev get].\nA merge is required."
+                               focus $w_name
+                               return
+                       }
+               }
+               reset {
+                       if {$mrb eq $cur} {
+                               # The current branch is older.
+                               #
+                               set reflog_msg "merge [$w_rev get]: Fast-forward"
+                       } else {
+                               # The current branch will lose things.
+                               #
+                               if {[_confirm_reset $this $newbranch $cur $new]} {
+                                       set reflog_msg "reset [$w_rev get]"
+                               } else {
+                                       return
+                               }
+                       }
+               }
+               default {
+                       tk_messageBox \
+                               -icon error \
+                               -type ok \
+                               -title [wm title $w] \
+                               -parent $w \
+                               -message "Branch '$newbranch' already exists."
+                       focus $w_name
+                       return
+               }
+               }
+       }
+
+       if {$new ne $cur} {
+               if {[catch {
+                               git update-ref -m $reflog_msg $ref $new $cur
+                       } err]} {
+                       tk_messageBox \
+                               -icon error \
+                               -type ok \
+                               -title [wm title $w] \
+                               -parent $w \
+                               -message "Failed to create '$newbranch'.\n\n$err"
+                       return
+               }
+       }
+
+       if {$cur eq $null_sha1} {
+               lappend all_heads $newbranch
+               set all_heads [lsort -uniq $all_heads]
+               populate_branch_menu
        }
 
-       lappend all_heads $newbranch
-       set all_heads [lsort $all_heads]
-       populate_branch_menu
        destroy $w
        if {$opt_checkout} {
                switch_branch $newbranch
        }
 }
 
+method _confirm_reset {newbranch cur new} {
+       set reset_ok 0
+       set gitk [list do_gitk [list $cur ^$new]]
+
+       set c $w.confirm_reset
+       toplevel $c
+       wm title $c "Confirm Branch Reset"
+       wm geometry $c "+[winfo rootx $w]+[winfo rooty $w]"
+
+       pack [label $c.msg1 \
+               -anchor w \
+               -justify left \
+               -text "Resetting '$newbranch' to [$w_rev get] will lose the following commits:" \
+               ] -anchor w
+
+       set list $c.list.l
+       frame $c.list
+       text $list \
+               -font font_diff \
+               -width 80 \
+               -height 10 \
+               -wrap none \
+               -xscrollcommand [list $c.list.sbx set] \
+               -yscrollcommand [list $c.list.sby set]
+       scrollbar $c.list.sbx -orient h -command [list $list xview]
+       scrollbar $c.list.sby -orient v -command [list $list yview]
+       pack $c.list.sbx -fill x -side bottom
+       pack $c.list.sby -fill y -side right
+       pack $list -fill both -expand 1
+       pack $c.list -fill both -expand 1 -padx 5 -pady 5
+
+       pack [label $c.msg2 \
+               -anchor w \
+               -justify left \
+               -text "Recovering lost commits may not be easy." \
+               ]
+       pack [label $c.msg3 \
+               -anchor w \
+               -justify left \
+               -text "Reset '$newbranch'?" \
+               ]
+
+       frame $c.buttons
+       button $c.buttons.visualize \
+               -text Visualize \
+               -command $gitk
+       pack $c.buttons.visualize -side left
+       button $c.buttons.reset \
+               -text Reset \
+               -command "
+                       set @reset_ok 1
+                       destroy $c
+               "
+       pack $c.buttons.reset -side right
+       button $c.buttons.cancel \
+               -default active \
+               -text Cancel \
+               -command [list destroy $c]
+       pack $c.buttons.cancel -side right -padx 5
+       pack $c.buttons -side bottom -fill x -pady 10 -padx 10
+
+       set fd [open "| git rev-list --pretty=oneline $cur ^$new" r]
+       while {[gets $fd line] > 0} {
+               set abbr [string range $line 0 7]
+               set subj [string range $line 41 end]
+               $list insert end "$abbr  $subj\n"
+       }
+       close $fd
+       $list configure -state disabled
+
+       bind $c    <Key-v> $gitk
+
+       bind $c <Visibility> "
+               grab $c
+               focus $c.buttons.cancel
+       "
+       bind $c <Key-Return> [list destroy $c]
+       bind $c <Key-Escape> [list destroy $c]
+       tkwait window $c
+       return $reset_ok
+}
+
 method _validate {d S} {
        if {$d == 1} {
                if {[regexp {[~^:?*\[\0- ]} $S]} {