git-gui: Allow users to rename branches through 'branch -m'
authorShawn O. Pearce <spearce@spearce.org>
Mon, 28 May 2007 16:52:57 +0000 (12:52 -0400)
committerShawn O. Pearce <spearce@spearce.org>
Mon, 28 May 2007 21:50:42 +0000 (17:50 -0400)
Git's native command line interface has had branch renaming
support for quite a while, through the -m/-M options to the
git-branch command line tool.  This is an extremely useful
feature as users may decide that the name of their current
branch is not an adequate description, or was just entered
incorrectly when it was created.

Even though most people would consider git-branch to be a
Porcelain tool I'm using it here in git-gui as it is the
only code that implements the rather complex set of logic
needed to successfully rename a branch in Git.  Currently
that is along the lines of:

 *) Backup the ref
 *) Backup the reflog
 *) Delete the old ref
 *) Create the new ref
 *) Move the backed up reflog to the new ref
 *) Record the rename event in the reflog
 *) If the current branch was renamed, update HEAD
 *) If HEAD changed, record the rename event in the HEAD reflog
 *) Rename the [branch "$name"] section in the config file

Since that is some rather ugly set of functionality to implement
and get right, and some of it isn't easily accessible through the
raw plumbing layer I'm just cheating by relying on the Porcelain.

Signed-off-by: Shawn O. Pearce <spearce@spearce.org>
git-gui.sh
lib/branch_rename.tcl [new file with mode: 0644]

index d640b143f31510f3a1de41444def5b71f0a47e4e..755f0643bec3346746e3ee03ed4d97becb4830a1 100755 (executable)
@@ -200,6 +200,15 @@ proc is_config_true {name} {
        }
 }
 
+proc get_config {name} {
+       global repo_config
+       if {[catch {set v $repo_config($name)}]} {
+               return {}
+       } else {
+               return $v
+       }
+}
+
 proc load_config {include_global} {
        global repo_config global_config default_config
 
@@ -1420,6 +1429,11 @@ if {[is_enabled branch]} {
        lappend disable_on_lock [list .mbar.branch entryconf \
                [.mbar.branch index last] -state]
 
+       .mbar.branch add command -label {Rename...} \
+               -command branch_rename::dialog
+       lappend disable_on_lock [list .mbar.branch entryconf \
+               [.mbar.branch index last] -state]
+
        .mbar.branch add command -label {Delete...} \
                -command do_delete_branch
        lappend disable_on_lock [list .mbar.branch entryconf \
diff --git a/lib/branch_rename.tcl b/lib/branch_rename.tcl
new file mode 100644 (file)
index 0000000..54c72b9
--- /dev/null
@@ -0,0 +1,137 @@
+# git-gui branch rename support
+# Copyright (C) 2007 Shawn Pearce
+
+class branch_rename {
+
+field w
+field oldname
+field newname
+
+constructor dialog {} {
+       global all_heads current_branch
+
+       make_toplevel top w
+       wm title $top "[appname] ([reponame]): Rename Branch"
+       if {$top ne {.}} {
+               wm geometry $top "+[winfo rootx .]+[winfo rooty .]"
+       }
+
+       set oldname $current_branch
+       set newname [get_config gui.newbranchtemplate]
+
+       label $w.header -text {Rename Branch} -font font_uibold
+       pack $w.header -side top -fill x
+
+       frame $w.buttons
+       button $w.buttons.rename -text Rename \
+               -default active \
+               -command [cb _rename]
+       pack $w.buttons.rename -side right
+       button $w.buttons.cancel -text {Cancel} \
+               -command [list destroy $w]
+       pack $w.buttons.cancel -side right -padx 5
+       pack $w.buttons -side bottom -fill x -pady 10 -padx 10
+
+       frame $w.rename
+       label $w.rename.oldname_l -text {Branch:}
+       eval tk_optionMenu $w.rename.oldname_m @oldname $all_heads
+
+       label $w.rename.newname_l -text {New Name:}
+       entry $w.rename.newname_t \
+               -borderwidth 1 \
+               -relief sunken \
+               -width 40 \
+               -textvariable @newname \
+               -validate key \
+               -validatecommand {
+                       if {%d == 1 && [regexp {[~^:?*\[\0- ]} %S]} {return 0}
+                       return 1
+               }
+
+       grid $w.rename.oldname_l $w.rename.oldname_m -sticky w  -padx {0 5}
+       grid $w.rename.newname_l $w.rename.newname_t -sticky we -padx {0 5}
+       grid columnconfigure $w.rename 1 -weight 1
+       pack $w.rename -anchor nw -fill x -pady 5 -padx 5
+
+       bind $w <Key-Return> [cb _rename]
+       bind $w <Key-Escape> [list destroy $w]
+       bind $w <Visibility> "
+               grab $w
+               $w.rename.newname_t icursor end
+               focus $w.rename.newname_t
+       "
+       bind $w.header <Destroy> [list delete_this $this]
+       tkwait window $w
+}
+
+method _rename {} {
+       global all_heads current_branch
+
+       if {$oldname eq {}} {
+               tk_messageBox \
+                       -icon error \
+                       -type ok \
+                       -title [wm title $w] \
+                       -parent $w \
+                       -message "Please select a branch to rename."
+               focus $w.rename.oldname_m
+               return
+       }
+       if {$newname eq {}
+               || $newname eq [get_config gui.newbranchtemplate]} {
+               tk_messageBox \
+                       -icon error \
+                       -type ok \
+                       -title [wm title $w] \
+                       -parent $w \
+                       -message "Please supply a branch name."
+               focus $w.rename.newname_t
+               return
+       }
+       if {![catch {git show-ref --verify -- "refs/heads/$newname"}]} {
+               tk_messageBox \
+                       -icon error \
+                       -type ok \
+                       -title [wm title $w] \
+                       -parent $w \
+                       -message "Branch '$newname' already exists."
+               focus $w.rename.newname_t
+               return
+       }
+       if {[catch {git check-ref-format "heads/$newname"}]} {
+               tk_messageBox \
+                       -icon error \
+                       -type ok \
+                       -title [wm title $w] \
+                       -parent $w \
+                       -message "We do not like '$newname' as a branch name."
+               focus $w.rename.newname_t
+               return
+       }
+
+       if {[catch {git branch -m $oldname $newname} err]} {
+               tk_messageBox \
+                       -icon error \
+                       -type ok \
+                       -title [wm title $w] \
+                       -parent $w \
+                       -message "Failed to rename '$oldname'.\n\n$err"
+               return
+       }
+
+       set oldidx [lsearch -exact -sorted $all_heads $oldname]
+       if {$oldidx >= 0} {
+               set all_heads [lreplace $all_heads $oldidx $oldidx]
+       }
+       lappend all_heads $newname
+       set all_heads [lsort $all_heads]
+       populate_branch_menu
+
+       if {$current_branch eq $oldname} {
+               set current_branch $newname
+       }
+
+       destroy $w
+}
+
+}