merge.tcl 5.9 KB
Newer Older
1 2 3
# git-gui branch merge support
# Copyright (C) 2006, 2007 Shawn Pearce

4 5 6
class merge {

field w         ; # top level window
7
field w_rev     ; # mega-widget to pick the revision to merge
8

9
method _can_merge {} {
10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33
	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
34
		rescan ui_ready
35 36 37 38 39 40 41 42 43 44 45 46 47
		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.

48
You must resolve them, stage the file, and commit to complete the current merge.  Only then can you begin another merge.
49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68
"
			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
}

69
method _rev {} {
70 71
	if {[catch {$w_rev commit_or_die}]} {
		return {}
72
	}
73
	return [$w_rev get]
74 75
}

76
method _visualize {} {
77 78 79 80
	set rev [_rev $this]
	if {$rev ne {}} {
		do_gitk [list $rev --not HEAD]
	}
81 82
}

83
method _start {} {
84
	global HEAD current_branch remote_url
85

86 87
	set name [_rev $this]
	if {$name eq {}} {
88 89 90
		return
	}

91 92 93 94 95 96 97 98 99 100 101 102 103 104
	set spec [$w_rev get_tracking_branch]
	set cmit [$w_rev get_commit]
	set cmd [list git]
	lappend cmd merge
	lappend cmd --strategy=recursive

	set fh [open [gitdir FETCH_HEAD] w]
	fconfigure $fh -translation lf
	if {$spec eq {}} {
		set remote .
		set branch $name
		set stitle $branch
	} else {
		set remote $remote_url([lindex $spec 1])
105 106 107
		if {[regexp {^[^:@]*@[^:]*:/} $remote]} {
			regsub {^[^:@]*@} $remote {} remote
		}
108 109 110 111 112 113 114 115 116 117 118 119
		set branch [lindex $spec 2]
		set stitle "$branch of $remote"
	}
	regsub ^refs/heads/ $branch {} branch
	puts $fh "$cmit\t\tbranch '$branch' of $remote"
	close $fh

	lappend cmd [git fmt-merge-msg <[gitdir FETCH_HEAD]]
	lappend cmd HEAD
	lappend cmd $cmit

	set msg "Merging $current_branch and $stitle"
120
	ui_status "$msg..."
121
	set cons [console::new "Merge" "merge $stitle"]
122
	console::exec $cons $cmd [cb _finish $cons]
123 124

	wm protocol $w WM_DELETE_WINDOW {}
125 126 127
	destroy $w
}

128
method _finish {cons ok} {
129
	console::done $cons $ok
130 131 132 133 134 135
	if {$ok} {
		set msg {Merge completed successfully.}
	} else {
		set msg {Merge failed.  Conflict resolution is required.}
	}
	unlock_index
136
	rescan [list ui_status $msg]
137
	delete_this
138 139
}

140
constructor dialog {} {
141
	global current_branch
142
	global M1B
143

144 145 146 147
	if {![_can_merge $this]} {
		delete_this
		return
	}
148

149 150 151 152 153
	make_toplevel top w
	wm title $top "[appname] ([reponame]): Merge"
	if {$top ne {.}} {
		wm geometry $top "+[winfo rootx .]+[winfo rooty .]"
	}
154

155
	set _start [cb _start]
156

157 158 159 160 161 162
	label $w.header \
		-text "Merge Into $current_branch" \
		-font font_uibold
	pack $w.header -side top -fill x

	frame $w.buttons
163 164 165
	button $w.buttons.visualize \
		-text Visualize \
		-command [cb _visualize]
166
	pack $w.buttons.visualize -side left
167 168 169 170
	button $w.buttons.merge \
		-text Merge \
		-command $_start
	pack $w.buttons.merge -side right
171 172
	button $w.buttons.cancel \
		-text {Cancel} \
173
		-command [cb _cancel]
174 175 176
	pack $w.buttons.cancel -side right -padx 5
	pack $w.buttons -side bottom -fill x -pady 10 -padx 10

177 178
	set w_rev [::choose_rev::new_unmerged $w.rev {Revision To Merge}]
	pack $w.rev -anchor nw -fill both -expand 1 -pady 5 -padx 5
179 180

	bind $w <$M1B-Key-Return> $_start
181
	bind $w <Key-Return> $_start
182 183
	bind $w <Key-Escape> [cb _cancel]
	wm protocol $w WM_DELETE_WINDOW [cb _cancel]
184 185

	bind $w.buttons.merge <Visibility> [cb _visible]
186 187 188
	tkwait window $w
}

189 190
method _visible {} {
	grab $w
191 192 193 194
	if {[is_config_true gui.matchtrackingbranch]} {
		$w_rev pick_tracking_branch
	}
	$w_rev focus_filter
195 196 197 198 199 200 201 202 203 204 205 206 207
}

method _cancel {} {
	wm protocol $w WM_DELETE_WINDOW {}
	unlock_index
	destroy $w
	delete_this
}

}

namespace eval merge {

208
proc reset_hard {} {
209 210 211 212 213 214 215 216 217 218 219 220 221
	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]} {
222 223 224 225 226
		set op_question "Abort merge?

Aborting the current merge will cause *ALL* uncommitted changes to be lost.

Continue with aborting the current merge?"
227
	} else {
228
		set op_question "Reset changes?
229

230
Resetting the changes will cause *ALL* uncommitted changes to be lost.
231

232 233
Continue with resetting the current changes?"
	}
234

235
	if {[ask_popup $op_question] eq {yes}} {
236
		set fd [git_read --stderr read-tree --reset -u -v HEAD]
237
		fconfigure $fd -blocking 0 -translation binary
238
		fileevent $fd readable [namespace code [list _reset_wait $fd]]
239
		$::main_status start {Aborting} {files reset}
240 241 242 243 244
	} else {
		unlock_index
	}
}

245
proc _reset_wait {fd} {
246 247
	global ui_comm

248 249 250
	$::main_status update_meter [read $fd]

	fconfigure $fd -blocking 1
251
	if {[eof $fd]} {
252 253
		set fail [catch {close $fd} err]
		$::main_status stop
254 255 256 257 258 259 260 261 262 263 264
		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]}

265 266 267
		if {$fail} {
			warn_popup "Abort failed.\n\n$err"
		}
268
		rescan {ui_status {Abort completed.  Ready.}}
269 270
	} else {
		fconfigure $fd -blocking 0
271 272
	}
}
273 274

}