summaryrefslogtreecommitdiff
path: root/git-gui/lib/merge.tcl
diff options
context:
space:
mode:
Diffstat (limited to 'git-gui/lib/merge.tcl')
-rw-r--r--git-gui/lib/merge.tcl312
1 files changed, 312 insertions, 0 deletions
diff --git a/git-gui/lib/merge.tcl b/git-gui/lib/merge.tcl
new file mode 100644
index 0000000000..24ed24b3d0
--- /dev/null
+++ b/git-gui/lib/merge.tcl
@@ -0,0 +1,312 @@
+# git-gui branch merge support
+# Copyright (C) 2006, 2007 Shawn Pearce
+
+namespace eval 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] {
+ _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 _refs {w list} {
+ set r {}
+ foreach i [$w.source.l curselection] {
+ lappend r [lindex [lindex $list $i] 0]
+ }
+ return $r
+}
+
+proc _visualize {w list} {
+ set revs [_refs $w $list]
+ if {$revs eq {}} return
+ lappend revs --not HEAD
+ do_gitk $revs
+}
+
+proc _start {w list} {
+ global HEAD ui_status_value current_branch
+
+ set cmd [list git merge]
+ set names [_refs $w $list]
+ set revcnt [llength $names]
+ append cmd { } $names
+
+ 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 [console::new "Merge" $msg]
+ console::exec $cons $cmd \
+ [namespace code [list _finish $revcnt $cons]]
+ bind $w <Destroy> {}
+ destroy $w
+}
+
+proc _finish {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 \
+ [namespace code [list _reset_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 dialog {} {
+ global current_branch
+ global M1B
+
+ if {![_can_merge]} return
+
+ set fmt {list %(objectname) %(*objectname) %(refname) %(subject)}
+ set cmd [list git for-each-ref --tcl --format=$fmt]
+ 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 [eval $line]
+ set ref [lindex $line 2]
+ regsub ^refs/(heads|remotes|tags)/ $ref {} ref
+ set subj($ref) [lindex $line 3]
+ lappend sha1([lindex $line 0]) $ref
+ if {[lindex $line 1] ne {}} {
+ lappend sha1([lindex $line 1]) $ref
+ }
+ }
+ 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
+ foreach n $ref {
+ lappend to_show [list $n $line]
+ }
+ }
+ close $fr_fd
+ set to_show [lsort -unique $to_show]
+
+ set w .merge_setup
+ toplevel $w
+ wm geometry $w "+[winfo rootx .]+[winfo rooty .]"
+
+ set _visualize [namespace code [list _visualize $w $to_show]]
+ set _start [namespace code [list _start $w $to_show]]
+
+ 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 $_visualize
+ pack $w.buttons.visualize -side left
+ button $w.buttons.create -text Merge -command $_start
+ 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 \
+ -font font_diff \
+ -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
+
+ foreach ref $to_show {
+ set n [lindex $ref 0]
+ if {[string length $n] > 20} {
+ set n "[string range $n 0 16]..."
+ }
+ $w.source.l insert end [format {%s %-20s %s} \
+ [string range [lindex $ref 1] 0 5] \
+ $n \
+ $subj([lindex $ref 0])]
+ }
+
+ bind $w.source.l <Key-K> [list event generate %W <Shift-Key-Up>]
+ bind $w.source.l <Key-J> [list event generate %W <Shift-Key-Down>]
+ bind $w.source.l <Key-k> [list event generate %W <Key-Up>]
+ bind $w.source.l <Key-j> [list event generate %W <Key-Down>]
+ bind $w.source.l <Key-h> [list event generate %W <Key-Left>]
+ bind $w.source.l <Key-l> [list event generate %W <Key-Right>]
+ bind $w.source.l <Key-v> $_visualize
+
+ bind $w <$M1B-Key-Return> $_start
+ bind $w <Visibility> "grab $w; focus $w.source.l"
+ bind $w <Key-Escape> "unlock_index;destroy $w"
+ bind $w <Destroy> unlock_index
+ wm title $w "[appname] ([reponame]): Merge"
+ tkwait window $w
+}
+
+proc 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 [namespace code [list _reset_wait $fd]]
+ set ui_status_value {Aborting... please wait...}
+ } else {
+ unlock_index
+ }
+}
+
+proc _reset_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.}}
+ }
+}
+
+}