1# git-gui branch merge support
2# Copyright (C) 2006, 2007 Shawn Pearce
3
4class merge {
5
6field w         ; # top level window
7field w_rev     ; # mega-widget to pick the revision to merge
8
9method _can_merge {} {
10	global HEAD commit_type file_states
11
12	if {[string match amend* $commit_type]} {
13		info_popup [mc "Cannot merge while amending.
14
15You must finish amending this commit before starting any type of merge.
16"]
17		return 0
18	}
19
20	if {[committer_ident] eq {}} {return 0}
21	if {![lock_index merge]} {return 0}
22
23	# -- Our in memory state should match the repository.
24	#
25	repository_state curType curHEAD curMERGE_HEAD
26	if {$commit_type ne $curType || $HEAD ne $curHEAD} {
27		info_popup [mc "Last scanned state does not match repository state.
28
29Another Git program has modified this repository since the last scan.  A rescan must be performed before a merge can be performed.
30
31The rescan will be automatically started now.
32"]
33		unlock_index
34		rescan ui_ready
35		return 0
36	}
37
38	foreach path [array names file_states] {
39		switch -glob -- [lindex $file_states($path) 0] {
40		_O {
41			continue; # and pray it works!
42		}
43		_U -
44		U? {
45			error_popup [mc "You are in the middle of a conflicted merge.
46
47File %s has merge conflicts.
48
49You must resolve them, stage the file, and commit to complete the current merge.  Only then can you begin another merge.
50" [short_path $path]]
51			unlock_index
52			return 0
53		}
54		?? {
55			error_popup [mc "You are in the middle of a change.
56
57File %s is modified.
58
59You should complete the current commit before starting a merge.  Doing so will help you abort a failed merge, should the need arise.
60" [short_path $path]]
61			unlock_index
62			return 0
63		}
64		}
65	}
66
67	return 1
68}
69
70method _rev {} {
71	if {[catch {$w_rev commit_or_die}]} {
72		return {}
73	}
74	return [$w_rev get]
75}
76
77method _visualize {} {
78	set rev [_rev $this]
79	if {$rev ne {}} {
80		do_gitk [list $rev --not HEAD]
81	}
82}
83
84method _start {} {
85	global HEAD current_branch remote_url
86	global _last_merged_branch
87
88	set name [_rev $this]
89	if {$name eq {}} {
90		return
91	}
92
93	set spec [$w_rev get_tracking_branch]
94	set cmit [$w_rev get_commit]
95
96	set fh [open [gitdir FETCH_HEAD] w]
97	fconfigure $fh -translation lf
98	if {$spec eq {}} {
99		set remote .
100		set branch $name
101		set stitle $branch
102	} else {
103		set remote $remote_url([lindex $spec 1])
104		if {[regexp {^[^:@]*@[^:]*:/} $remote]} {
105			regsub {^[^:@]*@} $remote {} remote
106		}
107		set branch [lindex $spec 2]
108		set stitle [mc "%s of %s" $branch $remote]
109	}
110	regsub ^refs/heads/ $branch {} branch
111	puts $fh "$cmit\t\tbranch '$branch' of $remote"
112	close $fh
113	set _last_merged_branch $branch
114
115	if {[git-version >= "2.5.0"]} {
116		set cmd [list git merge --strategy=recursive FETCH_HEAD]
117	} else {
118		set cmd [list git]
119		lappend cmd merge
120		lappend cmd --strategy=recursive
121		lappend cmd [git fmt-merge-msg <[gitdir FETCH_HEAD]]
122		lappend cmd HEAD
123		lappend cmd $name
124	}
125
126	ui_status [mc "Merging %s and %s..." $current_branch $stitle]
127	set cons [console::new [mc "Merge"] "merge $stitle"]
128	console::exec $cons $cmd [cb _finish $cons]
129
130	wm protocol $w WM_DELETE_WINDOW {}
131	destroy $w
132}
133
134method _finish {cons ok} {
135	console::done $cons $ok
136	if {$ok} {
137		set msg [mc "Merge completed successfully."]
138	} else {
139		set msg [mc "Merge failed.  Conflict resolution is required."]
140	}
141	unlock_index
142	rescan [list ui_status $msg]
143	delete_this
144}
145
146constructor dialog {} {
147	global current_branch
148	global M1B use_ttk NS
149
150	if {![_can_merge $this]} {
151		delete_this
152		return
153	}
154
155	make_dialog top w
156	wm title $top [mc "%s (%s): Merge" [appname] [reponame]]
157	if {$top ne {.}} {
158		wm geometry $top "+[winfo rootx .]+[winfo rooty .]"
159	}
160
161	set _start [cb _start]
162
163	${NS}::label $w.header \
164		-text [mc "Merge Into %s" $current_branch] \
165		-font font_uibold
166	pack $w.header -side top -fill x
167
168	${NS}::frame $w.buttons
169	${NS}::button $w.buttons.visualize \
170		-text [mc Visualize] \
171		-command [cb _visualize]
172	pack $w.buttons.visualize -side left
173	${NS}::button $w.buttons.merge \
174		-text [mc Merge] \
175		-command $_start
176	pack $w.buttons.merge -side right
177	${NS}::button $w.buttons.cancel \
178		-text [mc "Cancel"] \
179		-command [cb _cancel]
180	pack $w.buttons.cancel -side right -padx 5
181	pack $w.buttons -side bottom -fill x -pady 10 -padx 10
182
183	set w_rev [::choose_rev::new_unmerged $w.rev [mc "Revision To Merge"]]
184	pack $w.rev -anchor nw -fill both -expand 1 -pady 5 -padx 5
185
186	bind $w <$M1B-Key-Return> $_start
187	bind $w <Key-Return> $_start
188	bind $w <Key-Escape> [cb _cancel]
189	wm protocol $w WM_DELETE_WINDOW [cb _cancel]
190
191	bind $w.buttons.merge <Visibility> [cb _visible]
192	tkwait window $w
193}
194
195method _visible {} {
196	grab $w
197	if {[is_config_true gui.matchtrackingbranch]} {
198		$w_rev pick_tracking_branch
199	}
200	$w_rev focus_filter
201}
202
203method _cancel {} {
204	wm protocol $w WM_DELETE_WINDOW {}
205	unlock_index
206	destroy $w
207	delete_this
208}
209
210}
211
212namespace eval merge {
213
214proc reset_hard {} {
215	global HEAD commit_type file_states
216
217	if {[string match amend* $commit_type]} {
218		info_popup [mc "Cannot abort while amending.
219
220You must finish amending this commit.
221"]
222		return
223	}
224
225	if {![lock_index abort]} return
226
227	if {[string match *merge* $commit_type]} {
228		set op_question [mc "Abort merge?
229
230Aborting the current merge will cause *ALL* uncommitted changes to be lost.
231
232Continue with aborting the current merge?"]
233	} else {
234		set op_question [mc "Reset changes?
235
236Resetting the changes will cause *ALL* uncommitted changes to be lost.
237
238Continue with resetting the current changes?"]
239	}
240
241	if {[ask_popup $op_question] eq {yes}} {
242		set fd [git_read --stderr read-tree --reset -u -v HEAD]
243		fconfigure $fd -blocking 0 -translation binary
244		set status_bar_operation [$::main_status \
245			start \
246			[mc "Aborting"] \
247			[mc "files reset"]]
248		fileevent $fd readable [namespace code [list \
249			_reset_wait $fd $status_bar_operation]]
250	} else {
251		unlock_index
252	}
253}
254
255proc _reset_wait {fd status_bar_operation} {
256	global ui_comm
257
258	$status_bar_operation update_meter [read $fd]
259
260	fconfigure $fd -blocking 1
261	if {[eof $fd]} {
262		set fail [catch {close $fd} err]
263		unlock_index
264		$status_bar_operation stop
265
266		$ui_comm delete 0.0 end
267		$ui_comm edit modified false
268
269		catch {file delete [gitdir MERGE_HEAD]}
270		catch {file delete [gitdir rr-cache MERGE_RR]}
271		catch {file delete [gitdir MERGE_RR]}
272		catch {file delete [gitdir SQUASH_MSG]}
273		catch {file delete [gitdir MERGE_MSG]}
274		catch {file delete [gitdir GITGUI_MSG]}
275
276		if {$fail} {
277			warn_popup "[mc "Abort failed."]\n\n$err"
278		}
279		rescan {ui_status [mc "Abort completed.  Ready."]}
280	} else {
281		fconfigure $fd -blocking 0
282	}
283}
284
285}
286