diff options
Diffstat (limited to 'git-gui/lib/merge.tcl')
| -rw-r--r-- | git-gui/lib/merge.tcl | 309 | 
1 files changed, 309 insertions, 0 deletions
| diff --git a/git-gui/lib/merge.tcl b/git-gui/lib/merge.tcl new file mode 100644 index 0000000000..3dce856e5e --- /dev/null +++ b/git-gui/lib/merge.tcl @@ -0,0 +1,309 @@ +# 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]] +	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 <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.}} +	} +} + +} | 
