/******************************************************************************* * Copyright (c) 2010, 2013 SAP AG and others. * * All rights reserved. This program and the accompanying materials * are made available under the terms of the Eclipse Public License v1.0 * which accompanies this distribution, and is available at * http://www.eclipse.org/legal/epl-v10.html * * Contributors: * Stefan Lay (SAP AG) - initial implementation * Daniel Megert <daniel_megert@ch.ibm.com> - Create Patch... dialog should not set file location - http://bugs.eclipse.org/361405 * Tomasz Zarna <Tomasz.Zarna@pl.ibm.com> - Allow to save patches in Workspace * Tomasz Zarna <Tomasz.Zarna@pl.ibm.com> - Team > Create Patch... doesn't observe selection, bug 370332 * Daniel Megert <daniel_megert@ch.ibm.com> - Create Patch wizard's options page should remember values - http://bugs.eclipse.org/377390 *******************************************************************************/ package org.eclipse.egit.ui.internal.history; import java.io.BufferedWriter; import java.io.File; import java.io.FileOutputStream; import java.io.IOException; import java.io.OutputStreamWriter; import java.io.Writer; import java.lang.reflect.InvocationTargetException; import java.util.ArrayList; import java.util.Collection; import java.util.List; import org.eclipse.core.resources.IFile; import org.eclipse.core.resources.IResource; import org.eclipse.core.resources.ResourcesPlugin; import org.eclipse.core.runtime.CoreException; import org.eclipse.core.runtime.IProgressMonitor; import org.eclipse.core.runtime.SubMonitor; import org.eclipse.egit.core.op.CreatePatchOperation; import org.eclipse.egit.core.op.CreatePatchOperation.DiffHeaderFormat; import org.eclipse.egit.core.project.RepositoryMapping; import org.eclipse.egit.ui.Activator; import org.eclipse.egit.ui.internal.UIIcons; import org.eclipse.egit.ui.internal.UIText; import org.eclipse.jface.dialogs.Dialog; import org.eclipse.jface.dialogs.DialogSettings; import org.eclipse.jface.dialogs.IDialogConstants; import org.eclipse.jface.dialogs.MessageDialog; import org.eclipse.jface.layout.GridDataFactory; import org.eclipse.jface.operation.IRunnableWithProgress; import org.eclipse.jface.resource.ImageDescriptor; import org.eclipse.jface.viewers.ArrayContentProvider; import org.eclipse.jface.viewers.ComboViewer; import org.eclipse.jface.viewers.IStructuredSelection; import org.eclipse.jface.viewers.LabelProvider; import org.eclipse.jface.viewers.StructuredSelection; import org.eclipse.jface.viewers.Viewer; import org.eclipse.jface.viewers.ViewerFilter; import org.eclipse.jface.wizard.Wizard; import org.eclipse.jface.wizard.WizardDialog; import org.eclipse.jface.wizard.WizardPage; import org.eclipse.jgit.lib.Repository; import org.eclipse.jgit.revwalk.RevCommit; import org.eclipse.jgit.treewalk.filter.PathFilter; import org.eclipse.jgit.treewalk.filter.PathFilterGroup; import org.eclipse.jgit.treewalk.filter.TreeFilter; import org.eclipse.jgit.util.RawParseUtils; import org.eclipse.swt.SWT; import org.eclipse.swt.dnd.Clipboard; import org.eclipse.swt.dnd.TextTransfer; import org.eclipse.swt.dnd.Transfer; import org.eclipse.swt.events.ModifyEvent; import org.eclipse.swt.events.ModifyListener; import org.eclipse.swt.layout.GridData; import org.eclipse.swt.layout.GridLayout; import org.eclipse.swt.widgets.Composite; import org.eclipse.swt.widgets.Label; import org.eclipse.swt.widgets.Shell; import org.eclipse.swt.widgets.Text; /** * A wizard for creating a patch file by running the git diff command. */ public class GitCreatePatchWizard extends Wizard { private RevCommit commit; private Repository db; private Collection<? extends IResource> resources; private LocationPage locationPage; private OptionsPage optionsPage; // The initial size of this wizard. private final static int INITIAL_WIDTH = 300; private final static int INITIAL_HEIGHT = 150; private static final String FORMAT_KEY = "GitCreatePatchWizard.OptionsPage.format"; //$NON-NLS-1$ private static final String CONTEXT_LINES_KEY = "GitCreatePatchWizard.OptionsPage.contextLines"; //$NON-NLS-1$ /** * * @param shell * @param commit * @param db * @param resources */ public static void run(Shell shell, final RevCommit commit, Repository db, Collection<? extends IResource> resources) { final String title = UIText.GitCreatePatchWizard_CreatePatchTitle; final GitCreatePatchWizard wizard = new GitCreatePatchWizard(commit, db, resources); wizard.setWindowTitle(title); WizardDialog dialog = new WizardDialog(shell, wizard); dialog.setMinimumPageSize(INITIAL_WIDTH, INITIAL_HEIGHT); dialog.setHelpAvailable(false); dialog.open(); } /** * Creates a wizard which is used to export the changes introduced by a * commit. * * @param commit * @param db * @param resources */ public GitCreatePatchWizard(RevCommit commit, Repository db, Collection<? extends IResource> resources) { this.commit = commit; this.db = db; this.resources = resources; setDialogSettings(DialogSettings.getOrCreateSection(Activator .getDefault().getDialogSettings(), "GitCreatePatchWizard")); //$NON-NLS-1$ } @Override public void addPages() { String pageTitle = UIText.GitCreatePatchWizard_SelectLocationTitle; String pageDescription = UIText.GitCreatePatchWizard_SelectLocationDescription; locationPage = new LocationPage(pageTitle, pageTitle, UIIcons.WIZBAN_CREATE_PATCH); locationPage.setDescription(pageDescription); addPage(locationPage); pageTitle = UIText.GitCreatePatchWizard_SelectOptionsTitle; pageDescription = UIText.GitCreatePatchWizard_SelectOptionsDescription; optionsPage = new OptionsPage(pageTitle, pageTitle, UIIcons.WIZBAN_CREATE_PATCH); optionsPage.setDescription(pageDescription); addPage(optionsPage); } @Override public boolean performFinish() { final CreatePatchOperation operation = new CreatePatchOperation(db, commit); operation.setHeaderFormat(optionsPage.getSelectedHeaderFormat()); operation.setContextLines(Integer.parseInt(optionsPage.contextLines.getText())); operation.setPathFilter(createPathFilter(resources)); final File file = locationPage.getFile(); if (!(file == null || validateFile(file))) return false; try { getContainer().run(true, true, new IRunnableWithProgress() { @Override public void run(IProgressMonitor monitor) throws InvocationTargetException { SubMonitor progress = SubMonitor.convert(monitor, 2); try { operation.execute(progress.newChild(1)); String content = operation.getPatchContent(); if (file != null) { writeToFile(file, content); IFile[] files = ResourcesPlugin.getWorkspace() .getRoot() .findFilesForLocationURI(file.toURI()); progress.setWorkRemaining(files.length); for (IFile f : files) { f.refreshLocal(IResource.DEPTH_ZERO, progress.newChild(1)); } } else { copyToClipboard(content); } } catch (IOException e) { throw new InvocationTargetException(e); } catch (CoreException e) { throw new InvocationTargetException(e); } } }); } catch (InvocationTargetException e) { ((WizardPage) getContainer().getCurrentPage()).setErrorMessage(e .getMessage() == null ? e.getMessage() : UIText.GitCreatePatchWizard_InternalError); Activator.logError("Patch file was not written", e); //$NON-NLS-1$ return false; } catch (InterruptedException e) { Activator.logError("Patch file was not written", e); //$NON-NLS-1$ return false; } getDialogSettings().put(FORMAT_KEY, optionsPage.getSelectedHeaderFormat().name()); getDialogSettings().put(CONTEXT_LINES_KEY, optionsPage.contextLines.getText()); return true; } private void copyToClipboard(final String content) { getShell().getDisplay().syncExec(new Runnable() { @Override public void run() { TextTransfer plainTextTransfer = TextTransfer.getInstance(); Clipboard clipboard = new Clipboard(getShell().getDisplay()); clipboard.setContents(new String[] { content }, new Transfer[] { plainTextTransfer }); clipboard.dispose(); } }); } private TreeFilter createPathFilter(final Collection<? extends IResource> rs) { if (rs == null || rs.isEmpty()) return null; final List<PathFilter> filters = new ArrayList<>(); for (IResource r : rs) { RepositoryMapping rm = RepositoryMapping.getMapping(r); if (rm != null) { String repoRelativePath = rm.getRepoRelativePath(r); if (repoRelativePath != null) if (repoRelativePath.equals("")) //$NON-NLS-1$ // repository selected return TreeFilter.ALL; else filters.add(PathFilter.create(repoRelativePath)); } } if (filters.size() == 0) return null; if (filters.size() == 1) return filters.get(0); return PathFilterGroup.create(filters); } private void writeToFile(final File file, String content) throws IOException { Writer output = new BufferedWriter(new OutputStreamWriter( new FileOutputStream(file), RawParseUtils.UTF8_CHARSET)); try { // FileWriter always assumes default encoding is // OK! output.write(content); } finally { output.close(); } } private boolean validateFile(File file) { if (file == null) return false; // Consider file valid if it doesn't exist for now. if (!file.exists()) return true; // The file exists. if (!file.canWrite()) { final String title= UIText.GitCreatePatchWizard_ReadOnlyTitle; final String msg= UIText.GitCreatePatchWizard_ReadOnlyMsg; final MessageDialog dialog= new MessageDialog(getShell(), title, null, msg, MessageDialog.ERROR, new String[] { IDialogConstants.OK_LABEL }, 0); dialog.open(); return false; } final String title = UIText.GitCreatePatchWizard_OverwriteTitle; final String msg = UIText.GitCreatePatchWizard_OverwriteMsg; final MessageDialog dialog = new MessageDialog(getShell(), title, null, msg, MessageDialog.QUESTION, new String[] { IDialogConstants.YES_LABEL, IDialogConstants.CANCEL_LABEL }, 0); dialog.open(); if (dialog.getReturnCode() != 0) return false; return true; } /** * * A wizard Page used to specify options of the created patch */ public class OptionsPage extends WizardPage { private Label formatLabel; private ComboViewer formatCombo; private Text contextLines; private Label contextLinesLabel; /** * * @param pageName * @param title * @param titleImage */ protected OptionsPage(String pageName, String title, ImageDescriptor titleImage) { super(pageName, title, titleImage); } @Override public void createControl(Composite parent) { final Composite composite = new Composite(parent, SWT.NULL); GridLayout gridLayout = new GridLayout(2, false); composite.setLayout(gridLayout); composite.setLayoutData(new GridData(GridData.FILL_HORIZONTAL)); formatLabel = new Label(composite, SWT.NONE); formatLabel.setText(UIText.GitCreatePatchWizard_Format); formatCombo = new ComboViewer(composite, SWT.DROP_DOWN | SWT.READ_ONLY); formatCombo.getCombo().setLayoutData(new GridData(SWT.FILL, SWT.DEFAULT, true, false)); formatCombo.setContentProvider(ArrayContentProvider.getInstance()); formatCombo.setLabelProvider(new LabelProvider() { @Override public String getText(Object element) { return ((DiffHeaderFormat) element).getDescription(); } }); formatCombo.setInput(DiffHeaderFormat.values()); formatCombo.setFilters(new ViewerFilter[] { new ViewerFilter() { @Override public boolean select(Viewer viewer, Object parentElement, Object element) { return commit != null || !((DiffHeaderFormat) element).isCommitRequired(); } }}); String formatName = getDialogSettings().get(FORMAT_KEY); DiffHeaderFormat selection = DiffHeaderFormat.NONE; if (formatName != null) try { selection = DiffHeaderFormat.valueOf(formatName); } catch (IllegalArgumentException ex) { // Use default } formatCombo.setSelection(new StructuredSelection(selection)); contextLinesLabel = new Label(composite, SWT.NONE); contextLinesLabel.setText(UIText.GitCreatePatchWizard_LinesOfContext); String contextLineSetting = getDialogSettings().get(CONTEXT_LINES_KEY); if (contextLineSetting == null) contextLineSetting = String.valueOf(CreatePatchOperation.DEFAULT_CONTEXT_LINES); contextLines = new Text(composite, SWT.BORDER | SWT.RIGHT); contextLines.setText(contextLineSetting); validatePage(); contextLines.addModifyListener(new ModifyListener() { @Override public void modifyText(ModifyEvent e) { validatePage(); } }); GridDataFactory.swtDefaults().hint(30, SWT.DEFAULT).applyTo(contextLines); Dialog.applyDialogFont(composite); setControl(composite); } private void validatePage() { boolean pageValid = true; pageValid = validateContextLines(); if (pageValid) { setMessage(null); setErrorMessage(null); } setPageComplete(pageValid); } private boolean validateContextLines() { String text = contextLines.getText(); if(text == null || text.trim().length() == 0) { setErrorMessage(UIText.GitCreatePatchWizard_ContextMustBePositiveInt); return false; } text = text.trim(); char[] charArray = text.toCharArray(); for (char c : charArray) if(!Character.isDigit(c)) { setErrorMessage(UIText.GitCreatePatchWizard_ContextMustBePositiveInt); return false; } return true; } DiffHeaderFormat getSelectedHeaderFormat() { IStructuredSelection selection = (IStructuredSelection) formatCombo .getSelection(); return (DiffHeaderFormat) selection.getFirstElement(); } } Repository getRepository() { return db; } RevCommit getCommit() { return commit; } }