diff options
Diffstat (limited to 'lib/merge.tcl')
-rw-r--r-- | lib/merge.tcl | 281 |
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.}} + } +} |