summaryrefslogtreecommitdiff
path: root/lib/merge.tcl
diff options
context:
space:
mode:
Diffstat (limited to 'lib/merge.tcl')
-rw-r--r--lib/merge.tcl281
1 files changed, 281 insertions, 0 deletions
diff --git a/lib/merge.tcl b/lib/merge.tcl
new file mode 100644
index 0000000000..75724a930f
--- /dev/null
+++ b/lib/merge.tcl
@@ -0,0 +1,281 @@
+# git-gui branch merge support
+# Copyright (C) 2006, 2007 Shawn Pearce
+
+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] {
+ _O {
+ continue; # and pray it works!
+ }
+ 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
+ }
+ ?? {
+ error_popup "You are in the middle of a change.
+
+File [short_path $path] is modified.
+
+You should complete the current commit before starting a merge. Doing so will help you abort a failed merge, should the need arise.
+"
+ 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 ui_status_value current_branch
+
+ 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
+
+ if {[tk_dialog \
+ $w.confirm_octopus \
+ [wm title $w] \
+ "Use octopus merge strategy?
+
+You are merging $revcnt branches at once. This requires using the octopus merge driver, which may not succeed if there are file-level conflicts.
+" \
+ question \
+ 0 \
+ {Cancel} \
+ {Use octopus} \
+ ] != 1} return
+ } 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 msg "Merging $current_branch, [join $names {, }]"
+ set ui_status_value "$msg..."
+ set cons [new_console "Merge" $msg]
+ console_exec $cons $cmd [list finish_merge $revcnt]
+ bind $w <Destroy> {}
+ destroy $w
+}
+
+proc finish_merge {revcnt w ok} {
+ console_done $w $ok
+ if {$ok} {
+ set msg {Merge completed successfully.}
+ } else {
+ if {$revcnt != 1} {
+ info_popup "Octopus merge failed.
+
+Your merge of $revcnt branches has failed.
+
+There are file-level conflicts between the branches which must be resolved manually.
+
+The working directory will now be reset.
+
+You can attempt this merge again by merging only one branch at a time." $w
+
+ set fd [open "| git read-tree --reset -u HEAD" r]
+ fconfigure $fd -blocking 0 -translation binary
+ fileevent $fd readable [list reset_hard_wait $fd]
+ set ui_status_value {Aborting... please wait...}
+ return
+ }
+
+ 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 \
+ -command [list visualize_local_merge $w]
+ pack $w.buttons.visualize -side left
+ button $w.buttons.create -text Merge \
+ -command [list start_local_merge_action $w]
+ pack $w.buttons.create -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
+
+ labelframe $w.source -text {Source Branches}
+ listbox $w.source.l \
+ -height 10 \
+ -width 70 \
+ -selectmode extended \
+ -yscrollcommand [list $w.source.sby set]
+ 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) %(*objectname) %(refname)}
+ lappend cmd refs/heads
+ lappend cmd refs/remotes
+ lappend cmd refs/tags
+ 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 2]
+ set sha1([lindex $line 1]) [lindex $line 2]
+ }
+ 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|tags)/ $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
+}
+
+proc do_reset_hard {} {
+ global HEAD commit_type file_states
+
+ if {[string match amend* $commit_type]} {
+ info_popup {Cannot abort while amending.
+
+You must finish amending this commit.
+}
+ return
+ }
+
+ if {![lock_index abort]} return
+
+ if {[string match *merge* $commit_type]} {
+ set op merge
+ } else {
+ set op commit
+ }
+
+ if {[ask_popup "Abort $op?
+
+Aborting the current $op will cause *ALL* uncommitted changes to be lost.
+
+Continue with aborting the current $op?"] eq {yes}} {
+ set fd [open "| git read-tree --reset -u HEAD" r]
+ fconfigure $fd -blocking 0 -translation binary
+ fileevent $fd readable [list reset_hard_wait $fd]
+ set ui_status_value {Aborting... please wait...}
+ } else {
+ unlock_index
+ }
+}
+
+proc reset_hard_wait {fd} {
+ global ui_comm
+
+ read $fd
+ if {[eof $fd]} {
+ close $fd
+ unlock_index
+
+ $ui_comm delete 0.0 end
+ $ui_comm edit modified false
+
+ catch {file delete [gitdir MERGE_HEAD]}
+ catch {file delete [gitdir rr-cache MERGE_RR]}
+ catch {file delete [gitdir SQUASH_MSG]}
+ catch {file delete [gitdir MERGE_MSG]}
+ catch {file delete [gitdir GITGUI_MSG]}
+
+ rescan {set ui_status_value {Abort completed. Ready.}}
+ }
+}