diff --git a/lib/branch_create.tcl b/lib/branch_create.tcl index 7f82424eda..0272d6f024 100644 --- a/lib/branch_create.tcl +++ b/lib/branch_create.tcl @@ -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,7 +147,46 @@ 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 "'$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 "'$newbranch' is not an acceptable branch name." + focus $w_name + return + } + + if {[catch {set new [$w_rev get_commit]}]} { + tk_messageBox \ + -icon error \ + -type ok \ + -title [wm title $w] \ + -parent $w \ + -message "Invalid revision: [$w_rev get]" + return + } + + 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 \ @@ -133,52 +195,166 @@ method _create {} { -message "Branch '$newbranch' already exists." 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." - 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 {[catch {set cmt [$w_rev get_commit]}]} { - tk_messageBox \ - -icon error \ - -type ok \ - -title [wm title $w] \ - -parent $w \ - -message "Invalid starting revision: [$w_rev get]" - return - } - if {[catch { - git update-ref \ - -m "branch: Created from [$w_rev get]" \ - "refs/heads/$newbranch" \ - $cmt \ - $null_sha1 - } err]} { - tk_messageBox \ - -icon error \ - -type ok \ - -title [wm title $w] \ - -parent $w \ - -message "Failed to create '$newbranch'.\n\n$err" - 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 $gitk + + bind $c " + grab $c + focus $c.buttons.cancel + " + bind $c [list destroy $c] + bind $c [list destroy $c] + tkwait window $c + return $reset_ok +} + method _validate {d S} { if {$d == 1} { if {[regexp {[~^:?*\[\0- ]} $S]} {