]> asedeno.scripts.mit.edu Git - git.git/blob - lib/merge.tcl
6caf25f2bea19efbad2190587ea5c414604871f0
[git.git] / lib / merge.tcl
1 # git-gui branch merge support
2 # Copyright (C) 2006, 2007 Shawn Pearce
3
4 class merge {
5
6 field w         ; # top level window
7 field w_list    ; # widget of available branches
8 field list      ; # list of available branches
9
10 method _can_merge {} {
11         global HEAD commit_type file_states
12
13         if {[string match amend* $commit_type]} {
14                 info_popup {Cannot merge while amending.
15
16 You must finish amending this commit before starting any type of merge.
17 }
18                 return 0
19         }
20
21         if {[committer_ident] eq {}} {return 0}
22         if {![lock_index merge]} {return 0}
23
24         # -- Our in memory state should match the repository.
25         #
26         repository_state curType curHEAD curMERGE_HEAD
27         if {$commit_type ne $curType || $HEAD ne $curHEAD} {
28                 info_popup {Last scanned state does not match repository state.
29
30 Another Git program has modified this repository since the last scan.  A rescan must be performed before a merge can be performed.
31
32 The rescan will be automatically started now.
33 }
34                 unlock_index
35                 rescan ui_ready
36                 return 0
37         }
38
39         foreach path [array names file_states] {
40                 switch -glob -- [lindex $file_states($path) 0] {
41                 _O {
42                         continue; # and pray it works!
43                 }
44                 U? {
45                         error_popup "You are in the middle of a conflicted merge.
46
47 File [short_path $path] has merge conflicts.
48
49 You must resolve them, add the file, and commit to complete the current merge.  Only then can you begin another merge.
50 "
51                         unlock_index
52                         return 0
53                 }
54                 ?? {
55                         error_popup "You are in the middle of a change.
56
57 File [short_path $path] is modified.
58
59 You should complete the current commit before starting a merge.  Doing so will help you abort a failed merge, should the need arise.
60 "
61                         unlock_index
62                         return 0
63                 }
64                 }
65         }
66
67         return 1
68 }
69
70 method _refs {} {
71         set r {}
72         foreach i [$w_list curselection] {
73                 lappend r [lindex [lindex $list $i] 0]
74         }
75         return $r
76 }
77
78 method _visualize {} {
79         set revs [_refs $this]
80         if {$revs eq {}} return
81         lappend revs --not HEAD
82         do_gitk $revs
83 }
84
85 method _start {} {
86         global HEAD current_branch
87
88         set cmd [list git merge]
89         set names [_refs $this]
90         set revcnt [llength $names]
91         append cmd { } $names
92
93         if {$revcnt == 0} {
94                 return
95         } elseif {$revcnt == 1} {
96                 set unit branch
97         } elseif {$revcnt <= 15} {
98                 set unit branches
99
100                 if {[tk_dialog \
101                 $w.confirm_octopus \
102                 [wm title $w] \
103                 "Use octopus merge strategy?
104
105 You are merging $revcnt branches at once.  This requires using the octopus merge driver, which may not succeed if there are file-level conflicts.
106 " \
107                 question \
108                 0 \
109                 {Cancel} \
110                 {Use octopus} \
111                 ] != 1} return
112         } else {
113                 tk_messageBox \
114                         -icon error \
115                         -type ok \
116                         -title [wm title $w] \
117                         -parent $w \
118                         -message "Too many branches selected.
119
120 You have requested to merge $revcnt branches in an octopus merge.  This exceeds Git's internal limit of 15 branches per merge.
121
122 Please select fewer branches.  To merge more than 15 branches, merge the branches in batches.
123 "
124                 return
125         }
126
127         set msg "Merging $current_branch, [join $names {, }]"
128         ui_status "$msg..."
129         set cons [console::new "Merge" $msg]
130         console::exec $cons $cmd [cb _finish $revcnt $cons]
131
132         wm protocol $w WM_DELETE_WINDOW {}
133         destroy $w
134 }
135
136 method _finish {revcnt cons ok} {
137         console::done $cons $ok
138         if {$ok} {
139                 set msg {Merge completed successfully.}
140         } else {
141                 if {$revcnt != 1} {
142                         info_popup "Octopus merge failed.
143
144 Your merge of $revcnt branches has failed.
145
146 There are file-level conflicts between the branches which must be resolved manually.
147
148 The working directory will now be reset.
149
150 You can attempt this merge again by merging only one branch at a time." $w
151
152                         set fd [git_read read-tree --reset -u HEAD]
153                         fconfigure $fd -blocking 0 -translation binary
154                         fileevent $fd readable [cb _reset_wait $fd]
155                         ui_status {Aborting... please wait...}
156                         return
157                 }
158
159                 set msg {Merge failed.  Conflict resolution is required.}
160         }
161         unlock_index
162         rescan [list ui_status $msg]
163         delete_this
164 }
165
166 constructor dialog {} {
167         global current_branch
168         global M1B
169
170         if {![_can_merge $this]} {
171                 delete_this
172                 return
173         }
174
175         set fmt {list %(objectname) %(*objectname) %(refname) %(subject)}
176         set fr_fd [git_read for-each-ref \
177                 --tcl \
178                 --format=$fmt \
179                 refs/heads \
180                 refs/remotes \
181                 refs/tags \
182                 ]
183         fconfigure $fr_fd -translation binary
184         while {[gets $fr_fd line] > 0} {
185                 set line [eval $line]
186                 set ref [lindex $line 2]
187                 regsub ^refs/(heads|remotes|tags)/ $ref {} ref
188                 set subj($ref) [lindex $line 3]
189                 lappend sha1([lindex $line 0]) $ref
190                 if {[lindex $line 1] ne {}} {
191                         lappend sha1([lindex $line 1]) $ref
192                 }
193         }
194         close $fr_fd
195
196         set list [list]
197         set fr_fd [git_read rev-list --all --not HEAD]
198         while {[gets $fr_fd line] > 0} {
199                 if {[catch {set ref $sha1($line)}]} continue
200                 foreach n $ref {
201                         lappend list [list $n $line]
202                 }
203         }
204         close $fr_fd
205         set list [lsort -unique $list]
206
207         make_toplevel top w
208         wm title $top "[appname] ([reponame]): Merge"
209         if {$top ne {.}} {
210                 wm geometry $top "+[winfo rootx .]+[winfo rooty .]"
211         }
212
213         set _visualize [cb _visualize]
214         set _start [cb _start]
215
216         label $w.header \
217                 -text "Merge Into $current_branch" \
218                 -font font_uibold
219         pack $w.header -side top -fill x
220
221         frame $w.buttons
222         button $w.buttons.visualize -text Visualize -command $_visualize
223         pack $w.buttons.visualize -side left
224         button $w.buttons.create -text Merge -command $_start
225         pack $w.buttons.create -side right
226         button $w.buttons.cancel \
227                 -text {Cancel} \
228                 -command [cb _cancel]
229         pack $w.buttons.cancel -side right -padx 5
230         pack $w.buttons -side bottom -fill x -pady 10 -padx 10
231
232         labelframe $w.source -text {Source Branches}
233         set w_list $w.source.l
234         listbox $w_list \
235                 -height 10 \
236                 -width 70 \
237                 -font font_diff \
238                 -selectmode extended \
239                 -yscrollcommand [list $w.source.sby set]
240         scrollbar $w.source.sby -command [list $w_list yview]
241         pack $w.source.sby -side right -fill y
242         pack $w_list -side left -fill both -expand 1
243         pack $w.source -fill both -expand 1 -pady 5 -padx 5
244
245         foreach ref $list {
246                 set n [lindex $ref 0]
247                 if {[string length $n] > 20} {
248                         set n "[string range $n 0 16]..."
249                 }
250                 $w_list insert end [format {%s %-20s %s} \
251                         [string range [lindex $ref 1] 0 5] \
252                         $n \
253                         $subj([lindex $ref 0])]
254         }
255
256         bind $w_list <Key-K> [list event generate %W <Shift-Key-Up>]
257         bind $w_list <Key-J> [list event generate %W <Shift-Key-Down>]
258         bind $w_list <Key-k> [list event generate %W <Key-Up>]
259         bind $w_list <Key-j> [list event generate %W <Key-Down>]
260         bind $w_list <Key-h> [list event generate %W <Key-Left>]
261         bind $w_list <Key-l> [list event generate %W <Key-Right>]
262         bind $w_list <Key-v> $_visualize
263
264         bind $w <$M1B-Key-Return> $_start
265         bind $w <Visibility> [cb _visible]
266         bind $w <Key-Escape> [cb _cancel]
267         wm protocol $w WM_DELETE_WINDOW [cb _cancel]
268         tkwait window $w
269 }
270
271 method _visible {} {
272         grab $w
273         focus $w_list
274 }
275
276 method _cancel {} {
277         wm protocol $w WM_DELETE_WINDOW {}
278         unlock_index
279         destroy $w
280         delete_this
281 }
282
283 }
284
285 namespace eval merge {
286
287 proc reset_hard {} {
288         global HEAD commit_type file_states
289
290         if {[string match amend* $commit_type]} {
291                 info_popup {Cannot abort while amending.
292
293 You must finish amending this commit.
294 }
295                 return
296         }
297
298         if {![lock_index abort]} return
299
300         if {[string match *merge* $commit_type]} {
301                 set op merge
302         } else {
303                 set op commit
304         }
305
306         if {[ask_popup "Abort $op?
307
308 Aborting the current $op will cause *ALL* uncommitted changes to be lost.
309
310 Continue with aborting the current $op?"] eq {yes}} {
311                 set fd [git_read read-tree --reset -u HEAD]
312                 fconfigure $fd -blocking 0 -translation binary
313                 fileevent $fd readable [namespace code [list _reset_wait $fd]]
314                 ui_status {Aborting... please wait...}
315         } else {
316                 unlock_index
317         }
318 }
319
320 proc _reset_wait {fd} {
321         global ui_comm
322
323         read $fd
324         if {[eof $fd]} {
325                 close $fd
326                 unlock_index
327
328                 $ui_comm delete 0.0 end
329                 $ui_comm edit modified false
330
331                 catch {file delete [gitdir MERGE_HEAD]}
332                 catch {file delete [gitdir rr-cache MERGE_RR]}
333                 catch {file delete [gitdir SQUASH_MSG]}
334                 catch {file delete [gitdir MERGE_MSG]}
335                 catch {file delete [gitdir GITGUI_MSG]}
336
337                 rescan {ui_status {Abort completed.  Ready.}}
338         }
339 }
340
341 }