git-gui: Implement local merge operations.

To allow users to merge local heads and tracking branches we now offer
a dialog which lets the user select 1-15 branches and merge them using
the stock `git merge` Grand Unified Merge Driver.

Originally I had wanted to implement this merge internally within git-gui
as I consider GUMD to be mostly Porcelain-ish, but the truth is it does
its job exceedingly well and its a relatively complex chunk of code.
I'll probably circle back later and try to remove the invocation of GUMD
from git-gui, but right now it lets me get the job done faster.

Users cannot start a merge if they are currently in the middle of one,
or if they are amending a commit.  Trying to do either is just stupid
and should be stopped as early as possible.

I've also made it simple for users to startup a gitk session prior to
a merge by offering a Visualize button which runs `gitk $revs --not HEAD`,
where $revs is the list of branches currently selected in the merge
dialog.  This makes it quite simple to find out what the damage will
be to the current branch if you were to carry out the currently proposed
merge.

Signed-off-by: Shawn O. Pearce <spearce@spearce.org>
This commit is contained in:
Shawn O. Pearce 2007-01-26 03:33:56 -05:00
parent bc7452f5e7
commit e4834837a8

View File

@ -983,8 +983,8 @@ proc commit_tree {} {
global HEAD commit_type file_states ui_comm repo_config
global ui_status_value pch_error
if {![lock_index update]} return
if {[committer_ident] eq {}} return
if {![lock_index update]} return
# -- Our in memory state should match the repository.
#
@ -2591,6 +2591,201 @@ proc do_push_anywhere {} {
tkwait window $w
}
######################################################################
##
## merge
proc can_merge {} {
global HEAD commit_type file_states
if {[string match amend* $commit_type]} {
info_popup {Cannot merge while amending.
You must finish amending this commit before
starting any type of merge.
}
return 0
}
if {[committer_ident] eq {}} {return 0}
if {![lock_index merge]} {return 0}
# -- Our in memory state should match the repository.
#
repository_state curType curHEAD curMERGE_HEAD
if {$commit_type ne $curType || $HEAD ne $curHEAD} {
info_popup {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 a merge can be performed.
The rescan will be automatically started now.
}
unlock_index
rescan {set ui_status_value {Ready.}}
return 0
}
foreach path [array names file_states] {
switch -glob -- [lindex $file_states($path) 0] {
U? {
error_popup "You are in the middle of a conflicted merge.
File [short_path $path] has merge conflicts.
You must resolve them, add the file, and commit to
complete the current merge. Only then can you
begin another merge.
"
unlock_index
return 0
}
}
}
return 1
}
proc visualize_local_merge {w} {
set revs {}
foreach i [$w.source.l curselection] {
lappend revs [$w.source.l get $i]
}
if {$revs eq {}} return
lappend revs --not HEAD
do_gitk $revs
}
proc start_local_merge_action {w} {
global HEAD
set cmd [list git merge]
set names {}
set revcnt 0
foreach i [$w.source.l curselection] {
set b [$w.source.l get $i]
lappend cmd $b
lappend names $b
incr revcnt
}
if {$revcnt == 0} {
return
} elseif {$revcnt == 1} {
set unit branch
} elseif {$revcnt <= 15} {
set unit branches
} else {
tk_messageBox \
-icon error \
-type ok \
-title [wm title $w] \
-parent $w \
-message "Too many branches selected.
You have requested to merge $revcnt branches
in an octopus merge. This exceeds Git's
internal limit of 15 branches per merge.
Please select fewer branches. To merge more
than 15 branches, merge the branches in batches.
"
return
}
set cons [new_console "Merge" "Merging [join $names {, }]"]
console_exec $cons $cmd finish_merge
bind $w <Destroy> {}
destroy $w
}
proc finish_merge {w ok} {
console_done $w $ok
if {$ok} {
set msg {Merge completed successfully.}
} else {
set msg {Merge failed. Conflict resolution is required.}
}
unlock_index
rescan [list set ui_status_value $msg]
}
proc do_local_merge {} {
global current_branch
if {![can_merge]} return
set w .merge_setup
toplevel $w
wm geometry $w "+[winfo rootx .]+[winfo rooty .]"
label $w.header \
-text "Merge Into $current_branch" \
-font font_uibold
pack $w.header -side top -fill x
frame $w.buttons
button $w.buttons.visualize -text Visualize \
-font font_ui \
-command [list visualize_local_merge $w]
pack $w.buttons.visualize -side left
button $w.buttons.create -text Merge \
-font font_ui \
-command [list start_local_merge_action $w]
pack $w.buttons.create -side right
button $w.buttons.cancel -text {Cancel} \
-font font_ui \
-command [list destroy $w]
pack $w.buttons.cancel -side right -padx 5
pack $w.buttons -side bottom -fill x -pady 10 -padx 10
labelframe $w.source \
-text {Source Branches} \
-font font_ui
listbox $w.source.l \
-height 10 \
-width 25 \
-selectmode extended \
-yscrollcommand [list $w.source.sby set] \
-font font_ui
scrollbar $w.source.sby -command [list $w.source.l yview]
pack $w.source.sby -side right -fill y
pack $w.source.l -side left -fill both -expand 1
pack $w.source -fill both -expand 1 -pady 5 -padx 5
set cmd [list git for-each-ref]
lappend cmd {--format=%(objectname) %(refname)}
lappend cmd refs/heads
lappend cmd refs/remotes
set fr_fd [open "| $cmd" r]
fconfigure $fr_fd -translation binary
while {[gets $fr_fd line] > 0} {
set line [split $line { }]
set sha1([lindex $line 0]) [lindex $line 1]
}
close $fr_fd
set to_show {}
set fr_fd [open "| git rev-list --all --not HEAD"]
while {[gets $fr_fd line] > 0} {
if {[catch {set ref $sha1($line)}]} continue
regsub ^refs/(heads|remotes)/ $ref {} ref
lappend to_show $ref
}
close $fr_fd
foreach ref [lsort -unique $to_show] {
$w.source.l insert end $ref
}
bind $w <Visibility> "grab $w"
bind $w <Key-Escape> "unlock_index;destroy $w"
bind $w <Destroy> unlock_index
wm title $w "[appname] ([reponame]): Merge"
tkwait window $w
}
######################################################################
##
## icons
@ -3865,6 +4060,7 @@ if {!$single_commit} {
}
.mbar add cascade -label Commit -menu .mbar.commit
if {!$single_commit} {
.mbar add cascade -label Merge -menu .mbar.merge
.mbar add cascade -label Fetch -menu .mbar.fetch
.mbar add cascade -label Push -menu .mbar.push
}
@ -4039,17 +4235,6 @@ lappend disable_on_lock \
lappend disable_on_lock \
[list .mbar.commit entryconf [.mbar.commit index last] -state]
# -- Transport menus
#
if {!$single_commit} {
menu .mbar.fetch
menu .mbar.push
.mbar.push add command -label {Push...} \
-command do_push_anywhere \
-font font_ui
}
if {[is_MacOSX]} {
# -- Apple Menu (Mac OS X only)
#
@ -4130,6 +4315,22 @@ pack .branch.l1 -side left
pack .branch.cb -side left -fill x
pack .branch -side top -fill x
if {!$single_commit} {
menu .mbar.merge
.mbar.merge add command -label {Local Merge...} \
-command do_local_merge \
-font font_ui
lappend disable_on_lock \
[list .mbar.merge entryconf [.mbar.merge index last] -state]
menu .mbar.fetch
menu .mbar.push
.mbar.push add command -label {Push...} \
-command do_push_anywhere \
-font font_ui
}
# -- Main Window Layout
#
panedwindow .vpane -orient vertical