Added
Link Here
|
1 |
/* |
2 |
* DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS HEADER. |
3 |
* |
4 |
* Copyright 2014 Oracle and/or its affiliates. All rights reserved. |
5 |
* |
6 |
* Oracle and Java are registered trademarks of Oracle and/or its affiliates. |
7 |
* Other names may be trademarks of their respective owners. |
8 |
* |
9 |
* The contents of this file are subject to the terms of either the GNU |
10 |
* General Public License Version 2 only ("GPL") or the Common |
11 |
* Development and Distribution License("CDDL") (collectively, the |
12 |
* "License"). You may not use this file except in compliance with the |
13 |
* License. You can obtain a copy of the License at |
14 |
* http://www.netbeans.org/cddl-gplv2.html |
15 |
* or nbbuild/licenses/CDDL-GPL-2-CP. See the License for the |
16 |
* specific language governing permissions and limitations under the |
17 |
* License. When distributing the software, include this License Header |
18 |
* Notice in each file and include the License file at |
19 |
* nbbuild/licenses/CDDL-GPL-2-CP. Oracle designates this |
20 |
* particular file as subject to the "Classpath" exception as provided |
21 |
* by Oracle in the GPL Version 2 section of the License file that |
22 |
* accompanied this code. If applicable, add the following below the |
23 |
* License Header, with the fields enclosed by brackets [] replaced by |
24 |
* your own identifying information: |
25 |
* "Portions Copyrighted [year] [name of copyright owner]" |
26 |
* |
27 |
* If you wish your version of this file to be governed by only the CDDL |
28 |
* or only the GPL Version 2, indicate your decision by adding |
29 |
* "[Contributor] elects to include this software in this distribution |
30 |
* under the [CDDL or GPL Version 2] license." If you do not indicate a |
31 |
* single choice of license, a recipient has the option to distribute |
32 |
* your version of this file under either the CDDL, the GPL Version 2 or |
33 |
* to extend the choice of license to its licensees as provided above. |
34 |
* However, if you add GPL Version 2 code and therefore, elected the GPL |
35 |
* Version 2 license, then the option applies only if the new code is |
36 |
* made subject to such option by the copyright holder. |
37 |
* |
38 |
* Contributor(s): |
39 |
* |
40 |
* Portions Copyrighted 2014 Sun Microsystems, Inc. |
41 |
*/ |
42 |
|
43 |
package org.netbeans.modules.git.ui.branch; |
44 |
|
45 |
import java.io.File; |
46 |
import java.util.Arrays; |
47 |
import java.util.Collection; |
48 |
import java.util.Collections; |
49 |
import java.util.HashMap; |
50 |
import java.util.Map; |
51 |
import java.util.concurrent.Callable; |
52 |
import java.util.logging.Logger; |
53 |
import javax.swing.JButton; |
54 |
import org.netbeans.libs.git.GitCherryPickResult; |
55 |
import org.netbeans.libs.git.GitClient.CherryPickOperation; |
56 |
import org.netbeans.libs.git.GitException; |
57 |
import org.netbeans.libs.git.GitRepositoryState; |
58 |
import org.netbeans.libs.git.GitRevisionInfo; |
59 |
import org.netbeans.modules.git.Git; |
60 |
import org.netbeans.modules.git.client.GitClient; |
61 |
import org.netbeans.modules.git.client.GitClientExceptionHandler; |
62 |
import org.netbeans.modules.git.client.GitProgressSupport; |
63 |
import org.netbeans.modules.git.ui.actions.GitAction; |
64 |
import org.netbeans.modules.git.ui.actions.SingleRepositoryAction; |
65 |
import org.netbeans.modules.git.ui.commit.CommitAction; |
66 |
import org.netbeans.modules.git.ui.conflicts.ResolveConflictsExecutor; |
67 |
import org.netbeans.modules.git.ui.output.OutputLogger; |
68 |
import org.netbeans.modules.git.ui.repository.RepositoryInfo; |
69 |
import org.netbeans.modules.git.ui.status.StatusAction; |
70 |
import org.netbeans.modules.git.utils.GitUtils; |
71 |
import org.netbeans.modules.git.utils.ResultProcessor; |
72 |
import org.netbeans.modules.versioning.spi.VCSContext; |
73 |
import org.openide.DialogDisplayer; |
74 |
import org.openide.NotifyDescriptor; |
75 |
import org.openide.awt.ActionID; |
76 |
import org.openide.awt.ActionRegistration; |
77 |
import org.openide.awt.Mnemonics; |
78 |
import org.openide.nodes.AbstractNode; |
79 |
import org.openide.nodes.Children; |
80 |
import org.openide.nodes.Node; |
81 |
import org.openide.util.NbBundle; |
82 |
import org.openide.util.actions.SystemAction; |
83 |
import org.openide.util.lookup.Lookups; |
84 |
|
85 |
/** |
86 |
* |
87 |
* @author ondra |
88 |
*/ |
89 |
@ActionID(id = "org.netbeans.modules.git.ui.branch.CherryPickAction", category = "Git") |
90 |
@ActionRegistration(displayName = "#LBL_CherryPickAction_Name", lazy = false) |
91 |
@NbBundle.Messages({ |
92 |
"LBL_CherryPickAction_Name=C&herry Pick...", |
93 |
"LBL_CherryPickAction_PopupName=Cherry Pick...", |
94 |
"CTL_CherryPickAction_continueButton_text=&Continue", |
95 |
"CTL_CherryPickAction_continueButton_TTtext=Continue in cherry-picking scheduled commits.", |
96 |
"CTL_CherryPickAction_abortButton_text=&Abort", |
97 |
"CTL_CherryPickAction_abortButton_TTtext=Abort interrupted cherry-picking and rollback to the original commit.", |
98 |
"CTL_CherryPickAction_quitButton_text=&Quit", |
99 |
"CTL_CherryPickAction_quitButton_TTtext=Finish the currently cherry-picked commit but do not apply any other.", |
100 |
"LBL_CherryPick_cherryPickingState_title=Unfinished Cherry-Pick", |
101 |
"# {0} - repository name", "MSG_CherryPick_cherryPickingState_text=Repository {0} seems to be in the middle of an unfinished cherry-pick.\n\n" |
102 |
+ "You may continue with applying all scheduled commits\n" |
103 |
+ "or abort and rollback to the state before the cherry-pick started.", |
104 |
"# {0} - repository name", "MSG_CherryPick_cherryPickingScheduledState_text=Repository {0} seems to be in the middle of an unfinished cherry-pick.\n\n" |
105 |
+ "You may continue with applying all scheduled commits,\n" |
106 |
+ "abort and rollback to the state before the cherry-pick started\n" |
107 |
+ "or quit the cherry-pick and leave the already applied commits.", |
108 |
"# {0} - repository state", "MSG_CherryPickAction_notAllowed=Cherry picking not allowed in this state: \"{0}\"." |
109 |
}) |
110 |
public class CherryPickAction extends SingleRepositoryAction { |
111 |
|
112 |
private static final Logger LOG = Logger.getLogger(CherryPickAction.class.getName()); |
113 |
|
114 |
public void cherryPick (File repository, String preselectedRevision) { |
115 |
RepositoryInfo info = RepositoryInfo.getInstance(repository); |
116 |
info.refresh(); |
117 |
GitRepositoryState state = info.getRepositoryState(); |
118 |
boolean interrupted = isInterrupted(repository, state); |
119 |
if (state == GitRepositoryState.SAFE && !interrupted) { |
120 |
CherryPick cherryPick = new CherryPick(repository, preselectedRevision); |
121 |
if (cherryPick.showDialog()) { |
122 |
runCherryPick(repository, CherryPickOperation.BEGIN, new String[] { cherryPick.getRevision() }); |
123 |
} |
124 |
} else if (interrupted) { |
125 |
// abort or continue? |
126 |
JButton btnContinue = new JButton(); |
127 |
Mnemonics.setLocalizedText(btnContinue, Bundle.CTL_CherryPickAction_continueButton_text()); |
128 |
btnContinue.setToolTipText(Bundle.CTL_CherryPickAction_continueButton_TTtext()); |
129 |
JButton btnAbort = new JButton(); |
130 |
Mnemonics.setLocalizedText(btnAbort, Bundle.CTL_CherryPickAction_abortButton_text()); |
131 |
btnAbort.setToolTipText(Bundle.CTL_CherryPickAction_abortButton_TTtext()); |
132 |
JButton btnQuit = new JButton(); |
133 |
Mnemonics.setLocalizedText(btnQuit, Bundle.CTL_CherryPickAction_quitButton_text()); |
134 |
btnQuit.setToolTipText(Bundle.CTL_CherryPickAction_quitButton_TTtext()); |
135 |
Map<Object, CherryPickOperation> operations = new HashMap<>(); |
136 |
operations.put(btnContinue, CherryPickOperation.CONTINUE); |
137 |
operations.put(btnQuit, CherryPickOperation.QUIT); |
138 |
operations.put(btnAbort, CherryPickOperation.ABORT); |
139 |
Object[] options = interrupted |
140 |
? new Object[] { btnContinue, btnAbort, btnQuit, NotifyDescriptor.CANCEL_OPTION } |
141 |
: new Object[] { btnContinue, btnAbort, NotifyDescriptor.CANCEL_OPTION }; |
142 |
Object value = DialogDisplayer.getDefault().notify(new NotifyDescriptor( |
143 |
interrupted |
144 |
? Bundle.MSG_CherryPick_cherryPickingScheduledState_text(repository.getName()) |
145 |
: Bundle.MSG_CherryPick_cherryPickingState_text(repository.getName()), |
146 |
Bundle.LBL_CherryPick_cherryPickingState_title(), |
147 |
NotifyDescriptor.YES_NO_CANCEL_OPTION, |
148 |
NotifyDescriptor.QUESTION_MESSAGE, |
149 |
options, |
150 |
btnContinue)); |
151 |
CherryPickOperation op = operations.get(value); |
152 |
if (op != null) { |
153 |
runCherryPick(repository, op, null); |
154 |
} |
155 |
} else { |
156 |
GitClientExceptionHandler.annotate(Bundle.MSG_CherryPickAction_notAllowed(state)); |
157 |
} |
158 |
|
159 |
} |
160 |
|
161 |
public void finish (File repository) { |
162 |
RepositoryInfo info = RepositoryInfo.getInstance(repository); |
163 |
info.refresh(); |
164 |
if (isInterrupted(repository, info.getRepositoryState())) { |
165 |
cherryPick(repository, null); |
166 |
} |
167 |
} |
168 |
|
169 |
@Override |
170 |
protected void performAction (File repository, File[] roots, VCSContext context) { |
171 |
cherryPick(repository, null); |
172 |
} |
173 |
|
174 |
private boolean isInterrupted (File repository, GitRepositoryState state) { |
175 |
if (state == GitRepositoryState.CHERRY_PICKING || state == GitRepositoryState.CHERRY_PICKING_RESOLVED) { |
176 |
return true; |
177 |
} |
178 |
File sequencer = new File(GitUtils.getGitFolderForRoot(repository), "sequencer"); |
179 |
String[] fileNames = sequencer.list(); |
180 |
return fileNames != null && Arrays.asList(fileNames).contains("todo"); |
181 |
} |
182 |
|
183 |
@NbBundle.Messages("MSG_CherryPickAction_progress=Cherry Picking...") |
184 |
private void runCherryPick (final File repository, final CherryPickOperation op, final String[] revisions) { |
185 |
GitProgressSupport supp = new GitProgressSupport() { |
186 |
|
187 |
@Override |
188 |
protected void perform () { |
189 |
try { |
190 |
GitUtils.runWithoutIndexing(new Callable<Void>() { |
191 |
@Override |
192 |
public Void call () throws Exception { |
193 |
GitClient client = getClient(); |
194 |
CherryPickResultProcessor rp = new CherryPickResultProcessor(client, repository, getProgressSupport()); |
195 |
CherryPickOperation nextAction = op; |
196 |
while (nextAction != null && !isCanceled()) { |
197 |
GitCherryPickResult result = client.cherryPick(nextAction, revisions, getProgressMonitor()); |
198 |
rp.processResult(result, nextAction); |
199 |
nextAction = rp.getNextAction(); |
200 |
} |
201 |
return null; |
202 |
} |
203 |
}); |
204 |
} catch (GitException ex) { |
205 |
GitClientExceptionHandler.notifyException(ex, true); |
206 |
} finally { |
207 |
setDisplayName(NbBundle.getMessage(GitAction.class, "LBL_Progress.RefreshingStatuses")); //NOI18N |
208 |
Git.getInstance().getFileStatusCache().refreshAllRoots(Collections.<File, Collection<File>>singletonMap(repository, Git.getInstance().getSeenRoots(repository))); |
209 |
GitUtils.headChanged(repository); |
210 |
} |
211 |
} |
212 |
|
213 |
private GitProgressSupport getProgressSupport () { |
214 |
return this; |
215 |
} |
216 |
}; |
217 |
supp.start(Git.getInstance().getRequestProcessor(repository), repository, Bundle.MSG_CherryPickAction_progress()); |
218 |
} |
219 |
|
220 |
public static class CherryPickResultProcessor extends ResultProcessor { |
221 |
|
222 |
private final OutputLogger logger; |
223 |
private CherryPickOperation nextAction; |
224 |
private final GitProgressSupport supp; |
225 |
|
226 |
public CherryPickResultProcessor (GitClient client, File repository, GitProgressSupport supp) { |
227 |
super(client, repository, GitUtils.HEAD, supp.getProgressMonitor()); |
228 |
this.logger = supp.getLogger(); |
229 |
this.supp = supp; |
230 |
} |
231 |
|
232 |
@NbBundle.Messages({ |
233 |
"# {0} - rebase status", "MSG_CherryPickAction.result=Cherry-Pick Result: {0}\n", |
234 |
"# {0} - head commit id", "MSG_CherryPickAction.result.aborted=Cherry-picking aborted and the current branch reset to {0}\n", |
235 |
"MSG_CherryPickAction.result.failed=Working tree modifications prevent from cherry-picking:\n", |
236 |
"MSG_CherryPickAction.result.conflict=Cherry-picking interrupted because of conflicts in:\n", |
237 |
"MSG_CherryPickAction.result.ok=Cherry-picking successfully finished\n" |
238 |
}) |
239 |
public void processResult (GitCherryPickResult result, CherryPickOperation currentOp) { |
240 |
nextAction = null; |
241 |
StringBuilder sb = new StringBuilder(Bundle.MSG_CherryPickAction_result(result.getCherryPickStatus().toString())); |
242 |
GitRevisionInfo info = result.getCurrentHead(); |
243 |
switch (result.getCherryPickStatus()) { |
244 |
case ABORTED: |
245 |
sb.append(Bundle.MSG_CherryPickAction_result_aborted(info.getRevision())); |
246 |
GitUtils.printInfo(sb, info); |
247 |
break; |
248 |
case FAILED: |
249 |
sb.append(Bundle.MSG_CherryPickAction_result_failed()); |
250 |
printConflicts(logger, sb, result.getFailures()); |
251 |
try { |
252 |
if (resolveLocalChanges(result.getFailures().toArray(new File[result.getFailures().size()]))) { |
253 |
nextAction = CherryPickOperation.CONTINUE; |
254 |
} else if (currentOp == CherryPickOperation.BEGIN) { |
255 |
nextAction = CherryPickOperation.QUIT; |
256 |
} |
257 |
} catch (GitException ex) { |
258 |
GitClientExceptionHandler.notifyException(ex, true); |
259 |
} |
260 |
break; |
261 |
case CONFLICTING: |
262 |
sb.append(Bundle.MSG_CherryPickAction_result_conflict()); |
263 |
printConflicts(logger, sb, result.getConflicts()); |
264 |
nextAction = resolveCherryPickConflicts(result.getConflicts()); |
265 |
break; |
266 |
case OK: |
267 |
sb.append(Bundle.MSG_CherryPickAction_result_ok()); |
268 |
break; |
269 |
case UNCOMMITTED: |
270 |
askForCommit(); |
271 |
break; |
272 |
} |
273 |
for (GitRevisionInfo commit : result.getCherryPickedCommits()) { |
274 |
GitUtils.printInfo(sb, commit); |
275 |
} |
276 |
if (sb.length() > 0) { |
277 |
logger.outputLine(sb.toString()); |
278 |
} |
279 |
} |
280 |
|
281 |
public CherryPickOperation getNextAction () { |
282 |
return nextAction; |
283 |
} |
284 |
|
285 |
@NbBundle.Messages({ |
286 |
"LBL_CherryPickResultProcessor.abortButton.text=&Abort", |
287 |
"LBL_CherryPickResultProcessor.abortButton.TTtext=Abort the interrupted process and reset back to the original commit.", |
288 |
"LBL_CherryPickResultProcessor.resolveButton.text=&Resolve", |
289 |
"LBL_CherryPickResultProcessor.resolveButton.TTtext=Files in conflict will be opened in the Resolve Conflict dialog.", |
290 |
"LBL_CherryPickResultProcessor.resolveConflicts=Resolve Conflicts", |
291 |
"MSG_CherryPickResultProcessor.resolveConflicts=Cherry-picking produced unresolved conflicts.\n" |
292 |
+ "You can resolve them manually, review them in the Versioning view\n" |
293 |
+ "or completely abort the process and reset back to the original state.", |
294 |
"LBL_CherryPickResultProcessor.revertButton.text=&Revert", |
295 |
"LBL_CherryPickResultProcessor.revertButton.TTtext=Revert local changes to the state in the HEAD and removes unversioned files.", |
296 |
"LBL_CherryPickResultProcessor.reviewButton.text=Re&view", |
297 |
"LBL_CherryPickResultProcessor.reviewButton.TTtext=Opens the Versioning view and lists the conflicted files.", |
298 |
"MSG_CherryPick.resolving=Resolving conflicts..." |
299 |
}) |
300 |
private CherryPickOperation resolveCherryPickConflicts (Collection<File> conflicts) { |
301 |
CherryPickOperation action = null; |
302 |
JButton abort = new JButton(); |
303 |
Mnemonics.setLocalizedText(abort, Bundle.LBL_CherryPickResultProcessor_abortButton_text()); |
304 |
abort.setToolTipText(Bundle.LBL_CherryPickResultProcessor_abortButton_TTtext()); |
305 |
JButton resolve = new JButton(); |
306 |
Mnemonics.setLocalizedText(resolve, Bundle.LBL_CherryPickResultProcessor_resolveButton_text()); |
307 |
resolve.setToolTipText(Bundle.LBL_CherryPickResultProcessor_resolveButton_TTtext()); |
308 |
JButton review = new JButton(); |
309 |
Mnemonics.setLocalizedText(review, Bundle.LBL_CherryPickResultProcessor_reviewButton_text()); |
310 |
review.setToolTipText(Bundle.LBL_CherryPickResultProcessor_reviewButton_TTtext()); |
311 |
Object o = DialogDisplayer.getDefault().notify(new NotifyDescriptor( |
312 |
Bundle.MSG_CherryPickResultProcessor_resolveConflicts(), |
313 |
Bundle.LBL_CherryPickResultProcessor_resolveConflicts(), |
314 |
NotifyDescriptor.OK_CANCEL_OPTION, NotifyDescriptor.QUESTION_MESSAGE, |
315 |
new Object[] { resolve, review, abort, NotifyDescriptor.CANCEL_OPTION }, resolve)); |
316 |
if (o == review) { |
317 |
openInVersioningView(conflicts); |
318 |
} else if (o == resolve) { |
319 |
GitProgressSupport executor = new ResolveConflictsExecutor(conflicts.toArray(new File[conflicts.size()])); |
320 |
executor.start(Git.getInstance().getRequestProcessor(repository), repository, Bundle.MSG_CherryPick_resolving()); |
321 |
} else if (o == abort) { |
322 |
action = CherryPickOperation.ABORT; |
323 |
} |
324 |
return action; |
325 |
} |
326 |
|
327 |
@NbBundle.Messages({ |
328 |
"LBL_CherryPickResultProcessor.commit=Commit Required", |
329 |
"MSG_CherryPickResultProcessor.commit=Commit changes were applied into the current branch\n" |
330 |
+ "but a manual commit is required to make these changes permanent.\n\n" |
331 |
+ "Do you want to commit the changes now or review them first?", |
332 |
"LBL_CherryPickResultProcessor.commit.commitButton.text=&Commit", |
333 |
"LBL_CherryPickResultProcessor.commit.commitButton.TTtext=Opens the commit dialog.", |
334 |
"LBL_CherryPickResultProcessor.commit.reviewButton.text=&Review", |
335 |
"LBL_CherryPickResultProcessor.commit.reviewButton.TTtext=Review the changes in the status window." |
336 |
}) |
337 |
private void askForCommit () { |
338 |
JButton commit = new JButton(); |
339 |
Mnemonics.setLocalizedText(commit, Bundle.LBL_CherryPickResultProcessor_commit_commitButton_text()); |
340 |
commit.setToolTipText(Bundle.LBL_CherryPickResultProcessor_commit_commitButton_TTtext()); |
341 |
JButton review = new JButton(); |
342 |
Mnemonics.setLocalizedText(review, Bundle.LBL_CherryPickResultProcessor_commit_reviewButton_text()); |
343 |
review.setToolTipText(Bundle.LBL_CherryPickResultProcessor_commit_reviewButton_TTtext()); |
344 |
Object o = DialogDisplayer.getDefault().notify(new NotifyDescriptor( |
345 |
Bundle.MSG_CherryPickResultProcessor_commit(), |
346 |
Bundle.LBL_CherryPickResultProcessor_commit(), |
347 |
NotifyDescriptor.OK_CANCEL_OPTION, NotifyDescriptor.QUESTION_MESSAGE, |
348 |
new Object[] { commit, review, NotifyDescriptor.CANCEL_OPTION }, commit)); |
349 |
VCSContext context = VCSContext.forNodes(new Node[] { |
350 |
new AbstractNode(Children.LEAF, Lookups.fixed(repository)) { |
351 |
|
352 |
@Override |
353 |
public String getName () { |
354 |
return repository.getName(); |
355 |
} |
356 |
} |
357 |
}); |
358 |
if (o == commit) { |
359 |
SystemAction.get(CommitAction.class).performAction(context); |
360 |
} else if (o == review) { |
361 |
SystemAction.get(StatusAction.class).performContextAction(context); |
362 |
} |
363 |
} |
364 |
|
365 |
} |
366 |
|
367 |
} |