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